From af068219ba24d44f887166765b47e7ac864a24fe Mon Sep 17 00:00:00 2001 From: Eduard Kerkhoven Date: Wed, 9 Sep 2026 01:20:43 +0200 Subject: [PATCH 01/48] Report solver time limits instead of infeasible problems (SysBioChalmers/RAVEN#749) * solver: report a solver time limit through res.hitTimeLimit optimizeProb now translates the time-limit status of the solver it just called into a single logical field on res, so callers can tell a time out apart from an infeasible problem without knowing which solver ran. The statuses differ per solver and the same code can mean different things: 5 is optimal to glpk but the time limit to scip, so the translation happens where the solver is still known. For the "cobra" option the solver that COBRA dispatched to is looked up, since COBRA passes its status through. getMinNrFluxes uses this to report exitFlag -2 again. Gurobi is the case that matters most: on a MILP that hits the time limit with an incumbent solution, optimizeProb reports it as optimal, so the returned solution would otherwise be presented as the minimal set of fluxes when it may use more than the minimum. * solver: use the normalized time-limit flag in the ftINIT and INIT paths runINIT and ftINITInternalAlg tested res.origStat for gurobi's TIME_LIMIT string, so with any other solver a time limit was reported as "The problem is infeasible". ftINITFillGapsMILP inferred the time limit from a feasible but non-optimal status, which never fires for gurobi, since optimizeProb presents a MILP solution found before the limit as optimal. All three now read res.hitTimeLimit. The exit codes are lined up across getMinNrFluxes and ftINITFillGapsMILP: -1 means nothing usable came back, -2 means a solution came back that is not proven optimal. ftINITFillGapsForAllTasks depends on that split, as it marks a task failed on -1 but keeps the solution on -2. getMinNrFluxes therefore keeps returning -1 when the time limit is reached before any solution is found, and both functions now warn there, so the time limit is not reported as an infeasible problem. Original-commit: https://github.com/SysBioChalmers/RAVEN/commit/9079e542ea50dbd1195eff8728387a93fcbc54bf --- INIT/ftINITInternalAlg.m | 2 +- INIT/tINIT/runINIT.m | 2 +- gapfilling/fillGaps.m | 6 ++-- gapfilling/ftINITFillGaps.m | 4 ++- solver/getMinNrFluxes.m | 25 ++++++++++++-- solver/optimizeProb.m | 52 +++++++++++++++++++++++++++++- testing/function_tests/tAnalysis.m | 2 ++ testing/function_tests/tSolver.m | 8 +++++ 8 files changed, 92 insertions(+), 9 deletions(-) diff --git a/INIT/ftINITInternalAlg.m b/INIT/ftINITInternalAlg.m index 8a082cce..9e8ebc84 100644 --- a/INIT/ftINITInternalAlg.m +++ b/INIT/ftINITInternalAlg.m @@ -445,7 +445,7 @@ if ~checkSolution(res) - if strcmp(res.origStat, 'TIME_LIMIT') + if res.hitTimeLimit EM='Time limit reached without finding a solution. Try increasing the TimeLimit parameter.'; else EM='The problem is infeasible'; diff --git a/INIT/tINIT/runINIT.m b/INIT/tINIT/runINIT.m index 5cc71cbf..0d6824b4 100644 --- a/INIT/tINIT/runINIT.m +++ b/INIT/tINIT/runINIT.m @@ -336,7 +336,7 @@ %Problem should not be infeasible, but it is possible that the time limit %was reached before finding any solutions. if ~checkSolution(res) - if strcmp(res.origStat, 'TIME_LIMIT') + if res.hitTimeLimit EM='Time limit reached without finding a solution. Try increasing the TimeLimit parameter.'; else EM='The problem is infeasible'; diff --git a/gapfilling/fillGaps.m b/gapfilling/fillGaps.m index 6e7ebd4a..56480cf1 100755 --- a/gapfilling/fillGaps.m +++ b/gapfilling/fillGaps.m @@ -82,8 +82,10 @@ % exit status: % % - 1 : optimal solution found -% - -1 : no feasible solution found -% - -2 : optimization time out +% - -1 : no solution found, either because the problem is infeasible or +% because the solver reached its time limit before finding one +% - -2 : a solution was found but is not proven optimal, because the +% solver stopped on its time limit % % Examples % -------- diff --git a/gapfilling/ftINITFillGaps.m b/gapfilling/ftINITFillGaps.m index 11c5ccdf..8c0db5ad 100644 --- a/gapfilling/ftINITFillGaps.m +++ b/gapfilling/ftINITFillGaps.m @@ -28,7 +28,9 @@ % addedRxns the rxns added % newModel the tModel with reactions added to fill gaps % exitFlag 1: optimal solution found -% -1: no feasible solution found +% -1: no solution found, either because the problem is +% infeasible or because the solver reached its time +% limit before finding one % -2: a solution was found but is not proven optimal, % because the solver stopped before reaching % optimality diff --git a/solver/getMinNrFluxes.m b/solver/getMinNrFluxes.m index aac6ccbd..700b4742 100755 --- a/solver/getMinNrFluxes.m +++ b/solver/getMinNrFluxes.m @@ -59,7 +59,8 @@ % exit status: % % - 1 : optimal solution found -% - -1 : no feasible solution found +% - -1 : no solution found, either because the problem is infeasible or +% because the solver reached its time limit before finding one % - -2 : a solution was found but is not proven optimal, because the % solver stopped before reaching optimality. It is returned, but it % may use more fluxes than the minimum @@ -229,12 +230,21 @@ x=[]; I=[]; exitFlag=-1; + if res.hitTimeLimit + %Nothing is returned either way, but the caller reports -1 as "no + %feasible solution exists", which is not what happened here. + EM='Time limit reached before finding a solution. Try increasing the TimeLimit parameter.'; + warning('RAVEN:warning', '%s', EM); + end return; end -if ~isOptimal +if res.hitTimeLimit || ~isOptimal %A feasible but suboptimal solution, i.e. the solver stopped before %reaching optimality. The solution is still returned, but must not be %reported as optimal: it may use more fluxes than the minimum. + %Not every solver reports this in its status, gurobi for one presents a + %MILP solution found before the limit as optimal, so res.hitTimeLimit is + %consulted as well. exitFlag=-2; end @@ -415,12 +425,21 @@ x=[]; I=[]; exitFlag=-1; + if res.hitTimeLimit + %Nothing is returned either way, but the caller reports -1 as "no + %feasible solution exists", which is not what happened here. + EM='Time limit reached before finding a solution. Try increasing the TimeLimit parameter.'; + warning('RAVEN:warning', '%s', EM); + end return; end -if ~isOptimal +if res.hitTimeLimit || ~isOptimal %A feasible but suboptimal solution, i.e. the solver stopped before %reaching optimality. The solution is still returned, but must not be %reported as optimal: it may use more fluxes than the minimum. + %Not every solver reports this in its status, gurobi for one presents a + %MILP solution found before the limit as optimal, so res.hitTimeLimit is + %consulted as well. exitFlag=-2; end diff --git a/solver/optimizeProb.m b/solver/optimizeProb.m index 120b567a..047a39a0 100755 --- a/solver/optimizeProb.m +++ b/solver/optimizeProb.m @@ -22,7 +22,10 @@ % ------- % res : struct % the output structure from the selected solver RAVENSOLVER (COBRA -% style). +% style). In addition to the fields set by the solver, the logical field +% "hitTimeLimit" is true if the solver stopped because it reached its +% time limit. Whatever solution is in res is then either absent or not +% proven optimal. % % See also % -------- @@ -336,6 +339,13 @@ otherwise error('RAVEN solver not defined or unknown. Try using setRavenSolver(''solver'').'); end +%Flag a solve that ended on the time limit. res.stat cannot express this on +%its own: a solver that times out with an incumbent solution in hand reports +%the same status as one that finished, and a solver that times out with +%nothing reports the same status as an infeasible problem. The raw status in +%res.origStat does distinguish the two, but only in solver-specific terms, +%so it is translated here where the solver is still known. +res.hitTimeLimit=hitTimeLimit(solver,res,milp); if res.stat>0 res.full=res.full(1:size(prob.a,2)); end @@ -361,6 +371,46 @@ s_merged = cell2struct([struct2cell(s_merged); struct2cell(s_new)], names, 1); end +function timeLimit = hitTimeLimit(solver,res,milp) +%Report whether the solver that produced res stopped on its time limit. Each +%solver has its own status codes for this, and the "cobra" option hands the +%status of the solver that COBRA dispatched to straight through, so that +%solver is looked up as well. +global CBT_LP_SOLVER CBT_MILP_SOLVER +timeLimit=false; +if ~isfield(res,'origStat') + return; +end +if strcmp(solver,'cobra') + if milp + solver=CBT_MILP_SOLVER; + else + solver=CBT_LP_SOLVER; + end + if isempty(solver) || ~ischar(solver) + return; + end +end +origStat=res.origStat; +isText=ischar(origStat) || isstring(origStat); +isCode=isnumeric(origStat) && isscalar(origStat); +switch lower(solver) + case 'gurobi' + timeLimit=isText && strcmp(origStat,'TIME_LIMIT'); + case 'mosek' + timeLimit=isText && strcmp(origStat,'MSK_RES_TRM_MAX_TIME'); + case 'glpk' + %109 from the simplex method, 209 from the interior point method + timeLimit=isCode && ismember(origStat,[109 209]); + case {'scip','soplex'} + timeLimit=isCode && origStat==5; + case {'ibm_cplex','tomlab_cplex','cplex_direct'} + %11 for an LP, 107 and 108 for a MILP with and without an integer + %solution found before the limit was reached + timeLimit=isCode && ismember(origStat,[11 107 108]); +end +end + function paramlist = renameparams(paramlist,old,new) if ~iscell(paramlist) wasNoCell = true; diff --git a/testing/function_tests/tAnalysis.m b/testing/function_tests/tAnalysis.m index a3fce44f..d19f8000 100644 --- a/testing/function_tests/tAnalysis.m +++ b/testing/function_tests/tAnalysis.m @@ -33,6 +33,8 @@ function getMinNrFluxesReturnsFlux(testCase) testCase.assumeMILPSolver(); evalc('[x, I, exitFlag] = getMinNrFluxes(testCase.model, testCase.model.rxns);'); testCase.verifyNotEmpty(x); + %A problem this small is solved well within the time limit + testCase.verifyEqual(exitFlag, 1); end function getMinNrFluxesFormulationsAgree(testCase) diff --git a/testing/function_tests/tSolver.m b/testing/function_tests/tSolver.m index cab1f233..b86eb529 100644 --- a/testing/function_tests/tSolver.m +++ b/testing/function_tests/tSolver.m @@ -29,6 +29,14 @@ function optimizeProbSolvesLP(testCase) testCase.verifyTrue(isfield(res, 'obj')); end + function optimizeProbReportsTimeLimit(testCase) + res = optimizeProb(testCase.modelLP()); + testCase.verifyTrue(isfield(res, 'hitTimeLimit')); + testCase.verifyTrue(islogical(res.hitTimeLimit)); + %The problem is solved long before the time limit is reached + testCase.verifyFalse(res.hitTimeLimit); + end + function checkSolutionOnFeasibleProblem(testCase) res = optimizeProb(testCase.modelLP()); [isFeasible, isOptimal] = checkSolution(res); From f8cab59e99cfd865d03620dde140c898962cd986 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 8 Sep 2026 23:22:18 +0000 Subject: [PATCH 02/48] doc: regenerate HTML documentation for af06821 [skip ci] Original-commit: https://github.com/SysBioChalmers/RAVEN/commit/104a6e847350507cd7cc98f41cb9523cfe1e8da9 --- doc/INIT/ftINITInternalAlg.html | 2 +- doc/INIT/tINIT/runINIT.html | 2 +- doc/gapfilling/fillGaps.html | 440 +++++----- doc/gapfilling/ftINITFillGaps.html | 186 ++--- doc/solver/getMinNrFluxes.html | 940 +++++++++++----------- doc/solver/optimizeProb.html | 755 +++++++++-------- doc/testing/function_tests/tAnalysis.html | 628 ++++++++------- doc/testing/function_tests/tSolver.html | 74 +- 8 files changed, 1559 insertions(+), 1468 deletions(-) diff --git a/doc/INIT/ftINITInternalAlg.html b/doc/INIT/ftINITInternalAlg.html index e6b9005a..2481310e 100644 --- a/doc/INIT/ftINITInternalAlg.html +++ b/doc/INIT/ftINITInternalAlg.html @@ -555,7 +555,7 @@

SOURCE CODE ^if ~checkSolution(res) -0448 if strcmp(res.origStat, 'TIME_LIMIT') +0448 if res.hitTimeLimit 0449 EM='Time limit reached without finding a solution. Try increasing the TimeLimit parameter.'; 0450 else 0451 EM='The problem is infeasible'; diff --git a/doc/INIT/tINIT/runINIT.html b/doc/INIT/tINIT/runINIT.html index 035f8e0b..3f574415 100644 --- a/doc/INIT/tINIT/runINIT.html +++ b/doc/INIT/tINIT/runINIT.html @@ -454,7 +454,7 @@

SOURCE CODE ^%Problem should not be infeasible, but it is possible that the time limit 0337 %was reached before finding any solutions. 0338 if ~checkSolution(res) -0339 if strcmp(res.origStat, 'TIME_LIMIT') +0339 if res.hitTimeLimit 0340 EM='Time limit reached without finding a solution. Try increasing the TimeLimit parameter.'; 0341 else 0342 EM='The problem is infeasible'; diff --git a/doc/gapfilling/fillGaps.html b/doc/gapfilling/fillGaps.html index 597442ad..aeb0f66e 100644 --- a/doc/gapfilling/fillGaps.html +++ b/doc/gapfilling/fillGaps.html @@ -110,8 +110,10 @@

DESCRIPTION ^SOURCE CODE ^% exit status: 0083 % 0084 % - 1 : optimal solution found -0085 % - -1 : no feasible solution found -0086 % - -2 : optimization time out -0087 % -0088 % Examples -0089 % -------- -0090 % [newConnected, cannotConnect, addedRxns, newModel, exitFlag]=... -0091 % fillGaps(model,models,allowNetProduction,useModelConstraints,... -0092 % supressWarnings,rxnScores,params); -0093 % -0094 % See Also -0095 % -------- -0096 % ftINITFillGaps : the ftINIT-side counterpart, used when the reference -0097 % model already contains the model's own reactions, so no per-task -0098 % merge is needed. -0099 -0100 %If the user only supplied a single template model -0101 if ~iscell(models) -0102 models={models}; -0103 end -0104 -0105 p=parseRAVENargs(varargin, {'allowNetProduction',false; ... -0106 'useModelConstraints',false; ... -0107 'supressWarnings',false; ... -0108 'rxnScores',[]; ... -0109 'params',[]; ... -0110 'algorithm','reference'; ... -0111 'epsilon',1e-4; ... -0112 'minGrowth',[]; ... -0113 'verbose',true}); -0114 allowNetProduction=p.allowNetProduction; -0115 useModelConstraints=p.useModelConstraints; -0116 supressWarnings=p.supressWarnings; -0117 rxnScores=p.rxnScores; -0118 params=p.params; -0119 -0120 % ---- Dispatch to alternative gap-filling algorithms ---- -0121 switch lower(p.algorithm) -0122 case {'fastlp', 'swiftlp'} -0123 if numel(models) < 1 -0124 error('RAVEN:badInput', 'fillGaps: fastLP/swiftLP requires at least one reference model.'); -0125 end -0126 variant = strrep(lower(p.algorithm), 'lp', ''); -0127 [addedRxns, newModel, cannotConnect] = gapFillFastLP(model, models{1}, ... -0128 'epsilon', p.epsilon, 'verbose', p.verbose, 'variant', variant); -0129 newConnected = {}; -0130 exitFlag = 1; -0131 return; -0132 case 'gapfillmilp' -0133 if numel(models) < 1 -0134 error('RAVEN:badInput', 'fillGaps: gapfillMILP requires at least one reference model.'); -0135 end -0136 [addedRxns, ~, newModel, exitFlag] = gapFillMILP(model, models{1}, ... -0137 'minGrowth', p.minGrowth, 'verbose', p.verbose, 'params', params); -0138 newConnected = {}; -0139 cannotConnect = {}; -0140 return; -0141 case 'topological' -0142 if numel(models) < 1 -0143 error('RAVEN:badInput', 'fillGaps: topological requires at least one reference model.'); -0144 end -0145 result = gapFillTopological(model, models{1}, 'verbose', p.verbose); -0146 addedRxns = {}; -0147 newModel = model; -0148 cannotConnect = result.blockedMets; -0149 newConnected = {}; -0150 exitFlag = 1; -0151 return; -0152 end -0153 % Falls through to the 'reference' algorithm for all other values. -0154 -0155 if isempty(rxnScores) -0156 rxnScores=cell(numel(models),1); -0157 for i=1:numel(models) -0158 rxnScores{i}=ones(numel(models{i}.rxns),1)*-1; -0159 end -0160 end -0161 if isempty(params) -0162 params=struct(); -0163 end -0164 -0165 if ~iscell(rxnScores) -0166 rxnScores={rxnScores}; -0167 end -0168 -0169 models=models(:); -0170 rxnScores=rxnScores(:); -0171 -0172 %Check if the original model has an unconstrained field. If so, give a -0173 %warning -0174 if supressWarnings==false -0175 if isfield(model,'unconstrained') -0176 EM='This algorithm is meant to function on a model with exchange reactions for uptake and excretion of metabolites. The current model still has the "unconstrained" field'; -0177 warning('RAVEN:warning', '%s', EM); -0178 else -0179 if isempty(getExchangeRxns(model,'both')) -0180 fprintf('NOTE: This algorithm is meant to function on a model with exchange reactions for uptake and excretion of metabolites. The current model does not seem to contain any such reactions.\n'); -0181 end -0182 end -0183 end -0184 -0185 %Simplify the template models to remove constrained rxns. At the same time, -0186 %check that the id of the template models is not the same as the model. That -0187 %would cause an error further down -0188 for i=1:numel(models) -0189 models{i}.rxnScores=rxnScores{i}; -0190 models{i}=simplifyModel(models{i},false,false,true); -0191 if strcmpi(models{i}.id,model.id) -0192 EM='The reference model(s) cannot have the same id as the model'; -0193 error('RAVEN:badInput', '%s', EM); -0194 end -0195 end -0196 -0197 %This is a rather ugly solution to the issue that it is a bit tricky to keep -0198 %track of which scores belong to which reactions. This requires that -0199 %removeReactions and mergeModels are modified to check for the new field. -0200 model.rxnScores=zeros(numel(model.rxns),1); -0201 -0202 %First merge all models into one big one -0203 allModels=mergeModels([{model};models],'metNames',true); -0204 -0205 %Add that net production is ok -0206 if allowNetProduction==true -0207 %A second column in model.b means that the b field is lower and upper -0208 %bound on the RHS. -0209 model.b=[model.b(:,1) inf(numel(model.mets),1)]; -0210 allModels.b=[allModels.b(:,1) inf(numel(allModels.mets),1)]; -0211 end -0212 -0213 if useModelConstraints==true -0214 newConnected={}; -0215 cannotConnect={}; -0216 -0217 %Check that the input model is not solveable without any input -0218 sol=solveLP(model); -0219 if ~isempty(sol.f) -0220 addedRxns={}; -0221 newModel=model; -0222 exitFlag=1; -0223 return; -0224 end -0225 -0226 %Then check that the merged model is solveable -0227 sol=solveLP(allModels); -0228 if isempty(sol.f) -0229 EM='There are no reactions in the template model(s) that can make the model constraints satisfied'; -0230 error('RAVEN:badInput', '%s', EM); -0231 end -0232 -0233 %Remove dead ends for speed reasons. This has to be done here and -0234 %duplicate below because there is otherwise a risk that a reaction -0235 %which is constrained to something relevant is removed -0236 allModels=simplifyModel(allModels,false,false,false,true,false,false,false,[],true); -0237 allModels.c(:)=0; -0238 else -0239 %Remove dead ends for speed reasons -0240 allModels=simplifyModel(allModels,false,false,false,true,false,false,false,[],true); -0241 allModels.c(:)=0; -0242 -0243 %If model constraints should not be used, then determine which reactions -0244 %to force to have flux -0245 -0246 %Get the reactions that can carry flux in the original model -0247 originalFlux=haveFlux(model,1); -0248 -0249 %For the ones that cannot carry flux, see if they can do so in the -0250 %merged model. Reactions from model keep their id unchanged in -0251 %allModels (mergeModels renames colliding ids in the other input -0252 %models, never in model itself), so intersecting with allModels.rxns -0253 %both identifies them and drops any that simplifyModel has since -0254 %removed as dead ends. The previous strcmp(allModels.rxnFrom,model.id) -0255 %failed whenever model already carried a rxnFrom field of its own, as -0256 %getModelFromHomology output does, tagging each reaction with its -0257 %template rather than model.id, so mergeModels leaves it untouched and -0258 %it never matches model.id here. -0259 toCheck=intersect(model.rxns(~originalFlux),allModels.rxns); -0260 -0261 %Get the ones that still cannot carry flux. Guard against an empty -0262 %toCheck, since haveFlux interprets an empty reaction list as "all -0263 %reactions" rather than "none" -0264 if isempty(toCheck) -0265 K=toCheck; -0266 else -0267 I=haveFlux(allModels,1,toCheck); -0268 %Get the reactions that cannot carry flux in the original model, but can -0269 %in the merged one -0270 K=toCheck(I); -0271 end -0272 -0273 %This is a temporary thing to only look at the non-reversible rxns. -0274 %This is because all reversible rxns can have a flux in the -0275 %irreversible model format that is used by getMinNrFluxes -0276 [~, I]=ismember(K,model.rxns); -0277 K(model.rev(I)~=0)=[]; -0278 -0279 %Constrain all reactions in the original model to have a flux -0280 allModels.lb(ismember(allModels.rxns,K))=0.1; -0281 -0282 newConnected=K; -0283 cannotConnect=setdiff(model.rxns(~originalFlux),newConnected); -0284 end -0285 -0286 %Then minimize for the number of fluxes used. The fixed rxns (model's own) -0287 %do not need to participate; see the comment above on why this is -0288 %identified by rxn ID rather than allModels.rxnFrom. -0289 templateRxns=find(~ismember(allModels.rxns,model.rxns)); -0290 [~, J, exitFlag]=getMinNrFluxes(allModels,templateRxns,params,allModels.rxnScores(templateRxns)); -0291 -0292 %Remove everything except for the added ones -0293 I=true(numel(allModels.rxns),1); -0294 I(templateRxns(J))=false; -0295 addedModel=removeReactions(allModels,I,true,true,true); -0296 -0297 newModel=mergeModels({model;addedModel},'metNames',true); -0298 addedRxns=setdiff(newModel.rxns,model.rxns); -0299 newModel=rmfield(newModel,'rxnScores'); -0300 end +0085 % - -1 : no solution found, either because the problem is infeasible or +0086 % because the solver reached its time limit before finding one +0087 % - -2 : a solution was found but is not proven optimal, because the +0088 % solver stopped on its time limit +0089 % +0090 % Examples +0091 % -------- +0092 % [newConnected, cannotConnect, addedRxns, newModel, exitFlag]=... +0093 % fillGaps(model,models,allowNetProduction,useModelConstraints,... +0094 % supressWarnings,rxnScores,params); +0095 % +0096 % See Also +0097 % -------- +0098 % ftINITFillGaps : the ftINIT-side counterpart, used when the reference +0099 % model already contains the model's own reactions, so no per-task +0100 % merge is needed. +0101 +0102 %If the user only supplied a single template model +0103 if ~iscell(models) +0104 models={models}; +0105 end +0106 +0107 p=parseRAVENargs(varargin, {'allowNetProduction',false; ... +0108 'useModelConstraints',false; ... +0109 'supressWarnings',false; ... +0110 'rxnScores',[]; ... +0111 'params',[]; ... +0112 'algorithm','reference'; ... +0113 'epsilon',1e-4; ... +0114 'minGrowth',[]; ... +0115 'verbose',true}); +0116 allowNetProduction=p.allowNetProduction; +0117 useModelConstraints=p.useModelConstraints; +0118 supressWarnings=p.supressWarnings; +0119 rxnScores=p.rxnScores; +0120 params=p.params; +0121 +0122 % ---- Dispatch to alternative gap-filling algorithms ---- +0123 switch lower(p.algorithm) +0124 case {'fastlp', 'swiftlp'} +0125 if numel(models) < 1 +0126 error('RAVEN:badInput', 'fillGaps: fastLP/swiftLP requires at least one reference model.'); +0127 end +0128 variant = strrep(lower(p.algorithm), 'lp', ''); +0129 [addedRxns, newModel, cannotConnect] = gapFillFastLP(model, models{1}, ... +0130 'epsilon', p.epsilon, 'verbose', p.verbose, 'variant', variant); +0131 newConnected = {}; +0132 exitFlag = 1; +0133 return; +0134 case 'gapfillmilp' +0135 if numel(models) < 1 +0136 error('RAVEN:badInput', 'fillGaps: gapfillMILP requires at least one reference model.'); +0137 end +0138 [addedRxns, ~, newModel, exitFlag] = gapFillMILP(model, models{1}, ... +0139 'minGrowth', p.minGrowth, 'verbose', p.verbose, 'params', params); +0140 newConnected = {}; +0141 cannotConnect = {}; +0142 return; +0143 case 'topological' +0144 if numel(models) < 1 +0145 error('RAVEN:badInput', 'fillGaps: topological requires at least one reference model.'); +0146 end +0147 result = gapFillTopological(model, models{1}, 'verbose', p.verbose); +0148 addedRxns = {}; +0149 newModel = model; +0150 cannotConnect = result.blockedMets; +0151 newConnected = {}; +0152 exitFlag = 1; +0153 return; +0154 end +0155 % Falls through to the 'reference' algorithm for all other values. +0156 +0157 if isempty(rxnScores) +0158 rxnScores=cell(numel(models),1); +0159 for i=1:numel(models) +0160 rxnScores{i}=ones(numel(models{i}.rxns),1)*-1; +0161 end +0162 end +0163 if isempty(params) +0164 params=struct(); +0165 end +0166 +0167 if ~iscell(rxnScores) +0168 rxnScores={rxnScores}; +0169 end +0170 +0171 models=models(:); +0172 rxnScores=rxnScores(:); +0173 +0174 %Check if the original model has an unconstrained field. If so, give a +0175 %warning +0176 if supressWarnings==false +0177 if isfield(model,'unconstrained') +0178 EM='This algorithm is meant to function on a model with exchange reactions for uptake and excretion of metabolites. The current model still has the "unconstrained" field'; +0179 warning('RAVEN:warning', '%s', EM); +0180 else +0181 if isempty(getExchangeRxns(model,'both')) +0182 fprintf('NOTE: This algorithm is meant to function on a model with exchange reactions for uptake and excretion of metabolites. The current model does not seem to contain any such reactions.\n'); +0183 end +0184 end +0185 end +0186 +0187 %Simplify the template models to remove constrained rxns. At the same time, +0188 %check that the id of the template models is not the same as the model. That +0189 %would cause an error further down +0190 for i=1:numel(models) +0191 models{i}.rxnScores=rxnScores{i}; +0192 models{i}=simplifyModel(models{i},false,false,true); +0193 if strcmpi(models{i}.id,model.id) +0194 EM='The reference model(s) cannot have the same id as the model'; +0195 error('RAVEN:badInput', '%s', EM); +0196 end +0197 end +0198 +0199 %This is a rather ugly solution to the issue that it is a bit tricky to keep +0200 %track of which scores belong to which reactions. This requires that +0201 %removeReactions and mergeModels are modified to check for the new field. +0202 model.rxnScores=zeros(numel(model.rxns),1); +0203 +0204 %First merge all models into one big one +0205 allModels=mergeModels([{model};models],'metNames',true); +0206 +0207 %Add that net production is ok +0208 if allowNetProduction==true +0209 %A second column in model.b means that the b field is lower and upper +0210 %bound on the RHS. +0211 model.b=[model.b(:,1) inf(numel(model.mets),1)]; +0212 allModels.b=[allModels.b(:,1) inf(numel(allModels.mets),1)]; +0213 end +0214 +0215 if useModelConstraints==true +0216 newConnected={}; +0217 cannotConnect={}; +0218 +0219 %Check that the input model is not solveable without any input +0220 sol=solveLP(model); +0221 if ~isempty(sol.f) +0222 addedRxns={}; +0223 newModel=model; +0224 exitFlag=1; +0225 return; +0226 end +0227 +0228 %Then check that the merged model is solveable +0229 sol=solveLP(allModels); +0230 if isempty(sol.f) +0231 EM='There are no reactions in the template model(s) that can make the model constraints satisfied'; +0232 error('RAVEN:badInput', '%s', EM); +0233 end +0234 +0235 %Remove dead ends for speed reasons. This has to be done here and +0236 %duplicate below because there is otherwise a risk that a reaction +0237 %which is constrained to something relevant is removed +0238 allModels=simplifyModel(allModels,false,false,false,true,false,false,false,[],true); +0239 allModels.c(:)=0; +0240 else +0241 %Remove dead ends for speed reasons +0242 allModels=simplifyModel(allModels,false,false,false,true,false,false,false,[],true); +0243 allModels.c(:)=0; +0244 +0245 %If model constraints should not be used, then determine which reactions +0246 %to force to have flux +0247 +0248 %Get the reactions that can carry flux in the original model +0249 originalFlux=haveFlux(model,1); +0250 +0251 %For the ones that cannot carry flux, see if they can do so in the +0252 %merged model. Reactions from model keep their id unchanged in +0253 %allModels (mergeModels renames colliding ids in the other input +0254 %models, never in model itself), so intersecting with allModels.rxns +0255 %both identifies them and drops any that simplifyModel has since +0256 %removed as dead ends. The previous strcmp(allModels.rxnFrom,model.id) +0257 %failed whenever model already carried a rxnFrom field of its own, as +0258 %getModelFromHomology output does, tagging each reaction with its +0259 %template rather than model.id, so mergeModels leaves it untouched and +0260 %it never matches model.id here. +0261 toCheck=intersect(model.rxns(~originalFlux),allModels.rxns); +0262 +0263 %Get the ones that still cannot carry flux. Guard against an empty +0264 %toCheck, since haveFlux interprets an empty reaction list as "all +0265 %reactions" rather than "none" +0266 if isempty(toCheck) +0267 K=toCheck; +0268 else +0269 I=haveFlux(allModels,1,toCheck); +0270 %Get the reactions that cannot carry flux in the original model, but can +0271 %in the merged one +0272 K=toCheck(I); +0273 end +0274 +0275 %This is a temporary thing to only look at the non-reversible rxns. +0276 %This is because all reversible rxns can have a flux in the +0277 %irreversible model format that is used by getMinNrFluxes +0278 [~, I]=ismember(K,model.rxns); +0279 K(model.rev(I)~=0)=[]; +0280 +0281 %Constrain all reactions in the original model to have a flux +0282 allModels.lb(ismember(allModels.rxns,K))=0.1; +0283 +0284 newConnected=K; +0285 cannotConnect=setdiff(model.rxns(~originalFlux),newConnected); +0286 end +0287 +0288 %Then minimize for the number of fluxes used. The fixed rxns (model's own) +0289 %do not need to participate; see the comment above on why this is +0290 %identified by rxn ID rather than allModels.rxnFrom. +0291 templateRxns=find(~ismember(allModels.rxns,model.rxns)); +0292 [~, J, exitFlag]=getMinNrFluxes(allModels,templateRxns,params,allModels.rxnScores(templateRxns)); +0293 +0294 %Remove everything except for the added ones +0295 I=true(numel(allModels.rxns),1); +0296 I(templateRxns(J))=false; +0297 addedModel=removeReactions(allModels,I,true,true,true); +0298 +0299 newModel=mergeModels({model;addedModel},'metNames',true); +0300 addedRxns=setdiff(newModel.rxns,model.rxns); +0301 newModel=rmfield(newModel,'rxnScores'); +0302 end
Generated by m2html © 2005
\ No newline at end of file diff --git a/doc/gapfilling/ftINITFillGaps.html b/doc/gapfilling/ftINITFillGaps.html index f3e141d0..114a2fa0 100644 --- a/doc/gapfilling/ftINITFillGaps.html +++ b/doc/gapfilling/ftINITFillGaps.html @@ -56,7 +56,9 @@

DESCRIPTION ^SOURCE CODE ^% addedRxns the rxns added 0029 % newModel the tModel with reactions added to fill gaps 0030 % exitFlag 1: optimal solution found -0031 % -1: no feasible solution found -0032 % -2: a solution was found but is not proven optimal, -0033 % because the solver stopped before reaching -0034 % optimality -0035 % -0036 % This method works by merging the tModel to the reference model and -0037 % checking which reactions can carry flux. All reactions that can't -0038 % carry flux are removed. It then solves the MILP problem of including -0039 % as few additional reactions as possible from the reference model so -0040 % that the constraints already set on tModel (e.g. a required biomass -0041 % flux, or exchange bounds) can be satisfied. -0042 % -0043 % See also: fillGaps, the tINIT-side counterpart, which merges a -0044 % reference model with the model being gap-filled on every call instead -0045 % of expecting them pre-merged. -0046 % -0047 % Usage: [addedRxns, newModel, exitFlag]=... -0048 % ftINITFillGaps(tModel,origModel,tRefModel,allowNetProduction,... -0049 % supressWarnings,rxnScores,params,verbose,resolveTies) -0050 -0051 if nargin<9 || isempty(resolveTies) -0052 resolveTies=false; -0053 end -0054 if isempty(rxnScores) -0055 rxnScores=ones(numel(tRefModel.rxns),1)*-1; -0056 end -0057 -0058 %Simplify the template models to remove constrained rxns. At the same time, -0059 %check that the id of the template models isn't the same as the tModel. That -0060 %would cause an error further down -0061 tRefModel.rxnScores=rxnScores; -0062 tRefModel=simplifyModel(tRefModel,false,false,true); -0063 -0064 %This is a rather ugly solution to the issue that it's a bit tricky to keep -0065 %track of which scores belong to which reactions. This requires that -0066 %removeReactions and mergeModels are modified to check for the new field. -0067 tModel.rxnScores=zeros(numel(tModel.rxns),1); -0068 -0069 %First merge all models into one big one -0070 fullModel = tRefModel;%mergeModels([{tModel};models],'metNames',true); -0071 -0072 %Add that net production is ok -0073 if allowNetProduction==true -0074 %A second column in tModel.b means that the b field is lower and upper -0075 %bound on the RHS. -0076 tModel.b=[tModel.b(:,1) inf(numel(tModel.mets),1)]; -0077 fullModel.b=[fullModel.b(:,1) inf(numel(fullModel.mets),1)]; -0078 end -0079 -0080 -0081 fullModel.c(:)=0; +0031 % -1: no solution found, either because the problem is +0032 % infeasible or because the solver reached its time +0033 % limit before finding one +0034 % -2: a solution was found but is not proven optimal, +0035 % because the solver stopped before reaching +0036 % optimality +0037 % +0038 % This method works by merging the tModel to the reference model and +0039 % checking which reactions can carry flux. All reactions that can't +0040 % carry flux are removed. It then solves the MILP problem of including +0041 % as few additional reactions as possible from the reference model so +0042 % that the constraints already set on tModel (e.g. a required biomass +0043 % flux, or exchange bounds) can be satisfied. +0044 % +0045 % See also: fillGaps, the tINIT-side counterpart, which merges a +0046 % reference model with the model being gap-filled on every call instead +0047 % of expecting them pre-merged. +0048 % +0049 % Usage: [addedRxns, newModel, exitFlag]=... +0050 % ftINITFillGaps(tModel,origModel,tRefModel,allowNetProduction,... +0051 % supressWarnings,rxnScores,params,verbose,resolveTies) +0052 +0053 if nargin<9 || isempty(resolveTies) +0054 resolveTies=false; +0055 end +0056 if isempty(rxnScores) +0057 rxnScores=ones(numel(tRefModel.rxns),1)*-1; +0058 end +0059 +0060 %Simplify the template models to remove constrained rxns. At the same time, +0061 %check that the id of the template models isn't the same as the tModel. That +0062 %would cause an error further down +0063 tRefModel.rxnScores=rxnScores; +0064 tRefModel=simplifyModel(tRefModel,false,false,true); +0065 +0066 %This is a rather ugly solution to the issue that it's a bit tricky to keep +0067 %track of which scores belong to which reactions. This requires that +0068 %removeReactions and mergeModels are modified to check for the new field. +0069 tModel.rxnScores=zeros(numel(tModel.rxns),1); +0070 +0071 %First merge all models into one big one +0072 fullModel = tRefModel;%mergeModels([{tModel};models],'metNames',true); +0073 +0074 %Add that net production is ok +0075 if allowNetProduction==true +0076 %A second column in tModel.b means that the b field is lower and upper +0077 %bound on the RHS. +0078 tModel.b=[tModel.b(:,1) inf(numel(tModel.mets),1)]; +0079 fullModel.b=[fullModel.b(:,1) inf(numel(fullModel.mets),1)]; +0080 end +0081 0082 -0083 %Then minimize for the number of fluxes used. The fixed rxns doesn't need -0084 %to participate -0085 templateRxns = find(~ismember(fullModel.rxns, tModel.rxns)); %Check if this is slow, in that case keep track of this in fitTasksOpt and send it in -0086 -0087 if isempty(templateRxns) -0088 %The reference model holds no reaction that the model does not already -0089 %have, so no set of additions can satisfy the constraints. This has to -0090 %be caught first: an empty toMinimize means "all reactions" to -0091 %getMinNrFluxes, and its solution would then be indexed against an -0092 %empty templateRxns. -0093 addedRxns = {}; -0094 newModel = origModel; -0095 exitFlag = -1; -0096 return; -0097 end -0098 -0099 %The reversible formulation uses one binary per reaction rather than one -0100 %per irreversible reaction, which is what makes this fast enough to run -0101 %once per task on a genome-scale reference model -0102 [~, J, exitFlag]=getMinNrFluxes(fullModel,templateRxns,params,fullModel.rxnScores(templateRxns),'formulation','reversible','verbose',verbose,'resolveTies',resolveTies);%only the scores from the template rxns are used, so the others doesn't matter -0103 -0104 %Remove everything except for the added ones -0105 addedRxns = fullModel.rxns(templateRxns(J)); -0106 if isempty(addedRxns) -0107 %No reactions were selected, either because the MILP found no solution -0108 %or because none were needed. Handing an empty set to addRxns below -0109 %errors out, which would reach the caller as a thrown exception rather -0110 %than as the exitFlag it keys on. -0111 addedRxns = {}; -0112 newModel = origModel; -0113 return; -0114 end -0115 rxnsToAdd.rxns = addedRxns; -0116 rxnsToAdd.equations = constructEquations(fullModel, addedRxns); -0117 rxnsToAdd.ub = fullModel.ub(templateRxns(J)); -0118 rxnsToAdd.lb = fullModel.lb(templateRxns(J)); -0119 newModel = addRxns(origModel,rxnsToAdd, 3, [], true); %we ignore gene rules etc here, they are not needed - we regenerate the model in the end by subtracting rxns from the full model. -0120 -0121 end +0083 fullModel.c(:)=0; +0084 +0085 %Then minimize for the number of fluxes used. The fixed rxns doesn't need +0086 %to participate +0087 templateRxns = find(~ismember(fullModel.rxns, tModel.rxns)); %Check if this is slow, in that case keep track of this in fitTasksOpt and send it in +0088 +0089 if isempty(templateRxns) +0090 %The reference model holds no reaction that the model does not already +0091 %have, so no set of additions can satisfy the constraints. This has to +0092 %be caught first: an empty toMinimize means "all reactions" to +0093 %getMinNrFluxes, and its solution would then be indexed against an +0094 %empty templateRxns. +0095 addedRxns = {}; +0096 newModel = origModel; +0097 exitFlag = -1; +0098 return; +0099 end +0100 +0101 %The reversible formulation uses one binary per reaction rather than one +0102 %per irreversible reaction, which is what makes this fast enough to run +0103 %once per task on a genome-scale reference model +0104 [~, J, exitFlag]=getMinNrFluxes(fullModel,templateRxns,params,fullModel.rxnScores(templateRxns),'formulation','reversible','verbose',verbose,'resolveTies',resolveTies);%only the scores from the template rxns are used, so the others doesn't matter +0105 +0106 %Remove everything except for the added ones +0107 addedRxns = fullModel.rxns(templateRxns(J)); +0108 if isempty(addedRxns) +0109 %No reactions were selected, either because the MILP found no solution +0110 %or because none were needed. Handing an empty set to addRxns below +0111 %errors out, which would reach the caller as a thrown exception rather +0112 %than as the exitFlag it keys on. +0113 addedRxns = {}; +0114 newModel = origModel; +0115 return; +0116 end +0117 rxnsToAdd.rxns = addedRxns; +0118 rxnsToAdd.equations = constructEquations(fullModel, addedRxns); +0119 rxnsToAdd.ub = fullModel.ub(templateRxns(J)); +0120 rxnsToAdd.lb = fullModel.lb(templateRxns(J)); +0121 newModel = addRxns(origModel,rxnsToAdd, 3, [], true); %we ignore gene rules etc here, they are not needed - we regenerate the model in the end by subtracting rxns from the full model. +0122 +0123 end
Generated by m2html © 2005
\ No newline at end of file diff --git a/doc/solver/getMinNrFluxes.html b/doc/solver/getMinNrFluxes.html index 851663b6..d649905c 100644 --- a/doc/solver/getMinNrFluxes.html +++ b/doc/solver/getMinNrFluxes.html @@ -87,7 +87,8 @@

DESCRIPTION ^SOURCE CODE ^% exit status: 0060 % 0061 % - 1 : optimal solution found -0062 % - -1 : no feasible solution found -0063 % - -2 : a solution was found but is not proven optimal, because the -0064 % solver stopped before reaching optimality. It is returned, but it -0065 % may use more fluxes than the minimum -0066 % -0067 % Examples -0068 % -------- -0069 % [x, I, exitFlag] = getMinNrFluxes(model, toMinimize, params, scores); -0070 % -0071 % Notes -0072 % ----- -0073 % Uses 1000 mmol/gDW/h as an arbitary large flux. Could possibly cause -0074 % problems if the fluxes in the model are larger than that. -0075 -0076 p=parseRAVENargs(varargin, {'toMinimize',[]; 'params',[]; 'scores',[]; ... -0077 'formulation','irrev'; 'verbose',false; 'resolveTies',false}); -0078 toMinimize=p.toMinimize; -0079 params=p.params; -0080 scores=p.scores; -0081 formulation=p.formulation; -0082 verbose=p.verbose; -0083 resolveTies=p.resolveTies; -0084 if isempty(toMinimize) -0085 toMinimize=model.rxns; -0086 elseif ~islogical(toMinimize) && ~isnumeric(toMinimize) -0087 toMinimize=convertCharArray(toMinimize); -0088 else -0089 toMinimize=model.rxns(toMinimize); -0090 end -0091 -0092 if isempty(formulation) -0093 formulation='irrev'; -0094 end -0095 formulation=lower(char(formulation)); -0096 if ~ismember(formulation,{'irrev','reversible'}) -0097 EM='Valid options for formulation are "irrev" or "reversible"'; -0098 error('RAVEN:badInput', '%s', EM); -0099 end -0100 -0101 %For passing parameters to the solver -0102 if isempty(params) -0103 params=struct(); -0104 end -0105 -0106 if isempty(scores) -0107 %It says that the default is -1, but that is to fit with other code -0108 scores=ones(numel(toMinimize),1)*1; -0109 else -0110 if numel(scores)~=numel(toMinimize) -0111 EM='The number of scores must be the same as the number of reactions to minimize'; -0112 error('RAVEN:badInput', '%s', EM); -0113 end -0114 -0115 %Change positive scores to have a small negative weight. This is a -0116 %temporary solution. If no score is negative there is nothing to clamp -0117 %to (max([]) errors here rather than filling in a value, since the -0118 %right-hand side is a computed empty array, not the literal [] MATLAB -0119 %treats as a delete), so clamp to 0 instead: every reaction is then -0120 %equally (un)weighted. -0121 negScores=scores<0; -0122 if any(negScores) -0123 scores(~negScores)=max(scores(negScores)); -0124 else -0125 scores(~negScores)=0; -0126 end -0127 -0128 %It says that the default is -1, but that is to fit with other code -0129 scores=scores*-1; -0130 end -0131 -0132 if strcmp(formulation,'reversible') -0133 [x,I,exitFlag]=minNrFluxesReversible(model,toMinimize,scores,params,verbose,resolveTies); -0134 else -0135 if resolveTies -0136 EM='resolveTies is only implemented for formulation=''reversible'''; -0137 error('RAVEN:badInput', '%s', EM); -0138 end -0139 [x,I,exitFlag]=minNrFluxesIrrev(model,toMinimize,scores,params,verbose); -0140 end +0062 % - -1 : no solution found, either because the problem is infeasible or +0063 % because the solver reached its time limit before finding one +0064 % - -2 : a solution was found but is not proven optimal, because the +0065 % solver stopped before reaching optimality. It is returned, but it +0066 % may use more fluxes than the minimum +0067 % +0068 % Examples +0069 % -------- +0070 % [x, I, exitFlag] = getMinNrFluxes(model, toMinimize, params, scores); +0071 % +0072 % Notes +0073 % ----- +0074 % Uses 1000 mmol/gDW/h as an arbitary large flux. Could possibly cause +0075 % problems if the fluxes in the model are larger than that. +0076 +0077 p=parseRAVENargs(varargin, {'toMinimize',[]; 'params',[]; 'scores',[]; ... +0078 'formulation','irrev'; 'verbose',false; 'resolveTies',false}); +0079 toMinimize=p.toMinimize; +0080 params=p.params; +0081 scores=p.scores; +0082 formulation=p.formulation; +0083 verbose=p.verbose; +0084 resolveTies=p.resolveTies; +0085 if isempty(toMinimize) +0086 toMinimize=model.rxns; +0087 elseif ~islogical(toMinimize) && ~isnumeric(toMinimize) +0088 toMinimize=convertCharArray(toMinimize); +0089 else +0090 toMinimize=model.rxns(toMinimize); +0091 end +0092 +0093 if isempty(formulation) +0094 formulation='irrev'; +0095 end +0096 formulation=lower(char(formulation)); +0097 if ~ismember(formulation,{'irrev','reversible'}) +0098 EM='Valid options for formulation are "irrev" or "reversible"'; +0099 error('RAVEN:badInput', '%s', EM); +0100 end +0101 +0102 %For passing parameters to the solver +0103 if isempty(params) +0104 params=struct(); +0105 end +0106 +0107 if isempty(scores) +0108 %It says that the default is -1, but that is to fit with other code +0109 scores=ones(numel(toMinimize),1)*1; +0110 else +0111 if numel(scores)~=numel(toMinimize) +0112 EM='The number of scores must be the same as the number of reactions to minimize'; +0113 error('RAVEN:badInput', '%s', EM); +0114 end +0115 +0116 %Change positive scores to have a small negative weight. This is a +0117 %temporary solution. If no score is negative there is nothing to clamp +0118 %to (max([]) errors here rather than filling in a value, since the +0119 %right-hand side is a computed empty array, not the literal [] MATLAB +0120 %treats as a delete), so clamp to 0 instead: every reaction is then +0121 %equally (un)weighted. +0122 negScores=scores<0; +0123 if any(negScores) +0124 scores(~negScores)=max(scores(negScores)); +0125 else +0126 scores(~negScores)=0; +0127 end +0128 +0129 %It says that the default is -1, but that is to fit with other code +0130 scores=scores*-1; +0131 end +0132 +0133 if strcmp(formulation,'reversible') +0134 [x,I,exitFlag]=minNrFluxesReversible(model,toMinimize,scores,params,verbose,resolveTies); +0135 else +0136 if resolveTies +0137 EM='resolveTies is only implemented for formulation=''reversible'''; +0138 error('RAVEN:badInput', '%s', EM); +0139 end +0140 [x,I,exitFlag]=minNrFluxesIrrev(model,toMinimize,scores,params,verbose); 0141 end -0142 -0143 function [x,I,exitFlag]=minNrFluxesIrrev(model,toMinimize,scores,params,verbose) -0144 %One binary per irreversible reaction. Reversible reactions to minimize are -0145 %split by convertToIrrev and therefore carry two binaries each. -0146 -0147 exitFlag=1; -0148 -0149 %Check if the model is in irreversible format -0150 if any(model.rev) -0151 %Convert the model to irreversible format -0152 irrevModel=convertToIrrev(model); -0153 -0154 %Find the indexes for the reactions in toMinimize -0155 [indexes, I]=ismember(strrep(irrevModel.rxns,'_REV',''),toMinimize); -0156 else -0157 irrevModel=model; -0158 -0159 %Find the indexes for the reactions in toMinimize -0160 [indexes, I]=ismember(irrevModel.rxns,toMinimize); -0161 end -0162 -0163 indexes=find(indexes); -0164 %Adjust scores to fit with reversible -0165 scores=scores(I(indexes)); -0166 -0167 %Add binary constraints in the following manner: - Add one unique -0168 %"metabolite" for each integer reaction as a substrate. -0169 % These metabolites can have net production -0170 %- Add reactions for the production of each of those metabolites. The -0171 % amount produced in one reaction unit must be larger than the largest -0172 % possible flux in the model (but not too large to avoid bad scaling) -0173 -0174 %Calculate a solution to the problem without any constraints. This is to -0175 %get an estimate about the magnitude of fluxes in the model and to get a -0176 %feasible start solution. -0177 sol=solveLP(irrevModel,1); -0178 -0179 %Return an empty solution if the non-constrained problem could not be solved -0180 if isempty(sol.x) -0181 x=[]; -0182 I=[]; -0183 exitFlag=-1; -0184 return; -0185 end -0186 -0187 %Take the maximal times 5 to have a safe margin. If it is smaller than 1000, -0188 %then use 1000 instead. -0189 maxFlux=max(max(sol.x)*5,1000); -0190 -0191 intArray=speye(numel(irrevModel.rxns))*-1; -0192 intArray=intArray(indexes,:); -0193 prob.a=[irrevModel.S;intArray]; -0194 a=[sparse(numel(irrevModel.mets),numel(indexes));speye(numel(indexes))*maxFlux]; -0195 prob.a=[prob.a a]; -0196 prob.ints.sub=numel(irrevModel.rxns)+1:numel(irrevModel.rxns)+numel(indexes); -0197 -0198 prob.c=[zeros(numel(irrevModel.rxns),1);scores(:);zeros(size(prob.a,1),1)]; %Minimize the number of fluxes -0199 prob.A=[prob.a -speye(size(prob.a,1))]; -0200 prob.blc=[irrevModel.b(:,1);zeros(numel(indexes),1)]; -0201 if size(irrevModel.b,2)==2 -0202 prob.buc=[irrevModel.b(:,2);inf(numel(indexes),1)]; -0203 else -0204 prob.buc=[irrevModel.b(:,1);inf(numel(indexes),1)]; -0205 end -0206 prob.blx=[irrevModel.lb;zeros(numel(indexes),1)]; -0207 prob.bux=[irrevModel.ub;ones(numel(indexes),1)]; -0208 prob.lb = [prob.blx; prob.blc]; -0209 prob.ub = [prob.bux; prob.buc]; -0210 prob.osense=1; -0211 prob.csense=repmat('E', 1, size(prob.a,1),1); -0212 prob.b=zeros(size(prob.a,1), 1); -0213 -0214 %Use the output from the linear solution as starting point. Only the values -0215 %for the integer variables will be used, but all are supplied. -0216 prob.sol.int.xx=zeros(numel(prob.c),1); -0217 prob.sol.int.xx(prob.ints.sub(sol.x(indexes)>10^-12))=1; -0218 prob.x0=[]; -0219 prob.vartype=repmat('C', size(prob.A,2), 1); -0220 prob.vartype(prob.ints.sub) = 'I'; % with .lb = 0 and .ub = 1, they are binary -0221 % integers (glpk in octave only allows "continuous" or "", not "binary") -0222 prob=rmfield(prob,{'blx','bux','blc','buc'}); -0223 -0224 % Optimize the problem -0225 res = optimizeProb(prob,params,verbose); -0226 [isFeasible, isOptimal]=checkSolution(res); -0227 -0228 if ~isFeasible -0229 x=[]; -0230 I=[]; -0231 exitFlag=-1; -0232 return; -0233 end -0234 if ~isOptimal -0235 %A feasible but suboptimal solution, i.e. the solver stopped before -0236 %reaching optimality. The solution is still returned, but must not be -0237 %reported as optimal: it may use more fluxes than the minimum. -0238 exitFlag=-2; -0239 end -0240 -0241 xx=res.full(1:numel(irrevModel.rxns)); -0242 I=res.full(numel(xx)+1:end); -0243 -0244 %Map back to original model from irrevModel -0245 x=xx(1:numel(model.rxns)); -0246 if numel(irrevModel.rxns)>numel(model.rxns) -0247 x(model.rev~=0)=x(model.rev~=0)-xx(numel(model.rxns)+1:end); -0248 end -0249 -0250 I=ismember(toMinimize,strrep(irrevModel.rxns(indexes(I>10^-12)),'_REV','')); -0251 end -0252 -0253 function [x,I,exitFlag]=minNrFluxesReversible(model,toMinimize,scores,params,verbose,resolveTies) -0254 %One binary per reaction, on the reversible model. Trades a larger -0255 %constraint matrix for half the binaries on reversible reactions. -0256 -0257 % glpk solver as implemented by COBRA does not work well for MILP. -0258 global CBT_MILP_SOLVER -0259 if strcmp(getpref('RAVEN','solver'),'cobra') && strcmp(CBT_MILP_SOLVER,'glpk') -0260 error('RAVEN:badInput', '%s', 'The current solver is set to ''cobra'', while in COBRA the MILP solver has been set to ''glpk''. The COBRA implementation of glpk is not well suitable for solving MILPs. Please install the Gurobi or an alternative MILP solver.'); +0142 end +0143 +0144 function [x,I,exitFlag]=minNrFluxesIrrev(model,toMinimize,scores,params,verbose) +0145 %One binary per irreversible reaction. Reversible reactions to minimize are +0146 %split by convertToIrrev and therefore carry two binaries each. +0147 +0148 exitFlag=1; +0149 +0150 %Check if the model is in irreversible format +0151 if any(model.rev) +0152 %Convert the model to irreversible format +0153 irrevModel=convertToIrrev(model); +0154 +0155 %Find the indexes for the reactions in toMinimize +0156 [indexes, I]=ismember(strrep(irrevModel.rxns,'_REV',''),toMinimize); +0157 else +0158 irrevModel=model; +0159 +0160 %Find the indexes for the reactions in toMinimize +0161 [indexes, I]=ismember(irrevModel.rxns,toMinimize); +0162 end +0163 +0164 indexes=find(indexes); +0165 %Adjust scores to fit with reversible +0166 scores=scores(I(indexes)); +0167 +0168 %Add binary constraints in the following manner: - Add one unique +0169 %"metabolite" for each integer reaction as a substrate. +0170 % These metabolites can have net production +0171 %- Add reactions for the production of each of those metabolites. The +0172 % amount produced in one reaction unit must be larger than the largest +0173 % possible flux in the model (but not too large to avoid bad scaling) +0174 +0175 %Calculate a solution to the problem without any constraints. This is to +0176 %get an estimate about the magnitude of fluxes in the model and to get a +0177 %feasible start solution. +0178 sol=solveLP(irrevModel,1); +0179 +0180 %Return an empty solution if the non-constrained problem could not be solved +0181 if isempty(sol.x) +0182 x=[]; +0183 I=[]; +0184 exitFlag=-1; +0185 return; +0186 end +0187 +0188 %Take the maximal times 5 to have a safe margin. If it is smaller than 1000, +0189 %then use 1000 instead. +0190 maxFlux=max(max(sol.x)*5,1000); +0191 +0192 intArray=speye(numel(irrevModel.rxns))*-1; +0193 intArray=intArray(indexes,:); +0194 prob.a=[irrevModel.S;intArray]; +0195 a=[sparse(numel(irrevModel.mets),numel(indexes));speye(numel(indexes))*maxFlux]; +0196 prob.a=[prob.a a]; +0197 prob.ints.sub=numel(irrevModel.rxns)+1:numel(irrevModel.rxns)+numel(indexes); +0198 +0199 prob.c=[zeros(numel(irrevModel.rxns),1);scores(:);zeros(size(prob.a,1),1)]; %Minimize the number of fluxes +0200 prob.A=[prob.a -speye(size(prob.a,1))]; +0201 prob.blc=[irrevModel.b(:,1);zeros(numel(indexes),1)]; +0202 if size(irrevModel.b,2)==2 +0203 prob.buc=[irrevModel.b(:,2);inf(numel(indexes),1)]; +0204 else +0205 prob.buc=[irrevModel.b(:,1);inf(numel(indexes),1)]; +0206 end +0207 prob.blx=[irrevModel.lb;zeros(numel(indexes),1)]; +0208 prob.bux=[irrevModel.ub;ones(numel(indexes),1)]; +0209 prob.lb = [prob.blx; prob.blc]; +0210 prob.ub = [prob.bux; prob.buc]; +0211 prob.osense=1; +0212 prob.csense=repmat('E', 1, size(prob.a,1),1); +0213 prob.b=zeros(size(prob.a,1), 1); +0214 +0215 %Use the output from the linear solution as starting point. Only the values +0216 %for the integer variables will be used, but all are supplied. +0217 prob.sol.int.xx=zeros(numel(prob.c),1); +0218 prob.sol.int.xx(prob.ints.sub(sol.x(indexes)>10^-12))=1; +0219 prob.x0=[]; +0220 prob.vartype=repmat('C', size(prob.A,2), 1); +0221 prob.vartype(prob.ints.sub) = 'I'; % with .lb = 0 and .ub = 1, they are binary +0222 % integers (glpk in octave only allows "continuous" or "", not "binary") +0223 prob=rmfield(prob,{'blx','bux','blc','buc'}); +0224 +0225 % Optimize the problem +0226 res = optimizeProb(prob,params,verbose); +0227 [isFeasible, isOptimal]=checkSolution(res); +0228 +0229 if ~isFeasible +0230 x=[]; +0231 I=[]; +0232 exitFlag=-1; +0233 if res.hitTimeLimit +0234 %Nothing is returned either way, but the caller reports -1 as "no +0235 %feasible solution exists", which is not what happened here. +0236 EM='Time limit reached before finding a solution. Try increasing the TimeLimit parameter.'; +0237 warning('RAVEN:warning', '%s', EM); +0238 end +0239 return; +0240 end +0241 if res.hitTimeLimit || ~isOptimal +0242 %A feasible but suboptimal solution, i.e. the solver stopped before +0243 %reaching optimality. The solution is still returned, but must not be +0244 %reported as optimal: it may use more fluxes than the minimum. +0245 %Not every solver reports this in its status, gurobi for one presents a +0246 %MILP solution found before the limit as optimal, so res.hitTimeLimit is +0247 %consulted as well. +0248 exitFlag=-2; +0249 end +0250 +0251 xx=res.full(1:numel(irrevModel.rxns)); +0252 I=res.full(numel(xx)+1:end); +0253 +0254 %Map back to original model from irrevModel +0255 x=xx(1:numel(model.rxns)); +0256 if numel(irrevModel.rxns)>numel(model.rxns) +0257 x(model.rev~=0)=x(model.rev~=0)-xx(numel(model.rxns)+1:end); +0258 end +0259 +0260 I=ismember(toMinimize,strrep(irrevModel.rxns(indexes(I>10^-12)),'_REV','')); 0261 end 0262 -0263 exitFlag=1; -0264 -0265 %The trick to make this possible using a reversible model is that for -0266 %reversible reactions, we do the following. Set the flux Vi == Vipos - Vineg. -0267 %What happens then is that if the flux is positive, Vipos will have a nonzero value, -0268 %and when the flux is negative, Vineg will have a positive value. In addition -0269 %we can add an arbitrary constant to both Vipos and Vineg. For example, if the -0270 %flux Vi is -1, Vineg can be 4 and Vipos 3. This is however not a problem, -0271 %because we can be sure that Vineg + Vipos >= abs(Vi). Since we are interested -0272 %in forcing the ints to be on when there is a flux, it does not matter if we overestimate -0273 %the flux! So, we can simply constrain the boolean Yi to Yi*Maxflux >= Vineg + Vipos. +0263 function [x,I,exitFlag]=minNrFluxesReversible(model,toMinimize,scores,params,verbose,resolveTies) +0264 %One binary per reaction, on the reversible model. Trades a larger +0265 %constraint matrix for half the binaries on reversible reactions. +0266 +0267 % glpk solver as implemented by COBRA does not work well for MILP. +0268 global CBT_MILP_SOLVER +0269 if strcmp(getpref('RAVEN','solver'),'cobra') && strcmp(CBT_MILP_SOLVER,'glpk') +0270 error('RAVEN:badInput', '%s', 'The current solver is set to ''cobra'', while in COBRA the MILP solver has been set to ''glpk''. The COBRA implementation of glpk is not well suitable for solving MILPs. Please install the Gurobi or an alternative MILP solver.'); +0271 end +0272 +0273 exitFlag=1; 0274 -0275 %The matrix then becomes as this: -0276 % S p n int b var -0277 % SSSSSSSS - -0278 % SSSSSSSS - -0279 % SSSSSSSS - -0280 % SSSSSSSS - -0281 % SSSSSSSS - -0282 % SSSSSSSS - -0283 % - 1 - -0284 % - 1 - -0285 % - M - -0286 % - M - -0287 % - - M - -0288 % - - M - -0289 % An example with 8 rxns and 6 metabolites. - means -1, M max flux, and S is the S matrix. -0290 % 4 rxns are to be minimized(1,3,5,7) and 1,7 are reversible. The p and n -0291 % are the Vipos and Vineg variables (2 rxns of each). The ints are the Yi for -0292 % the variables that are to be minimized (the rest of the rxns does not have any). -0293 % The mets here are the constraints, so right under the S matrix, you have -0294 % Vi == Vipos - Vineg for the reactions 1 and 7 while the two next rows represent -0295 % the non-reversible rxns 3 and 5, where we simply say that yi*M >= Vi. The last -0296 % 2 rows are the reactions yi*M >= Vipos + Vineg. To the right, we first have a -0297 % -I matrix for setting the b constraints, and under that we have rxns that are -0298 % just variables (rxns) between 0 and Inf to complete the constraints mentioned above. -0299 %Ex: yi*M >= Vipos + Vineg is impl. as yi*M - Vipos - Vineg - var == 0, 0 <= var <= Inf. -0300 % -0301 %All rows should be equal to zero, so we do not set the b vector in the problem -0302 %The reactions should be constrained as follows -0303 %S - as given in model.lb and model.ub -0304 %pos and neg - between 0 and inf -0305 %ints - between 0 and 1 -0306 %b - as stated in the model.b vector - if this has one column, that is lb and ub (fixed value), if two columns, that is lb and ub -0307 %var - between zero and inf -0308 -0309 [minLog, I]=ismember(model.rxns,toMinimize); -0310 indexes=find(minLog); -0311 revIndexes = find(minLog & (model.rev == 1)); -0312 irrevIndexes = find(minLog & (model.rev == 0)); -0313 revIndexesInInd = find(model.rev(indexes) == 1); -0314 irrevIndexesInInd = find(model.rev(indexes) == 0); -0315 -0316 %The int columns are ordered as indexes, i.e. by position in the model, -0317 %while the scores arrive ordered as toMinimize -0318 scores=scores(I(indexes)); -0319 -0320 %Add binary constraints in the following manner: - Add one unique -0321 %"metabolite" for each integer reaction as a substrate. -0322 % These metabolites can have net production -0323 %- Add reactions for the production of each of those metabolites. The -0324 % amount produced in one reaction unit must be larger than the largest -0325 % possible flux in the model (but not too large to avoid bad scaling) -0326 -0327 maxFlux=1000; -0328 %we build the total matrix as blocks: [S pos neg int b var] +0275 %The trick to make this possible using a reversible model is that for +0276 %reversible reactions, we do the following. Set the flux Vi == Vipos - Vineg. +0277 %What happens then is that if the flux is positive, Vipos will have a nonzero value, +0278 %and when the flux is negative, Vineg will have a positive value. In addition +0279 %we can add an arbitrary constant to both Vipos and Vineg. For example, if the +0280 %flux Vi is -1, Vineg can be 4 and Vipos 3. This is however not a problem, +0281 %because we can be sure that Vineg + Vipos >= abs(Vi). Since we are interested +0282 %in forcing the ints to be on when there is a flux, it does not matter if we overestimate +0283 %the flux! So, we can simply constrain the boolean Yi to Yi*Maxflux >= Vineg + Vipos. +0284 +0285 %The matrix then becomes as this: +0286 % S p n int b var +0287 % SSSSSSSS - +0288 % SSSSSSSS - +0289 % SSSSSSSS - +0290 % SSSSSSSS - +0291 % SSSSSSSS - +0292 % SSSSSSSS - +0293 % - 1 - +0294 % - 1 - +0295 % - M - +0296 % - M - +0297 % - - M - +0298 % - - M - +0299 % An example with 8 rxns and 6 metabolites. - means -1, M max flux, and S is the S matrix. +0300 % 4 rxns are to be minimized(1,3,5,7) and 1,7 are reversible. The p and n +0301 % are the Vipos and Vineg variables (2 rxns of each). The ints are the Yi for +0302 % the variables that are to be minimized (the rest of the rxns does not have any). +0303 % The mets here are the constraints, so right under the S matrix, you have +0304 % Vi == Vipos - Vineg for the reactions 1 and 7 while the two next rows represent +0305 % the non-reversible rxns 3 and 5, where we simply say that yi*M >= Vi. The last +0306 % 2 rows are the reactions yi*M >= Vipos + Vineg. To the right, we first have a +0307 % -I matrix for setting the b constraints, and under that we have rxns that are +0308 % just variables (rxns) between 0 and Inf to complete the constraints mentioned above. +0309 %Ex: yi*M >= Vipos + Vineg is impl. as yi*M - Vipos - Vineg - var == 0, 0 <= var <= Inf. +0310 % +0311 %All rows should be equal to zero, so we do not set the b vector in the problem +0312 %The reactions should be constrained as follows +0313 %S - as given in model.lb and model.ub +0314 %pos and neg - between 0 and inf +0315 %ints - between 0 and 1 +0316 %b - as stated in the model.b vector - if this has one column, that is lb and ub (fixed value), if two columns, that is lb and ub +0317 %var - between zero and inf +0318 +0319 [minLog, I]=ismember(model.rxns,toMinimize); +0320 indexes=find(minLog); +0321 revIndexes = find(minLog & (model.rev == 1)); +0322 irrevIndexes = find(minLog & (model.rev == 0)); +0323 revIndexesInInd = find(model.rev(indexes) == 1); +0324 irrevIndexesInInd = find(model.rev(indexes) == 0); +0325 +0326 %The int columns are ordered as indexes, i.e. by position in the model, +0327 %while the scores arrive ordered as toMinimize +0328 scores=scores(I(indexes)); 0329 -0330 %s block -0331 intArray=speye(numel(model.rxns))*-1; -0332 intArrayRev=intArray(revIndexes,:); -0333 intArrayIrrev = intArray(irrevIndexes,:); -0334 sBlock=[model.S;intArrayRev;intArrayIrrev;sparse(numel(revIndexes), numel(model.rxns))]; %the S matrix and what is below -0335 -0336 %pos/neg blocks -0337 revposorneg1 = sparse(numel(model.mets), numel(revIndexes)); -0338 revpos2 = speye(numel(revIndexes)); -0339 revneg2 = -revpos2; -0340 revposneg3 = sparse(numel(irrevIndexes), numel(revIndexes)); -0341 revposneg4 = revneg2; -0342 posBlock = [revposorneg1;revpos2;revposneg3;revposneg4]; -0343 negBlock = [revposorneg1;revneg2;revposneg3;revposneg4]; -0344 -0345 %int block -0346 int1 = sparse(numel(model.mets), numel(indexes)); -0347 int2 = sparse(numel(revIndexes), numel(indexes)); -0348 tmpEye = speye(numel(indexes))*maxFlux; -0349 int3 = tmpEye(irrevIndexesInInd,:);%we here select only the irrev indexes from the indexes -0350 int4 = tmpEye(revIndexesInInd,:); -0351 intBlock = [int1;int2;int3;int4]; -0352 -0353 %b block -0354 b1 = speye(numel(model.mets))*-1; -0355 b2 = sparse(numel(indexes) + numel(revIndexes), numel(model.mets)); -0356 bBlock = [b1;b2]; -0357 -0358 %var block -0359 var1 = sparse(numel(model.mets), numel(indexes)); -0360 var2 = sparse(numel(revIndexes), numel(indexes)); -0361 tmpEye = speye(numel(indexes))*-1; -0362 var3 = tmpEye(irrevIndexesInInd,:); -0363 var4 = tmpEye(revIndexesInInd,:); -0364 varBlock = [var1;var2;var3;var4]; -0365 -0366 prob.A = [sBlock posBlock negBlock intBlock bBlock varBlock]; -0367 prob.a = prob.A;%I think this is needed as well -0368 -0369 prob.c=[zeros(numel(model.rxns),1);zeros(numel(revIndexes)*2,1); scores(:);zeros(numel(model.mets) + numel(indexes),1)]; %Minimize the sum of reaction scores for reactions that are on -0370 -0371 %ub and lb, text copied from above -0372 %S - as given in model.lb and model.ub -0373 %pos and neg - between 0 and inf -0374 %ints - between 0 and 1 -0375 %b - as stated in the model.b vector - if this has one column, that is lb and ub (fixed value), if two columns, that is lb and ub -0376 %var - between zero and inf -0377 -0378 if size(model.b,2)==2 -0379 bub = model.b(:,2); -0380 else -0381 bub = model.b(:,1); -0382 end -0383 -0384 prob.lb = [model.lb;zeros(numel(revIndexes)*2,1);zeros(numel(indexes),1);model.b(:,1);zeros(numel(indexes),1)]; -0385 prob.ub = [model.ub;inf(numel(revIndexes)*2,1);ones(numel(indexes),1);bub;inf(numel(indexes),1)]; -0386 -0387 prob.b=zeros(size(prob.a,1), 1); -0388 -0389 intsIndexes = find(prob.c ~= 0); -0390 prob.x0=[]; -0391 prob.vartype=repmat('C', 1, size(prob.A,2)); -0392 prob.vartype(intsIndexes) = 'B'; -0393 prob.csense = repmat('E', 1, size(prob.A,1)); -0394 prob.osense=1; %minimize the objective -0395 -0396 %Defaults that suit this formulation, without discarding what the caller set -0397 if ~isfield(params,'intTol') -0398 params.intTol = 10^-9; -0399 end -0400 if ~isfield(params,'TimeLimit') -0401 params.TimeLimit = 300; -0402 end -0403 if ~isfield(params,'Seed') -0404 %Although the solver reports "optimal solution found", different seeds -0405 %can return solutions with noticeably different objective values, so a -0406 %fixed one is used to keep results reproducible -0407 params.Seed = 26; -0408 end -0409 -0410 % Optimize the problem -0411 res = optimizeProb(prob,params,verbose); -0412 [isFeasible, isOptimal]=checkSolution(res); -0413 -0414 if ~isFeasible -0415 x=[]; -0416 I=[]; -0417 exitFlag=-1; -0418 return; -0419 end -0420 if ~isOptimal -0421 %A feasible but suboptimal solution, i.e. the solver stopped before -0422 %reaching optimality. The solution is still returned, but must not be -0423 %reported as optimal: it may use more fluxes than the minimum. -0424 exitFlag=-2; -0425 end -0426 -0427 if resolveTies -0428 %This MILP is highly degenerate: many reaction subsets reach the same -0429 %score optimum, and the solver returns an arbitrary one -- reproducible -0430 %for a fixed solver build + Seed, but fragile to a solver version bump -0431 %or an unrelated change upstream of the MILP (see raven-gecko-parity#104, -0432 %where this was demonstrated on genome-scale Human-GEM: the same Seed, -0433 %same problem, but a different MILP construction path flipped a tied -0434 %pair of reactions). This runs a lexicographic phase 2, holding the -0435 %score objective at its optimum with a floor constraint: first minimise -0436 %the count of "on" reactions (the sparsest optimum), then, among the -0437 %sparsest, minimise their summed reaction-id rank (prefer lower ids). -0438 %Ports raven-toolbox's _resolve_ties_fill (raven-toolbox#114). -0439 % -0440 %intCols identifies the int/binary variable columns directly from the -0441 %block layout (prob.c = [zeros(rxns); zeros(2*revIndexes); scores(:); -0442 %zeros(mets+indexes)]) rather than via find(prob.c~=0): a zero-scored -0443 %reaction would otherwise silently drop out of the column count and -0444 %misalign every later index into "indexes". -0445 intCols = numel(model.rxns) + 2*numel(revIndexes) + (1:numel(indexes)); -0446 primaryObj = full(prob.c(intCols)' * res.full(intCols)); -0447 tol = max(abs(primaryObj)*1e-7, 1e-7); -0448 -0449 floorRow = sparse(1,size(prob.A,2)); -0450 floorRow(intCols) = prob.c(intCols)'; -0451 tieProb = prob; -0452 tieProb.A = [prob.A; floorRow]; -0453 tieProb.a = tieProb.A; -0454 tieProb.b = [prob.b; primaryObj+tol]; -0455 tieProb.csense = [prob.csense 'L']; -0456 -0457 %An integer objective needs only a cheap absolute-gap proof, not a tiny -0458 %relative one -- RAVEN's own MIPGap default (1e-12) can be slow to prove -0459 %on a genome-scale problem where a gap below 1 already proves optimality -0460 %exactly (mirrors raven-toolbox's own MIPGap=0/MIPGapAbs=0.4 for the same -0461 %two phases). -0462 tieParams = params; -0463 tieParams.MIPGap = 0; -0464 tieParams.MIPGapAbs = 0.4; -0465 -0466 unproven = {}; +0330 %Add binary constraints in the following manner: - Add one unique +0331 %"metabolite" for each integer reaction as a substrate. +0332 % These metabolites can have net production +0333 %- Add reactions for the production of each of those metabolites. The +0334 % amount produced in one reaction unit must be larger than the largest +0335 % possible flux in the model (but not too large to avoid bad scaling) +0336 +0337 maxFlux=1000; +0338 %we build the total matrix as blocks: [S pos neg int b var] +0339 +0340 %s block +0341 intArray=speye(numel(model.rxns))*-1; +0342 intArrayRev=intArray(revIndexes,:); +0343 intArrayIrrev = intArray(irrevIndexes,:); +0344 sBlock=[model.S;intArrayRev;intArrayIrrev;sparse(numel(revIndexes), numel(model.rxns))]; %the S matrix and what is below +0345 +0346 %pos/neg blocks +0347 revposorneg1 = sparse(numel(model.mets), numel(revIndexes)); +0348 revpos2 = speye(numel(revIndexes)); +0349 revneg2 = -revpos2; +0350 revposneg3 = sparse(numel(irrevIndexes), numel(revIndexes)); +0351 revposneg4 = revneg2; +0352 posBlock = [revposorneg1;revpos2;revposneg3;revposneg4]; +0353 negBlock = [revposorneg1;revneg2;revposneg3;revposneg4]; +0354 +0355 %int block +0356 int1 = sparse(numel(model.mets), numel(indexes)); +0357 int2 = sparse(numel(revIndexes), numel(indexes)); +0358 tmpEye = speye(numel(indexes))*maxFlux; +0359 int3 = tmpEye(irrevIndexesInInd,:);%we here select only the irrev indexes from the indexes +0360 int4 = tmpEye(revIndexesInInd,:); +0361 intBlock = [int1;int2;int3;int4]; +0362 +0363 %b block +0364 b1 = speye(numel(model.mets))*-1; +0365 b2 = sparse(numel(indexes) + numel(revIndexes), numel(model.mets)); +0366 bBlock = [b1;b2]; +0367 +0368 %var block +0369 var1 = sparse(numel(model.mets), numel(indexes)); +0370 var2 = sparse(numel(revIndexes), numel(indexes)); +0371 tmpEye = speye(numel(indexes))*-1; +0372 var3 = tmpEye(irrevIndexesInInd,:); +0373 var4 = tmpEye(revIndexesInInd,:); +0374 varBlock = [var1;var2;var3;var4]; +0375 +0376 prob.A = [sBlock posBlock negBlock intBlock bBlock varBlock]; +0377 prob.a = prob.A;%I think this is needed as well +0378 +0379 prob.c=[zeros(numel(model.rxns),1);zeros(numel(revIndexes)*2,1); scores(:);zeros(numel(model.mets) + numel(indexes),1)]; %Minimize the sum of reaction scores for reactions that are on +0380 +0381 %ub and lb, text copied from above +0382 %S - as given in model.lb and model.ub +0383 %pos and neg - between 0 and inf +0384 %ints - between 0 and 1 +0385 %b - as stated in the model.b vector - if this has one column, that is lb and ub (fixed value), if two columns, that is lb and ub +0386 %var - between zero and inf +0387 +0388 if size(model.b,2)==2 +0389 bub = model.b(:,2); +0390 else +0391 bub = model.b(:,1); +0392 end +0393 +0394 prob.lb = [model.lb;zeros(numel(revIndexes)*2,1);zeros(numel(indexes),1);model.b(:,1);zeros(numel(indexes),1)]; +0395 prob.ub = [model.ub;inf(numel(revIndexes)*2,1);ones(numel(indexes),1);bub;inf(numel(indexes),1)]; +0396 +0397 prob.b=zeros(size(prob.a,1), 1); +0398 +0399 intsIndexes = find(prob.c ~= 0); +0400 prob.x0=[]; +0401 prob.vartype=repmat('C', 1, size(prob.A,2)); +0402 prob.vartype(intsIndexes) = 'B'; +0403 prob.csense = repmat('E', 1, size(prob.A,1)); +0404 prob.osense=1; %minimize the objective +0405 +0406 %Defaults that suit this formulation, without discarding what the caller set +0407 if ~isfield(params,'intTol') +0408 params.intTol = 10^-9; +0409 end +0410 if ~isfield(params,'TimeLimit') +0411 params.TimeLimit = 300; +0412 end +0413 if ~isfield(params,'Seed') +0414 %Although the solver reports "optimal solution found", different seeds +0415 %can return solutions with noticeably different objective values, so a +0416 %fixed one is used to keep results reproducible +0417 params.Seed = 26; +0418 end +0419 +0420 % Optimize the problem +0421 res = optimizeProb(prob,params,verbose); +0422 [isFeasible, isOptimal]=checkSolution(res); +0423 +0424 if ~isFeasible +0425 x=[]; +0426 I=[]; +0427 exitFlag=-1; +0428 if res.hitTimeLimit +0429 %Nothing is returned either way, but the caller reports -1 as "no +0430 %feasible solution exists", which is not what happened here. +0431 EM='Time limit reached before finding a solution. Try increasing the TimeLimit parameter.'; +0432 warning('RAVEN:warning', '%s', EM); +0433 end +0434 return; +0435 end +0436 if res.hitTimeLimit || ~isOptimal +0437 %A feasible but suboptimal solution, i.e. the solver stopped before +0438 %reaching optimality. The solution is still returned, but must not be +0439 %reported as optimal: it may use more fluxes than the minimum. +0440 %Not every solver reports this in its status, gurobi for one presents a +0441 %MILP solution found before the limit as optimal, so res.hitTimeLimit is +0442 %consulted as well. +0443 exitFlag=-2; +0444 end +0445 +0446 if resolveTies +0447 %This MILP is highly degenerate: many reaction subsets reach the same +0448 %score optimum, and the solver returns an arbitrary one -- reproducible +0449 %for a fixed solver build + Seed, but fragile to a solver version bump +0450 %or an unrelated change upstream of the MILP (see raven-gecko-parity#104, +0451 %where this was demonstrated on genome-scale Human-GEM: the same Seed, +0452 %same problem, but a different MILP construction path flipped a tied +0453 %pair of reactions). This runs a lexicographic phase 2, holding the +0454 %score objective at its optimum with a floor constraint: first minimise +0455 %the count of "on" reactions (the sparsest optimum), then, among the +0456 %sparsest, minimise their summed reaction-id rank (prefer lower ids). +0457 %Ports raven-toolbox's _resolve_ties_fill (raven-toolbox#114). +0458 % +0459 %intCols identifies the int/binary variable columns directly from the +0460 %block layout (prob.c = [zeros(rxns); zeros(2*revIndexes); scores(:); +0461 %zeros(mets+indexes)]) rather than via find(prob.c~=0): a zero-scored +0462 %reaction would otherwise silently drop out of the column count and +0463 %misalign every later index into "indexes". +0464 intCols = numel(model.rxns) + 2*numel(revIndexes) + (1:numel(indexes)); +0465 primaryObj = full(prob.c(intCols)' * res.full(intCols)); +0466 tol = max(abs(primaryObj)*1e-7, 1e-7); 0467 -0468 %Phase 2a: fewest "on" reactions among the score-optimal solutions. -0469 countObj = zeros(size(prob.c)); -0470 countObj(intCols) = 1; -0471 tieProb.c = countObj; -0472 res2a = optimizeProb(tieProb,tieParams,verbose); -0473 [isFeasible2a, isOptimal2a] = checkSolution(res2a); -0474 if ~isOptimal2a -0475 unproven{end+1} = 'phase2a-parsimony'; -0476 end -0477 if isFeasible2a -0478 kmin = full(countObj'*res2a.full); -0479 -0480 %Phase 2b: among the sparsest, prefer lower reaction ids -- a -0481 %deterministic tie-break independent of solver/seed. -0482 countRow = sparse(1,size(tieProb.A,2)); -0483 countRow(intCols) = 1; -0484 tieProb2b = tieProb; -0485 tieProb2b.A = [tieProb.A; countRow]; -0486 tieProb2b.a = tieProb2b.A; -0487 tieProb2b.b = [tieProb.b; kmin+0.5]; -0488 tieProb2b.csense = [tieProb.csense 'L']; -0489 -0490 [~, sortOrder] = sort(model.rxns(indexes)); -0491 ranks = zeros(numel(indexes),1); -0492 ranks(sortOrder) = (1:numel(indexes))'; -0493 idObj = zeros(size(prob.c)); -0494 idObj(intCols) = ranks; -0495 tieProb2b.c = idObj; -0496 res2b = optimizeProb(tieProb2b,tieParams,verbose); -0497 [isFeasible2b, isOptimal2b] = checkSolution(res2b); -0498 if ~isOptimal2b -0499 unproven{end+1} = 'phase2b-idrank'; -0500 end -0501 if isFeasible2b -0502 %Adopt the tie-resolved solution; exitFlag still reflects -0503 %whether the *primary* (scores) objective was proven optimal, -0504 %same as without resolveTies. -0505 res = res2b; -0506 end -0507 end -0508 if ~isempty(unproven) -0509 EM = ['getMinNrFluxes tie resolution did not converge (' strjoin(unproven,', ') '): the selection among equally score-optimal solutions is itself an unproven incumbent, so resolveTies has reduced but not removed the run-to-run spread. Raise params.TimeLimit for a proven tie-break.']; -0510 warning('RAVEN:warning', '%s', EM); -0511 end -0512 end -0513 -0514 x=res.full(1:numel(model.rxns));%the fluxes -0515 -0516 ints = res.full(intsIndexes); -0517 %The margin for integers in gurobi is 10^-5; use 10^-3 to have some margin. -0518 I=ismember(toMinimize, model.rxns(indexes(ints > 10^-3))); -0519 -0520 sel = (ints > 10^-12) & (ints < 0.5); -0521 if sum(sel) > 0 -0522 %This may indicate that there is a problem with the tolerances in the solver -0523 disp(['getMinNrFluxes: Some variables meant to be boolean in the MILP have intermediate values. Num vars: ' num2str(sum(sel))]) -0524 end -0525 -0526 end +0468 floorRow = sparse(1,size(prob.A,2)); +0469 floorRow(intCols) = prob.c(intCols)'; +0470 tieProb = prob; +0471 tieProb.A = [prob.A; floorRow]; +0472 tieProb.a = tieProb.A; +0473 tieProb.b = [prob.b; primaryObj+tol]; +0474 tieProb.csense = [prob.csense 'L']; +0475 +0476 %An integer objective needs only a cheap absolute-gap proof, not a tiny +0477 %relative one -- RAVEN's own MIPGap default (1e-12) can be slow to prove +0478 %on a genome-scale problem where a gap below 1 already proves optimality +0479 %exactly (mirrors raven-toolbox's own MIPGap=0/MIPGapAbs=0.4 for the same +0480 %two phases). +0481 tieParams = params; +0482 tieParams.MIPGap = 0; +0483 tieParams.MIPGapAbs = 0.4; +0484 +0485 unproven = {}; +0486 +0487 %Phase 2a: fewest "on" reactions among the score-optimal solutions. +0488 countObj = zeros(size(prob.c)); +0489 countObj(intCols) = 1; +0490 tieProb.c = countObj; +0491 res2a = optimizeProb(tieProb,tieParams,verbose); +0492 [isFeasible2a, isOptimal2a] = checkSolution(res2a); +0493 if ~isOptimal2a +0494 unproven{end+1} = 'phase2a-parsimony'; +0495 end +0496 if isFeasible2a +0497 kmin = full(countObj'*res2a.full); +0498 +0499 %Phase 2b: among the sparsest, prefer lower reaction ids -- a +0500 %deterministic tie-break independent of solver/seed. +0501 countRow = sparse(1,size(tieProb.A,2)); +0502 countRow(intCols) = 1; +0503 tieProb2b = tieProb; +0504 tieProb2b.A = [tieProb.A; countRow]; +0505 tieProb2b.a = tieProb2b.A; +0506 tieProb2b.b = [tieProb.b; kmin+0.5]; +0507 tieProb2b.csense = [tieProb.csense 'L']; +0508 +0509 [~, sortOrder] = sort(model.rxns(indexes)); +0510 ranks = zeros(numel(indexes),1); +0511 ranks(sortOrder) = (1:numel(indexes))'; +0512 idObj = zeros(size(prob.c)); +0513 idObj(intCols) = ranks; +0514 tieProb2b.c = idObj; +0515 res2b = optimizeProb(tieProb2b,tieParams,verbose); +0516 [isFeasible2b, isOptimal2b] = checkSolution(res2b); +0517 if ~isOptimal2b +0518 unproven{end+1} = 'phase2b-idrank'; +0519 end +0520 if isFeasible2b +0521 %Adopt the tie-resolved solution; exitFlag still reflects +0522 %whether the *primary* (scores) objective was proven optimal, +0523 %same as without resolveTies. +0524 res = res2b; +0525 end +0526 end +0527 if ~isempty(unproven) +0528 EM = ['getMinNrFluxes tie resolution did not converge (' strjoin(unproven,', ') '): the selection among equally score-optimal solutions is itself an unproven incumbent, so resolveTies has reduced but not removed the run-to-run spread. Raise params.TimeLimit for a proven tie-break.']; +0529 warning('RAVEN:warning', '%s', EM); +0530 end +0531 end +0532 +0533 x=res.full(1:numel(model.rxns));%the fluxes +0534 +0535 ints = res.full(intsIndexes); +0536 %The margin for integers in gurobi is 10^-5; use 10^-3 to have some margin. +0537 I=ismember(toMinimize, model.rxns(indexes(ints > 10^-3))); +0538 +0539 sel = (ints > 10^-12) & (ints < 0.5); +0540 if sum(sel) > 0 +0541 %This may indicate that there is a problem with the tolerances in the solver +0542 disp(['getMinNrFluxes: Some variables meant to be boolean in the MILP have intermediate values. Num vars: ' num2str(sum(sel))]) +0543 end +0544 +0545 end
Generated by m2html © 2005
\ No newline at end of file diff --git a/doc/solver/optimizeProb.html b/doc/solver/optimizeProb.html index 67281119..d7a4187f 100644 --- a/doc/solver/optimizeProb.html +++ b/doc/solver/optimizeProb.html @@ -50,7 +50,10 @@

DESCRIPTION ^CROSS-REFERENCE INFORMATION ^
 
 <h2><a name=SUBFUNCTIONS ^

+
  • function s_merged=structUpdate(s_old,s_new)
  • function timeLimit = hitTimeLimit(solver,res,milp)
  • function paramlist = renameparams(paramlist,old,new)
  • SOURCE CODE ^

    0001 function res = optimizeProb(prob,varargin)
    @@ -95,359 +98,409 @@ 

    SOURCE CODE ^% ------- 0023 % res : struct 0024 % the output structure from the selected solver RAVENSOLVER (COBRA -0025 % style). -0026 % -0027 % See also -0028 % -------- -0029 % splitProbForConditioning -0030 -0031 p=parseRAVENargs(varargin, {'params',[]; 'verbose',true}); -0032 params=p.params; -0033 if isempty(params) -0034 params=struct(); -0035 end -0036 verbose=p.verbose; -0037 -0038 %Set as global variable for speed improvement if optimizeProb is run many times -0039 global RAVENSOLVER; -0040 %CBT_MILP_SOLVER is a COBRA Toolbox global, set by changeCobraSolver, and -0041 %has to be declared as such to be readable in the MILP check below -0042 global CBT_MILP_SOLVER; -0043 if isempty(RAVENSOLVER) -0044 if(~ispref('RAVEN','solver')) -0045 error('RAVEN:badInput', '%s', 'RAVEN solver not defined or unknown. Try using setRavenSolver(''solver'').'); -0046 else -0047 RAVENSOLVER = getpref('RAVEN','solver'); -0048 end -0049 end -0050 solver=RAVENSOLVER; -0051 if ~all(lower(prob.vartype) == 'c') -0052 milp=true; -0053 errorText = 'glpk is not suitable for solving MILPs, '; -0054 switch solver -0055 case 'glpk' -0056 error([errorText 'select a different solver with setRavenSolver().']) -0057 case 'cobra' -0058 if strcmp(CBT_MILP_SOLVER,'glpk') -0059 error([errorText 'select a different solver with changeCobraSolver() or setRavenSolver().']) -0060 end -0061 end -0062 else -0063 milp=false; -0064 end -0065 -0066 %% Define default parameters, which will then be used to make solver- -0067 % specific solverparams structures -0068 defaultparams.feasTol = 1e-9; -0069 defaultparams.optTol = 1e-9; -0070 defaultparams.objTol = 1e-6; -0071 defaultparams.timeLimit = 1000; -0072 %defaultparams.iterationLimit = 1000; -0073 defaultparams.intTol = 1e-12; -0074 defaultparams.relMipGapTol = 1e-12; -0075 defaultparams.absMipGapTol = 1e-12; -0076 if milp -0077 defaultparams.MIPGap = 1e-12; -0078 defaultparams.Seed = 1; -0079 end -0080 -0081 %% Optionally improve numerical conditioning before solving. This is opt-in -0082 % through params.maxRatio and only applied to LPs. Columns whose coefficients -0083 % span more than maxRatio are split via auxiliary metabolites/variables, which -0084 % preserves the feasible region but reduces the spread within each column. The -0085 % added rows/columns are stripped from the result further down. -0086 condApplied = false; -0087 if isfield(params,'maxRatio') -0088 maxRatio = params.maxRatio; -0089 params = rmfield(params,'maxRatio'); -0090 if ~milp && isnumeric(maxRatio) && isscalar(maxRatio) && isfinite(maxRatio) && maxRatio>1 -0091 [prob, condInfo] = splitProbForConditioning(prob, maxRatio); -0092 condApplied = condInfo.applied; -0093 end -0094 end -0095 -0096 res.obj=[]; -0097 switch solver -0098 %% Use whatever solver is set by COBRA Toolbox changeCobraSolver -0099 case 'cobra' -0100 if milp -0101 cparams=struct('timeLimit',1e9,'printLevel',0,'intTol',1e-6,'relMipGapTol',1e-9); -0102 cparams=structUpdate(cparams,params); -0103 res=solveCobraMILP(prob,cparams); -0104 else -0105 res=solveCobraLP(prob); -0106 end -0107 %% Use Gurobi in a MATLAB environment -0108 case 'gurobi' -0109 if milp -0110 if verbose -0111 solverparams.OutputFlag = 1; -0112 else -0113 solverparams.OutputFlag = 0; -0114 end -0115 solverparams.IntFeasTol = 10^-9; %min val for gurobi -0116 solverparams.MIPGap = defaultparams.MIPGap; -0117 solverparams.Seed = defaultparams.Seed; -0118 else -0119 solverparams.OutputFlag = 0; -0120 end -0121 solverparams.DisplayInterval= 1; % Level of verbosity -0122 solverparams.TimeLimit = defaultparams.timeLimit; -0123 solverparams.FeasibilityTol = defaultparams.feasTol; -0124 solverparams.OptimalityTol = defaultparams.optTol; -0125 solverparams.Presolve = 2; -0126 % getCurrentTask requires the Parallel Computing Toolbox; without it -0127 % the call itself errors, so guard it rather than requiring the -0128 % toolbox for every gurobi solve. -0129 if exist('getCurrentTask','file') && ~isempty(getCurrentTask) % If run in parallel, then one thread per gurobi -0130 solverparams.Threads=1; -0131 end -0132 solverparams = structUpdate(solverparams,params); -0133 -0134 % Restructering problem according to gurobi format -0135 if isfield(prob, 'csense') -0136 prob.sense = renameparams(prob.csense, {'L','G','E'}, {'<','>','='}); -0137 prob = rmfield(prob, {'csense'}); -0138 end -0139 % gurobi minimises unless told otherwise, so an absent osense means -0140 % minimisation; osense is also needed below to sign the duals -0141 osense = 1; -0142 if isfield(prob, 'osense') -0143 osense = prob.osense; -0144 if osense < 0 -0145 prob.modelsense = 'max'; -0146 else -0147 prob.modelsense = 'min'; -0148 end -0149 prob = rmfield(prob, {'osense'}); -0150 end -0151 [prob.obj, prob.rhs, prob.vtype] = deal(prob.c, prob.b, prob.vartype); -0152 prob = rmfield(prob, {'c','b','vartype'}); -0153 -0154 resG = gurobi(prob,solverparams); -0155 -0156 % Name output fields the same as COBRA does. Every field below is -0157 % optional in the gurobi result: which ones are present depends on -0158 % the status and on the method gurobi chose (a barrier solve returns -0159 % no simplex basis, for instance). They are therefore read -0160 % individually rather than under one try/catch, which would have -0161 % reported a missing basis on an optimal solve as "infeasible". -0162 res.origStat = resG.status; -0163 if isfield(resG,'x') -0164 res.full = resG.x; -0165 end -0166 if isfield(resG,'objval') -0167 res.obj = resG.objval; +0025 % style). In addition to the fields set by the solver, the logical field +0026 % "hitTimeLimit" is true if the solver stopped because it reached its +0027 % time limit. Whatever solution is in res is then either absent or not +0028 % proven optimal. +0029 % +0030 % See also +0031 % -------- +0032 % splitProbForConditioning +0033 +0034 p=parseRAVENargs(varargin, {'params',[]; 'verbose',true}); +0035 params=p.params; +0036 if isempty(params) +0037 params=struct(); +0038 end +0039 verbose=p.verbose; +0040 +0041 %Set as global variable for speed improvement if optimizeProb is run many times +0042 global RAVENSOLVER; +0043 %CBT_MILP_SOLVER is a COBRA Toolbox global, set by changeCobraSolver, and +0044 %has to be declared as such to be readable in the MILP check below +0045 global CBT_MILP_SOLVER; +0046 if isempty(RAVENSOLVER) +0047 if(~ispref('RAVEN','solver')) +0048 error('RAVEN:badInput', '%s', 'RAVEN solver not defined or unknown. Try using setRavenSolver(''solver'').'); +0049 else +0050 RAVENSOLVER = getpref('RAVEN','solver'); +0051 end +0052 end +0053 solver=RAVENSOLVER; +0054 if ~all(lower(prob.vartype) == 'c') +0055 milp=true; +0056 errorText = 'glpk is not suitable for solving MILPs, '; +0057 switch solver +0058 case 'glpk' +0059 error([errorText 'select a different solver with setRavenSolver().']) +0060 case 'cobra' +0061 if strcmp(CBT_MILP_SOLVER,'glpk') +0062 error([errorText 'select a different solver with changeCobraSolver() or setRavenSolver().']) +0063 end +0064 end +0065 else +0066 milp=false; +0067 end +0068 +0069 %% Define default parameters, which will then be used to make solver- +0070 % specific solverparams structures +0071 defaultparams.feasTol = 1e-9; +0072 defaultparams.optTol = 1e-9; +0073 defaultparams.objTol = 1e-6; +0074 defaultparams.timeLimit = 1000; +0075 %defaultparams.iterationLimit = 1000; +0076 defaultparams.intTol = 1e-12; +0077 defaultparams.relMipGapTol = 1e-12; +0078 defaultparams.absMipGapTol = 1e-12; +0079 if milp +0080 defaultparams.MIPGap = 1e-12; +0081 defaultparams.Seed = 1; +0082 end +0083 +0084 %% Optionally improve numerical conditioning before solving. This is opt-in +0085 % through params.maxRatio and only applied to LPs. Columns whose coefficients +0086 % span more than maxRatio are split via auxiliary metabolites/variables, which +0087 % preserves the feasible region but reduces the spread within each column. The +0088 % added rows/columns are stripped from the result further down. +0089 condApplied = false; +0090 if isfield(params,'maxRatio') +0091 maxRatio = params.maxRatio; +0092 params = rmfield(params,'maxRatio'); +0093 if ~milp && isnumeric(maxRatio) && isscalar(maxRatio) && isfinite(maxRatio) && maxRatio>1 +0094 [prob, condInfo] = splitProbForConditioning(prob, maxRatio); +0095 condApplied = condInfo.applied; +0096 end +0097 end +0098 +0099 res.obj=[]; +0100 switch solver +0101 %% Use whatever solver is set by COBRA Toolbox changeCobraSolver +0102 case 'cobra' +0103 if milp +0104 cparams=struct('timeLimit',1e9,'printLevel',0,'intTol',1e-6,'relMipGapTol',1e-9); +0105 cparams=structUpdate(cparams,params); +0106 res=solveCobraMILP(prob,cparams); +0107 else +0108 res=solveCobraLP(prob); +0109 end +0110 %% Use Gurobi in a MATLAB environment +0111 case 'gurobi' +0112 if milp +0113 if verbose +0114 solverparams.OutputFlag = 1; +0115 else +0116 solverparams.OutputFlag = 0; +0117 end +0118 solverparams.IntFeasTol = 10^-9; %min val for gurobi +0119 solverparams.MIPGap = defaultparams.MIPGap; +0120 solverparams.Seed = defaultparams.Seed; +0121 else +0122 solverparams.OutputFlag = 0; +0123 end +0124 solverparams.DisplayInterval= 1; % Level of verbosity +0125 solverparams.TimeLimit = defaultparams.timeLimit; +0126 solverparams.FeasibilityTol = defaultparams.feasTol; +0127 solverparams.OptimalityTol = defaultparams.optTol; +0128 solverparams.Presolve = 2; +0129 % getCurrentTask requires the Parallel Computing Toolbox; without it +0130 % the call itself errors, so guard it rather than requiring the +0131 % toolbox for every gurobi solve. +0132 if exist('getCurrentTask','file') && ~isempty(getCurrentTask) % If run in parallel, then one thread per gurobi +0133 solverparams.Threads=1; +0134 end +0135 solverparams = structUpdate(solverparams,params); +0136 +0137 % Restructering problem according to gurobi format +0138 if isfield(prob, 'csense') +0139 prob.sense = renameparams(prob.csense, {'L','G','E'}, {'<','>','='}); +0140 prob = rmfield(prob, {'csense'}); +0141 end +0142 % gurobi minimises unless told otherwise, so an absent osense means +0143 % minimisation; osense is also needed below to sign the duals +0144 osense = 1; +0145 if isfield(prob, 'osense') +0146 osense = prob.osense; +0147 if osense < 0 +0148 prob.modelsense = 'max'; +0149 else +0150 prob.modelsense = 'min'; +0151 end +0152 prob = rmfield(prob, {'osense'}); +0153 end +0154 [prob.obj, prob.rhs, prob.vtype] = deal(prob.c, prob.b, prob.vartype); +0155 prob = rmfield(prob, {'c','b','vartype'}); +0156 +0157 resG = gurobi(prob,solverparams); +0158 +0159 % Name output fields the same as COBRA does. Every field below is +0160 % optional in the gurobi result: which ones are present depends on +0161 % the status and on the method gurobi chose (a barrier solve returns +0162 % no simplex basis, for instance). They are therefore read +0163 % individually rather than under one try/catch, which would have +0164 % reported a missing basis on an optimal solve as "infeasible". +0165 res.origStat = resG.status; +0166 if isfield(resG,'x') +0167 res.full = resG.x; 0168 end -0169 if isfield(resG,'pi') && isfield(resG,'rc') -0170 res.dual = -resG.pi*osense; -0171 res.rcost = -resG.rc*osense; -0172 end -0173 if milp && strcmp(resG.status, 'TIME_LIMIT') && isfield(resG,'x') && isfield(resG,'objval') -0174 % An incumbent was found before the time limit. It is usable but -0175 % not proven optimal, so it is reported as feasible rather than -0176 % optimal; callers that must not act on a suboptimal MILP -0177 % solution (getMinNrFluxes, solveLP) key on that distinction. -0178 resG.status = 'SUBOPTIMAL'; -0179 end -0180 switch resG.status -0181 case 'OPTIMAL' -0182 res.stat = 1; -0183 case {'UNBOUNDED','SUBOPTIMAL'} -0184 res.stat = 2; -0185 otherwise -0186 res.stat = 0; -0187 end -0188 if ~milp -0189 if isfield(resG,'vbasis') -0190 res.vbasis = resG.vbasis; -0191 end -0192 if isfield(resG,'cbasis') -0193 res.cbasis = resG.cbasis; +0169 if isfield(resG,'objval') +0170 res.obj = resG.objval; +0171 end +0172 if isfield(resG,'pi') && isfield(resG,'rc') +0173 res.dual = -resG.pi*osense; +0174 res.rcost = -resG.rc*osense; +0175 end +0176 if milp && strcmp(resG.status, 'TIME_LIMIT') && isfield(resG,'x') && isfield(resG,'objval') +0177 % An incumbent was found before the time limit. It is usable but +0178 % not proven optimal, so it is reported as feasible rather than +0179 % optimal; callers that must not act on a suboptimal MILP +0180 % solution (getMinNrFluxes, solveLP) key on that distinction. +0181 resG.status = 'SUBOPTIMAL'; +0182 end +0183 switch resG.status +0184 case 'OPTIMAL' +0185 res.stat = 1; +0186 case {'UNBOUNDED','SUBOPTIMAL'} +0187 res.stat = 2; +0188 otherwise +0189 res.stat = 0; +0190 end +0191 if ~milp +0192 if isfield(resG,'vbasis') +0193 res.vbasis = resG.vbasis; 0194 end -0195 elseif isfield(resG,'mipgap') -0196 res.mipgap = resG.mipgap; -0197 end -0198 if res.stat>0 && ~isfield(res,'full') -0199 % A status that claims a solution without returning one -0200 res.stat = 0; -0201 end -0202 %% Use GLPK using RAVEN-provided binary -0203 case 'glpk' -0204 solverparams.scale = 1; % Auto scaling -0205 %solverparams.tmlim = defaultparams.timeLimit; -0206 solverparams.tolbnd = defaultparams.feasTol; -0207 solverparams.toldj = defaultparams.optTol; -0208 solverparams.tolint = defaultparams.intTol; -0209 solverparams.tolobj = defaultparams.objTol; -0210 solverparams.msglev = 0; % Level of verbosity -0211 solverparams = structUpdate(solverparams,params); -0212 -0213 prob.csense = renameparams(prob.csense, {'L','G','E'}, {'U','L','S'}); -0214 -0215 if milp -0216 solverparams.tmlim = solverparams.tmlim*10; -0217 solverparams.msglev = 1; % Level of verbosity -0218 disp('Issues have been observed when using GLPK for MILP solving. Be advised to carefully observe the results, or us another solver.') -0219 end -0220 -0221 % Ensure that RAVEN glpk binary is used, return to original -0222 % directory afterwards -0223 [ravenDir,currDir]=findRAVENroot(); -0224 cd(fullfile(ravenDir,'software','GLPKmex')) -0225 [xopt, fmin, errnum, extra] = glpk(prob.c, prob.A, prob.b, prob.lb, prob.ub, prob.csense, prob.vartype, prob.osense, solverparams); -0226 cd(currDir) -0227 -0228 switch errnum % 1 = undefined; 2 = feasible; 3 = infeasible; 4 = no feasible solution; 5 = optimal; 6 = no unbounded solution -0229 case 5 -0230 res.stat = 1; % Optimal -0231 case 2 -0232 res.stat = 2; % Feasible, but not optimal -0233 otherwise -0234 res.stat = 0; -0235 end -0236 res.origStat = errnum; -0237 res.full = xopt; -0238 res.obj = fmin; -0239 res.dual = -extra.lambda*prob.osense; -0240 res.rcost = -extra.redcosts*prob.osense; -0241 %% Use scip -0242 case {'soplex','scip'} % Old 'soplex' option also allowed -0243 % scip takes a row range [rl,ru] rather than a right-hand side plus -0244 % a sense, so csense has to be translated into that range. Passing -0245 % [-b,b] is only the same problem when every row is an equality with -0246 % b = 0, which is the case for solveLP but for none of the MILPs -0247 % built elsewhere in RAVEN. -0248 rl = full(prob.b(:)); -0249 ru = rl; -0250 csense = prob.csense(:); -0251 rl(csense=='L') = -inf; % A*x <= b -0252 ru(csense=='G') = inf; % A*x >= b -0253 [xopt,fval,exitflag,scipStats] = scip([], prob.c, prob.A, rl, ru, prob.lb, prob.ub, prob.vartype); -0254 -0255 % [x,fval,exitflag,stats] = scip(H, f, A, rl, ru, lb, ub, xtype, sos, qc, nl, x0, opts) -0256 % -0257 % Input arguments*: -0258 % H - quadratic objective matrix (sparse, optional [NOT TRIL / TRIU]) -0259 % f - linear objective vector -0260 % A - linear constraint matrix (sparse) -0261 % rl - linear constraint lhs -0262 % ru - linear constraint rhs -0263 % lb - decision variable lower bounds -0264 % ub - decision variable upper bounds -0265 % xtype - string of variable integrality ("c" continuous, "i" integer, "b" binary) -0266 % sos - SOS structure with fields type, index and weight (see below) -0267 % qc - Quadratic Constraints structure with fields Q, l, qrl and qru (see below) -0268 % nl - Nonlinear Objective and Constraints structure (see below) -0269 % x0 - primal solution -0270 % opts - solver options (see below) -0271 % -0272 % Return arguments: -0273 % x - solution vector -0274 % fval - objective value at the solution -0275 % exitflag - exit status (see below) -0276 % stats - statistics structure -0277 % -0278 % Option Fields (all optional, see also optiset for a list): -0279 % solverOpts - specific SCIP options (list of pairs of parameter names and values) -0280 % maxiter - maximum LP solver iterations -0281 % maxnodes - maximum nodes to explore -0282 % maxtime - maximum execution time [s] -0283 % tolrfun - primal feasibility tolerance -0284 % display - solver display level [0-5] -0285 % probfile - write problem to given file -0286 % presolvedfile - write presolved problem to file -0287 % -0288 % Return Status, as returned by the MatlabSCIPInterface build that -0289 % RAVEN provides and that setRavenSolver points at -0290 % (github.com/scipopt/MatlabSCIPInterface). Verified against that -0291 % binary with a bounded, an infeasible and an unbounded problem: -0292 % 1 - Problem Solved to Optimality -0293 % 2 - Problem is Infeasible -0294 % 3 - Problem is Unbounded -0295 % -0296 % The OPTI Toolbox build of scip uses a different, longer set in -0297 % which optimality is 11, infeasibility 12 and unboundedness 13, -0298 % with the lower numbers meaning "a limit was reached". Those codes -0299 % are accepted below as well, so either build reports optimality -0300 % correctly; only OPTI's 1 ("User Interrupted") is ambiguous, and -0301 % reading it as optimal is no worse than the previous mapping, -0302 % which recognised no status from the shipped build at all. -0303 -0304 res.origStat = exitflag; -0305 res.full = xopt; -0306 res.obj = fval; -0307 -0308 switch exitflag -0309 case {1, 11} % solved to optimality -0310 res.stat = 1; -0311 case {3, 13} % unbounded -0312 res.stat = 2; -0313 case {5, 6, 7, 8, 9, 10} % OPTI: a limit was reached, so a -0314 res.stat = 2; % solution may exist but is not proven optimal -0315 otherwise % 2/12 infeasible, 0 unknown, 4, 14 -0316 res.stat = 0; -0317 end -0318 -0319 if milp -0320 % Callers such as ftINIT compare this against their own -0321 % params.MIPGap, so it has to be the quantity the gurobi branch -0322 % reports there: a relative gap. It is derived from the bounds -0323 % rather than read from scipStats.BBgap, whose units are not -0324 % documented. inf where no gap can be established, so that a -0325 % caller testing "gap small enough" does not accept the solution. -0326 if res.stat == 1 -0327 res.mipgap = 0; -0328 else -0329 res.mipgap = inf; -0330 primal = scipStats.PrimalBound; -0331 if isfinite(primal) && abs(primal) > 1e-10 -0332 res.mipgap = abs(primal-scipStats.DualBound)/abs(primal); -0333 end -0334 end -0335 end -0336 otherwise -0337 error('RAVEN solver not defined or unknown. Try using setRavenSolver(''solver'').'); -0338 end -0339 if res.stat>0 -0340 res.full=res.full(1:size(prob.a,2)); +0195 if isfield(resG,'cbasis') +0196 res.cbasis = resG.cbasis; +0197 end +0198 elseif isfield(resG,'mipgap') +0199 res.mipgap = resG.mipgap; +0200 end +0201 if res.stat>0 && ~isfield(res,'full') +0202 % A status that claims a solution without returning one +0203 res.stat = 0; +0204 end +0205 %% Use GLPK using RAVEN-provided binary +0206 case 'glpk' +0207 solverparams.scale = 1; % Auto scaling +0208 %solverparams.tmlim = defaultparams.timeLimit; +0209 solverparams.tolbnd = defaultparams.feasTol; +0210 solverparams.toldj = defaultparams.optTol; +0211 solverparams.tolint = defaultparams.intTol; +0212 solverparams.tolobj = defaultparams.objTol; +0213 solverparams.msglev = 0; % Level of verbosity +0214 solverparams = structUpdate(solverparams,params); +0215 +0216 prob.csense = renameparams(prob.csense, {'L','G','E'}, {'U','L','S'}); +0217 +0218 if milp +0219 solverparams.tmlim = solverparams.tmlim*10; +0220 solverparams.msglev = 1; % Level of verbosity +0221 disp('Issues have been observed when using GLPK for MILP solving. Be advised to carefully observe the results, or us another solver.') +0222 end +0223 +0224 % Ensure that RAVEN glpk binary is used, return to original +0225 % directory afterwards +0226 [ravenDir,currDir]=findRAVENroot(); +0227 cd(fullfile(ravenDir,'software','GLPKmex')) +0228 [xopt, fmin, errnum, extra] = glpk(prob.c, prob.A, prob.b, prob.lb, prob.ub, prob.csense, prob.vartype, prob.osense, solverparams); +0229 cd(currDir) +0230 +0231 switch errnum % 1 = undefined; 2 = feasible; 3 = infeasible; 4 = no feasible solution; 5 = optimal; 6 = no unbounded solution +0232 case 5 +0233 res.stat = 1; % Optimal +0234 case 2 +0235 res.stat = 2; % Feasible, but not optimal +0236 otherwise +0237 res.stat = 0; +0238 end +0239 res.origStat = errnum; +0240 res.full = xopt; +0241 res.obj = fmin; +0242 res.dual = -extra.lambda*prob.osense; +0243 res.rcost = -extra.redcosts*prob.osense; +0244 %% Use scip +0245 case {'soplex','scip'} % Old 'soplex' option also allowed +0246 % scip takes a row range [rl,ru] rather than a right-hand side plus +0247 % a sense, so csense has to be translated into that range. Passing +0248 % [-b,b] is only the same problem when every row is an equality with +0249 % b = 0, which is the case for solveLP but for none of the MILPs +0250 % built elsewhere in RAVEN. +0251 rl = full(prob.b(:)); +0252 ru = rl; +0253 csense = prob.csense(:); +0254 rl(csense=='L') = -inf; % A*x <= b +0255 ru(csense=='G') = inf; % A*x >= b +0256 [xopt,fval,exitflag,scipStats] = scip([], prob.c, prob.A, rl, ru, prob.lb, prob.ub, prob.vartype); +0257 +0258 % [x,fval,exitflag,stats] = scip(H, f, A, rl, ru, lb, ub, xtype, sos, qc, nl, x0, opts) +0259 % +0260 % Input arguments*: +0261 % H - quadratic objective matrix (sparse, optional [NOT TRIL / TRIU]) +0262 % f - linear objective vector +0263 % A - linear constraint matrix (sparse) +0264 % rl - linear constraint lhs +0265 % ru - linear constraint rhs +0266 % lb - decision variable lower bounds +0267 % ub - decision variable upper bounds +0268 % xtype - string of variable integrality ("c" continuous, "i" integer, "b" binary) +0269 % sos - SOS structure with fields type, index and weight (see below) +0270 % qc - Quadratic Constraints structure with fields Q, l, qrl and qru (see below) +0271 % nl - Nonlinear Objective and Constraints structure (see below) +0272 % x0 - primal solution +0273 % opts - solver options (see below) +0274 % +0275 % Return arguments: +0276 % x - solution vector +0277 % fval - objective value at the solution +0278 % exitflag - exit status (see below) +0279 % stats - statistics structure +0280 % +0281 % Option Fields (all optional, see also optiset for a list): +0282 % solverOpts - specific SCIP options (list of pairs of parameter names and values) +0283 % maxiter - maximum LP solver iterations +0284 % maxnodes - maximum nodes to explore +0285 % maxtime - maximum execution time [s] +0286 % tolrfun - primal feasibility tolerance +0287 % display - solver display level [0-5] +0288 % probfile - write problem to given file +0289 % presolvedfile - write presolved problem to file +0290 % +0291 % Return Status, as returned by the MatlabSCIPInterface build that +0292 % RAVEN provides and that setRavenSolver points at +0293 % (github.com/scipopt/MatlabSCIPInterface). Verified against that +0294 % binary with a bounded, an infeasible and an unbounded problem: +0295 % 1 - Problem Solved to Optimality +0296 % 2 - Problem is Infeasible +0297 % 3 - Problem is Unbounded +0298 % +0299 % The OPTI Toolbox build of scip uses a different, longer set in +0300 % which optimality is 11, infeasibility 12 and unboundedness 13, +0301 % with the lower numbers meaning "a limit was reached". Those codes +0302 % are accepted below as well, so either build reports optimality +0303 % correctly; only OPTI's 1 ("User Interrupted") is ambiguous, and +0304 % reading it as optimal is no worse than the previous mapping, +0305 % which recognised no status from the shipped build at all. +0306 +0307 res.origStat = exitflag; +0308 res.full = xopt; +0309 res.obj = fval; +0310 +0311 switch exitflag +0312 case {1, 11} % solved to optimality +0313 res.stat = 1; +0314 case {3, 13} % unbounded +0315 res.stat = 2; +0316 case {5, 6, 7, 8, 9, 10} % OPTI: a limit was reached, so a +0317 res.stat = 2; % solution may exist but is not proven optimal +0318 otherwise % 2/12 infeasible, 0 unknown, 4, 14 +0319 res.stat = 0; +0320 end +0321 +0322 if milp +0323 % Callers such as ftINIT compare this against their own +0324 % params.MIPGap, so it has to be the quantity the gurobi branch +0325 % reports there: a relative gap. It is derived from the bounds +0326 % rather than read from scipStats.BBgap, whose units are not +0327 % documented. inf where no gap can be established, so that a +0328 % caller testing "gap small enough" does not accept the solution. +0329 if res.stat == 1 +0330 res.mipgap = 0; +0331 else +0332 res.mipgap = inf; +0333 primal = scipStats.PrimalBound; +0334 if isfinite(primal) && abs(primal) > 1e-10 +0335 res.mipgap = abs(primal-scipStats.DualBound)/abs(primal); +0336 end +0337 end +0338 end +0339 otherwise +0340 error('RAVEN solver not defined or unknown. Try using setRavenSolver(''solver'').'); 0341 end -0342 %Strip the auxiliary rows/columns that were added for conditioning, so that -0343 %the result matches the dimensions of the original problem. res.full is -0344 %already truncated to the original variables above (prob.a is left untouched). -0345 if condApplied -0346 if isfield(res,'dual') && numel(res.dual)>condInfo.nOrigRows -0347 res.dual=res.dual(1:condInfo.nOrigRows); -0348 end -0349 if isfield(res,'rcost') && numel(res.rcost)>condInfo.nOrigCols -0350 res.rcost=res.rcost(1:condInfo.nOrigCols); -0351 end -0352 end -0353 end -0354 -0355 function s_merged=structUpdate(s_old,s_new) -0356 %Remove overlapping fields from first struct; -0357 %Obtain all unique names of remaining fields; -0358 %Merge both structs -0359 s_merged = rmfield(s_old, intersect(fieldnames(s_old), fieldnames(s_new))); -0360 names = [fieldnames(s_merged); fieldnames(s_new)]; -0361 s_merged = cell2struct([struct2cell(s_merged); struct2cell(s_new)], names, 1); +0342 %Flag a solve that ended on the time limit. res.stat cannot express this on +0343 %its own: a solver that times out with an incumbent solution in hand reports +0344 %the same status as one that finished, and a solver that times out with +0345 %nothing reports the same status as an infeasible problem. The raw status in +0346 %res.origStat does distinguish the two, but only in solver-specific terms, +0347 %so it is translated here where the solver is still known. +0348 res.hitTimeLimit=hitTimeLimit(solver,res,milp); +0349 if res.stat>0 +0350 res.full=res.full(1:size(prob.a,2)); +0351 end +0352 %Strip the auxiliary rows/columns that were added for conditioning, so that +0353 %the result matches the dimensions of the original problem. res.full is +0354 %already truncated to the original variables above (prob.a is left untouched). +0355 if condApplied +0356 if isfield(res,'dual') && numel(res.dual)>condInfo.nOrigRows +0357 res.dual=res.dual(1:condInfo.nOrigRows); +0358 end +0359 if isfield(res,'rcost') && numel(res.rcost)>condInfo.nOrigCols +0360 res.rcost=res.rcost(1:condInfo.nOrigCols); +0361 end 0362 end -0363 -0364 function paramlist = renameparams(paramlist,old,new) -0365 if ~iscell(paramlist) -0366 wasNoCell = true; -0367 paramlist={paramlist}; -0368 else -0369 wasNoCell = false; -0370 end -0371 for i=1:numel(old) -0372 paramlist = regexprep(paramlist,old{i},new{i}); -0373 end -0374 if wasNoCell -0375 paramlist=paramlist{1}; -0376 end -0377 end

    +0363 end +0364 +0365 function s_merged=structUpdate(s_old,s_new) +0366 %Remove overlapping fields from first struct; +0367 %Obtain all unique names of remaining fields; +0368 %Merge both structs +0369 s_merged = rmfield(s_old, intersect(fieldnames(s_old), fieldnames(s_new))); +0370 names = [fieldnames(s_merged); fieldnames(s_new)]; +0371 s_merged = cell2struct([struct2cell(s_merged); struct2cell(s_new)], names, 1); +0372 end +0373 +0374 function timeLimit = hitTimeLimit(solver,res,milp) +0375 %Report whether the solver that produced res stopped on its time limit. Each +0376 %solver has its own status codes for this, and the "cobra" option hands the +0377 %status of the solver that COBRA dispatched to straight through, so that +0378 %solver is looked up as well. +0379 global CBT_LP_SOLVER CBT_MILP_SOLVER +0380 timeLimit=false; +0381 if ~isfield(res,'origStat') +0382 return; +0383 end +0384 if strcmp(solver,'cobra') +0385 if milp +0386 solver=CBT_MILP_SOLVER; +0387 else +0388 solver=CBT_LP_SOLVER; +0389 end +0390 if isempty(solver) || ~ischar(solver) +0391 return; +0392 end +0393 end +0394 origStat=res.origStat; +0395 isText=ischar(origStat) || isstring(origStat); +0396 isCode=isnumeric(origStat) && isscalar(origStat); +0397 switch lower(solver) +0398 case 'gurobi' +0399 timeLimit=isText && strcmp(origStat,'TIME_LIMIT'); +0400 case 'mosek' +0401 timeLimit=isText && strcmp(origStat,'MSK_RES_TRM_MAX_TIME'); +0402 case 'glpk' +0403 %109 from the simplex method, 209 from the interior point method +0404 timeLimit=isCode && ismember(origStat,[109 209]); +0405 case {'scip','soplex'} +0406 timeLimit=isCode && origStat==5; +0407 case {'ibm_cplex','tomlab_cplex','cplex_direct'} +0408 %11 for an LP, 107 and 108 for a MILP with and without an integer +0409 %solution found before the limit was reached +0410 timeLimit=isCode && ismember(origStat,[11 107 108]); +0411 end +0412 end +0413 +0414 function paramlist = renameparams(paramlist,old,new) +0415 if ~iscell(paramlist) +0416 wasNoCell = true; +0417 paramlist={paramlist}; +0418 else +0419 wasNoCell = false; +0420 end +0421 for i=1:numel(old) +0422 paramlist = regexprep(paramlist,old{i},new{i}); +0423 end +0424 if wasNoCell +0425 paramlist=paramlist{1}; +0426 end +0427 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/doc/testing/function_tests/tAnalysis.html b/doc/testing/function_tests/tAnalysis.html index 049e282a..e84acb49 100644 --- a/doc/testing/function_tests/tAnalysis.html +++ b/doc/testing/function_tests/tAnalysis.html @@ -79,321 +79,323 @@

    SOURCE CODE ^'[x, I, exitFlag] = getMinNrFluxes(testCase.model, testCase.model.rxns);'); 0035 testCase.verifyNotEmpty(x); -0036 end -0037 -0038 function getMinNrFluxesFormulationsAgree(testCase) -0039 % The two formulations build different MILPs for the same -0040 % problem, so they may pick different reactions, but both -0041 % minimise the same objective and must therefore pick the same -0042 % number of them. -0043 testCase.assumeMILPSolver(); -0044 evalc('[xIrrev, Iirrev, flagIrrev] = getMinNrFluxes(testCase.model, testCase.model.rxns, [], [], ''irrev'');'); -0045 evalc('[xRev, Irev, flagRev] = getMinNrFluxes(testCase.model, testCase.model.rxns, [], [], ''reversible'');'); -0046 testCase.verifyEqual(flagIrrev, 1); -0047 testCase.verifyEqual(flagRev, 1); -0048 testCase.verifyNumElements(xRev, numel(testCase.model.rxns)); -0049 testCase.verifyNumElements(Irev, numel(testCase.model.rxns)); -0050 testCase.verifyEqual(sum(Irev), sum(Iirrev)); -0051 end -0052 -0053 function getMinNrFluxesResolveTiesIsDeterministic(testCase) -0054 % The reversible-formulation MILP is degenerate on a real model -0055 % (see getMinNrFluxesFormulationsAgree above): resolveTies pins -0056 % it to a canonical answer instead of leaving the choice to the -0057 % solver/seed, so three independent runs must all agree -0058 % (raven-gecko-parity#104). -0059 testCase.assumeMILPSolver(); -0060 evalc(['[~, I1, flag1] = getMinNrFluxes(testCase.model, testCase.model.rxns, ' ... -0061 '[], [], ''reversible'', false, ''resolveTies'', true);']); -0062 evalc(['[~, I2, flag2] = getMinNrFluxes(testCase.model, testCase.model.rxns, ' ... +0036 %A problem this small is solved well within the time limit +0037 testCase.verifyEqual(exitFlag, 1); +0038 end +0039 +0040 function getMinNrFluxesFormulationsAgree(testCase) +0041 % The two formulations build different MILPs for the same +0042 % problem, so they may pick different reactions, but both +0043 % minimise the same objective and must therefore pick the same +0044 % number of them. +0045 testCase.assumeMILPSolver(); +0046 evalc('[xIrrev, Iirrev, flagIrrev] = getMinNrFluxes(testCase.model, testCase.model.rxns, [], [], ''irrev'');'); +0047 evalc('[xRev, Irev, flagRev] = getMinNrFluxes(testCase.model, testCase.model.rxns, [], [], ''reversible'');'); +0048 testCase.verifyEqual(flagIrrev, 1); +0049 testCase.verifyEqual(flagRev, 1); +0050 testCase.verifyNumElements(xRev, numel(testCase.model.rxns)); +0051 testCase.verifyNumElements(Irev, numel(testCase.model.rxns)); +0052 testCase.verifyEqual(sum(Irev), sum(Iirrev)); +0053 end +0054 +0055 function getMinNrFluxesResolveTiesIsDeterministic(testCase) +0056 % The reversible-formulation MILP is degenerate on a real model +0057 % (see getMinNrFluxesFormulationsAgree above): resolveTies pins +0058 % it to a canonical answer instead of leaving the choice to the +0059 % solver/seed, so three independent runs must all agree +0060 % (raven-gecko-parity#104). +0061 testCase.assumeMILPSolver(); +0062 evalc(['[~, I1, flag1] = getMinNrFluxes(testCase.model, testCase.model.rxns, ' ... 0063 '[], [], ''reversible'', false, ''resolveTies'', true);']); -0064 evalc(['[~, I3, flag3] = getMinNrFluxes(testCase.model, testCase.model.rxns, ' ... +0064 evalc(['[~, I2, flag2] = getMinNrFluxes(testCase.model, testCase.model.rxns, ' ... 0065 '[], [], ''reversible'', false, ''resolveTies'', true);']); -0066 testCase.verifyEqual(flag1, 1); -0067 testCase.verifyEqual(flag2, 1); -0068 testCase.verifyEqual(flag3, 1); -0069 testCase.verifyEqual(I1, I2); -0070 testCase.verifyEqual(I2, I3); -0071 end -0072 -0073 function getMinNrFluxesResolveTiesOnlySupportsReversible(testCase) -0074 testCase.verifyError( ... -0075 @() getMinNrFluxes(testCase.model, testCase.model.rxns, [], [], 'irrev', ... -0076 false, 'resolveTies', true), ... -0077 'RAVEN:badInput'); -0078 end -0079 -0080 function getMinNrFluxesRejectsUnknownFormulation(testCase) -0081 testCase.verifyError( ... -0082 @() getMinNrFluxes(testCase.model, testCase.model.rxns, [], [], 'both'), ... -0083 'RAVEN:badInput'); -0084 end -0085 -0086 function getAllSubGraphsReturnsResult(testCase) -0087 sg = getAllSubGraphs(testCase.model); -0088 testCase.verifyNotEmpty(sg); -0089 end -0090 -0091 function findGeneDeletionsRuns(testCase) -0092 evalc('[genes, fluxes] = findGeneDeletions(testCase.model, ''sgd'');'); -0093 testCase.verifyNotEmpty(genes); -0094 end -0095 -0096 function findGeneDeletionsComputesGrRatio(testCase) -0097 evalc(['[genes, ~, ~, ~, grRatioMuts] = findGeneDeletions(testCase.model, ' ... -0098 '''sgd'');']); -0099 testCase.verifyNotEmpty(genes); -0100 testCase.verifyEqual(numel(grRatioMuts), numel(genes)); -0101 testCase.verifyGreaterThan(max(grRatioMuts), 0); -0102 end -0103 -0104 function traceFluxPathReturnsCells(testCase) -0105 testCase.assumeSolver('solveLP'); -0106 sol = solveLP(testCase.model); -0107 % Use two reactions that must be metabolically connected -0108 biomassRxn = testCase.model.rxns{find(testCase.model.c == 1, 1)}; -0109 [~, exchIdx] = getExchangeRxns(testCase.model); -0110 % Pick an exchange reaction with non-zero flux -0111 exchFlux = sol.x(exchIdx); -0112 active = exchIdx(abs(exchFlux) > 1e-8); -0113 if isempty(active), testCase.assumeTrue(false); end -0114 targetRxn = testCase.model.rxns{active(1)}; -0115 evalc('[p, m, f] = traceFluxPath(testCase.model, sol.x, biomassRxn, targetRxn, ''verbose'', false);'); -0116 % Either a path is found (cell + double) or not — both are valid -0117 testCase.verifyClass(f, 'double'); -0118 testCase.verifyGreaterThanOrEqual(f, 0); -0119 testCase.verifyLessThanOrEqual(f, 1); -0120 end -0121 -0122 function traceFluxPathSameReactionIsIdentity(testCase) -0123 testCase.assumeSolver('solveLP'); -0124 sol = solveLP(testCase.model); -0125 rxn1 = testCase.model.rxns{find(testCase.model.c == 1, 1)}; -0126 [p, m, f] = traceFluxPath(testCase.model, sol.x, rxn1, rxn1, 'verbose', false); -0127 testCase.verifyEqual(f, 1.0); -0128 testCase.verifyNumElements(p, 1); -0129 testCase.verifyEmpty(m); -0130 end -0131 -0132 function walkFluxesRefusesNonInteractive(testCase) -0133 % The test suite itself runs under -batch, so this exercises the -0134 % real guard rather than a mocked one. -0135 testCase.assumeTrue(batchStartupOptionUsed, ... -0136 'Test runner is not using -batch; the non-interactive guard cannot be exercised here.'); -0137 testCase.assumeSolver('solveLP'); -0138 sol = solveLP(testCase.model); -0139 biomassRxn = testCase.model.rxns{find(testCase.model.c == 1, 1)}; -0140 testCase.verifyError(@() walkFluxes(testCase.model, sol.x, biomassRxn), ... -0141 'walkFluxes:nonInteractive'); -0142 end -0143 -0144 function compareFluxesReturnsResult(testCase) -0145 testCase.assumeSolver('solveLP'); -0146 solA = solveLP(testCase.model); -0147 o2exch = find(strcmp(testCase.model.rxnNames, 'O2 exchange'), 1); -0148 modelAna = setParam(testCase.model, 'eq', testCase.model.rxns(o2exch), 0); -0149 solB = solveLP(modelAna); -0150 result = compareFluxes(testCase.model, solA.x, solB.x, 'verbose', false); -0151 testCase.verifyTrue(isfield(result, 'turnedOn')); -0152 testCase.verifyTrue(isfield(result, 'turnedOff')); -0153 testCase.verifyTrue(isfield(result, 'flipped')); -0154 testCase.verifyTrue(isfield(result, 'changed')); -0155 testCase.verifyClass(result.changed.rxn, 'cell'); -0156 testCase.verifyNotEmpty(result.changed.rxn); -0157 testCase.verifyEqual(numel(result.changed.flux1), numel(result.changed.rxn)); -0158 testCase.verifyEqual(numel(result.changed.absDelta), numel(result.changed.rxn)); -0159 % sorted descending -0160 testCase.verifyTrue(all(diff(result.changed.absDelta) <= 0)); -0161 end -0162 -0163 function compareFluxesIdenticalIsEmpty(testCase) -0164 testCase.assumeSolver('solveLP'); -0165 sol = solveLP(testCase.model); -0166 result = compareFluxes(testCase.model, sol.x, sol.x, 'verbose', false); -0167 testCase.verifyEmpty(result.changed.rxn); -0168 testCase.verifyEmpty(result.turnedOn); -0169 testCase.verifyEmpty(result.turnedOff); -0170 end -0171 -0172 function compareFluxesMetaboliteListRestrictsResult(testCase) -0173 testCase.assumeSolver('solveLP'); -0174 solA = solveLP(testCase.model); -0175 o2exch = find(strcmp(testCase.model.rxnNames, 'O2 exchange'), 1); -0176 modelAna = setParam(testCase.model, 'eq', testCase.model.rxns(o2exch), 0); -0177 solB = solveLP(modelAna); -0178 unfiltered = compareFluxes(testCase.model, solA.x, solB.x, 'verbose', false); -0179 testCase.assumeNotEmpty(unfiltered.changed.rxn); -0180 -0181 % Name a metabolite of the largest-changing reaction, so the -0182 % filtered result is guaranteed to be non-empty and the subset -0183 % checks below are not satisfied vacuously. -0184 topRxn = strcmp(testCase.model.rxns, unfiltered.changed.rxn{1}); -0185 met = testCase.model.metNames{find(testCase.model.S(:,topRxn) ~= 0, 1)}; -0186 filtered = compareFluxes(testCase.model, solA.x, solB.x, ... -0187 'metaboliteList', {met}, 'verbose', false); -0188 testCase.verifyNotEmpty(filtered.changed.rxn); -0189 -0190 % Every kept reaction must involve the named metabolite, and the -0191 % filtered result can only be a subset of the unfiltered one. -0192 metIdx = strcmpi(met, testCase.model.metNames); -0193 withMet = testCase.model.rxns(any(testCase.model.S(metIdx,:) ~= 0, 1)); -0194 testCase.verifyTrue(all(ismember(filtered.changed.rxn, withMet))); -0195 testCase.verifyTrue(all(ismember(filtered.changed.rxn, unfiltered.changed.rxn))); -0196 end -0197 -0198 function compareFluxesUnknownMetaboliteWarns(testCase) -0199 testCase.assumeSolver('solveLP'); -0200 sol = solveLP(testCase.model); -0201 testCase.verifyWarning(@() compareFluxes(testCase.model, sol.x, sol.x, ... -0202 'metaboliteList', {'no such metabolite'}, 'verbose', false), ... -0203 'RAVEN:warning'); -0204 end -0205 -0206 function getFluxZZeroVarianceSignMatchesGeneralCase(testCase) -0207 % The zero-variance branch's sign must agree with the general -0208 % branch: positive when flux increased from A to B, negative -0209 % when it decreased. -0210 solA = [1 1 1; 5 5 5]; % rxn1 constant at 1, rxn2 constant at 5 -0211 solB = [5 5 5; 1 1 1]; % rxn1 increased to 5, rxn2 decreased to 1 -0212 Z = getFluxZ(solA, solB); -0213 testCase.verifyEqual(Z(1), 100); -0214 testCase.verifyEqual(Z(2), -100); -0215 end -0216 -0217 function getFluxZComputesScores(testCase) -0218 n = numel(testCase.model.rxns); -0219 Z = getFluxZ(rand(n, 20), rand(n, 20)); -0220 testCase.verifyNumElements(Z, n); -0221 end -0222 -0223 function analyzeSamplingRuns(testCase) -0224 n = numel(testCase.model.rxns); -0225 Tex = randn(n, 1); % per-reaction expression t-scores -0226 solutionsA = rand(n, 20); -0227 solutionsB = rand(n, 20); -0228 evalc('scores = analyzeSampling(Tex, 18, solutionsA, solutionsB, false);'); -0229 testCase.verifySize(scores, [n 3]); -0230 end -0231 -0232 function randomSamplingRuns(testCase) -0233 % Bare call exercises the default method (ACHR). -0234 evalc('sols = randomSampling(testCase.model, 5);'); -0235 testCase.verifyEqual(size(sols, 1), numel(testCase.model.rxns)); -0236 end -0237 -0238 function randomSamplingDeterministicWithSeed(testCase) -0239 % goodRxns reuse is specific to the random-objective method. -0240 evalc('[~, gR] = randomSampling(testCase.model, 0, ''method'', ''randomObjective'', ''runParallel'', false);'); -0241 evalc('s1 = randomSampling(testCase.model, 5, ''method'', ''randomObjective'', ''seed'', 42, ''runParallel'', false, ''goodRxns'', gR);'); -0242 evalc('s2 = randomSampling(testCase.model, 5, ''method'', ''randomObjective'', ''seed'', 42, ''runParallel'', false, ''goodRxns'', gR);'); -0243 testCase.verifyEqual(s1, s2); -0244 end -0245 -0246 function reporterMetabolitesRuns(testCase) -0247 pvals = rand(numel(testCase.model.genes), 1); -0248 rm = reporterMetabolites(testCase.model, testCase.model.genes, pvals); -0249 testCase.verifyClass(rm, 'struct'); -0250 end -0251 -0252 function reporterMetabolitesKeepsPercentInMetNames(testCase) -0253 % A metNames entry containing "%" must survive intact in the -0254 % outputFile report, not be truncated by fprintf misreading it -0255 % as a format directive. -0256 m = testCase.model; -0257 m.metNames{1} = 'metabolite 30% pure'; -0258 pvals = rand(numel(m.genes), 1); -0259 outFile = [tempname '.txt']; -0260 c = onCleanup(@() delete(outFile)); -0261 evalc('reporterMetabolites(m, m.genes, pvals, ''outputFile'', outFile);'); -0262 content = fileread(outFile); -0263 testCase.verifySubstring(content, 'metabolite 30% pure'); -0264 end -0265 -0266 function reporterMetabolitesIsDeterministic(testCase) -0267 % Closed-form background correction (RM1) must produce identical -0268 % Z-scores on repeated calls with identical inputs. -0269 pvals = rand(numel(testCase.model.genes), 1); -0270 rm1 = reporterMetabolites(testCase.model, testCase.model.genes, pvals); -0271 rm2 = reporterMetabolites(testCase.model, testCase.model.genes, pvals); -0272 testCase.verifyEqual(rm1.metZScores, rm2.metZScores); -0273 end -0274 -0275 function FSEOFRuns(testCase) -0276 biomassRxn = testCase.model.rxns{find(testCase.model.c == 1, 1)}; -0277 [~, exchIdx] = getExchangeRxns(testCase.model); -0278 targetRxn = testCase.model.rxns{exchIdx(1)}; -0279 outFile = [tempname '.txt']; -0280 evalc('targets = FSEOF(testCase.model, biomassRxn, targetRxn, 5, 0.9, outFile);'); -0281 testCase.verifyClass(targets, 'struct'); -0282 end -0283 -0284 function runRobustnessAnalysisRuns(testCase) -0285 [~, exchIdx] = getExchangeRxns(testCase.model); -0286 controlRxn = testCase.model.rxns{exchIdx(1)}; -0287 evalc('[cFlux, oFlux] = runRobustnessAnalysis(testCase.model, controlRxn, 5);'); -0288 testCase.verifyNumElements(cFlux, 5); -0289 end -0290 -0291 function runProductionEnvelopeRuns(testCase) -0292 biomassRxn = testCase.model.rxns{find(testCase.model.c == 1, 1)}; -0293 [~, exchIdx] = getExchangeRxns(testCase.model); -0294 targetRxn = testCase.model.rxns{exchIdx(1)}; -0295 evalc('[bio, tgt] = runProductionEnvelope(testCase.model, targetRxn, biomassRxn, 5);'); -0296 testCase.verifyNotEmpty(bio); -0297 end -0298 -0299 function runPhenotypePhasePlaneRuns(testCase) -0300 [~, exchIdx] = getExchangeRxns(testCase.model); -0301 r1 = testCase.model.rxns{exchIdx(1)}; -0302 r2 = testCase.model.rxns{exchIdx(2)}; -0303 evalc('g = runPhenotypePhasePlane(testCase.model, r1, r2, 3, 3);'); -0304 testCase.verifyNotEmpty(g); -0305 end -0306 -0307 function runDynamicFBARuns(testCase) -0308 % Use glucose (consumed) as substrate and plot target; the function -0309 % always plots, so the plotted reaction must carry non-zero flux. -0310 [~, exchIdx] = getExchangeRxns(testCase.model); -0311 glcMet = find(strcmp(testCase.model.metNames, 'D-Glucose'), 1); -0312 glcExch = exchIdx(any(testCase.model.S(glcMet, exchIdx) ~= 0, 1)); -0313 subRxn = testCase.model.rxns(glcExch); -0314 evalc(['c = runDynamicFBA(testCase.model, subRxn, 10, 0.1, 0.1, 2, ' ... -0315 'subRxn, {});']); -0316 testCase.verifyNotEmpty(c); -0317 end -0318 -0319 function getMinimalMediumReturnsMedium(testCase) -0320 testCase.assumeMILPSolver(); -0321 evalc('[med, idx] = getMinimalMedium(testCase.model, ''verbose'', false);'); -0322 testCase.verifyClass(med, 'cell'); -0323 testCase.verifyClass(idx, 'double'); -0324 % Every returned reaction must have lb < 0 (it is an uptake exchange) -0325 [~, exchIdx] = getExchangeRxns(testCase.model); -0326 testCase.verifyTrue(all(ismember(idx, exchIdx))); -0327 testCase.verifyTrue(all(testCase.model.lb(idx) < 0)); -0328 end -0329 -0330 function getMinimalMediumExplicitGrowth(testCase) -0331 testCase.assumeMILPSolver(); -0332 sol = solveLP(testCase.model); -0333 minG = 0.05 * sol.f; -0334 evalc('[med, idx] = getMinimalMedium(testCase.model, ''minGrowth'', minG, ''verbose'', false);'); -0335 testCase.verifyClass(med, 'cell'); -0336 testCase.verifyNotEmpty(med); -0337 end -0338 -0339 function runSimpleOptKnockRuns(testCase) -0340 testCase.assumeMILPSolver(); -0341 biomassRxn = testCase.model.rxns{find(testCase.model.c == 1, 1)}; -0342 [~, exchIdx] = getExchangeRxns(testCase.model); -0343 targetRxn = testCase.model.rxns{exchIdx(1)}; -0344 evalc(['out = runSimpleOptKnock(testCase.model, targetRxn, biomassRxn, ' ... -0345 'testCase.model.rxns(1:5), ''rxns'', 1);']); -0346 testCase.verifyClass(out, 'struct'); -0347 end -0348 -0349 end -0350 end +0066 evalc(['[~, I3, flag3] = getMinNrFluxes(testCase.model, testCase.model.rxns, ' ... +0067 '[], [], ''reversible'', false, ''resolveTies'', true);']); +0068 testCase.verifyEqual(flag1, 1); +0069 testCase.verifyEqual(flag2, 1); +0070 testCase.verifyEqual(flag3, 1); +0071 testCase.verifyEqual(I1, I2); +0072 testCase.verifyEqual(I2, I3); +0073 end +0074 +0075 function getMinNrFluxesResolveTiesOnlySupportsReversible(testCase) +0076 testCase.verifyError( ... +0077 @() getMinNrFluxes(testCase.model, testCase.model.rxns, [], [], 'irrev', ... +0078 false, 'resolveTies', true), ... +0079 'RAVEN:badInput'); +0080 end +0081 +0082 function getMinNrFluxesRejectsUnknownFormulation(testCase) +0083 testCase.verifyError( ... +0084 @() getMinNrFluxes(testCase.model, testCase.model.rxns, [], [], 'both'), ... +0085 'RAVEN:badInput'); +0086 end +0087 +0088 function getAllSubGraphsReturnsResult(testCase) +0089 sg = getAllSubGraphs(testCase.model); +0090 testCase.verifyNotEmpty(sg); +0091 end +0092 +0093 function findGeneDeletionsRuns(testCase) +0094 evalc('[genes, fluxes] = findGeneDeletions(testCase.model, ''sgd'');'); +0095 testCase.verifyNotEmpty(genes); +0096 end +0097 +0098 function findGeneDeletionsComputesGrRatio(testCase) +0099 evalc(['[genes, ~, ~, ~, grRatioMuts] = findGeneDeletions(testCase.model, ' ... +0100 '''sgd'');']); +0101 testCase.verifyNotEmpty(genes); +0102 testCase.verifyEqual(numel(grRatioMuts), numel(genes)); +0103 testCase.verifyGreaterThan(max(grRatioMuts), 0); +0104 end +0105 +0106 function traceFluxPathReturnsCells(testCase) +0107 testCase.assumeSolver('solveLP'); +0108 sol = solveLP(testCase.model); +0109 % Use two reactions that must be metabolically connected +0110 biomassRxn = testCase.model.rxns{find(testCase.model.c == 1, 1)}; +0111 [~, exchIdx] = getExchangeRxns(testCase.model); +0112 % Pick an exchange reaction with non-zero flux +0113 exchFlux = sol.x(exchIdx); +0114 active = exchIdx(abs(exchFlux) > 1e-8); +0115 if isempty(active), testCase.assumeTrue(false); end +0116 targetRxn = testCase.model.rxns{active(1)}; +0117 evalc('[p, m, f] = traceFluxPath(testCase.model, sol.x, biomassRxn, targetRxn, ''verbose'', false);'); +0118 % Either a path is found (cell + double) or not — both are valid +0119 testCase.verifyClass(f, 'double'); +0120 testCase.verifyGreaterThanOrEqual(f, 0); +0121 testCase.verifyLessThanOrEqual(f, 1); +0122 end +0123 +0124 function traceFluxPathSameReactionIsIdentity(testCase) +0125 testCase.assumeSolver('solveLP'); +0126 sol = solveLP(testCase.model); +0127 rxn1 = testCase.model.rxns{find(testCase.model.c == 1, 1)}; +0128 [p, m, f] = traceFluxPath(testCase.model, sol.x, rxn1, rxn1, 'verbose', false); +0129 testCase.verifyEqual(f, 1.0); +0130 testCase.verifyNumElements(p, 1); +0131 testCase.verifyEmpty(m); +0132 end +0133 +0134 function walkFluxesRefusesNonInteractive(testCase) +0135 % The test suite itself runs under -batch, so this exercises the +0136 % real guard rather than a mocked one. +0137 testCase.assumeTrue(batchStartupOptionUsed, ... +0138 'Test runner is not using -batch; the non-interactive guard cannot be exercised here.'); +0139 testCase.assumeSolver('solveLP'); +0140 sol = solveLP(testCase.model); +0141 biomassRxn = testCase.model.rxns{find(testCase.model.c == 1, 1)}; +0142 testCase.verifyError(@() walkFluxes(testCase.model, sol.x, biomassRxn), ... +0143 'walkFluxes:nonInteractive'); +0144 end +0145 +0146 function compareFluxesReturnsResult(testCase) +0147 testCase.assumeSolver('solveLP'); +0148 solA = solveLP(testCase.model); +0149 o2exch = find(strcmp(testCase.model.rxnNames, 'O2 exchange'), 1); +0150 modelAna = setParam(testCase.model, 'eq', testCase.model.rxns(o2exch), 0); +0151 solB = solveLP(modelAna); +0152 result = compareFluxes(testCase.model, solA.x, solB.x, 'verbose', false); +0153 testCase.verifyTrue(isfield(result, 'turnedOn')); +0154 testCase.verifyTrue(isfield(result, 'turnedOff')); +0155 testCase.verifyTrue(isfield(result, 'flipped')); +0156 testCase.verifyTrue(isfield(result, 'changed')); +0157 testCase.verifyClass(result.changed.rxn, 'cell'); +0158 testCase.verifyNotEmpty(result.changed.rxn); +0159 testCase.verifyEqual(numel(result.changed.flux1), numel(result.changed.rxn)); +0160 testCase.verifyEqual(numel(result.changed.absDelta), numel(result.changed.rxn)); +0161 % sorted descending +0162 testCase.verifyTrue(all(diff(result.changed.absDelta) <= 0)); +0163 end +0164 +0165 function compareFluxesIdenticalIsEmpty(testCase) +0166 testCase.assumeSolver('solveLP'); +0167 sol = solveLP(testCase.model); +0168 result = compareFluxes(testCase.model, sol.x, sol.x, 'verbose', false); +0169 testCase.verifyEmpty(result.changed.rxn); +0170 testCase.verifyEmpty(result.turnedOn); +0171 testCase.verifyEmpty(result.turnedOff); +0172 end +0173 +0174 function compareFluxesMetaboliteListRestrictsResult(testCase) +0175 testCase.assumeSolver('solveLP'); +0176 solA = solveLP(testCase.model); +0177 o2exch = find(strcmp(testCase.model.rxnNames, 'O2 exchange'), 1); +0178 modelAna = setParam(testCase.model, 'eq', testCase.model.rxns(o2exch), 0); +0179 solB = solveLP(modelAna); +0180 unfiltered = compareFluxes(testCase.model, solA.x, solB.x, 'verbose', false); +0181 testCase.assumeNotEmpty(unfiltered.changed.rxn); +0182 +0183 % Name a metabolite of the largest-changing reaction, so the +0184 % filtered result is guaranteed to be non-empty and the subset +0185 % checks below are not satisfied vacuously. +0186 topRxn = strcmp(testCase.model.rxns, unfiltered.changed.rxn{1}); +0187 met = testCase.model.metNames{find(testCase.model.S(:,topRxn) ~= 0, 1)}; +0188 filtered = compareFluxes(testCase.model, solA.x, solB.x, ... +0189 'metaboliteList', {met}, 'verbose', false); +0190 testCase.verifyNotEmpty(filtered.changed.rxn); +0191 +0192 % Every kept reaction must involve the named metabolite, and the +0193 % filtered result can only be a subset of the unfiltered one. +0194 metIdx = strcmpi(met, testCase.model.metNames); +0195 withMet = testCase.model.rxns(any(testCase.model.S(metIdx,:) ~= 0, 1)); +0196 testCase.verifyTrue(all(ismember(filtered.changed.rxn, withMet))); +0197 testCase.verifyTrue(all(ismember(filtered.changed.rxn, unfiltered.changed.rxn))); +0198 end +0199 +0200 function compareFluxesUnknownMetaboliteWarns(testCase) +0201 testCase.assumeSolver('solveLP'); +0202 sol = solveLP(testCase.model); +0203 testCase.verifyWarning(@() compareFluxes(testCase.model, sol.x, sol.x, ... +0204 'metaboliteList', {'no such metabolite'}, 'verbose', false), ... +0205 'RAVEN:warning'); +0206 end +0207 +0208 function getFluxZZeroVarianceSignMatchesGeneralCase(testCase) +0209 % The zero-variance branch's sign must agree with the general +0210 % branch: positive when flux increased from A to B, negative +0211 % when it decreased. +0212 solA = [1 1 1; 5 5 5]; % rxn1 constant at 1, rxn2 constant at 5 +0213 solB = [5 5 5; 1 1 1]; % rxn1 increased to 5, rxn2 decreased to 1 +0214 Z = getFluxZ(solA, solB); +0215 testCase.verifyEqual(Z(1), 100); +0216 testCase.verifyEqual(Z(2), -100); +0217 end +0218 +0219 function getFluxZComputesScores(testCase) +0220 n = numel(testCase.model.rxns); +0221 Z = getFluxZ(rand(n, 20), rand(n, 20)); +0222 testCase.verifyNumElements(Z, n); +0223 end +0224 +0225 function analyzeSamplingRuns(testCase) +0226 n = numel(testCase.model.rxns); +0227 Tex = randn(n, 1); % per-reaction expression t-scores +0228 solutionsA = rand(n, 20); +0229 solutionsB = rand(n, 20); +0230 evalc('scores = analyzeSampling(Tex, 18, solutionsA, solutionsB, false);'); +0231 testCase.verifySize(scores, [n 3]); +0232 end +0233 +0234 function randomSamplingRuns(testCase) +0235 % Bare call exercises the default method (ACHR). +0236 evalc('sols = randomSampling(testCase.model, 5);'); +0237 testCase.verifyEqual(size(sols, 1), numel(testCase.model.rxns)); +0238 end +0239 +0240 function randomSamplingDeterministicWithSeed(testCase) +0241 % goodRxns reuse is specific to the random-objective method. +0242 evalc('[~, gR] = randomSampling(testCase.model, 0, ''method'', ''randomObjective'', ''runParallel'', false);'); +0243 evalc('s1 = randomSampling(testCase.model, 5, ''method'', ''randomObjective'', ''seed'', 42, ''runParallel'', false, ''goodRxns'', gR);'); +0244 evalc('s2 = randomSampling(testCase.model, 5, ''method'', ''randomObjective'', ''seed'', 42, ''runParallel'', false, ''goodRxns'', gR);'); +0245 testCase.verifyEqual(s1, s2); +0246 end +0247 +0248 function reporterMetabolitesRuns(testCase) +0249 pvals = rand(numel(testCase.model.genes), 1); +0250 rm = reporterMetabolites(testCase.model, testCase.model.genes, pvals); +0251 testCase.verifyClass(rm, 'struct'); +0252 end +0253 +0254 function reporterMetabolitesKeepsPercentInMetNames(testCase) +0255 % A metNames entry containing "%" must survive intact in the +0256 % outputFile report, not be truncated by fprintf misreading it +0257 % as a format directive. +0258 m = testCase.model; +0259 m.metNames{1} = 'metabolite 30% pure'; +0260 pvals = rand(numel(m.genes), 1); +0261 outFile = [tempname '.txt']; +0262 c = onCleanup(@() delete(outFile)); +0263 evalc('reporterMetabolites(m, m.genes, pvals, ''outputFile'', outFile);'); +0264 content = fileread(outFile); +0265 testCase.verifySubstring(content, 'metabolite 30% pure'); +0266 end +0267 +0268 function reporterMetabolitesIsDeterministic(testCase) +0269 % Closed-form background correction (RM1) must produce identical +0270 % Z-scores on repeated calls with identical inputs. +0271 pvals = rand(numel(testCase.model.genes), 1); +0272 rm1 = reporterMetabolites(testCase.model, testCase.model.genes, pvals); +0273 rm2 = reporterMetabolites(testCase.model, testCase.model.genes, pvals); +0274 testCase.verifyEqual(rm1.metZScores, rm2.metZScores); +0275 end +0276 +0277 function FSEOFRuns(testCase) +0278 biomassRxn = testCase.model.rxns{find(testCase.model.c == 1, 1)}; +0279 [~, exchIdx] = getExchangeRxns(testCase.model); +0280 targetRxn = testCase.model.rxns{exchIdx(1)}; +0281 outFile = [tempname '.txt']; +0282 evalc('targets = FSEOF(testCase.model, biomassRxn, targetRxn, 5, 0.9, outFile);'); +0283 testCase.verifyClass(targets, 'struct'); +0284 end +0285 +0286 function runRobustnessAnalysisRuns(testCase) +0287 [~, exchIdx] = getExchangeRxns(testCase.model); +0288 controlRxn = testCase.model.rxns{exchIdx(1)}; +0289 evalc('[cFlux, oFlux] = runRobustnessAnalysis(testCase.model, controlRxn, 5);'); +0290 testCase.verifyNumElements(cFlux, 5); +0291 end +0292 +0293 function runProductionEnvelopeRuns(testCase) +0294 biomassRxn = testCase.model.rxns{find(testCase.model.c == 1, 1)}; +0295 [~, exchIdx] = getExchangeRxns(testCase.model); +0296 targetRxn = testCase.model.rxns{exchIdx(1)}; +0297 evalc('[bio, tgt] = runProductionEnvelope(testCase.model, targetRxn, biomassRxn, 5);'); +0298 testCase.verifyNotEmpty(bio); +0299 end +0300 +0301 function runPhenotypePhasePlaneRuns(testCase) +0302 [~, exchIdx] = getExchangeRxns(testCase.model); +0303 r1 = testCase.model.rxns{exchIdx(1)}; +0304 r2 = testCase.model.rxns{exchIdx(2)}; +0305 evalc('g = runPhenotypePhasePlane(testCase.model, r1, r2, 3, 3);'); +0306 testCase.verifyNotEmpty(g); +0307 end +0308 +0309 function runDynamicFBARuns(testCase) +0310 % Use glucose (consumed) as substrate and plot target; the function +0311 % always plots, so the plotted reaction must carry non-zero flux. +0312 [~, exchIdx] = getExchangeRxns(testCase.model); +0313 glcMet = find(strcmp(testCase.model.metNames, 'D-Glucose'), 1); +0314 glcExch = exchIdx(any(testCase.model.S(glcMet, exchIdx) ~= 0, 1)); +0315 subRxn = testCase.model.rxns(glcExch); +0316 evalc(['c = runDynamicFBA(testCase.model, subRxn, 10, 0.1, 0.1, 2, ' ... +0317 'subRxn, {});']); +0318 testCase.verifyNotEmpty(c); +0319 end +0320 +0321 function getMinimalMediumReturnsMedium(testCase) +0322 testCase.assumeMILPSolver(); +0323 evalc('[med, idx] = getMinimalMedium(testCase.model, ''verbose'', false);'); +0324 testCase.verifyClass(med, 'cell'); +0325 testCase.verifyClass(idx, 'double'); +0326 % Every returned reaction must have lb < 0 (it is an uptake exchange) +0327 [~, exchIdx] = getExchangeRxns(testCase.model); +0328 testCase.verifyTrue(all(ismember(idx, exchIdx))); +0329 testCase.verifyTrue(all(testCase.model.lb(idx) < 0)); +0330 end +0331 +0332 function getMinimalMediumExplicitGrowth(testCase) +0333 testCase.assumeMILPSolver(); +0334 sol = solveLP(testCase.model); +0335 minG = 0.05 * sol.f; +0336 evalc('[med, idx] = getMinimalMedium(testCase.model, ''minGrowth'', minG, ''verbose'', false);'); +0337 testCase.verifyClass(med, 'cell'); +0338 testCase.verifyNotEmpty(med); +0339 end +0340 +0341 function runSimpleOptKnockRuns(testCase) +0342 testCase.assumeMILPSolver(); +0343 biomassRxn = testCase.model.rxns{find(testCase.model.c == 1, 1)}; +0344 [~, exchIdx] = getExchangeRxns(testCase.model); +0345 targetRxn = testCase.model.rxns{exchIdx(1)}; +0346 evalc(['out = runSimpleOptKnock(testCase.model, targetRxn, biomassRxn, ' ... +0347 'testCase.model.rxns(1:5), ''rxns'', 1);']); +0348 testCase.verifyClass(out, 'struct'); +0349 end +0350 +0351 end +0352 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/doc/testing/function_tests/tSolver.html b/doc/testing/function_tests/tSolver.html index 2809b627..eaaddb1e 100644 --- a/doc/testing/function_tests/tSolver.html +++ b/doc/testing/function_tests/tSolver.html @@ -41,7 +41,7 @@

    CROSS-REFERENCE INFORMATION ^
 
 <h2><a name=SUBFUNCTIONS ^

    +
  • function setRavenSolverAcceptsGlpk(testCase)
  • function setRavenSolverRejectsUnknown(testCase)
  • function solveLPFindsOptimum(testCase)
  • function solveLPMinimiseFluxes(testCase)
  • function optimizeProbSolvesLP(testCase)
  • function optimizeProbReportsTimeLimit(testCase)
  • function checkSolutionOnFeasibleProblem(testCase)
  • function splitProbForConditioningReturnsProb(testCase)
  • function prob = modelLP(testCase)
  • SOURCE CODE ^

    0001 classdef tSolver < RavenTestCase
    @@ -75,39 +75,47 @@ 

    SOURCE CODE ^'obj')); 0030 end 0031 -0032 function checkSolutionOnFeasibleProblem(testCase) +0032 function optimizeProbReportsTimeLimit(testCase) 0033 res = optimizeProb(testCase.modelLP()); -0034 [isFeasible, isOptimal] = checkSolution(res); -0035 testCase.verifyTrue(logical(isFeasible)); -0036 testCase.verifyTrue(logical(isOptimal)); -0037 end -0038 -0039 function splitProbForConditioningReturnsProb(testCase) -0040 [p2, condInfo] = splitProbForConditioning(testCase.modelLP(), 1e6); %#ok<ASGLU> -0041 testCase.verifyClass(p2, 'struct'); -0042 end -0043 -0044 end -0045 -0046 methods (Access = private) -0047 function prob = modelLP(testCase) -0048 % Build the COBRA-style LP problem for the test model exactly as -0049 % solveLP does, so the low-level solver functions get a valid input. -0050 m = testCase.model; -0051 nMets = size(m.S, 1); -0052 prob = []; -0053 prob.c = [m.c * -1; zeros(nMets, 1)]; -0054 prob.b = zeros(nMets, 1); -0055 prob.lb = [m.lb; m.b(:, 1)]; -0056 prob.ub = [m.ub; m.b(:, min(size(m.b, 2), 2))]; -0057 prob.csense = repmat('E', 1, numel(prob.b)); -0058 prob.osense = 1; -0059 prob.vartype = repmat('C', 1, numel(prob.c)); -0060 prob.A = [m.S, -speye(nMets)]; -0061 prob.a = m.S; -0062 end -0063 end -0064 end

    +0034 testCase.verifyTrue(isfield(res, 'hitTimeLimit')); +0035 testCase.verifyTrue(islogical(res.hitTimeLimit)); +0036 %The problem is solved long before the time limit is reached +0037 testCase.verifyFalse(res.hitTimeLimit); +0038 end +0039 +0040 function checkSolutionOnFeasibleProblem(testCase) +0041 res = optimizeProb(testCase.modelLP()); +0042 [isFeasible, isOptimal] = checkSolution(res); +0043 testCase.verifyTrue(logical(isFeasible)); +0044 testCase.verifyTrue(logical(isOptimal)); +0045 end +0046 +0047 function splitProbForConditioningReturnsProb(testCase) +0048 [p2, condInfo] = splitProbForConditioning(testCase.modelLP(), 1e6); %#ok<ASGLU> +0049 testCase.verifyClass(p2, 'struct'); +0050 end +0051 +0052 end +0053 +0054 methods (Access = private) +0055 function prob = modelLP(testCase) +0056 % Build the COBRA-style LP problem for the test model exactly as +0057 % solveLP does, so the low-level solver functions get a valid input. +0058 m = testCase.model; +0059 nMets = size(m.S, 1); +0060 prob = []; +0061 prob.c = [m.c * -1; zeros(nMets, 1)]; +0062 prob.b = zeros(nMets, 1); +0063 prob.lb = [m.lb; m.b(:, 1)]; +0064 prob.ub = [m.ub; m.b(:, min(size(m.b, 2), 2))]; +0065 prob.csense = repmat('E', 1, numel(prob.b)); +0066 prob.osense = 1; +0067 prob.vartype = repmat('C', 1, numel(prob.c)); +0068 prob.A = [m.S, -speye(nMets)]; +0069 prob.a = m.S; +0070 end +0071 end +0072 end
    Generated by m2html © 2005
    \ No newline at end of file From 90dc76bc7f421bbbfed81d45428b105b08540efa Mon Sep 17 00:00:00 2001 From: Eduard Kerkhoven Date: Wed, 9 Sep 2026 10:30:30 +0200 Subject: [PATCH 03/48] Move HPA parsers and scoreModel into INIT/ (SysBioChalmers/RAVEN#750) parseHPA, parseHPArna, and scoreModel are contextualization inputs consumed only by ftINIT/tINIT, not general-purpose omics utilities; omics/ held nothing else and is now gone. - `omics/parseHPA.m`, `omics/parseHPArna.m`, `omics/scoreModel.m`: moved to `INIT/`. - `testing/function_tests/tOmics.m`: folded into `tINIT.m`; the `parseHPA`/`parseHPArna` tests moved over, the `scoreModel` smoke test dropped as a duplicate of `tINIT.m`'s own `scoreModelRuns`. - `INIT/tINIT/README.md`, `docs/v3_review.md`: folder references updated. Original-commit: https://github.com/SysBioChalmers/RAVEN/commit/4430754bcab6edda2cbee8547833ad0b3f87ff8e --- {omics => INIT}/parseHPA.m | 0 {omics => INIT}/parseHPArna.m | 0 {omics => INIT}/scoreModel.m | 0 INIT/tINIT/README.md | 4 +-- docs/v3_review.md | 8 ++--- testing/function_tests/tINIT.m | 42 +++++++++++++++++++++++-- testing/function_tests/tOmics.m | 54 --------------------------------- 7 files changed, 46 insertions(+), 62 deletions(-) rename {omics => INIT}/parseHPA.m (100%) rename {omics => INIT}/parseHPArna.m (100%) rename {omics => INIT}/scoreModel.m (100%) delete mode 100644 testing/function_tests/tOmics.m diff --git a/omics/parseHPA.m b/INIT/parseHPA.m similarity index 100% rename from omics/parseHPA.m rename to INIT/parseHPA.m diff --git a/omics/parseHPArna.m b/INIT/parseHPArna.m similarity index 100% rename from omics/parseHPArna.m rename to INIT/parseHPArna.m diff --git a/omics/scoreModel.m b/INIT/scoreModel.m similarity index 100% rename from omics/scoreModel.m rename to INIT/scoreModel.m diff --git a/INIT/tINIT/README.md b/INIT/tINIT/README.md index 9b88823e..d7d875bc 100644 --- a/INIT/tINIT/README.md +++ b/INIT/tINIT/README.md @@ -13,7 +13,7 @@ appear to overlap they do not: | | tINIT | ftINIT | |---|---|---| | Entry point | `getINITModel` | `prepINITModel`, then `ftINIT` | -| Reaction scoring | `scoreModel` (isozyme/complex scoring fixed to `'max'`, `dataPrecedence` `'reaction'`) | `scoreModel` (`omics/`), `groupRxnScores` | +| Reaction scoring | `scoreModel` (isozyme/complex scoring fixed to `'max'`, `dataPrecedence` `'reaction'`) | `scoreModel` (`INIT/`), `groupRxnScores` | | Core MILP | `runINIT` | `ftINITInternalAlg`, scheduled by `getINITSteps` | | Task gap-filling | `fitTasks` | `fitTasks` (`gapFillMode` `'preMerged'`), `ftINITFillGaps` | | Gene pruning | inline in `getINITModel` | `removeLowScoreGenes` | @@ -24,7 +24,7 @@ solver layer, and the model-manipulation and I/O functions. Reaction scoring and task gap-filling look forked in the table above only because the two entry points call their shared functions with different -settings, not because two implementations exist. `scoreModel` (`omics/`) is +settings, not because two implementations exist. `scoreModel` (`INIT/`) is one function for both: `getINITModel` calls it with the fixed argument combination the original tINIT algorithm needs — a single operator for both `and`/`or` in a grRule, `dataPrecedence` `'reaction'`, and geneScores rewritten diff --git a/docs/v3_review.md b/docs/v3_review.md index 98b1a52f..691c4568 100644 --- a/docs/v3_review.md +++ b/docs/v3_review.md @@ -109,7 +109,7 @@ reconstruction / strain-design audience. | 13 | **Regulatory integration** — PROM / CoRegFlux (new application domain) (§1.4) | (b) | M each | med | | 14 | **Community modeling** — SteadyCom (new application domain) (§1.5) | (b) | M | med | | 15 | **E-Flux** next to ftINIT (§1.6) | (b)(c) | S | low–med | -| 14 | **HPA omics parser consolidation** — extract shared file-read + header-validation into `omics/private/` helper *(§6.2 column fix already done; this is the remaining extract step)* (§5.5) | (d) | S | low | +| 14 | **HPA omics parser consolidation** — extract shared file-read + header-validation into `INIT/private/` helper *(§6.2 column fix already done; this is the remaining extract step)* (§5.5) | (d) | S | low | --- @@ -326,8 +326,8 @@ See §3 — split the monolith into directional internals. - **`annotation/loadDeltaGfromCSV.m` + `saveDeltaGtoCSV.m`** *(done)* — collapsed into `deltaGCSV(model, direction, metCsv, rxnCsv)`. The four near-identical met/rxn blocks are handled by two shared local functions (`loadField`/`saveField`). -- **`omics/parseHPA.m` vs `parseHPArna.m`** — column detection is now header-driven (§6.2 done). - Remaining: extract the shared file-read loop into `omics/private/`. **S.** +- **`INIT/parseHPA.m` vs `parseHPArna.m`** — column detection is now header-driven (§6.2 done). + Remaining: extract the shared file-read loop into `INIT/private/`. **S.** - **`localization/getWoLFScores.m` vs `parseScores.m`** *(already done)* — `getWoLFScores` already delegates to `parseScores(file,'wolf')`; no normalization duplication exists. Remaining question: whether the Linux+Perl-only WoLF runner still earns its place. @@ -352,7 +352,7 @@ MEMOTE-style, cheap given `extractMiriam` already unpacks the data. Pairs with t also auto-detect the delimiter (tab vs comma). Upstream format changes that add or reorder columns will no longer silently break the functions. -The remaining consolidation item (extracting a shared `omics/private/` file-read helper) is +The remaining consolidation item (extracting a shared `INIT/private/` file-read helper) is tracked as a low-priority item in the Tier-3 table. ### 6.3 Smaller expansions *(done)* diff --git a/testing/function_tests/tINIT.m b/testing/function_tests/tINIT.m index 855f0e00..56214098 100644 --- a/testing/function_tests/tINIT.m +++ b/testing/function_tests/tINIT.m @@ -9,8 +9,8 @@ % gap-filling step (fitTasks with gapFillMode 'preMerged', ftINIT's mode), % all guarded on a MILP solver (Gurobi/SCIP). The self-contained helpers % (mergeLinear, groupRxnScores, reverseRxns, rescaleModelForINIT, -% scoreModel, getExprForRxnScore) are tested directly against known -% results. +% scoreModel, getExprForRxnScore, parseHPA, parseHPArna) are tested +% directly against known results. methods (Test) @@ -156,6 +156,44 @@ function scoreModelExactScores(testCase) testCase.verifyTrue(all(abs(rxnScores - getTstModelRxnScores()) < 10^-10)); end + function parseHPAparsesSyntheticDump(testCase) + % parseHPA reads a tab-separated HPA dump with the columns + % Gene / Gene name / Tissue / Cell type / Level / Reliability. + % Synthesise a tiny one so the parser is exercised without the + % multi-gigabyte external download. + g = testCase.model.genes(1:3); + f = [tempname '.tsv']; + testCase.addTeardown(@() delete(f)); + fid = fopen(f, 'w'); + fprintf(fid, 'Gene\tGene name\tTissue\tCell type\tLevel\tReliability\n'); + fprintf(fid, '%s\t%s\tliver\thepatocyte\tHigh\tApproved\n', g{1}, g{1}); + fprintf(fid, '%s\t%s\tliver\thepatocyte\tMedium\tApproved\n', g{2}, g{2}); + fprintf(fid, '%s\t%s\tbrain\tneuron\tLow\tSupported\n', g{3}, g{3}); + fclose(fid); + evalc('hpa = parseHPA(f);'); + testCase.verifyClass(hpa, 'struct'); + testCase.verifyEqual(sort(hpa.genes), sort(g)); + testCase.verifyNumElements(hpa.tissues, 2); % liver+brain cell types + end + + function parseHPArnaParsesSyntheticDump(testCase) + % parseHPArna (version 19) reads Gene / Gene name / Tissue / + % TPM / pTPM / NX and builds a genes-by-tissues TPM matrix. + g = testCase.model.genes(1:3); + f = [tempname '.tsv']; + testCase.addTeardown(@() delete(f)); + fid = fopen(f, 'w'); + fprintf(fid, 'Gene\tGene name\tTissue\tTPM\tpTPM\tNX\n'); + fprintf(fid, '%s\t%s\tliver\t12.5\t10.1\t8.0\n', g{1}, g{1}); + fprintf(fid, '%s\t%s\tbrain\t3.2\t2.9\t1.5\n', g{2}, g{2}); + fprintf(fid, '%s\t%s\tliver\t0.0\t0.0\t0.0\n', g{3}, g{3}); + fclose(fid); + evalc('rna = parseHPArna(f);'); + testCase.verifyClass(rna, 'struct'); + testCase.verifyNumElements(rna.genes, 3); + testCase.verifyEqual(size(rna.levels), [3 2]); % 3 genes x 2 tissues + end + function getINITModelRuns(testCase) % The tINIT path is legacy but supported, and until now nothing % exercised it: a refactor of the machinery it shares with ftINIT diff --git a/testing/function_tests/tOmics.m b/testing/function_tests/tOmics.m deleted file mode 100644 index 22b6baa8..00000000 --- a/testing/function_tests/tOmics.m +++ /dev/null @@ -1,54 +0,0 @@ -classdef tOmics < RavenTestCase -% tOmics Tests for the omics-integration functions in omics/. - - methods (Test) - - function scoreModelFromArrayData(testCase) - arrayData.genes = testCase.model.genes; - arrayData.tissues = {'tissue1'}; - arrayData.levels = abs(randn(numel(testCase.model.genes), 1)) + 1; - arrayData.threshold = 1; - evalc('rxnScores = scoreModel(testCase.model, [], arrayData, ''tissue1'', []);'); - testCase.verifyNumElements(rxnScores, numel(testCase.model.rxns)); - end - - function parseHPAparsesSyntheticDump(testCase) - % parseHPA reads a tab-separated HPA dump with the columns - % Gene / Gene name / Tissue / Cell type / Level / Reliability. - % Synthesise a tiny one so the parser is exercised without the - % multi-gigabyte external download. - g = testCase.model.genes(1:3); - f = [tempname '.tsv']; - testCase.addTeardown(@() delete(f)); - fid = fopen(f, 'w'); - fprintf(fid, 'Gene\tGene name\tTissue\tCell type\tLevel\tReliability\n'); - fprintf(fid, '%s\t%s\tliver\thepatocyte\tHigh\tApproved\n', g{1}, g{1}); - fprintf(fid, '%s\t%s\tliver\thepatocyte\tMedium\tApproved\n', g{2}, g{2}); - fprintf(fid, '%s\t%s\tbrain\tneuron\tLow\tSupported\n', g{3}, g{3}); - fclose(fid); - evalc('hpa = parseHPA(f);'); - testCase.verifyClass(hpa, 'struct'); - testCase.verifyEqual(sort(hpa.genes), sort(g)); - testCase.verifyNumElements(hpa.tissues, 2); % liver+brain cell types - end - - function parseHPArnaParsesSyntheticDump(testCase) - % parseHPArna (version 19) reads Gene / Gene name / Tissue / - % TPM / pTPM / NX and builds a genes-by-tissues TPM matrix. - g = testCase.model.genes(1:3); - f = [tempname '.tsv']; - testCase.addTeardown(@() delete(f)); - fid = fopen(f, 'w'); - fprintf(fid, 'Gene\tGene name\tTissue\tTPM\tpTPM\tNX\n'); - fprintf(fid, '%s\t%s\tliver\t12.5\t10.1\t8.0\n', g{1}, g{1}); - fprintf(fid, '%s\t%s\tbrain\t3.2\t2.9\t1.5\n', g{2}, g{2}); - fprintf(fid, '%s\t%s\tliver\t0.0\t0.0\t0.0\n', g{3}, g{3}); - fclose(fid); - evalc('rna = parseHPArna(f);'); - testCase.verifyClass(rna, 'struct'); - testCase.verifyNumElements(rna.genes, 3); - testCase.verifyEqual(size(rna.levels), [3 2]); % 3 genes x 2 tissues - end - - end -end From 7efa09451b1ef7402c84e38f0f51359b3c87e915 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 9 Sep 2026 08:32:15 +0000 Subject: [PATCH 04/48] doc: regenerate HTML documentation for 90dc76b [skip ci] Original-commit: https://github.com/SysBioChalmers/RAVEN/commit/7efb0dc7186885ed9d545b43e2bfcfd5d7144f2d --- doc/INIT/ftINIT.html | 8 +- doc/INIT/index.html | 2 +- doc/{omics => INIT}/parseHPA.html | 4 +- doc/{omics => INIT}/parseHPArna.html | 4 +- doc/{omics => INIT}/scoreModel.html | 6 +- doc/index.html | 2 +- doc/omics/index.html | 29 - doc/testing/function_tests/RavenTestCase.html | 2 +- doc/testing/function_tests/index.html | 2 +- doc/testing/function_tests/tINIT.html | 1540 +++++++++-------- doc/testing/function_tests/tOmics.html | 103 -- 11 files changed, 804 insertions(+), 898 deletions(-) rename doc/{omics => INIT}/parseHPA.html (98%) rename doc/{omics => INIT}/parseHPArna.html (98%) rename doc/{omics => INIT}/scoreModel.html (99%) delete mode 100644 doc/omics/index.html delete mode 100644 doc/testing/function_tests/tOmics.html diff --git a/doc/INIT/ftINIT.html b/doc/INIT/ftINIT.html index 03405943..2a58a646 100644 --- a/doc/INIT/ftINIT.html +++ b/doc/INIT/ftINIT.html @@ -33,7 +33,7 @@

    DESCRIPTION ^CROSS-REFERENCE INFORMATION ^

    This function calls: +
  • ftINIT
  • ftINITInternalAlg ftINITInternalAlg
  • getINITSteps getINITSteps Define the steps used as input to ftINIT.
  • groupRxnScores groupRxnScores
  • scoreModel scoreModel Score reactions and genes in a model with complex gene rules.
  • This function is called by: @@ -222,7 +222,7 @@

    SOURCE CODE ^end 0177 0178 % Get rxn scores and adapt them to the minimized model -0179 origRxnScores = scoreModel(prepData.refModel,hpaData,transcrData,tissue,celltype); +0179 origRxnScores = scoreModel(prepData.refModel,hpaData,transcrData,tissue,celltype); 0180 origRxnScores(origRxnScores > -0.1 & origRxnScores <= 0) = -0.1;%we don't want reaction scores that are exactly 0 (or close), this causes problems in the milp 0181 origRxnScores(origRxnScores < 0.1 & origRxnScores > 0) = 0.1; 0182 @@ -501,7 +501,7 @@

    SOURCE CODE ^% See the "removeLowScoreGenes" function more more details, and to adjust 0456 % any default parameters therein. 0457 if ( removeGenes ) -0458 [~, geneScores] = scoreModel(outModel,hpaData,transcrData,tissue,celltype); +0458 [~, geneScores] = scoreModel(outModel,hpaData,transcrData,tissue,celltype); 0459 outModel = removeLowScoreGenes(outModel,geneScores); 0460 end 0461 @@ -512,7 +512,7 @@

    SOURCE CODE ^%This is for printing a summary of a model 0468 function [rxnS, geneS] = printScores(model,name,hpaData,transcrData,tissue,celltype) -0469 [a, b] = scoreModel(model,hpaData,transcrData,tissue,celltype); +0469 [a, b] = scoreModel(model,hpaData,transcrData,tissue,celltype); 0470 rxnS = mean(a); 0471 geneS = mean(b,'omitnan'); 0472 fprintf([name ':\n']); diff --git a/doc/INIT/index.html b/doc/INIT/index.html index abb05745..4baaf28d 100644 --- a/doc/INIT/index.html +++ b/doc/INIT/index.html @@ -19,7 +19,7 @@

    Index for INIT

    Matlab files in this directory:

    -
     INITStepDesc
     ftINIT
     ftINITInternalAlgftINITInternalAlg
     getINITStepsgetINITSteps Define the steps used as input to ftINIT.
     groupRxnScoresgroupRxnScores
     mergeLinearmergeLinear
     prepINITModelprepINITModel Precompute omics-independent data for ftINIT.
     rescaleModelForINITrescaleModelForINIT Rescale a model to avoid large stoichiometric differences.
    + INITStepDesc  ftINIT  ftINITInternalAlgftINITInternalAlg  getINITStepsgetINITSteps Define the steps used as input to ftINIT.  groupRxnScoresgroupRxnScores  mergeLinearmergeLinear  parseHPAparseHPA Parse a database dump of the Human Protein Atlas (HPA).  parseHPArnaparseHPArna Parse a dump of Human Protein Atlas (HPA) RNA-Seq data.  prepINITModelprepINITModel Precompute omics-independent data for ftINIT.  rescaleModelForINITrescaleModelForINIT Rescale a model to avoid large stoichiometric differences.  scoreModelscoreModel Score reactions and genes in a model with complex gene rules.

    Subsequent directories:

    diff --git a/doc/omics/parseHPA.html b/doc/INIT/parseHPA.html similarity index 98% rename from doc/omics/parseHPA.html rename to doc/INIT/parseHPA.html index 9dfefcad..eebd67fb 100644 --- a/doc/omics/parseHPA.html +++ b/doc/INIT/parseHPA.html @@ -12,10 +12,10 @@ -
    Home > omics > parseHPA.m
    +
    Home > INIT > parseHPA.m
    +Index for INIT >-->

    parseHPA

    diff --git a/doc/omics/parseHPArna.html b/doc/INIT/parseHPArna.html similarity index 98% rename from doc/omics/parseHPArna.html rename to doc/INIT/parseHPArna.html index 40d646c2..11094e4b 100644 --- a/doc/omics/parseHPArna.html +++ b/doc/INIT/parseHPArna.html @@ -12,10 +12,10 @@ -
    Home > omics > parseHPArna.m
    +
    Home > INIT > parseHPArna.m
    +Index for INIT >-->

    parseHPArna

    diff --git a/doc/omics/scoreModel.html b/doc/INIT/scoreModel.html similarity index 99% rename from doc/omics/scoreModel.html rename to doc/INIT/scoreModel.html index 3279f4c6..56019e56 100644 --- a/doc/omics/scoreModel.html +++ b/doc/INIT/scoreModel.html @@ -12,10 +12,10 @@ -
    Home > omics > scoreModel.m
    +
    Home > INIT > scoreModel.m
    +Index for INIT >-->

    scoreModel

    @@ -117,7 +117,7 @@

    CROSS-REFERENCE INFORMATION ^
 </ul>
 This function is called by:
 <ul style= - +
  • ftINIT
  • SUBFUNCTIONS ^

    diff --git a/doc/index.html b/doc/index.html index 8f1b761a..0253ebde 100644 --- a/doc/index.html +++ b/doc/index.html @@ -24,7 +24,7 @@

    Matlab files in this directory:

    Subsequent directories:

    +
  • .git
  • .github
  • INIT
  • analysis
  • annotation
  • biomass
  • comparison
  • conditions
  • conversion
  • curation
  • deprecated
  • doc
  • docs
  • gapfilling
  • installation
  • io
  • localization
  • manipulation
  • queries
  • readme
  • reconstruction
  • software
  • solver
  • tasks
  • testing
  • tutorial
  • utils

  • Generated by m2html © 2005
    diff --git a/doc/omics/index.html b/doc/omics/index.html deleted file mode 100644 index 896ac0e9..00000000 --- a/doc/omics/index.html +++ /dev/null @@ -1,29 +0,0 @@ - - - - Index for Directory omics - - - - - - - - - - -
    < Master indexIndex for omics >
    - -

    Index for omics

    - -

    Matlab files in this directory:

    - -
     parseHPAparseHPA Parse a database dump of the Human Protein Atlas (HPA).
     parseHPArnaparseHPArna Parse a dump of Human Protein Atlas (HPA) RNA-Seq data.
     scoreModelscoreModel Score reactions and genes in a model with complex gene rules.
    - - - - -
    Generated by m2html © 2005
    - - \ No newline at end of file diff --git a/doc/testing/function_tests/RavenTestCase.html b/doc/testing/function_tests/RavenTestCase.html index 79e8cb43..28f2b591 100644 --- a/doc/testing/function_tests/RavenTestCase.html +++ b/doc/testing/function_tests/RavenTestCase.html @@ -36,7 +36,7 @@

    CROSS-REFERENCE INFORMATION ^
 <li><a href=RavenTestCase This function is called by: +
  • RavenTestCase
  • tAnalysis
  • tAnnotation
  • tAssignCompartments
  • tBiomass
  • tComparison
  • tConditions
  • tConversion
  • tCuration
  • tGapfilling
  • tGenomeData
  • tINIT
  • tIO
  • tInstallation
  • tLocalization
  • tManipulation
  • tNamedArgs
  • tQueries
  • tReconstruction
  • tSampling
  • tSolver
  • tSyntax
  • tTasks
  • tUtils
  • SUBFUNCTIONS ^

    diff --git a/doc/testing/function_tests/index.html b/doc/testing/function_tests/index.html index 14515ef8..fa1d7670 100644 --- a/doc/testing/function_tests/index.html +++ b/doc/testing/function_tests/index.html @@ -19,7 +19,7 @@

    Index for testing/function_tests

    Matlab files in this directory:

    -
     RavenTestCase
     tAnalysis
     tAnnotation
     tAssignCompartments
     tBinaries
     tBiomass
     tComparison
     tConditions
     tConversion
     tCuration
     tGapfilling
     tGenomeData
     tINIT
     tIO
     tInstallation
     tLocalization
     tManipulation
     tNamedArgs
     tOmics
     tParseRAVENargs
     tQueries
     tReconstruction
     tSampling
     tSolver
     tSyntax
     tTasks
     tUtils
    + RavenTestCase  tAnalysis  tAnnotation  tAssignCompartments  tBinaries  tBiomass  tComparison  tConditions  tConversion  tCuration  tGapfilling  tGenomeData  tINIT  tIO  tInstallation  tLocalization  tManipulation  tNamedArgs  tParseRAVENargs  tQueries  tReconstruction  tSampling  tSolver  tSyntax  tTasks  tUtils

    Subsequent directories:

    diff --git a/doc/testing/function_tests/tINIT.html b/doc/testing/function_tests/tINIT.html index 9a325284..ad3b01a4 100644 --- a/doc/testing/function_tests/tINIT.html +++ b/doc/testing/function_tests/tINIT.html @@ -41,7 +41,7 @@

    CROSS-REFERENCE INFORMATION ^
 
 <h2><a name=SUBFUNCTIONS ^

    +
  • function getINITStepsReturnsSteps(testCase)
  • function getExprForRxnScoreRuns(testCase)
  • function removeLowScoreGenesRuns(testCase)
  • function removeLowScoreGenesKeepsFieldsAligned(testCase)
  • function reverseRxnsRuns(testCase)
  • function rescaleModelForINITRuns(testCase)
  • function mergeLinearGroupsAndScores(testCase)
  • function scoreModelRuns(testCase)
  • function scoreModelHpaUnmeasuredCellTypeIsNotZero(testCase)
  • function scoreModelExactScores(testCase)
  • function parseHPAparsesSyntheticDump(testCase)
  • function parseHPArnaParsesSyntheticDump(testCase)
  • function getINITModelRuns(testCase)
  • function ftINITPipelineRuns(testCase)
  • function ftINITResolveTiesAndProveAbsGapStillRunCorrectly(testCase)
  • function ftINITWithTaskRuns(testCase)
  • function fitTasksPreMergedRuns(testCase)
  • function fitTasksPreMergedResolveTiesStillFillsCorrectly(testCase)
  • function fitTasksPreMergedReportsUnfillableTask(testCase)
  • function fitTasksPreMergedHandlesEmptyReferenceSet(testCase)
  • function fitTasksPreMergedWarningHasRealNewlineAndPercent(testCase)
  • function ftINITFillGapsReportsTaskNeedingAnOrphanMet(testCase)
  • function ftINITFillGapsReportsNoCandidateReactions(testCase)
  • function ftINITMetabolomicsRuns(testCase)
  • function ftINITFullVsThreeStepRuns(testCase)
  • function ftINITSeriesVariantsRun(testCase)
  • function scoreModelDataPrecedenceReactionPrefersHpaWholeRule(testCase)
  • function scoreModelAverageReducesDownTheRule(testCase)
  • function scoreModelReadsGrRulesNotRxnGeneMat(testCase)
  • function scoreModelRejectsUnknownDataPrecedence(testCase)
  • function testModel = getTstModel()
  • function testModelRxnScores = getTstModelRxnScores()
  • function model = setTaskBounds(model)
  • function testModelTasks = getTstModelTasks()
  • function testModel2 = getTstModel2()
  • function testModel4 = getTstModel4()
  • function testModel5 = getTstModel5()
  • function testModel5RxnScores = getTstModel5RxnScores()
  • function testModelL = getTstModelL()
  • function testModelLGeneScores = getTstModelLGeneScores()
  • function m = scoringTestModel()
  • function a = scoringArrayData()
  • function h = scoringHpaDataLowG1()
  • SOURCE CODE ^

    0001 classdef tINIT < RavenTestCase
    @@ -55,8 +55,8 @@ 

    SOURCE CODE ^% gap-filling step (fitTasks with gapFillMode 'preMerged', ftINIT's mode), 0010 % all guarded on a MILP solver (Gurobi/SCIP). The self-contained helpers 0011 % (mergeLinear, groupRxnScores, reverseRxns, rescaleModelForINIT, -0012 % scoreModel, getExprForRxnScore) are tested directly against known -0013 % results. +0012 % scoreModel, getExprForRxnScore, parseHPA, parseHPArna) are tested +0013 % directly against known results. 0014 0015 methods (Test) 0016 @@ -110,7 +110,7 @@

    SOURCE CODE ^function reverseRxnsRuns(testCase) 0066 % R1 = '=> a[s]'; R3 = 'a[c] <=> b[c] + c[c]' -0067 testModel = getTstModel(); +0067 testModel = getTstModel(); 0068 tmpModel = reverseRxns(testModel, {'R1';'R3'}); 0069 res = constructEquations(tmpModel, {'R1';'R3'}); 0070 expRes = {'a[s] => ';'b[c] + c[c] <=> a[c]'}; @@ -130,8 +130,8 @@

    SOURCE CODE ^function mergeLinearGroupsAndScores(testCase) 0085 % mergeLinear merges linear reaction chains; groupRxnScores then 0086 % aggregates the per-reaction scores onto the merged groups. -0087 testModel = getTstModel(); -0088 testRxnScores = getTstModelRxnScores(); +0087 testModel = getTstModel(); +0088 testRxnScores = getTstModelRxnScores(); 0089 evalc('[reducedModel,origRxnIds,groupIds,reversedRxns] = mergeLinear(testModel, {});'); 0090 % {R1,R2}, {R3,R5}, {R4,R6}, {R7,R8}, {R9,R10} are merged 0091 testCase.verifyTrue(all(groupIds == [1;1;2;3;2;3;4;4;5;5])); @@ -144,7 +144,7 @@

    SOURCE CODE ^% testModel4 has reactions that are not all linearly merged -0101 testModel4 = getTstModel4(); +0101 testModel4 = getTstModel4(); 0102 evalc('[reducedModel,origRxnIds,groupIds,reversedRxns] = mergeLinear(testModel4, {});'); 0103 % {R5,R6}, {R7,R8}, {R9,R10} are merged 0104 testCase.verifyTrue(all(groupIds == [0;0;0;0;1;1;2;2;3;3;0])); @@ -192,768 +192,806 @@

    SOURCE CODE ^% scoreModel must reproduce the known reaction scores for 0147 % the reference model prepared by prepINITModel. 0148 testCase.assumeMILPSolver(); -0149 testModel = getTstModel(); +0149 testModel = getTstModel(); 0150 evalc('prepData = prepINITModel(testModel, {}, {}, false, {}, ''s'');'); 0151 arrayData.genes = testModel.genes; 0152 arrayData.tissues = {'a'}; -0153 arrayData.levels = getExprForRxnScore(getTstModelRxnScores()); +0153 arrayData.levels = getExprForRxnScore(getTstModelRxnScores()); 0154 arrayData.threshold = 1; 0155 rxnScores = scoreModel(prepData.refModel, [], arrayData, arrayData.tissues{1}, []); -0156 testCase.verifyTrue(all(abs(rxnScores - getTstModelRxnScores()) < 10^-10)); +0156 testCase.verifyTrue(all(abs(rxnScores - getTstModelRxnScores()) < 10^-10)); 0157 end 0158 -0159 function getINITModelRuns(testCase) -0160 % The tINIT path is legacy but supported, and until now nothing -0161 % exercised it: a refactor of the machinery it shares with ftINIT -0162 % (checkTasks, simplifyModel, solveLP, removeReactions) could -0163 % break it without any test noticing. -0164 testCase.assumeMILPSolver(); -0165 refModel = getTstModel(); -0166 % getINITModel wants the closed form, which this fixture is not -0167 % built in; its own documentation gives this as the way to add it -0168 refModel.unconstrained = false(numel(refModel.mets),1); -0169 -0170 arrayData.genes = refModel.genes; -0171 arrayData.tissues = {'a'}; -0172 arrayData.levels = getExprForRxnScore(getTstModelRxnScores()); -0173 arrayData.threshold = 1; -0174 -0175 evalc(['m = getINITModel(refModel, arrayData.tissues{1}, ' ... -0176 '''arrayData'', arrayData, ''printReport'', false);']); -0177 testCase.verifyClass(m, 'struct'); -0178 testCase.verifyNotEmpty(m.rxns); -0179 testCase.verifyTrue(all(ismember(m.rxns, refModel.rxns))); -0180 % R4 and R10 carry the highest scores, so neither should be cut -0181 testCase.verifyTrue(all(ismember({'R4';'R10'}, m.rxns))); -0182 end -0183 -0184 function ftINITPipelineRuns(testCase) -0185 % prepINITModel + ftINIT end-to-end on testModel without tasks. -0186 testCase.assumeMILPSolver(); -0187 testModel = getTstModel(); -0188 testParams = struct(); -0189 evalc('prepData = prepINITModel(testModel, {}, {}, false, {}, ''s'');'); -0190 % R1 and R8 are exchange reactions without a GPR -0191 testCase.verifyTrue(all(strcmp( ... -0192 prepData.refModel.rxns(prepData.toIgnoreExch), {'R1';'R8'}))); -0193 -0194 arrayData.genes = testModel.genes; -0195 arrayData.tissues = {'a'}; -0196 arrayData.levels = getExprForRxnScore(getTstModelRxnScores()); -0197 arrayData.threshold = 1; -0198 -0199 evalc(['resModel = ftINIT(prepData,arrayData.tissues{1},[],[],' ... -0200 'arrayData,[],getINITSteps(),true,true,testParams,false);']); -0201 % R1 and R8 are added (no GPR, exchange); R2 (transport, no GPR) is -0202 % removed by the standard third step; R7 is not added (it has a GPR). -0203 testCase.verifyTrue(all(strcmp(resModel.rxns, ... -0204 {'R1';'R4';'R6';'R8';'R9';'R10'}))); -0205 -0206 % make R7 and R10 spontaneous -0207 evalc('prepData = prepINITModel(testModel, {}, {''R7'';''R10''}, false, {}, ''s'');'); -0208 testCase.verifyTrue(all(strcmp( ... -0209 prepData.refModel.rxns(prepData.toIgnoreExch | prepData.toIgnoreSpont), ... -0210 {'R1';'R7';'R8';'R10'}))); -0211 evalc(['resModel = ftINIT(prepData,arrayData.tissues{1},[],[],' ... -0212 'arrayData,[],getINITSteps(),true,true,testParams,false);']); -0213 % the model now takes the "correct" path (incl. R2), skipping R9/R10 -0214 testCase.verifyTrue(all(strcmp(resModel.rxns, ... -0215 {'R1';'R2';'R4';'R6';'R7';'R8'}))); -0216 end -0217 -0218 function ftINITResolveTiesAndProveAbsGapStillRunCorrectly(testCase) -0219 % resolveTies and proveAbsGap are threaded through ftINIT -> -0220 % ftINITInternalAlg (main extraction) and, for resolveTies, also -0221 % into fitTasks' gap-filling (raven-gecko-parity#104). This -0222 % fixture (same as ftINITPipelineRuns above) is too small to carry -0223 % a genuine tie or to expose the escalation's near-zero-objective -0224 % gap defect -- both are covered at genome scale in the PR -0225 % description -- so this only checks that turning either or both -0226 % on does not change the correct result or throw. -0227 % proveAbsGap is deliberately set to 0.05 here, not the -0228 % genome-scale-recommended 1.0: this fixture's reaction scores -0229 % (getTstModelRxnScores, as small as 0.5) make 1.0 genuinely loose -0230 % enough to accept a worse, different incumbent by design -- a -0231 % real property of a fixed absolute gap, not a bug -- so a value -0232 % below the model's own score granularity is what actually proves -0233 % the true optimum here. -0234 testCase.assumeMILPSolver(); -0235 testModel = getTstModel(); -0236 testParams = struct(); -0237 evalc('prepData = prepINITModel(testModel, {}, {}, false, {}, ''s'');'); -0238 -0239 arrayData.genes = testModel.genes; -0240 arrayData.tissues = {'a'}; -0241 arrayData.levels = getExprForRxnScore(getTstModelRxnScores()); -0242 arrayData.threshold = 1; +0159 function parseHPAparsesSyntheticDump(testCase) +0160 % parseHPA reads a tab-separated HPA dump with the columns +0161 % Gene / Gene name / Tissue / Cell type / Level / Reliability. +0162 % Synthesise a tiny one so the parser is exercised without the +0163 % multi-gigabyte external download. +0164 g = testCase.model.genes(1:3); +0165 f = [tempname '.tsv']; +0166 testCase.addTeardown(@() delete(f)); +0167 fid = fopen(f, 'w'); +0168 fprintf(fid, 'Gene\tGene name\tTissue\tCell type\tLevel\tReliability\n'); +0169 fprintf(fid, '%s\t%s\tliver\thepatocyte\tHigh\tApproved\n', g{1}, g{1}); +0170 fprintf(fid, '%s\t%s\tliver\thepatocyte\tMedium\tApproved\n', g{2}, g{2}); +0171 fprintf(fid, '%s\t%s\tbrain\tneuron\tLow\tSupported\n', g{3}, g{3}); +0172 fclose(fid); +0173 evalc('hpa = parseHPA(f);'); +0174 testCase.verifyClass(hpa, 'struct'); +0175 testCase.verifyEqual(sort(hpa.genes), sort(g)); +0176 testCase.verifyNumElements(hpa.tissues, 2); % liver+brain cell types +0177 end +0178 +0179 function parseHPArnaParsesSyntheticDump(testCase) +0180 % parseHPArna (version 19) reads Gene / Gene name / Tissue / +0181 % TPM / pTPM / NX and builds a genes-by-tissues TPM matrix. +0182 g = testCase.model.genes(1:3); +0183 f = [tempname '.tsv']; +0184 testCase.addTeardown(@() delete(f)); +0185 fid = fopen(f, 'w'); +0186 fprintf(fid, 'Gene\tGene name\tTissue\tTPM\tpTPM\tNX\n'); +0187 fprintf(fid, '%s\t%s\tliver\t12.5\t10.1\t8.0\n', g{1}, g{1}); +0188 fprintf(fid, '%s\t%s\tbrain\t3.2\t2.9\t1.5\n', g{2}, g{2}); +0189 fprintf(fid, '%s\t%s\tliver\t0.0\t0.0\t0.0\n', g{3}, g{3}); +0190 fclose(fid); +0191 evalc('rna = parseHPArna(f);'); +0192 testCase.verifyClass(rna, 'struct'); +0193 testCase.verifyNumElements(rna.genes, 3); +0194 testCase.verifyEqual(size(rna.levels), [3 2]); % 3 genes x 2 tissues +0195 end +0196 +0197 function getINITModelRuns(testCase) +0198 % The tINIT path is legacy but supported, and until now nothing +0199 % exercised it: a refactor of the machinery it shares with ftINIT +0200 % (checkTasks, simplifyModel, solveLP, removeReactions) could +0201 % break it without any test noticing. +0202 testCase.assumeMILPSolver(); +0203 refModel = getTstModel(); +0204 % getINITModel wants the closed form, which this fixture is not +0205 % built in; its own documentation gives this as the way to add it +0206 refModel.unconstrained = false(numel(refModel.mets),1); +0207 +0208 arrayData.genes = refModel.genes; +0209 arrayData.tissues = {'a'}; +0210 arrayData.levels = getExprForRxnScore(getTstModelRxnScores()); +0211 arrayData.threshold = 1; +0212 +0213 evalc(['m = getINITModel(refModel, arrayData.tissues{1}, ' ... +0214 '''arrayData'', arrayData, ''printReport'', false);']); +0215 testCase.verifyClass(m, 'struct'); +0216 testCase.verifyNotEmpty(m.rxns); +0217 testCase.verifyTrue(all(ismember(m.rxns, refModel.rxns))); +0218 % R4 and R10 carry the highest scores, so neither should be cut +0219 testCase.verifyTrue(all(ismember({'R4';'R10'}, m.rxns))); +0220 end +0221 +0222 function ftINITPipelineRuns(testCase) +0223 % prepINITModel + ftINIT end-to-end on testModel without tasks. +0224 testCase.assumeMILPSolver(); +0225 testModel = getTstModel(); +0226 testParams = struct(); +0227 evalc('prepData = prepINITModel(testModel, {}, {}, false, {}, ''s'');'); +0228 % R1 and R8 are exchange reactions without a GPR +0229 testCase.verifyTrue(all(strcmp( ... +0230 prepData.refModel.rxns(prepData.toIgnoreExch), {'R1';'R8'}))); +0231 +0232 arrayData.genes = testModel.genes; +0233 arrayData.tissues = {'a'}; +0234 arrayData.levels = getExprForRxnScore(getTstModelRxnScores()); +0235 arrayData.threshold = 1; +0236 +0237 evalc(['resModel = ftINIT(prepData,arrayData.tissues{1},[],[],' ... +0238 'arrayData,[],getINITSteps(),true,true,testParams,false);']); +0239 % R1 and R8 are added (no GPR, exchange); R2 (transport, no GPR) is +0240 % removed by the standard third step; R7 is not added (it has a GPR). +0241 testCase.verifyTrue(all(strcmp(resModel.rxns, ... +0242 {'R1';'R4';'R6';'R8';'R9';'R10'}))); 0243 -0244 expected = {'R1';'R4';'R6';'R8';'R9';'R10'}; -0245 -0246 evalc(['resTies = ftINIT(prepData,arrayData.tissues{1},[],[],' ... -0247 'arrayData,[],getINITSteps(),true,true,testParams,false,' ... -0248 '''resolveTies'',true);']); -0249 testCase.verifyTrue(all(strcmp(resTies.rxns, expected))); -0250 -0251 evalc(['resGap = ftINIT(prepData,arrayData.tissues{1},[],[],' ... -0252 'arrayData,[],getINITSteps(),true,true,testParams,false,' ... -0253 '''proveAbsGap'',0.05);']); -0254 testCase.verifyTrue(all(strcmp(resGap.rxns, expected))); +0244 % make R7 and R10 spontaneous +0245 evalc('prepData = prepINITModel(testModel, {}, {''R7'';''R10''}, false, {}, ''s'');'); +0246 testCase.verifyTrue(all(strcmp( ... +0247 prepData.refModel.rxns(prepData.toIgnoreExch | prepData.toIgnoreSpont), ... +0248 {'R1';'R7';'R8';'R10'}))); +0249 evalc(['resModel = ftINIT(prepData,arrayData.tissues{1},[],[],' ... +0250 'arrayData,[],getINITSteps(),true,true,testParams,false);']); +0251 % the model now takes the "correct" path (incl. R2), skipping R9/R10 +0252 testCase.verifyTrue(all(strcmp(resModel.rxns, ... +0253 {'R1';'R2';'R4';'R6';'R7';'R8'}))); +0254 end 0255 -0256 evalc(['resBoth = ftINIT(prepData,arrayData.tissues{1},[],[],' ... -0257 'arrayData,[],getINITSteps(),true,true,testParams,false,' ... -0258 '''resolveTies'',true,''proveAbsGap'',0.05);']); -0259 testCase.verifyTrue(all(strcmp(resBoth.rxns, expected))); -0260 end -0261 -0262 function ftINITWithTaskRuns(testCase) -0263 % A task requiring e[s] from a[s] forces R2 and R7 to be essential. -0264 testCase.assumeMILPSolver(); -0265 testModel = getTstModel(); -0266 testModelTasks = getTstModelTasks(); -0267 testParams = struct(); -0268 evalc('prepData = prepINITModel(testModel, testModelTasks, {}, false, {}, ''s'');'); -0269 % essential rxns are reported on the linearly merged model (R1, R7) -0270 testCase.verifyTrue(all(strcmp(prepData.essentialRxns, {'R1';'R7'}))); -0271 -0272 arrayData.genes = testModel.genes; -0273 arrayData.tissues = {'a'}; -0274 arrayData.levels = getExprForRxnScore(getTstModelRxnScores()); -0275 arrayData.threshold = 1; -0276 evalc(['resModel = ftINIT(prepData,arrayData.tissues{1},[],[],' ... -0277 'arrayData,[],getINITSteps(),true,true,testParams,false);']); -0278 % R2 and R7 essential => all rxns on except R3 and R5 (negative -0279 % score, not needed for the task) -0280 testCase.verifyTrue(all(strcmp(resModel.rxns, ... -0281 {'R1';'R2';'R4';'R6';'R7';'R8';'R9';'R10'}))); -0282 end +0256 function ftINITResolveTiesAndProveAbsGapStillRunCorrectly(testCase) +0257 % resolveTies and proveAbsGap are threaded through ftINIT -> +0258 % ftINITInternalAlg (main extraction) and, for resolveTies, also +0259 % into fitTasks' gap-filling (raven-gecko-parity#104). This +0260 % fixture (same as ftINITPipelineRuns above) is too small to carry +0261 % a genuine tie or to expose the escalation's near-zero-objective +0262 % gap defect -- both are covered at genome scale in the PR +0263 % description -- so this only checks that turning either or both +0264 % on does not change the correct result or throw. +0265 % proveAbsGap is deliberately set to 0.05 here, not the +0266 % genome-scale-recommended 1.0: this fixture's reaction scores +0267 % (getTstModelRxnScores, as small as 0.5) make 1.0 genuinely loose +0268 % enough to accept a worse, different incumbent by design -- a +0269 % real property of a fixed absolute gap, not a bug -- so a value +0270 % below the model's own score granularity is what actually proves +0271 % the true optimum here. +0272 testCase.assumeMILPSolver(); +0273 testModel = getTstModel(); +0274 testParams = struct(); +0275 evalc('prepData = prepINITModel(testModel, {}, {}, false, {}, ''s'');'); +0276 +0277 arrayData.genes = testModel.genes; +0278 arrayData.tissues = {'a'}; +0279 arrayData.levels = getExprForRxnScore(getTstModelRxnScores()); +0280 arrayData.threshold = 1; +0281 +0282 expected = {'R1';'R4';'R6';'R8';'R9';'R10'}; 0283 -0284 function fitTasksPreMergedRuns(testCase) -0285 % Remove exchange rxns (required for gap filling) and create a gap -0286 % by removing R7; fitTasks with gapFillMode 'preMerged' must add -0287 % R7 back. -0288 testCase.assumeMILPSolver(); -0289 testModel = getTstModel(); -0290 testModelTasks = getTstModelTasks(); -0291 testRxnScores = getTstModelRxnScores(); -0292 -0293 mTempRef = closeModel(testModel); -0294 mTempRef = removeReactions(mTempRef, {'R1';'R8'}); -0295 mTemp = removeReactions(mTempRef, {'R7'}); -0296 mTemp.id = 'tmp'; -0297 tmpRxnScores = testRxnScores([2;3;4;5;6;7;9;10]); -0298 evalc(['[~,addedRxnMat] = fitTasks(mTemp,mTempRef,[],' ... -0299 '''printOutput'',false,''rxnScores'',min(tmpRxnScores,-0.1),' ... -0300 '''taskStructure'',testModelTasks,''gapFillMode'',''preMerged'',' ... -0301 '''params'',struct(),''verbose'',false);']); -0302 % An equality rather than all(strcmp(...)): an empty selection -0303 % makes strcmp return an empty logical, which all() reports as -0304 % true, so a run that added nothing would pass silently. -0305 testCase.verifyEqual(mTempRef.rxns(any(addedRxnMat,2)), {'R7'}); -0306 end -0307 -0308 function fitTasksPreMergedResolveTiesStillFillsCorrectly(testCase) -0309 % resolveTies is threaded through fitTasks (gapFillMode preMerged) -0310 % -> ftINITFillGaps -> getMinNrFluxes (raven-gecko-parity#104); this -0311 % exercises that whole chain end to end. Same fixture as -0312 % fitTasksPreMergedRuns above -- correctness (R7 still gets added -0313 % back) is what this checks, since that fixture has a unique fill -0314 % (no tie to actually break); getMinNrFluxesResolveTiesIsDeterministic -0315 % in tAnalysis.m covers the tie-breaking property itself. -0316 testCase.assumeMILPSolver(); -0317 testModel = getTstModel(); -0318 testModelTasks = getTstModelTasks(); -0319 testRxnScores = getTstModelRxnScores(); -0320 -0321 mTempRef = closeModel(testModel); -0322 mTempRef = removeReactions(mTempRef, {'R1';'R8'}); -0323 mTemp = removeReactions(mTempRef, {'R7'}); -0324 mTemp.id = 'tmp'; -0325 tmpRxnScores = testRxnScores([2;3;4;5;6;7;9;10]); -0326 evalc(['[~,addedRxnMat] = fitTasks(mTemp,mTempRef,[],' ... -0327 '''printOutput'',false,''rxnScores'',min(tmpRxnScores,-0.1),' ... -0328 '''taskStructure'',testModelTasks,''gapFillMode'',''preMerged'',' ... -0329 '''params'',struct(),''verbose'',false,''resolveTies'',true);']); -0330 testCase.verifyEqual(mTempRef.rxns(any(addedRxnMat,2)), {'R7'}); -0331 end -0332 -0333 function fitTasksPreMergedReportsUnfillableTask(testCase) -0334 % R7 is the only producer of e[s], so removing it from the -0335 % reference model as well leaves the task unfillable. R8 is kept -0336 % so that e[s] still takes part in a reaction, and R9 is withheld -0337 % from the model so the reference has something to offer: the -0338 % MILP is then the thing that has to report the task as -0339 % unfillable rather than as "Added 0 reaction(s)". -0340 testCase.assumeMILPSolver(); -0341 testModel = getTstModel(); -0342 testModelTasks = getTstModelTasks(); -0343 testRxnScores = getTstModelRxnScores(); -0344 -0345 mTempRef = closeModel(testModel); -0346 mTempRef = removeReactions(mTempRef, {'R1';'R7'}); -0347 mTemp = removeReactions(mTempRef, {'R9'}); -0348 mTemp.id = 'tmp'; -0349 tmpRxnScores = testRxnScores([2;3;4;5;6;8;9;10]); -0350 lastwarn(''); -0351 evalc(['[~,addedRxnMat,failedTasks] = fitTasks(mTemp,mTempRef,[],' ... -0352 '''printOutput'',false,''rxnScores'',min(tmpRxnScores,-0.1),' ... -0353 '''taskStructure'',testModelTasks,''gapFillMode'',''preMerged'');']); -0354 testCase.verifyTrue(failedTasks(1)); -0355 testCase.verifyFalse(any(addedRxnMat(:))); -0356 % Every way of failing sets failedTasks, so the reason has to be -0357 % checked too: without this the test would also pass if the -0358 % gap-filling attempt threw before it reached the solver. -0359 testCase.verifySubstring(lastwarn, 'no feasible solution exists'); -0360 end -0361 -0362 function fitTasksPreMergedHandlesEmptyReferenceSet(testCase) -0363 % The reference model holds nothing the model does not already -0364 % have, so no reaction can be added. An empty reaction list -0365 % means "all reactions" to the MILP, which would otherwise -0366 % report the model itself as a solution to an unfillable task. -0367 testCase.assumeMILPSolver(); -0368 testModel = getTstModel(); -0369 testModelTasks = getTstModelTasks(); -0370 testRxnScores = getTstModelRxnScores(); -0371 -0372 mTempRef = closeModel(testModel); -0373 mTempRef = removeReactions(mTempRef, {'R1';'R7';'R8'}); -0374 mTemp = mTempRef; -0375 mTemp.id = 'tmp'; -0376 tmpRxnScores = testRxnScores([2;3;4;5;6;9;10]); -0377 lastwarn(''); -0378 evalc(['[~,addedRxnMat,failedTasks] = fitTasks(mTemp,mTempRef,[],' ... -0379 '''printOutput'',false,''rxnScores'',min(tmpRxnScores,-0.1),' ... -0380 '''taskStructure'',testModelTasks,''gapFillMode'',''preMerged'',' ... -0381 '''params'',struct(),''verbose'',false);']); -0382 testCase.verifyTrue(failedTasks(1)); -0383 testCase.verifyFalse(any(addedRxnMat(:))); -0384 testCase.verifySubstring(lastwarn, 'no feasible solution exists'); -0385 end -0386 -0387 function fitTasksPreMergedWarningHasRealNewlineAndPercent(testCase) -0388 % The "could not be gap-filled" warning embeds the task -0389 % id/description; a literal "\n" must become a real newline -0390 % (not print as the two characters backslash-n), and a "%" in -0391 % the task id must survive intact. -0392 testCase.assumeMILPSolver(); -0393 testModel = getTstModel(); -0394 testModelTasks = getTstModelTasks(); -0395 testModelTasks.id = 'Task 50% test'; -0396 testModelTasks.description = testModelTasks.id; -0397 testRxnScores = getTstModelRxnScores(); -0398 -0399 mTempRef = closeModel(testModel); -0400 mTempRef = removeReactions(mTempRef, {'R1';'R7';'R8'}); -0401 mTemp = mTempRef; -0402 mTemp.id = 'tmp'; -0403 tmpRxnScores = testRxnScores([2;3;4;5;6;9;10]); -0404 lastwarn(''); -0405 evalc(['fitTasks(mTemp,mTempRef,[],' ... -0406 '''printOutput'',false,''rxnScores'',min(tmpRxnScores,-0.1),' ... -0407 '''taskStructure'',testModelTasks,''gapFillMode'',''preMerged'',' ... -0408 '''params'',struct(),''verbose'',false);']); -0409 msg = lastwarn(); -0410 testCase.verifySubstring(msg, 'Task 50% test'); -0411 testCase.verifyFalse(contains(msg, '\n')); -0412 end -0413 -0414 function ftINITFillGapsReportsTaskNeedingAnOrphanMet(testCase) -0415 % e[s] takes part only in R7 and R8. With both gone from the -0416 % reference model no reaction touches e[s] at all, so a task -0417 % requiring net production of it cannot be filled from that -0418 % reference. That requirement is carried in b, and simplifyModel -0419 % must leave the metabolite in place: without its row the MILP -0420 % solves a problem that no longer asks for e[s], and reports the -0421 % task as satisfiable. -0422 testCase.assumeMILPSolver(); -0423 testModel = getTstModel(); -0424 testRxnScores = getTstModelRxnScores(); -0425 -0426 mTempRef = closeModel(testModel); -0427 mTempRef = removeReactions(mTempRef, {'R1';'R7';'R8'}); -0428 % R5 is dropped from the model only so that the reference offers -0429 % a candidate reaction to consider; adding it back does not make -0430 % e[s] reachable. -0431 mTemp = removeReactions(mTempRef, {'R5'}); -0432 mTemp.id = 'tmp'; -0433 tmpRxnScores = min(testRxnScores([2;3;4;5;6;9;10]), -0.1); -0434 -0435 tModel = setTaskBounds(mTemp); -0436 tRef = setTaskBounds(mTempRef); -0437 sol = solveLP(tModel); -0438 testCase.assertEmpty(sol.x, 'the task must start out infeasible'); -0439 -0440 evalc(['[addedRxns,~,exitFlag] = ftINITFillGaps(tModel,mTemp,tRef,' ... -0441 'false,true,tmpRxnScores,struct(),false);']); -0442 testCase.verifyEqual(exitFlag, -1); -0443 testCase.verifyEmpty(addedRxns); -0444 end -0445 -0446 function ftINITFillGapsReportsNoCandidateReactions(testCase) -0447 % The reference model holds no reaction the model does not -0448 % already have, so nothing can be added to satisfy the task. An -0449 % empty reaction list means "all reactions" to the MILP, and its -0450 % solution is then sized for every reaction rather than for the -0451 % empty set of candidates it is indexed against. -0452 testCase.assumeMILPSolver(); -0453 testModel = getTstModel(); -0454 testRxnScores = getTstModelRxnScores(); -0455 -0456 m = closeModel(testModel); -0457 m = removeReactions(m, {'R1'}); % R7/R8 stay, so e[s] is reachable -0458 mTemp = m; -0459 mTemp.id = 'tmp'; -0460 tmpRxnScores = min(testRxnScores(2:10), -0.1); -0461 -0462 % Both sides hold the same reactions, so there is no candidate -0463 % to add. Only the reference may take up a[s], so only it can -0464 % satisfy the task. -0465 tRef = setTaskBounds(m); -0466 tModel = setTaskBounds(mTemp); -0467 tModel.b(strcmp(tModel.mets, 'as'), 1) = 0; -0468 sol = solveLP(tModel); -0469 testCase.assertEmpty(sol.x, 'the task must start out infeasible'); -0470 -0471 evalc(['[addedRxns,~,exitFlag] = ftINITFillGaps(tModel,mTemp,tRef,' ... -0472 'false,true,tmpRxnScores,struct(),false);']); -0473 testCase.verifyEqual(exitFlag, -1); -0474 testCase.verifyEmpty(addedRxns); -0475 end -0476 -0477 function ftINITMetabolomicsRuns(testCase) -0478 % Detected metabolites steer ftINIT towards alternative pathways. -0479 testCase.assumeMILPSolver(); -0480 testModel = getTstModel(); -0481 testParams = struct(); -0482 evalc('prepData = prepINITModel(testModel, {}, {''R7'';''R10''}, false, {}, ''s'');'); +0284 evalc(['resTies = ftINIT(prepData,arrayData.tissues{1},[],[],' ... +0285 'arrayData,[],getINITSteps(),true,true,testParams,false,' ... +0286 '''resolveTies'',true);']); +0287 testCase.verifyTrue(all(strcmp(resTies.rxns, expected))); +0288 +0289 evalc(['resGap = ftINIT(prepData,arrayData.tissues{1},[],[],' ... +0290 'arrayData,[],getINITSteps(),true,true,testParams,false,' ... +0291 '''proveAbsGap'',0.05);']); +0292 testCase.verifyTrue(all(strcmp(resGap.rxns, expected))); +0293 +0294 evalc(['resBoth = ftINIT(prepData,arrayData.tissues{1},[],[],' ... +0295 'arrayData,[],getINITSteps(),true,true,testParams,false,' ... +0296 '''resolveTies'',true,''proveAbsGap'',0.05);']); +0297 testCase.verifyTrue(all(strcmp(resBoth.rxns, expected))); +0298 end +0299 +0300 function ftINITWithTaskRuns(testCase) +0301 % A task requiring e[s] from a[s] forces R2 and R7 to be essential. +0302 testCase.assumeMILPSolver(); +0303 testModel = getTstModel(); +0304 testModelTasks = getTstModelTasks(); +0305 testParams = struct(); +0306 evalc('prepData = prepINITModel(testModel, testModelTasks, {}, false, {}, ''s'');'); +0307 % essential rxns are reported on the linearly merged model (R1, R7) +0308 testCase.verifyTrue(all(strcmp(prepData.essentialRxns, {'R1';'R7'}))); +0309 +0310 arrayData.genes = testModel.genes; +0311 arrayData.tissues = {'a'}; +0312 arrayData.levels = getExprForRxnScore(getTstModelRxnScores()); +0313 arrayData.threshold = 1; +0314 evalc(['resModel = ftINIT(prepData,arrayData.tissues{1},[],[],' ... +0315 'arrayData,[],getINITSteps(),true,true,testParams,false);']); +0316 % R2 and R7 essential => all rxns on except R3 and R5 (negative +0317 % score, not needed for the task) +0318 testCase.verifyTrue(all(strcmp(resModel.rxns, ... +0319 {'R1';'R2';'R4';'R6';'R7';'R8';'R9';'R10'}))); +0320 end +0321 +0322 function fitTasksPreMergedRuns(testCase) +0323 % Remove exchange rxns (required for gap filling) and create a gap +0324 % by removing R7; fitTasks with gapFillMode 'preMerged' must add +0325 % R7 back. +0326 testCase.assumeMILPSolver(); +0327 testModel = getTstModel(); +0328 testModelTasks = getTstModelTasks(); +0329 testRxnScores = getTstModelRxnScores(); +0330 +0331 mTempRef = closeModel(testModel); +0332 mTempRef = removeReactions(mTempRef, {'R1';'R8'}); +0333 mTemp = removeReactions(mTempRef, {'R7'}); +0334 mTemp.id = 'tmp'; +0335 tmpRxnScores = testRxnScores([2;3;4;5;6;7;9;10]); +0336 evalc(['[~,addedRxnMat] = fitTasks(mTemp,mTempRef,[],' ... +0337 '''printOutput'',false,''rxnScores'',min(tmpRxnScores,-0.1),' ... +0338 '''taskStructure'',testModelTasks,''gapFillMode'',''preMerged'',' ... +0339 '''params'',struct(),''verbose'',false);']); +0340 % An equality rather than all(strcmp(...)): an empty selection +0341 % makes strcmp return an empty logical, which all() reports as +0342 % true, so a run that added nothing would pass silently. +0343 testCase.verifyEqual(mTempRef.rxns(any(addedRxnMat,2)), {'R7'}); +0344 end +0345 +0346 function fitTasksPreMergedResolveTiesStillFillsCorrectly(testCase) +0347 % resolveTies is threaded through fitTasks (gapFillMode preMerged) +0348 % -> ftINITFillGaps -> getMinNrFluxes (raven-gecko-parity#104); this +0349 % exercises that whole chain end to end. Same fixture as +0350 % fitTasksPreMergedRuns above -- correctness (R7 still gets added +0351 % back) is what this checks, since that fixture has a unique fill +0352 % (no tie to actually break); getMinNrFluxesResolveTiesIsDeterministic +0353 % in tAnalysis.m covers the tie-breaking property itself. +0354 testCase.assumeMILPSolver(); +0355 testModel = getTstModel(); +0356 testModelTasks = getTstModelTasks(); +0357 testRxnScores = getTstModelRxnScores(); +0358 +0359 mTempRef = closeModel(testModel); +0360 mTempRef = removeReactions(mTempRef, {'R1';'R8'}); +0361 mTemp = removeReactions(mTempRef, {'R7'}); +0362 mTemp.id = 'tmp'; +0363 tmpRxnScores = testRxnScores([2;3;4;5;6;7;9;10]); +0364 evalc(['[~,addedRxnMat] = fitTasks(mTemp,mTempRef,[],' ... +0365 '''printOutput'',false,''rxnScores'',min(tmpRxnScores,-0.1),' ... +0366 '''taskStructure'',testModelTasks,''gapFillMode'',''preMerged'',' ... +0367 '''params'',struct(),''verbose'',false,''resolveTies'',true);']); +0368 testCase.verifyEqual(mTempRef.rxns(any(addedRxnMat,2)), {'R7'}); +0369 end +0370 +0371 function fitTasksPreMergedReportsUnfillableTask(testCase) +0372 % R7 is the only producer of e[s], so removing it from the +0373 % reference model as well leaves the task unfillable. R8 is kept +0374 % so that e[s] still takes part in a reaction, and R9 is withheld +0375 % from the model so the reference has something to offer: the +0376 % MILP is then the thing that has to report the task as +0377 % unfillable rather than as "Added 0 reaction(s)". +0378 testCase.assumeMILPSolver(); +0379 testModel = getTstModel(); +0380 testModelTasks = getTstModelTasks(); +0381 testRxnScores = getTstModelRxnScores(); +0382 +0383 mTempRef = closeModel(testModel); +0384 mTempRef = removeReactions(mTempRef, {'R1';'R7'}); +0385 mTemp = removeReactions(mTempRef, {'R9'}); +0386 mTemp.id = 'tmp'; +0387 tmpRxnScores = testRxnScores([2;3;4;5;6;8;9;10]); +0388 lastwarn(''); +0389 evalc(['[~,addedRxnMat,failedTasks] = fitTasks(mTemp,mTempRef,[],' ... +0390 '''printOutput'',false,''rxnScores'',min(tmpRxnScores,-0.1),' ... +0391 '''taskStructure'',testModelTasks,''gapFillMode'',''preMerged'');']); +0392 testCase.verifyTrue(failedTasks(1)); +0393 testCase.verifyFalse(any(addedRxnMat(:))); +0394 % Every way of failing sets failedTasks, so the reason has to be +0395 % checked too: without this the test would also pass if the +0396 % gap-filling attempt threw before it reached the solver. +0397 testCase.verifySubstring(lastwarn, 'no feasible solution exists'); +0398 end +0399 +0400 function fitTasksPreMergedHandlesEmptyReferenceSet(testCase) +0401 % The reference model holds nothing the model does not already +0402 % have, so no reaction can be added. An empty reaction list +0403 % means "all reactions" to the MILP, which would otherwise +0404 % report the model itself as a solution to an unfillable task. +0405 testCase.assumeMILPSolver(); +0406 testModel = getTstModel(); +0407 testModelTasks = getTstModelTasks(); +0408 testRxnScores = getTstModelRxnScores(); +0409 +0410 mTempRef = closeModel(testModel); +0411 mTempRef = removeReactions(mTempRef, {'R1';'R7';'R8'}); +0412 mTemp = mTempRef; +0413 mTemp.id = 'tmp'; +0414 tmpRxnScores = testRxnScores([2;3;4;5;6;9;10]); +0415 lastwarn(''); +0416 evalc(['[~,addedRxnMat,failedTasks] = fitTasks(mTemp,mTempRef,[],' ... +0417 '''printOutput'',false,''rxnScores'',min(tmpRxnScores,-0.1),' ... +0418 '''taskStructure'',testModelTasks,''gapFillMode'',''preMerged'',' ... +0419 '''params'',struct(),''verbose'',false);']); +0420 testCase.verifyTrue(failedTasks(1)); +0421 testCase.verifyFalse(any(addedRxnMat(:))); +0422 testCase.verifySubstring(lastwarn, 'no feasible solution exists'); +0423 end +0424 +0425 function fitTasksPreMergedWarningHasRealNewlineAndPercent(testCase) +0426 % The "could not be gap-filled" warning embeds the task +0427 % id/description; a literal "\n" must become a real newline +0428 % (not print as the two characters backslash-n), and a "%" in +0429 % the task id must survive intact. +0430 testCase.assumeMILPSolver(); +0431 testModel = getTstModel(); +0432 testModelTasks = getTstModelTasks(); +0433 testModelTasks.id = 'Task 50% test'; +0434 testModelTasks.description = testModelTasks.id; +0435 testRxnScores = getTstModelRxnScores(); +0436 +0437 mTempRef = closeModel(testModel); +0438 mTempRef = removeReactions(mTempRef, {'R1';'R7';'R8'}); +0439 mTemp = mTempRef; +0440 mTemp.id = 'tmp'; +0441 tmpRxnScores = testRxnScores([2;3;4;5;6;9;10]); +0442 lastwarn(''); +0443 evalc(['fitTasks(mTemp,mTempRef,[],' ... +0444 '''printOutput'',false,''rxnScores'',min(tmpRxnScores,-0.1),' ... +0445 '''taskStructure'',testModelTasks,''gapFillMode'',''preMerged'',' ... +0446 '''params'',struct(),''verbose'',false);']); +0447 msg = lastwarn(); +0448 testCase.verifySubstring(msg, 'Task 50% test'); +0449 testCase.verifyFalse(contains(msg, '\n')); +0450 end +0451 +0452 function ftINITFillGapsReportsTaskNeedingAnOrphanMet(testCase) +0453 % e[s] takes part only in R7 and R8. With both gone from the +0454 % reference model no reaction touches e[s] at all, so a task +0455 % requiring net production of it cannot be filled from that +0456 % reference. That requirement is carried in b, and simplifyModel +0457 % must leave the metabolite in place: without its row the MILP +0458 % solves a problem that no longer asks for e[s], and reports the +0459 % task as satisfiable. +0460 testCase.assumeMILPSolver(); +0461 testModel = getTstModel(); +0462 testRxnScores = getTstModelRxnScores(); +0463 +0464 mTempRef = closeModel(testModel); +0465 mTempRef = removeReactions(mTempRef, {'R1';'R7';'R8'}); +0466 % R5 is dropped from the model only so that the reference offers +0467 % a candidate reaction to consider; adding it back does not make +0468 % e[s] reachable. +0469 mTemp = removeReactions(mTempRef, {'R5'}); +0470 mTemp.id = 'tmp'; +0471 tmpRxnScores = min(testRxnScores([2;3;4;5;6;9;10]), -0.1); +0472 +0473 tModel = setTaskBounds(mTemp); +0474 tRef = setTaskBounds(mTempRef); +0475 sol = solveLP(tModel); +0476 testCase.assertEmpty(sol.x, 'the task must start out infeasible'); +0477 +0478 evalc(['[addedRxns,~,exitFlag] = ftINITFillGaps(tModel,mTemp,tRef,' ... +0479 'false,true,tmpRxnScores,struct(),false);']); +0480 testCase.verifyEqual(exitFlag, -1); +0481 testCase.verifyEmpty(addedRxns); +0482 end 0483 -0484 arrayData.genes = testModel.genes; -0485 arrayData.tissues = {'a'}; -0486 arrayData.levels = getExprForRxnScore(getTstModelRxnScores()); -0487 arrayData.threshold = 1; -0488 -0489 % adding metabolite f favours R9/R10 again (presence of R2 is random) -0490 evalc(['resModel = ftINIT(prepData,arrayData.tissues{1},[],[],' ... -0491 'arrayData,{''f''},getINITSteps(),true,true,testParams,false);']); -0492 if length(resModel.rxns) == 7 -0493 testCase.verifyTrue(all(strcmp(resModel.rxns, ... -0494 {'R1';'R4';'R6';'R7';'R8';'R9';'R10'}))); -0495 else -0496 testCase.verifyTrue(all(strcmp(resModel.rxns, ... -0497 {'R1';'R2';'R4';'R6';'R7';'R8';'R9';'R10'}))); -0498 end +0484 function ftINITFillGapsReportsNoCandidateReactions(testCase) +0485 % The reference model holds no reaction the model does not +0486 % already have, so nothing can be added to satisfy the task. An +0487 % empty reaction list means "all reactions" to the MILP, and its +0488 % solution is then sized for every reaction rather than for the +0489 % empty set of candidates it is indexed against. +0490 testCase.assumeMILPSolver(); +0491 testModel = getTstModel(); +0492 testRxnScores = getTstModelRxnScores(); +0493 +0494 m = closeModel(testModel); +0495 m = removeReactions(m, {'R1'}); % R7/R8 stay, so e[s] is reachable +0496 mTemp = m; +0497 mTemp.id = 'tmp'; +0498 tmpRxnScores = min(testRxnScores(2:10), -0.1); 0499 -0500 % metabolites a, e, f give the same result -0501 evalc(['resModel = ftINIT(prepData,arrayData.tissues{1},[],[],' ... -0502 'arrayData,{''f'';''a'';''e''},getINITSteps(),true,true,testParams,false);']); -0503 if length(resModel.rxns) == 7 -0504 testCase.verifyTrue(all(strcmp(resModel.rxns, ... -0505 {'R1';'R4';'R6';'R7';'R8';'R9';'R10'}))); -0506 else -0507 testCase.verifyTrue(all(strcmp(resModel.rxns, ... -0508 {'R1';'R2';'R4';'R6';'R7';'R8';'R9';'R10'}))); -0509 end -0510 -0511 % metabolite b turns on R2 and R3/R5 and turns off R9 -0512 evalc(['resModel = ftINIT(prepData,arrayData.tissues{1},[],[],' ... -0513 'arrayData,{''b''},getINITSteps(),true,true,testParams,false);']); -0514 testCase.verifyTrue(all(strcmp(resModel.rxns, ... -0515 {'R1';'R2';'R3';'R4';'R5';'R6';'R7';'R8'}))); -0516 -0517 % testModel5 contains reactions that are not linearly merged -0518 testModel5 = getTstModel5(); -0519 arrayData.genes = testModel5.genes; -0520 arrayData.levels = getExprForRxnScore(getTstModel5RxnScores()); +0500 % Both sides hold the same reactions, so there is no candidate +0501 % to add. Only the reference may take up a[s], so only it can +0502 % satisfy the task. +0503 tRef = setTaskBounds(m); +0504 tModel = setTaskBounds(mTemp); +0505 tModel.b(strcmp(tModel.mets, 'as'), 1) = 0; +0506 sol = solveLP(tModel); +0507 testCase.assertEmpty(sol.x, 'the task must start out infeasible'); +0508 +0509 evalc(['[addedRxns,~,exitFlag] = ftINITFillGaps(tModel,mTemp,tRef,' ... +0510 'false,true,tmpRxnScores,struct(),false);']); +0511 testCase.verifyEqual(exitFlag, -1); +0512 testCase.verifyEmpty(addedRxns); +0513 end +0514 +0515 function ftINITMetabolomicsRuns(testCase) +0516 % Detected metabolites steer ftINIT towards alternative pathways. +0517 testCase.assumeMILPSolver(); +0518 testModel = getTstModel(); +0519 testParams = struct(); +0520 evalc('prepData = prepINITModel(testModel, {}, {''R7'';''R10''}, false, {}, ''s'');'); 0521 -0522 evalc('prepData5 = prepINITModel(testModel5, {}, {''R7'';''R10''}, false, {}, ''s'');'); -0523 evalc(['resModel = ftINIT(prepData5,arrayData.tissues{1},[],[],' ... -0524 'arrayData,{},getINITSteps(),true,true,testParams,false);']); -0525 % a->g->e via R11/R13 (score -2) ties the R2 path (score -2), so -0526 % which of the two is returned is a tie-break that differs between -0527 % solvers. Neither route uses R9/R10. isequal rather than strcmp, -0528 % which errors on two lists of different length. -0529 testCase.verifyTrue( ... -0530 isequal(resModel.rxns, {'R1';'R4';'R6';'R7';'R8';'R11';'R13'}) || ... -0531 isequal(resModel.rxns, {'R1';'R2';'R4';'R6';'R7';'R8'})); -0532 -0533 % adding metabolite g drops R7 -0534 evalc('prepData5 = prepINITModel(testModel5, {}, {''R10''}, false, {}, ''s'');'); -0535 arrayData.levels(7) = getExprForRxnScore(-1.1); % avoid randomness -0536 evalc(['resModel = ftINIT(prepData5,arrayData.tissues{1},[],[],' ... -0537 'arrayData,{''g''},getINITSteps(),true,true,testParams,false);']); -0538 testCase.verifyTrue(all(strcmp(resModel.rxns, ... -0539 {'R1';'R4';'R6';'R8';'R11';'R13'}))); -0540 end -0541 -0542 function ftINITFullVsThreeStepRuns(testCase) -0543 % On a larger model the three-step and "full" variants must give -0544 % consistent results. -0545 testCase.assumeMILPSolver(); -0546 testModelL = getTstModelL(); -0547 testModelLGeneScores = getTstModelLGeneScores(); -0548 testParams = struct(); -0549 -0550 arrayDataL.genes = testModelL.genes; -0551 arrayDataL.tissues = {'t1'}; -0552 arrayDataL.levels = getExprForRxnScore(testModelLGeneScores, 1); -0553 arrayDataL.threshold = 1; +0522 arrayData.genes = testModel.genes; +0523 arrayData.tissues = {'a'}; +0524 arrayData.levels = getExprForRxnScore(getTstModelRxnScores()); +0525 arrayData.threshold = 1; +0526 +0527 % adding metabolite f favours R9/R10 again (presence of R2 is random) +0528 evalc(['resModel = ftINIT(prepData,arrayData.tissues{1},[],[],' ... +0529 'arrayData,{''f''},getINITSteps(),true,true,testParams,false);']); +0530 if length(resModel.rxns) == 7 +0531 testCase.verifyTrue(all(strcmp(resModel.rxns, ... +0532 {'R1';'R4';'R6';'R7';'R8';'R9';'R10'}))); +0533 else +0534 testCase.verifyTrue(all(strcmp(resModel.rxns, ... +0535 {'R1';'R2';'R4';'R6';'R7';'R8';'R9';'R10'}))); +0536 end +0537 +0538 % metabolites a, e, f give the same result +0539 evalc(['resModel = ftINIT(prepData,arrayData.tissues{1},[],[],' ... +0540 'arrayData,{''f'';''a'';''e''},getINITSteps(),true,true,testParams,false);']); +0541 if length(resModel.rxns) == 7 +0542 testCase.verifyTrue(all(strcmp(resModel.rxns, ... +0543 {'R1';'R4';'R6';'R7';'R8';'R9';'R10'}))); +0544 else +0545 testCase.verifyTrue(all(strcmp(resModel.rxns, ... +0546 {'R1';'R2';'R4';'R6';'R7';'R8';'R9';'R10'}))); +0547 end +0548 +0549 % metabolite b turns on R2 and R3/R5 and turns off R9 +0550 evalc(['resModel = ftINIT(prepData,arrayData.tissues{1},[],[],' ... +0551 'arrayData,{''b''},getINITSteps(),true,true,testParams,false);']); +0552 testCase.verifyTrue(all(strcmp(resModel.rxns, ... +0553 {'R1';'R2';'R3';'R4';'R5';'R6';'R7';'R8'}))); 0554 -0555 evalc('prepDataL = prepINITModel(testModelL, [], {}, false, {}, ''s'');'); -0556 evalc(['mres = ftINIT(prepDataL,arrayDataL.tissues{1},[],[],' ... -0557 'arrayDataL,[],getINITSteps(),true,true,testParams,false);']); -0558 evalc(['mres2 = ftINIT(prepDataL,arrayDataL.tissues{1},[],[],' ... -0559 'arrayDataL,[],getINITSteps([], ''full''),true,true,testParams,false);']); -0560 -0561 expResult = {'S1';'S2';'S3';'S4';'S5';'S6';'S7';'S8';'S9';'E1';'E2'; ... -0562 'E3';'E4';'E5';'E6';'E8';'E9';'R1';'R2';'R3';'R4';'R5';'R6';'R7'; ... -0563 'R8';'R9';'R12';'R13';'R14';'R15'}; -0564 testCase.verifyTrue(all(contains(mres.rxns, expResult))); -0565 testCase.verifyTrue(all(contains(mres2.rxns, expResult))); -0566 end -0567 -0568 function ftINITSeriesVariantsRun(testCase) -0569 % Exercises the 2-step series from the paper, including the -0570 % allowExcretion constraint it relies on. -0571 testCase.assumeMILPSolver(); -0572 testModel = getTstModel(); -0573 testParams = struct(); -0574 evalc('prepData = prepINITModel(testModel, {}, {}, false, {}, ''s'');'); -0575 arrayData.genes = testModel.genes; -0576 arrayData.tissues = {'a'}; -0577 arrayData.levels = getExprForRxnScore(getTstModelRxnScores()); -0578 arrayData.threshold = 1; +0555 % testModel5 contains reactions that are not linearly merged +0556 testModel5 = getTstModel5(); +0557 arrayData.genes = testModel5.genes; +0558 arrayData.levels = getExprForRxnScore(getTstModel5RxnScores()); +0559 +0560 evalc('prepData5 = prepINITModel(testModel5, {}, {''R7'';''R10''}, false, {}, ''s'');'); +0561 evalc(['resModel = ftINIT(prepData5,arrayData.tissues{1},[],[],' ... +0562 'arrayData,{},getINITSteps(),true,true,testParams,false);']); +0563 % a->g->e via R11/R13 (score -2) ties the R2 path (score -2), so +0564 % which of the two is returned is a tie-break that differs between +0565 % solvers. Neither route uses R9/R10. isequal rather than strcmp, +0566 % which errors on two lists of different length. +0567 testCase.verifyTrue( ... +0568 isequal(resModel.rxns, {'R1';'R4';'R6';'R7';'R8';'R11';'R13'}) || ... +0569 isequal(resModel.rxns, {'R1';'R2';'R4';'R6';'R7';'R8'})); +0570 +0571 % adding metabolite g drops R7 +0572 evalc('prepData5 = prepINITModel(testModel5, {}, {''R10''}, false, {}, ''s'');'); +0573 arrayData.levels(7) = getExprForRxnScore(-1.1); % avoid randomness +0574 evalc(['resModel = ftINIT(prepData5,arrayData.tissues{1},[],[],' ... +0575 'arrayData,{''g''},getINITSteps(),true,true,testParams,false);']); +0576 testCase.verifyTrue(all(strcmp(resModel.rxns, ... +0577 {'R1';'R4';'R6';'R8';'R11';'R13'}))); +0578 end 0579 -0580 steps = getINITSteps([], '2+1'); -0581 evalc(['resModel = ftINIT(prepData,arrayData.tissues{1},[],[],' ... -0582 'arrayData,[],steps,true,true,testParams,false);']); -0583 % Same answer as the '1+1' series in ftINITPipelineRuns. -0584 testCase.verifyEqual(resModel.rxns, {'R1';'R4';'R6';'R8';'R9';'R10'}); -0585 -0586 steps = getINITSteps([], '2+0'); -0587 evalc(['resModel = ftINIT(prepData,arrayData.tissues{1},[],[],' ... -0588 'arrayData,[],steps,true,true,testParams,false);']); -0589 % '2+0' skips step 3, so the GPR-less transport R2 survives. -0590 testCase.verifyEqual(resModel.rxns, {'R1';'R2';'R4';'R6';'R8';'R9';'R10'}); -0591 end +0580 function ftINITFullVsThreeStepRuns(testCase) +0581 % On a larger model the three-step and "full" variants must give +0582 % consistent results. +0583 testCase.assumeMILPSolver(); +0584 testModelL = getTstModelL(); +0585 testModelLGeneScores = getTstModelLGeneScores(); +0586 testParams = struct(); +0587 +0588 arrayDataL.genes = testModelL.genes; +0589 arrayDataL.tissues = {'t1'}; +0590 arrayDataL.levels = getExprForRxnScore(testModelLGeneScores, 1); +0591 arrayDataL.threshold = 1; 0592 -0593 function scoreModelDataPrecedenceReactionPrefersHpaWholeRule(testCase) -0594 % dataPrecedence 'reaction': any gene of a reaction having HPA -0595 % data means array data is ignored for the whole reaction, not -0596 % just that gene. R7 is "G1 or G4", G1 has (low) HPA data and G4 -0597 % only array data; per-gene scoring would let G4's higher array -0598 % score win, per-reaction scoring must not. -0599 m = scoringTestModel(); -0600 a = scoringArrayData(); -0601 h = scoringHpaDataLowG1(); -0602 perGene = scoreModel(m, h, a, 't1', ... -0603 'isozymeScoring', 'max', 'complexScoring', 'max'); -0604 perRxn = scoreModel(m, h, a, 't1', ... -0605 'isozymeScoring', 'max', 'complexScoring', 'max', ... -0606 'dataPrecedence', 'reaction'); -0607 r7 = strcmp(m.rxns, 'R7'); -0608 -0609 testCase.verifyEqual(perRxn(r7), -8, 'AbsTol', 1e-12); -0610 % Per gene, G4's array score is seen too, and it is higher -0611 testCase.verifyEqual(perGene(r7), 5*log(3), 'AbsTol', 1e-12); -0612 testCase.verifyNotEqual(perRxn(r7), perGene(r7)); -0613 end -0614 -0615 function scoreModelAverageReducesDownTheRule(testCase) -0616 % multipleGeneScoring 'average' averages down the grRule, so a -0617 % complex counts once against its isozymes rather than once per -0618 % subunit. R5 is "(G1 and G2) or G3". -0619 m = scoringTestModel(); -0620 a = scoringArrayData(); -0621 scores = scoreModel(m, [], a, 't1', ... -0622 'isozymeScoring', 'average', 'complexScoring', 'average'); -0623 r5 = strcmp(m.rxns, 'R5'); -0624 -0625 g = 5*log([1.5; 2; 2.5]); % scores of G1, G2, G3 -0626 testCase.verifyEqual(scores(r5), mean([mean(g(1:2)); g(3)]), ... -0627 'AbsTol', 1e-12); -0628 testCase.verifyNotEqual(scores(r5), mean(g)); % not a flat mean +0593 evalc('prepDataL = prepINITModel(testModelL, [], {}, false, {}, ''s'');'); +0594 evalc(['mres = ftINIT(prepDataL,arrayDataL.tissues{1},[],[],' ... +0595 'arrayDataL,[],getINITSteps(),true,true,testParams,false);']); +0596 evalc(['mres2 = ftINIT(prepDataL,arrayDataL.tissues{1},[],[],' ... +0597 'arrayDataL,[],getINITSteps([], ''full''),true,true,testParams,false);']); +0598 +0599 expResult = {'S1';'S2';'S3';'S4';'S5';'S6';'S7';'S8';'S9';'E1';'E2'; ... +0600 'E3';'E4';'E5';'E6';'E8';'E9';'R1';'R2';'R3';'R4';'R5';'R6';'R7'; ... +0601 'R8';'R9';'R12';'R13';'R14';'R15'}; +0602 testCase.verifyTrue(all(contains(mres.rxns, expResult))); +0603 testCase.verifyTrue(all(contains(mres2.rxns, expResult))); +0604 end +0605 +0606 function ftINITSeriesVariantsRun(testCase) +0607 % Exercises the 2-step series from the paper, including the +0608 % allowExcretion constraint it relies on. +0609 testCase.assumeMILPSolver(); +0610 testModel = getTstModel(); +0611 testParams = struct(); +0612 evalc('prepData = prepINITModel(testModel, {}, {}, false, {}, ''s'');'); +0613 arrayData.genes = testModel.genes; +0614 arrayData.tissues = {'a'}; +0615 arrayData.levels = getExprForRxnScore(getTstModelRxnScores()); +0616 arrayData.threshold = 1; +0617 +0618 steps = getINITSteps([], '2+1'); +0619 evalc(['resModel = ftINIT(prepData,arrayData.tissues{1},[],[],' ... +0620 'arrayData,[],steps,true,true,testParams,false);']); +0621 % Same answer as the '1+1' series in ftINITPipelineRuns. +0622 testCase.verifyEqual(resModel.rxns, {'R1';'R4';'R6';'R8';'R9';'R10'}); +0623 +0624 steps = getINITSteps([], '2+0'); +0625 evalc(['resModel = ftINIT(prepData,arrayData.tissues{1},[],[],' ... +0626 'arrayData,[],steps,true,true,testParams,false);']); +0627 % '2+0' skips step 3, so the GPR-less transport R2 survives. +0628 testCase.verifyEqual(resModel.rxns, {'R1';'R2';'R4';'R6';'R8';'R9';'R10'}); 0629 end 0630 -0631 function scoreModelReadsGrRulesNotRxnGeneMat(testCase) -0632 % A model carrying its gene associations only in grRules (no -0633 % rxnGeneMat) must still score every reaction correctly. -0634 m = scoringTestModel(); -0635 a = scoringArrayData(); -0636 noMat = m; -0637 noMat.rxnGeneMat = []; -0638 withMat = scoreModel(m, [], a, 't1'); -0639 withoutMat = scoreModel(noMat, [], a, 't1'); -0640 testCase.verifyEqual(withoutMat, withMat, 'AbsTol', 1e-12); -0641 end -0642 -0643 function scoreModelRejectsUnknownDataPrecedence(testCase) -0644 m = scoringTestModel(); -0645 a = scoringArrayData(); -0646 testCase.verifyError( ... -0647 @() scoreModel(m, [], a, 't1', 'dataPrecedence', 'model'), ... -0648 'RAVEN:badInput'); -0649 end -0650 -0651 end -0652 end -0653 -0654 %========================================================================== -0655 % Synthetic test models, illustrated in tINITtestInfo/06.svg and 07.svg. -0656 %========================================================================== -0657 -0658 function testModel = getTstModel() -0659 testModel = struct(); -0660 testModel.id = 'testModel'; -0661 testModel.rxns = {}; -0662 testModel.S = []; -0663 testModel.rev = []; -0664 testModel.mets = {'as';'ac';'bc';'cc';'dc';'ec';'es';'fc'}; -0665 testModel.metNames = {'a';'a';'b';'c';'d';'e';'e';'f'}; -0666 testModel.comps = {'s';'c'}; -0667 testModel.compNames = testModel.comps; -0668 testModel.metComps = [1;2;2;2;2;2;1;2]; -0669 testModel.genes = {'G1';'G2';'G3';'G4';'G5';'G6';'G7';'G8';'G9';'G10'}; -0670 testModel.grRules = {}; -0671 testModel.rxnGeneMat = []; -0672 -0673 rxnsToAdd = struct(); -0674 rxnsToAdd.rxns = {'R1';'R2';'R3';'R4';'R5';'R6';'R7';'R8';'R9';'R10'}; -0675 rxnsToAdd.equations = {'=> a[s]';... -0676 'a[s] <=> a[c]';... -0677 'a[c] <=> b[c] + c[c]';... -0678 'a[c] <=> 2 d[c]';... -0679 'b[c] + c[c] => e[c]';... -0680 '2 d[c] => e[c]';... -0681 'e[c] => e[s]';... -0682 'e[s] =>';... -0683 'a[c] <=> f[c]';... -0684 'f[c] <=> e[c]'}; -0685 rxnsToAdd.grRules = {'';'';'G3';'G4';'G5';'G6';'G7';'';'G9';'G10'}; -0686 evalc('testModel = addRxns(testModel,rxnsToAdd, 3);'); -0687 testModel.c = [0;0;0;0;0;0;0;1;0;0]; -0688 testModel.ub = repmat(1000,10,1); -0689 testModel.lb = [0;-1000;-1000;-1000;0;0;0;0;-1000;-1000]; -0690 testModel.rxnNames = testModel.rxns; -0691 testModel.b = repmat(0,8,1); -0692 end -0693 -0694 function testModelRxnScores = getTstModelRxnScores() -0695 testModelRxnScores = [-2;-2;-1;7;0.5;0.5;-1;-2;-3;3.5]; -0696 end -0697 -0698 function model = setTaskBounds(model) -0699 % The b bounds that fitTasks (gapFillMode 'preMerged') derives from getTstModelTasks: -0700 % a[s] may be taken up freely, e[s] has to leave at exactly one unit. -0701 model.b = zeros(numel(model.mets), 2); -0702 model.b(strcmp(model.mets, 'as'), :) = [-inf 0]; -0703 model.b(strcmp(model.mets, 'es'), :) = [1 1]; -0704 end -0705 -0706 function testModelTasks = getTstModelTasks() -0707 testModelTasks = struct(); -0708 testModelTasks.id = 'Gen e[s] from a[s]'; -0709 testModelTasks.description = 'Gen e[s] from a[s]'; -0710 testModelTasks.shouldFail = false; -0711 testModelTasks.printFluxes = false; -0712 testModelTasks.comments = ''; -0713 testModelTasks.inputs = {'a[s]'}; -0714 testModelTasks.LBin = 0; -0715 testModelTasks.UBin = inf; -0716 testModelTasks.outputs = {'e[s]'}; -0717 testModelTasks.LBout = 1; -0718 testModelTasks.UBout = 1; -0719 testModelTasks.equations = {}; -0720 testModelTasks.LBequ = []; -0721 testModelTasks.UBequ = []; -0722 testModelTasks.changed = {}; -0723 testModelTasks.LBrxn = {}; -0724 testModelTasks.UBrxn = {}; -0725 end -0726 -0727 function testModel2 = getTstModel2() -0728 testModel2 = struct(); -0729 testModel2.id = 'testModel2'; -0730 testModel2.rxns = {}; -0731 testModel2.S = []; -0732 testModel2.rev = []; -0733 testModel2.mets = {'a';'b'}; -0734 testModel2.metNames = {'a';'b'}; -0735 testModel2.comps = {'s'}; -0736 testModel2.compNames = testModel2.comps; -0737 testModel2.metComps = [1;1]; -0738 testModel2.genes = {'G1';'G2';'G3';'G4'}; -0739 testModel2.grRules = {}; -0740 testModel2.rxnGeneMat = []; -0741 -0742 rxnsToAdd = struct(); -0743 rxnsToAdd.rxns = {'R1';'R2';'R3';'R4'}; -0744 rxnsToAdd.equations = {'a[s] <=>';... -0745 'a[s] => b[s]';... -0746 'a[s] <=> b[s]';... -0747 'b[s] =>'}; -0748 rxnsToAdd.grRules = testModel2.genes; -0749 evalc('testModel2 = addRxns(testModel2,rxnsToAdd,3,'''',true,true);'); -0750 testModel2.c = [0;0;0;1]; -0751 testModel2.ub = repmat(1000,4,1); -0752 testModel2.lb = [-1000;0;-1000;0]; -0753 testModel2.rxnNames = testModel2.rxns; -0754 testModel2.b = zeros(2,1); -0755 end -0756 -0757 function testModel4 = getTstModel4() -0758 testModel4 = getTstModel2(); -0759 -0760 rxnsToAdd = struct(); -0761 rxnsToAdd.rxns = {'R5';'R6';'R7';'R8';'R9';'R10';'R11'}; -0762 rxnsToAdd.equations = {'5 a[s] <=> 5 d[s]';... -0763 'e[s] <=> d[s]'; -0764 'f[s] + g[s] <=> e[s]'; -0765 'b[s] <=> f[s]'; -0766 'h[s] <=> g[s]'; -0767 'h[s] =>'; -0768 'e[s] => g[s]'}; -0769 rxnsToAdd.grRules = {'G5';'G6';'G7';'G8';'G9';'G10';'G11'}; -0770 [~,testModel4] = evalc("addRxns(testModel4,rxnsToAdd, 3, [], true, true);"); -0771 end -0772 -0773 function testModel5 = getTstModel5() -0774 testModel = getTstModel(); -0775 -0776 rxnsToAdd = struct(); -0777 rxnsToAdd.rxns = {'R11';'R12';'R13';'R14'}; -0778 rxnsToAdd.equations = {'a[c] <=> g[c]';... -0779 'a[c] <=> g[c]';... -0780 'g[c] <=> e[c]';... -0781 'g[c] <=> e[c]'}; -0782 rxnsToAdd.grRules = {'G11';'G12';'G13';'G14'}; -0783 [~,testModel5] = evalc("addRxns(testModel,rxnsToAdd, 3, [], true, true);"); -0784 end -0785 -0786 function testModel5RxnScores = getTstModel5RxnScores() -0787 testModel5RxnScores = [getTstModelRxnScores();-1;-1.5;-1;-1.5]; -0788 end -0789 -0790 function testModelL = getTstModelL() -0791 testModelL = struct(); -0792 testModelL.id = 'testModel'; -0793 testModelL.rxns = {}; -0794 testModelL.S = []; -0795 testModelL.rev = []; -0796 testModelL.metNames = {'e1';'e2';'e3';'e4';'e5';'e6';'e7';'e8';'e9';'e1';'e2';'e3';'e4';'e5';'e6';'e7';'e8';'e9';'x1';'x2';'x3';'x4';'x5';'x6';'x7';'x8';'x9';'x10';'x11'}; -0797 testModelL.comps = {'s';'c'}; -0798 testModelL.compNames = testModelL.comps; -0799 testModelL.metComps = [1;1;1;1;1;1;1;1;1;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2]; -0800 testModelL.mets = strcat(testModelL.metNames, testModelL.comps(testModelL.metComps)); -0801 -0802 testModelL.grRules = {}; -0803 testModelL.rxnGeneMat = []; -0804 -0805 testModelL.genes = {'Ge1';'Ge2';'Ge4';'Ge5';'Ge7';'Ge9'; 'Gr1';'Gr2';'Gr3';'Gr5';'Gr6';'Gr7';'Gr8';'Gr9';'Gr10';'Gr11';'Gr12';'Gr14';'Gr15'}; -0806 -0807 testModelL.ub = []; -0808 testModelL.lb = []; -0809 -0810 rxnsToAdd = struct(); -0811 rxnsToAdd.rxns = { 'S1';'S2';'S3';'S4';'S5';'S6';'S7';'S8';'S9';'E1';'E2';'E2b';'E3';'E4';'E5';'E6';'E7';'E8';'E9';'R1';'R2';'R3';'R4';'R5';'R6';'R7';'R8';'R9';'R10';'R11';'R12';'R13';'R14';'R15'}; -0812 rxnsToAdd.grRules = {''; ''; ''; ''; ''; ''; ''; ''; ''; 'Ge1';'Ge2';'';'';'Ge4';'Ge5';'';'Ge7';'';'Ge9'; 'Gr1';'Gr2';'Gr3';'';'Gr5';'Gr6';'Gr7';'Gr8';'Gr9';'Gr10';'Gr11';'Gr12';'';'Gr14';'Gr15'}; -0813 rxnsToAdd.equations = {'e1[s] <=>';... -0814 'e2[s] <=>';... -0815 'e3[s] <=>';... -0816 'e4[s] <=>';... -0817 'e5[s] <=>';... -0818 'e6[s] <=>';... -0819 'e7[s] <=>';... -0820 'e8[s] <=>';... -0821 'e9[s] <=>';... -0822 'e1[s] <=> e1[c]';... -0823 'e2[s] <=> e2[c]';... -0824 'e2[s] <=> e2[c]';... %b variant -0825 'e3[s] <=> e3[c]';... -0826 'e4[s] <=> e4[c]';... -0827 'e5[s] <=> e5[c]';... -0828 'e6[s] <=> e6[c]';... -0829 'e7[s] <=> e7[c]';... -0830 'e8[s] <=> e8[c]';... -0831 'e9[s] <=> e9[c]';... -0832 'e1[c] + e2[c] <=> x1[c]';... %R1 -0833 'e1[c] + e3[c] => x2[c] + x3[c]';... %R2 -0834 'e4[c] + x3[c] => x4[c] + x5[c]';... %R3 -0835 'e5[c] + e6[c] + x4[c] => 2 x2[c] + x6[c]';... %R4 -0836 'x1[c] + x2[c] <=> x7[c] + 2 x8[c]';... %R5 -0837 'x2[c] + x8[c] => x3[c] + x9[c]';... %R6 -0838 'x4[c] <=> x9[c]';... %R7 -0839 'x5[c] <=> x9[c]';... %R8 -0840 'x6[c] <=> x10[c]';... %R9 -0841 'x6[c] <=> x11[c]';... %R10 -0842 'x10[c] + 2 x11[c] => e7[c]';... %R11 -0843 'x9[c] + x10[c] <=> e8[c]';... %R12 -0844 'x7[c] + x8[c] + x9[c] => e9[c]';... %R13 -0845 'x6[c] => x9[c]';... %R14 -0846 'x3[c] => x9[c]'... %R15 -0847 }; -0848 evalc('testModelL = addRxns(testModelL,rxnsToAdd, 3);'); -0849 testModelL.c = [0;0;0;0;0;0;0;1;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0]; -0850 testModelL.rxnNames = testModelL.rxns; -0851 testModelL.b = repmat(0,length(testModelL.mets),1); -0852 end -0853 -0854 function testModelLGeneScores = getTstModelLGeneScores() -0855 %testModelL.genes = {'Ge1';'Ge2';'Ge4';'Ge5';'Ge7';'Ge9'; 'Gr1';'Gr2';'Gr3';'Gr5';'Gr6';'Gr7';'Gr8';'Gr9';'Gr10';'Gr11';'Gr12';'Gr14';'Gr15'}; -0856 testModelLGeneScores = [3; -1; 8; 6; -5; 5; 4; 5; 2; 3; 6; 1; 3; 1; -3; 1; 3; 1; 2]; -0857 end -0858 -0859 %========================================================================== -0860 % Fixtures for the scoreModel* tests: a small model whose grRules cover -0861 % every rule shape the scoring treats differently (none, single, OR, AND, -0862 % nested, mixed-data-source OR), and matching array/HPA data. -0863 %========================================================================== -0864 -0865 function m = scoringTestModel() -0866 m = struct(); -0867 m.id = 'scoringTest'; -0868 m.rxns = {}; m.S = []; m.rev = []; -0869 m.mets = {'ac'; 'bc'; 'cc'; 'dc'; 'ec'}; -0870 m.metNames = {'a'; 'b'; 'c'; 'd'; 'e'}; -0871 m.comps = {'c'}; m.compNames = m.comps; -0872 m.metComps = [1; 1; 1; 1; 1]; -0873 m.genes = {'G1'; 'G2'; 'G3'; 'G4'}; -0874 m.grRules = {}; m.rxnGeneMat = []; -0875 r = struct(); -0876 r.rxns = {'R1'; 'R2'; 'R3'; 'R4'; 'R5'; 'R6'; 'R7'}; -0877 r.equations = {'=> a[c]'; 'a[c] => b[c]'; 'b[c] => c[c]'; ... -0878 'c[c] => d[c]'; 'a[c] => d[c]'; 'd[c] => e[c]'; 'e[c] =>'}; -0879 r.grRules = {''; 'G1'; 'G1 or G2'; 'G1 and G2'; ... -0880 '(G1 and G2) or G3'; 'G4'; 'G1 or G4'}; -0881 evalc('m = addRxns(m, r, 3);'); -0882 m.c = zeros(7, 1); -0883 m.lb = zeros(7, 1); -0884 m.ub = repmat(1000, 7, 1); -0885 m.rxnNames = m.rxns; -0886 m.b = zeros(5, 1); -0887 evalc('[m.grRules, m.rxnGeneMat] = standardizeGrRules(m, true);'); -0888 end -0889 -0890 function a = scoringArrayData() -0891 % Distinct, uncapped scores: with a threshold of 1 the score of each gene -0892 % is 5*log(level), i.e. 2.03, 3.47, 4.58 and 5.49. -0893 a = struct(); -0894 a.genes = {'G1'; 'G2'; 'G3'; 'G4'}; -0895 a.tissues = {'t1'; 't2'}; -0896 a.celltypes = {'ct1'; 'ct2'}; -0897 a.levels = [1.5 1; 2 1; 2.5 1; 3 1]; -0898 a.threshold = ones(4, 1); -0899 end -0900 -0901 function h = scoringHpaDataLowG1() -0902 % Only G1 has HPA data, and its level is the lowest one, so any array -0903 % score for the other genes outranks it. -0904 h = struct(); -0905 h.genes = {'G1'}; -0906 h.tissues = {'t1'}; -0907 h.celltypes = {'ct1'}; -0908 h.levels = {'High', 'Medium', 'Low', 'None'}; -0909 h.gene2Level = 4; -0910 end

    +0631 function scoreModelDataPrecedenceReactionPrefersHpaWholeRule(testCase) +0632 % dataPrecedence 'reaction': any gene of a reaction having HPA +0633 % data means array data is ignored for the whole reaction, not +0634 % just that gene. R7 is "G1 or G4", G1 has (low) HPA data and G4 +0635 % only array data; per-gene scoring would let G4's higher array +0636 % score win, per-reaction scoring must not. +0637 m = scoringTestModel(); +0638 a = scoringArrayData(); +0639 h = scoringHpaDataLowG1(); +0640 perGene = scoreModel(m, h, a, 't1', ... +0641 'isozymeScoring', 'max', 'complexScoring', 'max'); +0642 perRxn = scoreModel(m, h, a, 't1', ... +0643 'isozymeScoring', 'max', 'complexScoring', 'max', ... +0644 'dataPrecedence', 'reaction'); +0645 r7 = strcmp(m.rxns, 'R7'); +0646 +0647 testCase.verifyEqual(perRxn(r7), -8, 'AbsTol', 1e-12); +0648 % Per gene, G4's array score is seen too, and it is higher +0649 testCase.verifyEqual(perGene(r7), 5*log(3), 'AbsTol', 1e-12); +0650 testCase.verifyNotEqual(perRxn(r7), perGene(r7)); +0651 end +0652 +0653 function scoreModelAverageReducesDownTheRule(testCase) +0654 % multipleGeneScoring 'average' averages down the grRule, so a +0655 % complex counts once against its isozymes rather than once per +0656 % subunit. R5 is "(G1 and G2) or G3". +0657 m = scoringTestModel(); +0658 a = scoringArrayData(); +0659 scores = scoreModel(m, [], a, 't1', ... +0660 'isozymeScoring', 'average', 'complexScoring', 'average'); +0661 r5 = strcmp(m.rxns, 'R5'); +0662 +0663 g = 5*log([1.5; 2; 2.5]); % scores of G1, G2, G3 +0664 testCase.verifyEqual(scores(r5), mean([mean(g(1:2)); g(3)]), ... +0665 'AbsTol', 1e-12); +0666 testCase.verifyNotEqual(scores(r5), mean(g)); % not a flat mean +0667 end +0668 +0669 function scoreModelReadsGrRulesNotRxnGeneMat(testCase) +0670 % A model carrying its gene associations only in grRules (no +0671 % rxnGeneMat) must still score every reaction correctly. +0672 m = scoringTestModel(); +0673 a = scoringArrayData(); +0674 noMat = m; +0675 noMat.rxnGeneMat = []; +0676 withMat = scoreModel(m, [], a, 't1'); +0677 withoutMat = scoreModel(noMat, [], a, 't1'); +0678 testCase.verifyEqual(withoutMat, withMat, 'AbsTol', 1e-12); +0679 end +0680 +0681 function scoreModelRejectsUnknownDataPrecedence(testCase) +0682 m = scoringTestModel(); +0683 a = scoringArrayData(); +0684 testCase.verifyError( ... +0685 @() scoreModel(m, [], a, 't1', 'dataPrecedence', 'model'), ... +0686 'RAVEN:badInput'); +0687 end +0688 +0689 end +0690 end +0691 +0692 %========================================================================== +0693 % Synthetic test models, illustrated in tINITtestInfo/06.svg and 07.svg. +0694 %========================================================================== +0695 +0696 function testModel = getTstModel() +0697 testModel = struct(); +0698 testModel.id = 'testModel'; +0699 testModel.rxns = {}; +0700 testModel.S = []; +0701 testModel.rev = []; +0702 testModel.mets = {'as';'ac';'bc';'cc';'dc';'ec';'es';'fc'}; +0703 testModel.metNames = {'a';'a';'b';'c';'d';'e';'e';'f'}; +0704 testModel.comps = {'s';'c'}; +0705 testModel.compNames = testModel.comps; +0706 testModel.metComps = [1;2;2;2;2;2;1;2]; +0707 testModel.genes = {'G1';'G2';'G3';'G4';'G5';'G6';'G7';'G8';'G9';'G10'}; +0708 testModel.grRules = {}; +0709 testModel.rxnGeneMat = []; +0710 +0711 rxnsToAdd = struct(); +0712 rxnsToAdd.rxns = {'R1';'R2';'R3';'R4';'R5';'R6';'R7';'R8';'R9';'R10'}; +0713 rxnsToAdd.equations = {'=> a[s]';... +0714 'a[s] <=> a[c]';... +0715 'a[c] <=> b[c] + c[c]';... +0716 'a[c] <=> 2 d[c]';... +0717 'b[c] + c[c] => e[c]';... +0718 '2 d[c] => e[c]';... +0719 'e[c] => e[s]';... +0720 'e[s] =>';... +0721 'a[c] <=> f[c]';... +0722 'f[c] <=> e[c]'}; +0723 rxnsToAdd.grRules = {'';'';'G3';'G4';'G5';'G6';'G7';'';'G9';'G10'}; +0724 evalc('testModel = addRxns(testModel,rxnsToAdd, 3);'); +0725 testModel.c = [0;0;0;0;0;0;0;1;0;0]; +0726 testModel.ub = repmat(1000,10,1); +0727 testModel.lb = [0;-1000;-1000;-1000;0;0;0;0;-1000;-1000]; +0728 testModel.rxnNames = testModel.rxns; +0729 testModel.b = repmat(0,8,1); +0730 end +0731 +0732 function testModelRxnScores = getTstModelRxnScores() +0733 testModelRxnScores = [-2;-2;-1;7;0.5;0.5;-1;-2;-3;3.5]; +0734 end +0735 +0736 function model = setTaskBounds(model) +0737 % The b bounds that fitTasks (gapFillMode 'preMerged') derives from getTstModelTasks: +0738 % a[s] may be taken up freely, e[s] has to leave at exactly one unit. +0739 model.b = zeros(numel(model.mets), 2); +0740 model.b(strcmp(model.mets, 'as'), :) = [-inf 0]; +0741 model.b(strcmp(model.mets, 'es'), :) = [1 1]; +0742 end +0743 +0744 function testModelTasks = getTstModelTasks() +0745 testModelTasks = struct(); +0746 testModelTasks.id = 'Gen e[s] from a[s]'; +0747 testModelTasks.description = 'Gen e[s] from a[s]'; +0748 testModelTasks.shouldFail = false; +0749 testModelTasks.printFluxes = false; +0750 testModelTasks.comments = ''; +0751 testModelTasks.inputs = {'a[s]'}; +0752 testModelTasks.LBin = 0; +0753 testModelTasks.UBin = inf; +0754 testModelTasks.outputs = {'e[s]'}; +0755 testModelTasks.LBout = 1; +0756 testModelTasks.UBout = 1; +0757 testModelTasks.equations = {}; +0758 testModelTasks.LBequ = []; +0759 testModelTasks.UBequ = []; +0760 testModelTasks.changed = {}; +0761 testModelTasks.LBrxn = {}; +0762 testModelTasks.UBrxn = {}; +0763 end +0764 +0765 function testModel2 = getTstModel2() +0766 testModel2 = struct(); +0767 testModel2.id = 'testModel2'; +0768 testModel2.rxns = {}; +0769 testModel2.S = []; +0770 testModel2.rev = []; +0771 testModel2.mets = {'a';'b'}; +0772 testModel2.metNames = {'a';'b'}; +0773 testModel2.comps = {'s'}; +0774 testModel2.compNames = testModel2.comps; +0775 testModel2.metComps = [1;1]; +0776 testModel2.genes = {'G1';'G2';'G3';'G4'}; +0777 testModel2.grRules = {}; +0778 testModel2.rxnGeneMat = []; +0779 +0780 rxnsToAdd = struct(); +0781 rxnsToAdd.rxns = {'R1';'R2';'R3';'R4'}; +0782 rxnsToAdd.equations = {'a[s] <=>';... +0783 'a[s] => b[s]';... +0784 'a[s] <=> b[s]';... +0785 'b[s] =>'}; +0786 rxnsToAdd.grRules = testModel2.genes; +0787 evalc('testModel2 = addRxns(testModel2,rxnsToAdd,3,'''',true,true);'); +0788 testModel2.c = [0;0;0;1]; +0789 testModel2.ub = repmat(1000,4,1); +0790 testModel2.lb = [-1000;0;-1000;0]; +0791 testModel2.rxnNames = testModel2.rxns; +0792 testModel2.b = zeros(2,1); +0793 end +0794 +0795 function testModel4 = getTstModel4() +0796 testModel4 = getTstModel2(); +0797 +0798 rxnsToAdd = struct(); +0799 rxnsToAdd.rxns = {'R5';'R6';'R7';'R8';'R9';'R10';'R11'}; +0800 rxnsToAdd.equations = {'5 a[s] <=> 5 d[s]';... +0801 'e[s] <=> d[s]'; +0802 'f[s] + g[s] <=> e[s]'; +0803 'b[s] <=> f[s]'; +0804 'h[s] <=> g[s]'; +0805 'h[s] =>'; +0806 'e[s] => g[s]'}; +0807 rxnsToAdd.grRules = {'G5';'G6';'G7';'G8';'G9';'G10';'G11'}; +0808 [~,testModel4] = evalc("addRxns(testModel4,rxnsToAdd, 3, [], true, true);"); +0809 end +0810 +0811 function testModel5 = getTstModel5() +0812 testModel = getTstModel(); +0813 +0814 rxnsToAdd = struct(); +0815 rxnsToAdd.rxns = {'R11';'R12';'R13';'R14'}; +0816 rxnsToAdd.equations = {'a[c] <=> g[c]';... +0817 'a[c] <=> g[c]';... +0818 'g[c] <=> e[c]';... +0819 'g[c] <=> e[c]'}; +0820 rxnsToAdd.grRules = {'G11';'G12';'G13';'G14'}; +0821 [~,testModel5] = evalc("addRxns(testModel,rxnsToAdd, 3, [], true, true);"); +0822 end +0823 +0824 function testModel5RxnScores = getTstModel5RxnScores() +0825 testModel5RxnScores = [getTstModelRxnScores();-1;-1.5;-1;-1.5]; +0826 end +0827 +0828 function testModelL = getTstModelL() +0829 testModelL = struct(); +0830 testModelL.id = 'testModel'; +0831 testModelL.rxns = {}; +0832 testModelL.S = []; +0833 testModelL.rev = []; +0834 testModelL.metNames = {'e1';'e2';'e3';'e4';'e5';'e6';'e7';'e8';'e9';'e1';'e2';'e3';'e4';'e5';'e6';'e7';'e8';'e9';'x1';'x2';'x3';'x4';'x5';'x6';'x7';'x8';'x9';'x10';'x11'}; +0835 testModelL.comps = {'s';'c'}; +0836 testModelL.compNames = testModelL.comps; +0837 testModelL.metComps = [1;1;1;1;1;1;1;1;1;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2]; +0838 testModelL.mets = strcat(testModelL.metNames, testModelL.comps(testModelL.metComps)); +0839 +0840 testModelL.grRules = {}; +0841 testModelL.rxnGeneMat = []; +0842 +0843 testModelL.genes = {'Ge1';'Ge2';'Ge4';'Ge5';'Ge7';'Ge9'; 'Gr1';'Gr2';'Gr3';'Gr5';'Gr6';'Gr7';'Gr8';'Gr9';'Gr10';'Gr11';'Gr12';'Gr14';'Gr15'}; +0844 +0845 testModelL.ub = []; +0846 testModelL.lb = []; +0847 +0848 rxnsToAdd = struct(); +0849 rxnsToAdd.rxns = { 'S1';'S2';'S3';'S4';'S5';'S6';'S7';'S8';'S9';'E1';'E2';'E2b';'E3';'E4';'E5';'E6';'E7';'E8';'E9';'R1';'R2';'R3';'R4';'R5';'R6';'R7';'R8';'R9';'R10';'R11';'R12';'R13';'R14';'R15'}; +0850 rxnsToAdd.grRules = {''; ''; ''; ''; ''; ''; ''; ''; ''; 'Ge1';'Ge2';'';'';'Ge4';'Ge5';'';'Ge7';'';'Ge9'; 'Gr1';'Gr2';'Gr3';'';'Gr5';'Gr6';'Gr7';'Gr8';'Gr9';'Gr10';'Gr11';'Gr12';'';'Gr14';'Gr15'}; +0851 rxnsToAdd.equations = {'e1[s] <=>';... +0852 'e2[s] <=>';... +0853 'e3[s] <=>';... +0854 'e4[s] <=>';... +0855 'e5[s] <=>';... +0856 'e6[s] <=>';... +0857 'e7[s] <=>';... +0858 'e8[s] <=>';... +0859 'e9[s] <=>';... +0860 'e1[s] <=> e1[c]';... +0861 'e2[s] <=> e2[c]';... +0862 'e2[s] <=> e2[c]';... %b variant +0863 'e3[s] <=> e3[c]';... +0864 'e4[s] <=> e4[c]';... +0865 'e5[s] <=> e5[c]';... +0866 'e6[s] <=> e6[c]';... +0867 'e7[s] <=> e7[c]';... +0868 'e8[s] <=> e8[c]';... +0869 'e9[s] <=> e9[c]';... +0870 'e1[c] + e2[c] <=> x1[c]';... %R1 +0871 'e1[c] + e3[c] => x2[c] + x3[c]';... %R2 +0872 'e4[c] + x3[c] => x4[c] + x5[c]';... %R3 +0873 'e5[c] + e6[c] + x4[c] => 2 x2[c] + x6[c]';... %R4 +0874 'x1[c] + x2[c] <=> x7[c] + 2 x8[c]';... %R5 +0875 'x2[c] + x8[c] => x3[c] + x9[c]';... %R6 +0876 'x4[c] <=> x9[c]';... %R7 +0877 'x5[c] <=> x9[c]';... %R8 +0878 'x6[c] <=> x10[c]';... %R9 +0879 'x6[c] <=> x11[c]';... %R10 +0880 'x10[c] + 2 x11[c] => e7[c]';... %R11 +0881 'x9[c] + x10[c] <=> e8[c]';... %R12 +0882 'x7[c] + x8[c] + x9[c] => e9[c]';... %R13 +0883 'x6[c] => x9[c]';... %R14 +0884 'x3[c] => x9[c]'... %R15 +0885 }; +0886 evalc('testModelL = addRxns(testModelL,rxnsToAdd, 3);'); +0887 testModelL.c = [0;0;0;0;0;0;0;1;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0]; +0888 testModelL.rxnNames = testModelL.rxns; +0889 testModelL.b = repmat(0,length(testModelL.mets),1); +0890 end +0891 +0892 function testModelLGeneScores = getTstModelLGeneScores() +0893 %testModelL.genes = {'Ge1';'Ge2';'Ge4';'Ge5';'Ge7';'Ge9'; 'Gr1';'Gr2';'Gr3';'Gr5';'Gr6';'Gr7';'Gr8';'Gr9';'Gr10';'Gr11';'Gr12';'Gr14';'Gr15'}; +0894 testModelLGeneScores = [3; -1; 8; 6; -5; 5; 4; 5; 2; 3; 6; 1; 3; 1; -3; 1; 3; 1; 2]; +0895 end +0896 +0897 %========================================================================== +0898 % Fixtures for the scoreModel* tests: a small model whose grRules cover +0899 % every rule shape the scoring treats differently (none, single, OR, AND, +0900 % nested, mixed-data-source OR), and matching array/HPA data. +0901 %========================================================================== +0902 +0903 function m = scoringTestModel() +0904 m = struct(); +0905 m.id = 'scoringTest'; +0906 m.rxns = {}; m.S = []; m.rev = []; +0907 m.mets = {'ac'; 'bc'; 'cc'; 'dc'; 'ec'}; +0908 m.metNames = {'a'; 'b'; 'c'; 'd'; 'e'}; +0909 m.comps = {'c'}; m.compNames = m.comps; +0910 m.metComps = [1; 1; 1; 1; 1]; +0911 m.genes = {'G1'; 'G2'; 'G3'; 'G4'}; +0912 m.grRules = {}; m.rxnGeneMat = []; +0913 r = struct(); +0914 r.rxns = {'R1'; 'R2'; 'R3'; 'R4'; 'R5'; 'R6'; 'R7'}; +0915 r.equations = {'=> a[c]'; 'a[c] => b[c]'; 'b[c] => c[c]'; ... +0916 'c[c] => d[c]'; 'a[c] => d[c]'; 'd[c] => e[c]'; 'e[c] =>'}; +0917 r.grRules = {''; 'G1'; 'G1 or G2'; 'G1 and G2'; ... +0918 '(G1 and G2) or G3'; 'G4'; 'G1 or G4'}; +0919 evalc('m = addRxns(m, r, 3);'); +0920 m.c = zeros(7, 1); +0921 m.lb = zeros(7, 1); +0922 m.ub = repmat(1000, 7, 1); +0923 m.rxnNames = m.rxns; +0924 m.b = zeros(5, 1); +0925 evalc('[m.grRules, m.rxnGeneMat] = standardizeGrRules(m, true);'); +0926 end +0927 +0928 function a = scoringArrayData() +0929 % Distinct, uncapped scores: with a threshold of 1 the score of each gene +0930 % is 5*log(level), i.e. 2.03, 3.47, 4.58 and 5.49. +0931 a = struct(); +0932 a.genes = {'G1'; 'G2'; 'G3'; 'G4'}; +0933 a.tissues = {'t1'; 't2'}; +0934 a.celltypes = {'ct1'; 'ct2'}; +0935 a.levels = [1.5 1; 2 1; 2.5 1; 3 1]; +0936 a.threshold = ones(4, 1); +0937 end +0938 +0939 function h = scoringHpaDataLowG1() +0940 % Only G1 has HPA data, and its level is the lowest one, so any array +0941 % score for the other genes outranks it. +0942 h = struct(); +0943 h.genes = {'G1'}; +0944 h.tissues = {'t1'}; +0945 h.celltypes = {'ct1'}; +0946 h.levels = {'High', 'Medium', 'Low', 'None'}; +0947 h.gene2Level = 4; +0948 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/doc/testing/function_tests/tOmics.html b/doc/testing/function_tests/tOmics.html deleted file mode 100644 index 4e0f8c42..00000000 --- a/doc/testing/function_tests/tOmics.html +++ /dev/null @@ -1,103 +0,0 @@ - - - - Description of tOmics - - - - - - - - - -
    Home > testing > function_tests > tOmics.m
    - - - -

    tOmics -

    - -

    PURPOSE ^

    -
    - -

    SYNOPSIS ^

    -
    This is a script file.
    - -

    DESCRIPTION ^

    -
    - - -

    CROSS-REFERENCE INFORMATION ^

    -This function calls: - -This function is called by: - - - -

    SUBFUNCTIONS ^

    - - -

    SOURCE CODE ^

    -
    0001 classdef tOmics < RavenTestCase
    -0002 % tOmics  Tests for the omics-integration functions in omics/.
    -0003 
    -0004     methods (Test)
    -0005 
    -0006         function scoreModelFromArrayData(testCase)
    -0007             arrayData.genes = testCase.model.genes;
    -0008             arrayData.tissues = {'tissue1'};
    -0009             arrayData.levels = abs(randn(numel(testCase.model.genes), 1)) + 1;
    -0010             arrayData.threshold = 1;
    -0011             evalc('rxnScores = scoreModel(testCase.model, [], arrayData, ''tissue1'', []);');
    -0012             testCase.verifyNumElements(rxnScores, numel(testCase.model.rxns));
    -0013         end
    -0014 
    -0015         function parseHPAparsesSyntheticDump(testCase)
    -0016             % parseHPA reads a tab-separated HPA dump with the columns
    -0017             % Gene / Gene name / Tissue / Cell type / Level / Reliability.
    -0018             % Synthesise a tiny one so the parser is exercised without the
    -0019             % multi-gigabyte external download.
    -0020             g = testCase.model.genes(1:3);
    -0021             f = [tempname '.tsv'];
    -0022             testCase.addTeardown(@() delete(f));
    -0023             fid = fopen(f, 'w');
    -0024             fprintf(fid, 'Gene\tGene name\tTissue\tCell type\tLevel\tReliability\n');
    -0025             fprintf(fid, '%s\t%s\tliver\thepatocyte\tHigh\tApproved\n', g{1}, g{1});
    -0026             fprintf(fid, '%s\t%s\tliver\thepatocyte\tMedium\tApproved\n', g{2}, g{2});
    -0027             fprintf(fid, '%s\t%s\tbrain\tneuron\tLow\tSupported\n', g{3}, g{3});
    -0028             fclose(fid);
    -0029             evalc('hpa = parseHPA(f);');
    -0030             testCase.verifyClass(hpa, 'struct');
    -0031             testCase.verifyEqual(sort(hpa.genes), sort(g));
    -0032             testCase.verifyNumElements(hpa.tissues, 2);   % liver+brain cell types
    -0033         end
    -0034 
    -0035         function parseHPArnaParsesSyntheticDump(testCase)
    -0036             % parseHPArna (version 19) reads Gene / Gene name / Tissue /
    -0037             % TPM / pTPM / NX and builds a genes-by-tissues TPM matrix.
    -0038             g = testCase.model.genes(1:3);
    -0039             f = [tempname '.tsv'];
    -0040             testCase.addTeardown(@() delete(f));
    -0041             fid = fopen(f, 'w');
    -0042             fprintf(fid, 'Gene\tGene name\tTissue\tTPM\tpTPM\tNX\n');
    -0043             fprintf(fid, '%s\t%s\tliver\t12.5\t10.1\t8.0\n', g{1}, g{1});
    -0044             fprintf(fid, '%s\t%s\tbrain\t3.2\t2.9\t1.5\n', g{2}, g{2});
    -0045             fprintf(fid, '%s\t%s\tliver\t0.0\t0.0\t0.0\n', g{3}, g{3});
    -0046             fclose(fid);
    -0047             evalc('rna = parseHPArna(f);');
    -0048             testCase.verifyClass(rna, 'struct');
    -0049             testCase.verifyNumElements(rna.genes, 3);
    -0050             testCase.verifyEqual(size(rna.levels), [3 2]);   % 3 genes x 2 tissues
    -0051         end
    -0052 
    -0053     end
    -0054 end
    -
    Generated by m2html © 2005
    - - \ No newline at end of file From 17be63f434e37204552ce663d52e1102a357b669 Mon Sep 17 00:00:00 2001 From: Eduard Kerkhoven Date: Fri, 11 Sep 2026 23:15:28 +0200 Subject: [PATCH 05/48] writeYAMLmodel: nest ec-rxns enzymes; printOrange: print \n as line break (SysBioChalmers/RAVEN#751) * writeYAMLmodel: nest ec-rxns enzyme entries under their "enzymes" key Each ec-rxns entry writes its enzymes as an !!omap under "- enzymes", but the enzyme lines were written at the same indentation as that key. readYAMLmodel does not look at indentation and reads them back, but a YAML parser sees an empty "enzymes" mapping followed by sibling keys: ruamel.yaml, as used by raven-toolbox and geckopy, fails with "'ScalarNode' object has no attribute 'flow_style'", so no ecModel saved by GECKO 4 could be opened in Python. The lines are now indented two spaces deeper, like the metabolite coefficients under "- metabolites". Tested by re-saving the GECKO full_ecModel tutorial's stage-2 ecModel: RAVEN reads it back with identical rxnEnzMat and kcat, and geckopy now loads it (4834 ec reactions, same maximum growth as in MATLAB). * printOrange: print "\n" as a line break printOrange prints its text with fprintf('%s',...) so that a "%" in model text is not read as a format directive. That also stopped "\n" from being expanded, and every caller that ends a message or breaks a line with "\n" printed a literal backslash-n instead: 15 calls in RAVEN and 17 in GECKO, including GECKO 4's backward-compatibility notice. printOrange now turns "\n" into a line break; no other escape sequence or format directive is interpreted. Original-commit: https://github.com/SysBioChalmers/RAVEN/commit/29bcc55a7cc4d58a00652bc1b5f01293c6301490 --- io/writeYAMLmodel.m | 2 +- testing/function_tests/tIO.m | 35 +++++++++++++++++++++++++++++++++ testing/function_tests/tUtils.m | 7 +++++++ utils/printOrange.m | 11 +++++++---- 4 files changed, 50 insertions(+), 5 deletions(-) diff --git a/io/writeYAMLmodel.m b/io/writeYAMLmodel.m index 5b9b892b..28879551 100755 --- a/io/writeYAMLmodel.m +++ b/io/writeYAMLmodel.m @@ -234,7 +234,7 @@ function writeField(model,fid,fieldName,type,pos,name,keyIndent) [model.enzymes,order] = sort(model.enzymes); model.coeffs = model.coeffs(order); for i = 1:length(model.enzymes) - writeField(model, fid, 'coeffs', 'flt', i, [' - ' model.enzymes{i}], 4) + writeField(model, fid, 'coeffs', 'flt', i, [' - ' model.enzymes{i}], 6) end end diff --git a/testing/function_tests/tIO.m b/testing/function_tests/tIO.m index 5cebd05b..53712c11 100644 --- a/testing/function_tests/tIO.m +++ b/testing/function_tests/tIO.m @@ -207,6 +207,41 @@ function writeReadYAMLRoundTrip(testCase) testCase.verifyEqual(numel(m2.rxns), numel(testCase.model.rxns)); end + function writeReadYAMLNestsEcRxnEnzymes(testCase) + % Each ec-rxns entry lists its enzymes as a mapping under the + % "enzymes" key, so the enzyme lines are indented deeper than + % that key; at the same depth a YAML parser reads them as + % siblings of "enzymes" and the mapping as empty. + model = testCase.model; + model.ec.geckoLight = false; + model.ec.rxns = model.rxns(1:2); + model.ec.kcat = [13.7; 2]; + model.ec.source = {'brenda'; 'dlkcat'}; + model.ec.notes = {''; ''}; + model.ec.eccodes = {'1.1.1.1'; '2.7.1.1;2.7.1.2'}; + model.ec.genes = model.genes(1:2); + model.ec.enzymes = {'P0A1'; 'P0A2'}; + model.ec.mw = [51000; 42000]; + model.ec.sequence = {'MABC'; 'MDEF'}; + model.ec.concs = [NaN; NaN]; + model.ec.rxnEnzMat = [1 2; 0 1]; + + f = [tempname '.yml']; + testCase.addTeardown(@() delete(f)); + evalc('writeYAMLmodel(model, f);'); + + lines = splitlines(fileread(f)); + enzKey = find(strcmp(lines, ' - enzymes: !!omap')); + testCase.verifyNumElements(enzKey, 2); + testCase.verifyEqual(lines(enzKey(1)+(1:2)), ... + {' - P0A1: 1.0'; ' - P0A2: 2.0'}); + testCase.verifyEqual(lines(enzKey(2)+1), {' - P0A2: 1.0'}); + + evalc('m2 = readYAMLmodel(f);'); + testCase.verifyEqual(m2.ec.enzymes, model.ec.enzymes); + testCase.verifyEqual(m2.ec.rxnEnzMat, model.ec.rxnEnzMat); + end + function writeReadYAMLPreservesCompartmentAnnotations(testCase) % Compartment annotations must round-trip, and must not be read % back as additional compartments. diff --git a/testing/function_tests/tUtils.m b/testing/function_tests/tUtils.m index 541fa093..858ed0b2 100644 --- a/testing/function_tests/tUtils.m +++ b/testing/function_tests/tUtils.m @@ -98,6 +98,13 @@ function printOrangeKeepsPercentWhenPrinting(testCase) testCase.verifySubstring(out, '50% complete'); end + function printOrangeExpandsNewlineWhenPrinting(testCase) + % "\n" in the input is a line break, as with fprintf. + out = evalc('printOrange(''first\nsecond\n'');'); + testCase.verifySubstring(out, ['first' newline 'second' newline]); + testCase.verifyFalse(contains(out, '\n')); + end + function parallelWorkersRAVENFalseReturnsZero(testCase) testCase.verifyEqual(parallelWorkersRAVEN(false), 0); end diff --git a/utils/printOrange.m b/utils/printOrange.m index 0d867891..7611e23b 100755 --- a/utils/printOrange.m +++ b/utils/printOrange.m @@ -8,7 +8,9 @@ % Parameters % ---------- % stringToPrint : char -% string that should be printed in orange colour. +% string that should be printed in orange colour. "\n" marks a line +% break, as in fprintf; no other escape sequence or format directive +% is interpreted, so text containing "%" prints as-is. % % Returns % ------- @@ -32,9 +34,10 @@ orangeString = strjoin(orangeString,newline); %stringToPrint is arbitrary text (e.g. a model id) and may contain a %literal "%", which fprintf would otherwise reinterpret as a format - %directive; the desktop "[\b ... ]\b" highlight markers still need - %interpreting, so expand those explicitly instead of using orangeString - %itself as the format string. + %directive; the "\n" line breaks and the desktop "[\b ... ]\b" + %highlight markers still need interpreting, so expand those explicitly + %instead of using orangeString itself as the format string. + orangeString = strrep(orangeString,'\n',newline); orangeString = strrep(orangeString,'[\b',sprintf('[\b')); orangeString = strrep(orangeString,']\b',sprintf(']\b')); fprintf('%s',orangeString); From 51f0fdd21560f2e46e48d99a8e62c2baa9410ed4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 11 Sep 2026 21:17:11 +0000 Subject: [PATCH 06/48] doc: regenerate HTML documentation for 17be63f [skip ci] Original-commit: https://github.com/SysBioChalmers/RAVEN/commit/083dccc96f956d179f9eb96b481b4276639b3f82 --- doc/io/writeYAMLmodel.html | 2 +- doc/testing/function_tests/tIO.html | 203 +++++++++++++++---------- doc/testing/function_tests/tUtils.html | 193 ++++++++++++----------- doc/utils/printOrange.html | 71 +++++---- 4 files changed, 258 insertions(+), 211 deletions(-) diff --git a/doc/io/writeYAMLmodel.html b/doc/io/writeYAMLmodel.html index 89356722..c0346294 100644 --- a/doc/io/writeYAMLmodel.html +++ b/doc/io/writeYAMLmodel.html @@ -306,7 +306,7 @@

    SOURCE CODE ^for i = 1:length(model.enzymes) -0237 writeField(model, fid, 'coeffs', 'flt', i, [' - ' model.enzymes{i}], 4) +0237 writeField(model, fid, 'coeffs', 'flt', i, [' - ' model.enzymes{i}], 6) 0238 end 0239 end 0240 diff --git a/doc/testing/function_tests/tIO.html b/doc/testing/function_tests/tIO.html index f6f7a480..527bfec2 100644 --- a/doc/testing/function_tests/tIO.html +++ b/doc/testing/function_tests/tIO.html @@ -41,7 +41,7 @@

    CROSS-REFERENCE INFORMATION ^
 
 <h2><a name=SUBFUNCTIONS ^

    +
  • function exportForGitWritesDependencies(testCase)
  • function exportForGitMainBranchFlagRejectsNonMain(testCase)
  • function importModelHandlesSpeciesAndReactionEdgeCases(testCase)
  • function importModelStripsRegexSpecialCompartmentName(testCase)
  • function importModelReadsSBML(testCase)
  • function readYAMLmodelReadsYml(testCase)
  • function parseYAMLReturnsNestedTree(testCase)
  • function exportImportSBMLRoundTrip(testCase)
  • function exportImportSBMLRoundTripPreservesDeltaG(testCase)
  • function exportModelOmitsDeltaGFieldWhenAllNaN(testCase)
  • function exportToExcelFormatWritesFile(testCase)
  • function exportToExcelFormatWritesEcSheets(testCase)
  • function writeReadYAMLRoundTrip(testCase)
  • function writeReadYAMLNestsEcRxnEnzymes(testCase)
  • function writeReadYAMLPreservesCompartmentAnnotations(testCase)
  • function writeReadYAMLPreservesUnknownAnnotationFields(testCase)
  • function exportForGitWritesRepo(testCase)
  • function checkFileExistenceFindsFile(testCase)
  • function checkFileExistenceAcceptsForwardSlashes(testCase)
  • function cleanSheetTrimsComments(testCase)
  • function writeExcelRoundTrips(testCase)
  • SOURCE CODE ^

    0001 classdef tIO < RavenTestCase
    @@ -253,91 +253,126 @@ 

    SOURCE CODE ^end 0209 -0210 function writeReadYAMLPreservesCompartmentAnnotations(testCase) -0211 % Compartment annotations must round-trip, and must not be read -0212 % back as additional compartments. -0213 m = testCase.model; -0214 m.compMiriams = cell(numel(m.comps),1); -0215 m.compMiriams{1}.name = {'go'}; -0216 m.compMiriams{1}.value = {'GO:0005737'}; -0217 f = [tempname '.yml']; -0218 testCase.addTeardown(@() delete(f)); -0219 evalc('writeYAMLmodel(m, f);'); -0220 evalc('m2 = readYAMLmodel(f);'); -0221 testCase.verifyEqual(m2.comps, m.comps); -0222 testCase.verifyEqual(m2.compNames, m.compNames); -0223 testCase.verifyEqual(m2.compMiriams{1}.name, {'go'}); -0224 testCase.verifyEqual(m2.compMiriams{1}.value, {'GO:0005737'}); -0225 end -0226 -0227 function writeReadYAMLPreservesUnknownAnnotationFields(testCase) -0228 % A caller-specific metaData field with no RAVEN-defined slot -0229 % (e.g. a `geckopy_version` field) must survive a write/read -0230 % round trip instead of being silently dropped, same as the -0231 % fixed annoFields set (taxonomy here). -0232 m = testCase.model; -0233 m.annotation.taxonomy = 'taxonomy/4932'; -0234 m.annotation.geckopyVersion = '0.2.1'; -0235 f = [tempname '.yml']; -0236 testCase.addTeardown(@() delete(f)); -0237 evalc('writeYAMLmodel(m, f);'); -0238 evalc('m2 = readYAMLmodel(f);'); -0239 testCase.verifyEqual(m2.annotation.taxonomy, 'taxonomy/4932'); -0240 testCase.verifyEqual(m2.annotation.geckopyVersion, '0.2.1'); -0241 end -0242 -0243 function exportForGitWritesRepo(testCase) -0244 d = [tempname filesep]; -0245 mkdir(d); -0246 testCase.addTeardown(@() rmdir(d, 's')); -0247 evalc('exportForGit(testCase.model, ''ec'', d, {''yml''});'); -0248 testCase.verifyNotEmpty(dir(fullfile(d,'**','*.yml'))); -0249 end -0250 -0251 function checkFileExistenceFindsFile(testCase) -0252 f = fullfile(testCase.ravenRoot,'testing','function_tests', ... -0253 'test_data','ecoli_textbook.mat'); -0254 out = checkFileExistence(f, 1, false, true); -0255 testCase.verifyNotEmpty(out); -0256 end -0257 -0258 function checkFileExistenceAcceptsForwardSlashes(testCase) -0259 % An absolute Windows path may be written with forward slashes: -0260 % MATLAB accepts them everywhere, and anything handing RAVEN a path -0261 % built by another tool is likely to use them. Treating such a path -0262 % as relative appends the working directory and turns -0263 % "C:/data/model.mat" into "C:\somewhere\C:\data\model.mat". -0264 f = fullfile(testCase.ravenRoot,'testing','function_tests', ... -0265 'test_data','ecoli_textbook.mat'); -0266 out = checkFileExistence(strrep(f,'\','/'), 1, false, true); -0267 testCase.verifyNotEmpty(out); -0268 end -0269 -0270 function cleanSheetTrimsComments(testCase) -0271 raw = {'#comment', '#comment'; 'a', 'b'; '', ''}; -0272 out = cleanSheet(raw); -0273 testCase.verifyClass(out, 'cell'); -0274 end -0275 -0276 function writeExcelRoundTrips(testCase) -0277 % writeExcel generates the .xlsx (Office Open XML) directly, with -0278 % no external library; read it back with base MATLAB to confirm -0279 % the sheets, values and types survive. -0280 f = [tempname '.xlsx']; -0281 testCase.addTeardown(@() delete(f)); -0282 s(1).name='RXNS'; s(1).header={'ID','VALUE'}; -0283 s(1).data={'r1',1.5; 'r2',-3}; -0284 s(2).name='METS'; s(2).header={'ID'}; s(2).data={'m1'}; -0285 writeExcel(f, s); -0286 testCase.verifyEqual(sort(string(sheetnames(f))),["METS";"RXNS"]); -0287 raw = readcell(f,'Sheet','RXNS'); -0288 testCase.verifyEqual(string(raw(1,:)),["ID","VALUE"]); -0289 testCase.verifyEqual(string(raw{2,1}),"r1"); -0290 testCase.verifyEqual(raw{2,2},1.5,'AbsTol',1e-9); +0210 function writeReadYAMLNestsEcRxnEnzymes(testCase) +0211 % Each ec-rxns entry lists its enzymes as a mapping under the +0212 % "enzymes" key, so the enzyme lines are indented deeper than +0213 % that key; at the same depth a YAML parser reads them as +0214 % siblings of "enzymes" and the mapping as empty. +0215 model = testCase.model; +0216 model.ec.geckoLight = false; +0217 model.ec.rxns = model.rxns(1:2); +0218 model.ec.kcat = [13.7; 2]; +0219 model.ec.source = {'brenda'; 'dlkcat'}; +0220 model.ec.notes = {''; ''}; +0221 model.ec.eccodes = {'1.1.1.1'; '2.7.1.1;2.7.1.2'}; +0222 model.ec.genes = model.genes(1:2); +0223 model.ec.enzymes = {'P0A1'; 'P0A2'}; +0224 model.ec.mw = [51000; 42000]; +0225 model.ec.sequence = {'MABC'; 'MDEF'}; +0226 model.ec.concs = [NaN; NaN]; +0227 model.ec.rxnEnzMat = [1 2; 0 1]; +0228 +0229 f = [tempname '.yml']; +0230 testCase.addTeardown(@() delete(f)); +0231 evalc('writeYAMLmodel(model, f);'); +0232 +0233 lines = splitlines(fileread(f)); +0234 enzKey = find(strcmp(lines, ' - enzymes: !!omap')); +0235 testCase.verifyNumElements(enzKey, 2); +0236 testCase.verifyEqual(lines(enzKey(1)+(1:2)), ... +0237 {' - P0A1: 1.0'; ' - P0A2: 2.0'}); +0238 testCase.verifyEqual(lines(enzKey(2)+1), {' - P0A2: 1.0'}); +0239 +0240 evalc('m2 = readYAMLmodel(f);'); +0241 testCase.verifyEqual(m2.ec.enzymes, model.ec.enzymes); +0242 testCase.verifyEqual(m2.ec.rxnEnzMat, model.ec.rxnEnzMat); +0243 end +0244 +0245 function writeReadYAMLPreservesCompartmentAnnotations(testCase) +0246 % Compartment annotations must round-trip, and must not be read +0247 % back as additional compartments. +0248 m = testCase.model; +0249 m.compMiriams = cell(numel(m.comps),1); +0250 m.compMiriams{1}.name = {'go'}; +0251 m.compMiriams{1}.value = {'GO:0005737'}; +0252 f = [tempname '.yml']; +0253 testCase.addTeardown(@() delete(f)); +0254 evalc('writeYAMLmodel(m, f);'); +0255 evalc('m2 = readYAMLmodel(f);'); +0256 testCase.verifyEqual(m2.comps, m.comps); +0257 testCase.verifyEqual(m2.compNames, m.compNames); +0258 testCase.verifyEqual(m2.compMiriams{1}.name, {'go'}); +0259 testCase.verifyEqual(m2.compMiriams{1}.value, {'GO:0005737'}); +0260 end +0261 +0262 function writeReadYAMLPreservesUnknownAnnotationFields(testCase) +0263 % A caller-specific metaData field with no RAVEN-defined slot +0264 % (e.g. a `geckopy_version` field) must survive a write/read +0265 % round trip instead of being silently dropped, same as the +0266 % fixed annoFields set (taxonomy here). +0267 m = testCase.model; +0268 m.annotation.taxonomy = 'taxonomy/4932'; +0269 m.annotation.geckopyVersion = '0.2.1'; +0270 f = [tempname '.yml']; +0271 testCase.addTeardown(@() delete(f)); +0272 evalc('writeYAMLmodel(m, f);'); +0273 evalc('m2 = readYAMLmodel(f);'); +0274 testCase.verifyEqual(m2.annotation.taxonomy, 'taxonomy/4932'); +0275 testCase.verifyEqual(m2.annotation.geckopyVersion, '0.2.1'); +0276 end +0277 +0278 function exportForGitWritesRepo(testCase) +0279 d = [tempname filesep]; +0280 mkdir(d); +0281 testCase.addTeardown(@() rmdir(d, 's')); +0282 evalc('exportForGit(testCase.model, ''ec'', d, {''yml''});'); +0283 testCase.verifyNotEmpty(dir(fullfile(d,'**','*.yml'))); +0284 end +0285 +0286 function checkFileExistenceFindsFile(testCase) +0287 f = fullfile(testCase.ravenRoot,'testing','function_tests', ... +0288 'test_data','ecoli_textbook.mat'); +0289 out = checkFileExistence(f, 1, false, true); +0290 testCase.verifyNotEmpty(out); 0291 end 0292 -0293 end -0294 end

    +0293 function checkFileExistenceAcceptsForwardSlashes(testCase) +0294 % An absolute Windows path may be written with forward slashes: +0295 % MATLAB accepts them everywhere, and anything handing RAVEN a path +0296 % built by another tool is likely to use them. Treating such a path +0297 % as relative appends the working directory and turns +0298 % "C:/data/model.mat" into "C:\somewhere\C:\data\model.mat". +0299 f = fullfile(testCase.ravenRoot,'testing','function_tests', ... +0300 'test_data','ecoli_textbook.mat'); +0301 out = checkFileExistence(strrep(f,'\','/'), 1, false, true); +0302 testCase.verifyNotEmpty(out); +0303 end +0304 +0305 function cleanSheetTrimsComments(testCase) +0306 raw = {'#comment', '#comment'; 'a', 'b'; '', ''}; +0307 out = cleanSheet(raw); +0308 testCase.verifyClass(out, 'cell'); +0309 end +0310 +0311 function writeExcelRoundTrips(testCase) +0312 % writeExcel generates the .xlsx (Office Open XML) directly, with +0313 % no external library; read it back with base MATLAB to confirm +0314 % the sheets, values and types survive. +0315 f = [tempname '.xlsx']; +0316 testCase.addTeardown(@() delete(f)); +0317 s(1).name='RXNS'; s(1).header={'ID','VALUE'}; +0318 s(1).data={'r1',1.5; 'r2',-3}; +0319 s(2).name='METS'; s(2).header={'ID'}; s(2).data={'m1'}; +0320 writeExcel(f, s); +0321 testCase.verifyEqual(sort(string(sheetnames(f))),["METS";"RXNS"]); +0322 raw = readcell(f,'Sheet','RXNS'); +0323 testCase.verifyEqual(string(raw(1,:)),["ID","VALUE"]); +0324 testCase.verifyEqual(string(raw{2,1}),"r1"); +0325 testCase.verifyEqual(raw{2,2},1.5,'AbsTol',1e-9); +0326 end +0327 +0328 end +0329 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/doc/testing/function_tests/tUtils.html b/doc/testing/function_tests/tUtils.html index 3971ed84..4341bfec 100644 --- a/doc/testing/function_tests/tUtils.html +++ b/doc/testing/function_tests/tUtils.html @@ -41,7 +41,7 @@

    CROSS-REFERENCE INFORMATION ^
 
 <h2><a name=SUBFUNCTIONS ^

    +
  • function deprecationWarningWarnsOncePerSession(testCase)
  • function deprecationWarningNamesTheReplacement(testCase)
  • function legacyMethodNoticeWarnsOncePerSession(testCase)
  • function legacyMethodNoticePromisesNoRemoval(testCase)
  • function convertCharArrayFromChar(testCase)
  • function convertCharArrayFromCell(testCase)
  • function convertCharArrayFromString(testCase)
  • function ravenListFormatsItems(testCase)
  • function ravenListIndentsItemsWithATab(testCase)
  • function ravenListTrimsToTenByDefault(testCase)
  • function ravenListNoTrimWhenFalse(testCase)
  • function printOrangeReturnsTextContainingInput(testCase)
  • function printOrangeKeepsPercentWhenPrinting(testCase)
  • function printOrangeExpandsNewlineWhenPrinting(testCase)
  • function parallelWorkersRAVENFalseReturnsZero(testCase)
  • function parallelWorkersRAVENTrueNoPCTReturnsZero(testCase)
  • function parallelWorkersRAVENTruePCTReturnsInf(testCase)
  • function runRAVENtestsExists(testCase)
  • function parseGrRuleBuildsTree(testCase)
  • function parseGrRuleEmptyIsEmpty(testCase)
  • function parseGrRuleTakesWholeWordOperators(testCase)
  • function parseGrRuleAcceptsSymbolOperators(testCase)
  • function parseGrRuleRejectsMalformed(testCase)
  • function isDnfGrRuleClassifies(testCase)
  • function isDnfGrRuleIgnoresRedundantBrackets(testCase)
  • function grRuleToDNFDistributes(testCase)
  • function grRuleToDNFSimpleCases(testCase)
  • function grRuleToStringRoundTrips(testCase)
  • SOURCE CODE ^

    0001 classdef tUtils < RavenTestCase
    @@ -144,98 +144,105 @@ 

    SOURCE CODE ^'50% complete'); 0099 end 0100 -0101 function parallelWorkersRAVENFalseReturnsZero(testCase) -0102 testCase.verifyEqual(parallelWorkersRAVEN(false), 0); -0103 end -0104 -0105 function parallelWorkersRAVENTrueNoPCTReturnsZero(testCase) -0106 testCase.assumeFalse(license('test','Distrib_Computing_Toolbox'), ... -0107 'Parallel Computing Toolbox present; skipping no-PCT path.'); -0108 testCase.verifyEqual(parallelWorkersRAVEN(true), 0); -0109 end -0110 -0111 function parallelWorkersRAVENTruePCTReturnsInf(testCase) -0112 testCase.assumeTrue(license('test','Distrib_Computing_Toolbox'), ... -0113 'Parallel Computing Toolbox not present; skipping PCT path.'); -0114 testCase.verifyEqual(parallelWorkersRAVEN(true), Inf); -0115 end -0116 -0117 function runRAVENtestsExists(testCase) -0118 % Do not execute it (would run the legacy suite); just confirm presence. -0119 testCase.verifyEqual(exist('runRAVENtests','file'), 2); -0120 end -0121 -0122 function parseGrRuleBuildsTree(testCase) -0123 t = parseGrRule('(G1 and G2) or G3'); -0124 testCase.verifyEqual(t.type, 'or'); -0125 testCase.verifyEqual(numel(t.children), 2); -0126 testCase.verifyEqual(t.children{1}.type, 'and'); -0127 testCase.verifyEqual(t.children{2}.id, 'G3'); -0128 end -0129 -0130 function parseGrRuleEmptyIsEmpty(testCase) -0131 testCase.verifyEmpty(parseGrRule('')); -0132 testCase.verifyEmpty(parseGrRule(' ')); -0133 end -0134 -0135 function parseGrRuleTakesWholeWordOperators(testCase) -0136 % Gene IDs containing "and"/"or" are genes, not operators. -0137 t = parseGrRule('RAND1 or ORF2'); -0138 testCase.verifyEqual(t.type, 'or'); -0139 testCase.verifyEqual(t.children{1}.id, 'RAND1'); -0140 testCase.verifyEqual(t.children{2}.id, 'ORF2'); -0141 end -0142 -0143 function parseGrRuleAcceptsSymbolOperators(testCase) -0144 testCase.verifyEqual(parseGrRule('G1 & G2').type, 'and'); -0145 testCase.verifyEqual(parseGrRule('G1 | G2').type, 'or'); -0146 testCase.verifyEqual(parseGrRule('G1 AND G2').type, 'and'); -0147 end -0148 -0149 function parseGrRuleRejectsMalformed(testCase) -0150 testCase.verifyError(@() parseGrRule('(G1 and G2'), 'RAVEN:badGrRule'); -0151 testCase.verifyError(@() parseGrRule('G1 and'), 'RAVEN:badGrRule'); -0152 testCase.verifyError(@() parseGrRule('G1 G2'), 'RAVEN:badGrRule'); -0153 end -0154 -0155 function isDnfGrRuleClassifies(testCase) -0156 testCase.verifyTrue(isDnfGrRule('(G1 and G2) or G3')); -0157 testCase.verifyTrue(isDnfGrRule('G1')); -0158 testCase.verifyTrue(isDnfGrRule('')); -0159 testCase.verifyFalse(isDnfGrRule('(G1 or G2) and G3')); -0160 testCase.verifyFalse(isDnfGrRule('G1 and (G2 or G3)')); -0161 end -0162 -0163 function isDnfGrRuleIgnoresRedundantBrackets(testCase) -0164 % Bracketing alone never makes a rule non-DNF. -0165 testCase.verifyTrue(isDnfGrRule('((G1 and G2) or G3)')); -0166 testCase.verifyTrue(isDnfGrRule('(G1 or G2) or (G3 and G4)')); -0167 end -0168 -0169 function grRuleToDNFDistributes(testCase) -0170 testCase.verifyEqual(grRuleToDNF('g1 and (g2 or g3)'), ... -0171 {{'g1','g2'}, {'g1','g3'}}); -0172 % Two or:s, but four isozymes: the count of ' or ' is not the -0173 % count of clauses. -0174 testCase.verifyEqual(numel(grRuleToDNF('(g1 or g2) and (g3 or g4)')), 4); -0175 end -0176 -0177 function grRuleToDNFSimpleCases(testCase) -0178 testCase.verifyEqual(grRuleToDNF(''), {}); -0179 testCase.verifyEqual(grRuleToDNF('G1'), {{'G1'}}); -0180 testCase.verifyEqual(grRuleToDNF('(G1 and G2) or G3'), {{'G1','G2'},{'G3'}}); -0181 end -0182 -0183 function grRuleToStringRoundTrips(testCase) -0184 rules = {'(G1 and G2) or G3', 'G1', 'G1 and G2', 'G1 or G2'}; -0185 for k = 1:numel(rules) -0186 testCase.verifyEqual(grRuleToString(parseGrRule(rules{k})), rules{k}); -0187 end -0188 testCase.verifyEqual(grRuleToString(parseGrRule('')), ''); -0189 end -0190 -0191 end -0192 end

    +0101 function printOrangeExpandsNewlineWhenPrinting(testCase) +0102 % "\n" in the input is a line break, as with fprintf. +0103 out = evalc('printOrange(''first\nsecond\n'');'); +0104 testCase.verifySubstring(out, ['first' newline 'second' newline]); +0105 testCase.verifyFalse(contains(out, '\n')); +0106 end +0107 +0108 function parallelWorkersRAVENFalseReturnsZero(testCase) +0109 testCase.verifyEqual(parallelWorkersRAVEN(false), 0); +0110 end +0111 +0112 function parallelWorkersRAVENTrueNoPCTReturnsZero(testCase) +0113 testCase.assumeFalse(license('test','Distrib_Computing_Toolbox'), ... +0114 'Parallel Computing Toolbox present; skipping no-PCT path.'); +0115 testCase.verifyEqual(parallelWorkersRAVEN(true), 0); +0116 end +0117 +0118 function parallelWorkersRAVENTruePCTReturnsInf(testCase) +0119 testCase.assumeTrue(license('test','Distrib_Computing_Toolbox'), ... +0120 'Parallel Computing Toolbox not present; skipping PCT path.'); +0121 testCase.verifyEqual(parallelWorkersRAVEN(true), Inf); +0122 end +0123 +0124 function runRAVENtestsExists(testCase) +0125 % Do not execute it (would run the legacy suite); just confirm presence. +0126 testCase.verifyEqual(exist('runRAVENtests','file'), 2); +0127 end +0128 +0129 function parseGrRuleBuildsTree(testCase) +0130 t = parseGrRule('(G1 and G2) or G3'); +0131 testCase.verifyEqual(t.type, 'or'); +0132 testCase.verifyEqual(numel(t.children), 2); +0133 testCase.verifyEqual(t.children{1}.type, 'and'); +0134 testCase.verifyEqual(t.children{2}.id, 'G3'); +0135 end +0136 +0137 function parseGrRuleEmptyIsEmpty(testCase) +0138 testCase.verifyEmpty(parseGrRule('')); +0139 testCase.verifyEmpty(parseGrRule(' ')); +0140 end +0141 +0142 function parseGrRuleTakesWholeWordOperators(testCase) +0143 % Gene IDs containing "and"/"or" are genes, not operators. +0144 t = parseGrRule('RAND1 or ORF2'); +0145 testCase.verifyEqual(t.type, 'or'); +0146 testCase.verifyEqual(t.children{1}.id, 'RAND1'); +0147 testCase.verifyEqual(t.children{2}.id, 'ORF2'); +0148 end +0149 +0150 function parseGrRuleAcceptsSymbolOperators(testCase) +0151 testCase.verifyEqual(parseGrRule('G1 & G2').type, 'and'); +0152 testCase.verifyEqual(parseGrRule('G1 | G2').type, 'or'); +0153 testCase.verifyEqual(parseGrRule('G1 AND G2').type, 'and'); +0154 end +0155 +0156 function parseGrRuleRejectsMalformed(testCase) +0157 testCase.verifyError(@() parseGrRule('(G1 and G2'), 'RAVEN:badGrRule'); +0158 testCase.verifyError(@() parseGrRule('G1 and'), 'RAVEN:badGrRule'); +0159 testCase.verifyError(@() parseGrRule('G1 G2'), 'RAVEN:badGrRule'); +0160 end +0161 +0162 function isDnfGrRuleClassifies(testCase) +0163 testCase.verifyTrue(isDnfGrRule('(G1 and G2) or G3')); +0164 testCase.verifyTrue(isDnfGrRule('G1')); +0165 testCase.verifyTrue(isDnfGrRule('')); +0166 testCase.verifyFalse(isDnfGrRule('(G1 or G2) and G3')); +0167 testCase.verifyFalse(isDnfGrRule('G1 and (G2 or G3)')); +0168 end +0169 +0170 function isDnfGrRuleIgnoresRedundantBrackets(testCase) +0171 % Bracketing alone never makes a rule non-DNF. +0172 testCase.verifyTrue(isDnfGrRule('((G1 and G2) or G3)')); +0173 testCase.verifyTrue(isDnfGrRule('(G1 or G2) or (G3 and G4)')); +0174 end +0175 +0176 function grRuleToDNFDistributes(testCase) +0177 testCase.verifyEqual(grRuleToDNF('g1 and (g2 or g3)'), ... +0178 {{'g1','g2'}, {'g1','g3'}}); +0179 % Two or:s, but four isozymes: the count of ' or ' is not the +0180 % count of clauses. +0181 testCase.verifyEqual(numel(grRuleToDNF('(g1 or g2) and (g3 or g4)')), 4); +0182 end +0183 +0184 function grRuleToDNFSimpleCases(testCase) +0185 testCase.verifyEqual(grRuleToDNF(''), {}); +0186 testCase.verifyEqual(grRuleToDNF('G1'), {{'G1'}}); +0187 testCase.verifyEqual(grRuleToDNF('(G1 and G2) or G3'), {{'G1','G2'},{'G3'}}); +0188 end +0189 +0190 function grRuleToStringRoundTrips(testCase) +0191 rules = {'(G1 and G2) or G3', 'G1', 'G1 and G2', 'G1 or G2'}; +0192 for k = 1:numel(rules) +0193 testCase.verifyEqual(grRuleToString(parseGrRule(rules{k})), rules{k}); +0194 end +0195 testCase.verifyEqual(grRuleToString(parseGrRule('')), ''); +0196 end +0197 +0198 end +0199 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/doc/utils/printOrange.html b/doc/utils/printOrange.html index d91ea7c2..dec51b6c 100644 --- a/doc/utils/printOrange.html +++ b/doc/utils/printOrange.html @@ -36,7 +36,9 @@

    DESCRIPTION ^SOURCE CODE ^% Parameters 0009 % ---------- 0010 % stringToPrint : char -0011 % string that should be printed in orange colour. -0012 % -0013 % Returns -0014 % ------- -0015 % orangeString : char -0016 % the input string wrapped with the formatting codes for orange colour. -0017 % -0018 % Examples -0019 % -------- -0020 % printOrange(stringToPrint); -0021 -0022 try useDesktop = usejava('desktop'); catch, useDesktop = false; end -0023 if useDesktop -0024 orangeString = ['[\b' stringToPrint,']\b']; -0025 else -0026 orangeString = stringToPrint; -0027 end -0028 if nargout < 1 -0029 % Wrap text to command window size -0030 sz = get(0, 'CommandWindowSize'); -0031 orangeString = textwrap({orangeString},sz(1)); -0032 orangeString = strjoin(orangeString,newline); -0033 %stringToPrint is arbitrary text (e.g. a model id) and may contain a -0034 %literal "%", which fprintf would otherwise reinterpret as a format -0035 %directive; the desktop "[\b ... ]\b" highlight markers still need -0036 %interpreting, so expand those explicitly instead of using orangeString -0037 %itself as the format string. -0038 orangeString = strrep(orangeString,'[\b',sprintf('[\b')); -0039 orangeString = strrep(orangeString,']\b',sprintf(']\b')); -0040 fprintf('%s',orangeString); -0041 end -0042 end +0011 % string that should be printed in orange colour. "\n" marks a line +0012 % break, as in fprintf; no other escape sequence or format directive +0013 % is interpreted, so text containing "%" prints as-is. +0014 % +0015 % Returns +0016 % ------- +0017 % orangeString : char +0018 % the input string wrapped with the formatting codes for orange colour. +0019 % +0020 % Examples +0021 % -------- +0022 % printOrange(stringToPrint); +0023 +0024 try useDesktop = usejava('desktop'); catch, useDesktop = false; end +0025 if useDesktop +0026 orangeString = ['[\b' stringToPrint,']\b']; +0027 else +0028 orangeString = stringToPrint; +0029 end +0030 if nargout < 1 +0031 % Wrap text to command window size +0032 sz = get(0, 'CommandWindowSize'); +0033 orangeString = textwrap({orangeString},sz(1)); +0034 orangeString = strjoin(orangeString,newline); +0035 %stringToPrint is arbitrary text (e.g. a model id) and may contain a +0036 %literal "%", which fprintf would otherwise reinterpret as a format +0037 %directive; the "\n" line breaks and the desktop "[\b ... ]\b" +0038 %highlight markers still need interpreting, so expand those explicitly +0039 %instead of using orangeString itself as the format string. +0040 orangeString = strrep(orangeString,'\n',newline); +0041 orangeString = strrep(orangeString,'[\b',sprintf('[\b')); +0042 orangeString = strrep(orangeString,']\b',sprintf(']\b')); +0043 fprintf('%s',orangeString); +0044 end +0045 end
    Generated by m2html © 2005
    \ No newline at end of file From 687271fb7c4546e87304e3185ba75b4d4df3fcb2 Mon Sep 17 00:00:00 2001 From: Eduard Kerkhoven Date: Wed, 7 Oct 2026 15:17:28 +0200 Subject: [PATCH 07/48] feat: complexPolicy, keepGeneFree and preserveNotes in getModelFromHomology, and no repeated genes (SysBioChalmers/RAVEN#757) complexPolicy decides what happens to an and-linked subunit without an ortholog (flag, keep, drop), keepGeneFree also transfers template reactions without a grRule, and preserveNotes keeps the template's rxnNotes, rxnReferences and rxnConfidenceScores. A gene that several template genes map to appears once in a grRule, and the draft no longer carries the templates' version, date and description. Matches get_model_from_homology in raven-toolbox. Original-commit: https://github.com/SysBioChalmers/RAVEN/commit/5b5b1eec0333df31cdc2f69ba2391b44fa180166 --- .../homology/getModelFromHomology.m | 201 +++++++++++++++++- testing/function_tests/tReconstruction.m | 51 +++++ 2 files changed, 241 insertions(+), 11 deletions(-) diff --git a/reconstruction/homology/getModelFromHomology.m b/reconstruction/homology/getModelFromHomology.m index e6cbca09..ca26e70a 100755 --- a/reconstruction/homology/getModelFromHomology.m +++ b/reconstruction/homology/getModelFromHomology.m @@ -56,6 +56,23 @@ % determines how to match genes if not looking at only 1-1 orthologs. % Either map the new genes to the old or old genes to new. The default % is to map the new genes (default true). +% complexPolicy : char +% what happens to an "and"-linked subunit without an ortholog: +% "flag" keeps the reaction with the subunit renamed +% OLD_MODELID_geneName, for a curator to review; "keep" drops just that +% subunit; "drop" drops the whole reaction (default "flag"). +% keepGeneFree : logical +% also transfer the template reactions that have no grRule (spontaneous, +% transport, exchange, artificial). By default they are dropped, because +% nothing supports them in the new organism. Set it when the template is +% a curated reference whose gene-free reactions belong in every derived +% model. With several templates, a gene-free reaction comes in once per +% template (default false). +% preserveNotes : logical +% keep each transferred reaction's rxnNotes, rxnReferences and +% rxnConfidenceScores from the template. By default the notes say the +% reaction was included by getModelFromHomology and the confidence score +% is 2 (default false). % % Returns % ------- @@ -77,9 +94,12 @@ % grRules (use expandModel). % % The resulting draft model contains only reactions associated with -% orthologous genes. The old (original) genes involved in "and" relations +% orthologous genes, plus the gene-free ones with keepGeneFree. With +% complexPolicy "flag", the old (original) genes involved in "and" relations % in grRules without any orthologs are still included in the draft model as -% OLD_MODELID_geneName. +% OLD_MODELID_geneName. A gene that several template genes map to appears +% once in a grRule. The draft does not keep the version, date or +% descriptive annotation of the templates. % % "to" and "from" means relative to the new organism. @@ -90,7 +110,14 @@ p=parseRAVENargs(varargin, {'preferredOrder',[]; 'strictness',1; ... 'onlyGenesInModels',false; 'maxE',10^-30; 'minLen',100; 'minIde',40; ... - 'mapNewGenesToOld',true}); + 'mapNewGenesToOld',true; 'complexPolicy','flag'; 'keepGeneFree',false; ... + 'preserveNotes',false}); +complexPolicy=lower(char(p.complexPolicy)); +if ~ismember(complexPolicy,{'flag','keep','drop'}) + error('complexPolicy must be "flag", "keep" or "drop", not "%s"',complexPolicy); +end +keepGeneFree=p.keepGeneFree; +preserveNotes=p.preserveNotes; preferredOrder=p.preferredOrder; if isempty(preferredOrder) preferredOrder=[]; @@ -194,14 +221,20 @@ blastStructure(i).ppos(~indexes)=[]; end -%Remove all reactions from the models that have no genes encoding for them. -%Also remove all genes that encode for no reactions. There should not be any -%but there might be mistakes +%Remove all reactions from the models that have no genes encoding for them, +%unless keepGeneFree. Also remove all genes that encode for no reactions. +%There should not be any but there might be mistakes +geneFreeRxns=cell(numel(models),1); +geneFreeRxns(:)={{}}; for i=1:numel(models) [hasGenes, ~]=find(models{i}.rxnGeneMat); hasNoGenes=1:numel(models{i}.rxns); hasNoGenes(hasGenes)=[]; - models{i}=removeReactions(models{i},hasNoGenes,true,true); + if keepGeneFree + geneFreeRxns{i}=models{i}.rxns(hasNoGenes); + else + models{i}=removeReactions(models{i},hasNoGenes,true,true); + end end %Create a structure that contains all genes used in the blasts in any @@ -409,6 +442,7 @@ [rxnsToKeep,~] = find(models{useOrderIndexes(i)}.rxnGeneMat(:,a)); rxnsToRemove = repmat(1,numel(models{useOrderIndexes(i)}.rxns),1); rxnsToRemove(rxnsToKeep) = 0; + rxnsToRemove(ismember(models{useOrderIndexes(i)}.rxns,geneFreeRxns{useOrderIndexes(i)})) = 0; rxnsToRemove = find(rxnsToRemove); models{useOrderIndexes(i)}=removeReactions(models{useOrderIndexes(i)},rxnsToRemove,true,true,true); end @@ -571,6 +605,27 @@ draftModel.grRules{oldI}=gprBuildRule(tkns); end +%Apply the complex policy to the OLD_ genes left in "and" relations, and +%write each grRule without repeated genes or redundant brackets +toRemove=false(numel(draftModel.rxns),1); +for j=1:numel(draftModel.grRules) + rule=draftModel.grRules{j}; + if isempty(rule) + continue + end + if contains(rule,'OLD_') && strcmp(complexPolicy,'drop') + toRemove(j)=true; + elseif contains(rule,'OLD_') && strcmp(complexPolicy,'keep') + draftModel.grRules{j}=gprSimplify(rule,'OLD_'); + toRemove(j)=isempty(draftModel.grRules{j}); + else + draftModel.grRules{j}=gprSimplify(rule,''); + end +end +if any(toRemove) + draftModel=removeReactions(draftModel,draftModel.rxns(toRemove),true,true,true); +end + %Change name of the resulting model draftModel.id=getModelFor; name='Generated by getModelFromHomology using '; @@ -582,10 +637,21 @@ end end draftModel.name=name; -draftModel.rxnNotes=cell(length(draftModel.rxns),1); -draftModel.rxnNotes(:)={'Included by getModelFromHomology'}; -draftModel.rxnConfidenceScores=NaN(length(draftModel.rxns),1); -draftModel.rxnConfidenceScores(:)=2; +%The draft is a new model: the templates' version, date and description are +%not its own +draftModel=rmfield(draftModel,intersect({'version','date','description'},fieldnames(draftModel))); +if isfield(draftModel,'annotation') + descriptive={'taxonomy','sourceUrl','note','givenName','familyName','authors','email','organization'}; + draftModel.annotation=rmfield(draftModel.annotation,intersect(descriptive,fieldnames(draftModel.annotation))); +end +if ~preserveNotes || ~isfield(draftModel,'rxnNotes') + draftModel.rxnNotes=cell(length(draftModel.rxns),1); + draftModel.rxnNotes(:)={'Included by getModelFromHomology'}; +end +if ~preserveNotes || ~isfield(draftModel,'rxnConfidenceScores') + draftModel.rxnConfidenceScores=NaN(length(draftModel.rxns),1); + draftModel.rxnConfidenceScores(:)=2; +end draftModel=deleteUnusedGenes(draftModel,0); %Standardize grRules and notify if problematic grRules are found [draftModel.grRules,draftModel.rxnGeneMat]=standardizeGrRules(draftModel,false); @@ -646,3 +712,116 @@ end end end + + +function rule=gprSimplify(rule,removePrefix) +% Rewrite a grRule as a tree: genes starting with removePrefix (if any) are +% dropped from their group, groups of the same operator are flattened, +% repeated genes or groups are removed, and brackets appear only around a +% group of the other operator. A rule with no gene left becomes ''. +tokens=gprTokenize(rule); +if isempty(tokens) + rule=''; + return +end +[node,~]=gprParseOr(tokens,1); +node=gprPrune(node,removePrefix); +if isempty(node) + rule=''; +else + rule=gprRender(node,''); +end +end + + +function [node,pos]=gprParseOr(tokens,pos) +[node,pos]=gprParseAnd(tokens,pos); +kids={node}; +while pos<=numel(tokens) && strcmpi(tokens{pos},'or') + [node,pos]=gprParseAnd(tokens,pos+1); + kids{end+1}=node; %#ok +end +if numel(kids)>1 + node=struct('op','or','gene','','kids',{kids}); +end +end + + +function [node,pos]=gprParseAnd(tokens,pos) +[node,pos]=gprParseAtom(tokens,pos); +kids={node}; +while pos<=numel(tokens) && strcmpi(tokens{pos},'and') + [node,pos]=gprParseAtom(tokens,pos+1); + kids{end+1}=node; %#ok +end +if numel(kids)>1 + node=struct('op','and','gene','','kids',{kids}); +end +end + + +function [node,pos]=gprParseAtom(tokens,pos) +if pos>numel(tokens) + error('Malformed grRule: it ends with an operator'); +end +if strcmp(tokens{pos},'(') + [node,pos]=gprParseOr(tokens,pos+1); + if pos>numel(tokens) || ~strcmp(tokens{pos},')') + error('Malformed grRule: unbalanced brackets'); + end + pos=pos+1; +else + node=struct('op','gene','gene',tokens{pos},'kids',{{}}); + pos=pos+1; +end +end + + +function node=gprPrune(node,removePrefix) +if strcmp(node.op,'gene') + if ~isempty(removePrefix) && startsWith(node.gene,removePrefix) + node=[]; + end + return +end +kids={}; +seen={}; +for k=1:numel(node.kids) + kid=gprPrune(node.kids{k},removePrefix); + if isempty(kid) + continue + end + if strcmp(kid.op,node.op) + parts=kid.kids; + else + parts={kid}; + end + for m=1:numel(parts) + key=gprRender(parts{m},node.op); + if ~ismember(key,seen) + seen{end+1}=key; %#ok + kids{end+1}=parts{m}; %#ok + end + end +end +if isempty(kids) + node=[]; +elseif numel(kids)==1 + node=kids{1}; +else + node.kids=kids; +end +end + + +function rule=gprRender(node,parentOp) +if strcmp(node.op,'gene') + rule=node.gene; + return +end +parts=cellfun(@(k) gprRender(k,node.op),node.kids,'UniformOutput',false); +rule=strjoin(parts,[' ' node.op ' ']); +if ~isempty(parentOp) && ~strcmp(parentOp,node.op) + rule=['(' rule ')']; +end +end diff --git a/testing/function_tests/tReconstruction.m b/testing/function_tests/tReconstruction.m index dfe9624e..96e3b277 100644 --- a/testing/function_tests/tReconstruction.m +++ b/testing/function_tests/tReconstruction.m @@ -40,6 +40,57 @@ function getModelFromHomologyBuildsDraft(testCase) testCase.verifyClass(draft, 'struct'); end + function getModelFromHomologyComplexPolicyAndOptions(testCase) + % A small template: a complex, an isozyme pair, a gene-free + % reaction, and two template genes that map to one new gene. + t = struct(); + t.id = 'tmpl'; t.name = 'tmpl'; + t.mets = {'a_c';'b_c';'d_c'}; t.metNames = {'A';'B';'D'}; + t.comps = {'c'}; t.compNames = {'cytosol'}; t.metComps = [1;1;1]; + t.rxns = {'Rcplx';'Riso';'Rfree';'Rdup'}; + t.rxnNames = t.rxns; + t.S = sparse([-1 0 0 -1; 0 -1 0 1; 1 1 -1 0]); + t.lb = [0;0;0;0]; t.ub = [1000;1000;1000;1000]; t.c = [0;0;0;0]; t.b = [0;0;0]; + t.rev = [0;0;0;0]; + t.grRules = {'tg1 and tg2'; 'tg3 or tg4'; ''; 'tg3 or tg4'}; + t.genes = {'tg1';'tg2';'tg3';'tg4'}; + t.rxnGeneMat = sparse([1 1 0 0; 0 0 1 1; 0 0 0 0; 0 0 1 1]); + t.rxnNotes = {'n1';'n2';'n3';'n4'}; + t.rxnConfidenceScores = [4;4;4;4]; + t.version = '9.9.9'; + % makeFakeBlastStructure wants at least 10 pairs; the extra ones + % are genes the template does not have + pairs = [{'tg1','ng1'; 'tg3','ng3'; 'tg4','ng3'}; ... + [strcat('x', string(1:7))', strcat('nx', string(1:7))']]; + pairs = cellstr(pairs); + bs = makeFakeBlastStructure(pairs, 'tmpl', 'new'); + evalc('flag = getModelFromHomology({t}, bs, ''new'', ''minLen'', 0, ''minIde'', 0);'); + evalc('keep = getModelFromHomology({t}, bs, ''new'', ''minLen'', 0, ''minIde'', 0, ''complexPolicy'', ''keep'', ''keepGeneFree'', true, ''preserveNotes'', true);'); + evalc('drop = getModelFromHomology({t}, bs, ''new'', ''minLen'', 0, ''minIde'', 0, ''complexPolicy'', ''drop'');'); + % flag: unmapped subunit kept as OLD_; gene-free reaction dropped + testCase.verifyTrue(contains(flag.grRules{strcmp(flag.rxns,'Rcplx')}, 'OLD_tmpl_tg2')); + testCase.verifyFalse(ismember('Rfree', flag.rxns)); + % two template genes mapping to one new gene appear once + testCase.verifyEqual(flag.grRules{strcmp(flag.rxns,'Riso')}, 'ng3'); + % keep: subunit dropped, gene-free kept, template notes kept + testCase.verifyEqual(keep.grRules{strcmp(keep.rxns,'Rcplx')}, 'ng1'); + testCase.verifyTrue(ismember('Rfree', keep.rxns)); + testCase.verifyEqual(keep.rxnNotes{strcmp(keep.rxns,'Rcplx')}, 'n1'); + testCase.verifyEqual(keep.rxnConfidenceScores(strcmp(keep.rxns,'Rcplx')), 4); + % drop: the incomplete complex is removed + testCase.verifyFalse(ismember('Rcplx', drop.rxns)); + testCase.verifyTrue(ismember('Riso', drop.rxns)); + % the template version is not inherited + testCase.verifyFalse(isfield(flag, 'version') && strcmp(flag.version, '9.9.9')); + testCase.verifyEqual(flag.rxnNotes{1}, 'Included by getModelFromHomology'); + end + + function getModelFromHomologyRejectsUnknownComplexPolicy(testCase) + src = testCase.model; src.id = 'srcModel'; + bs = makeFakeBlastStructure([src.genes(1:10), strcat('t_', src.genes(1:10))], 'srcModel', 'tgtOrg'); + testCase.verifyError(@() getModelFromHomology({src}, bs, 'tgtOrg', 'complexPolicy', 'maybe'), ?MException); + end + function getBlastRunsWhenAvailable(testCase) fa1 = fullfile(testCase.ravenRoot,'testing','function_tests','test_data','human_galactosidases.fa'); fa2 = fullfile(testCase.ravenRoot,'testing','function_tests','test_data','yeast_galactosidases.fa'); From 45fe35cfdba6a3a2f70bb9141a0e008290fc298d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 7 Oct 2026 13:19:11 +0000 Subject: [PATCH 08/48] doc: regenerate HTML documentation for 687271f [skip ci] Original-commit: https://github.com/SysBioChalmers/RAVEN/commit/f07802c07e0f787f46862307ed4b01d54029b94a --- .../homology/getModelFromHomology.html | 1373 ++++++++++------- .../function_tests/tReconstruction.html | 209 ++- 2 files changed, 916 insertions(+), 666 deletions(-) diff --git a/doc/reconstruction/homology/getModelFromHomology.html b/doc/reconstruction/homology/getModelFromHomology.html index d1a458c2..cee939ad 100644 --- a/doc/reconstruction/homology/getModelFromHomology.html +++ b/doc/reconstruction/homology/getModelFromHomology.html @@ -83,6 +83,23 @@

    DESCRIPTION ^DESCRIPTION ^CROSS-REFERENCE INFORMATION ^
 
 <h2><a name=SUBFUNCTIONS ^

    +
  • function tokens=gprTokenize(rule)
  • function rule=gprBuildRule(tokens)
  • function tokens=gprReplaceToken(tokens,oldGene,newGenes)
  • function rule=gprSimplify(rule,removePrefix)
  • function [node,pos]=gprParseOr(tokens,pos)
  • function [node,pos]=gprParseAnd(tokens,pos)
  • function [node,pos]=gprParseAtom(tokens,pos)
  • function node=gprPrune(node,removePrefix)
  • function rule=gprRender(node,parentOp)
  • SOURCE CODE ^

    0001 function [draftModel, hitGenes]=getModelFromHomology(models,blastStructure,...
    @@ -183,596 +203,775 @@ 

    SOURCE CODE ^% determines how to match genes if not looking at only 1-1 orthologs. 0057 % Either map the new genes to the old or old genes to new. The default 0058 % is to map the new genes (default true). -0059 % -0060 % Returns -0061 % ------- -0062 % draftModel : struct -0063 % a model structure for the new organism. -0064 % hitGenes : struct -0065 % collect the old and new genes. -0066 % -0067 % Examples -0068 % -------- -0069 % draftModel = getModelFromHomology(models, blastStructure, getModelFor); -0070 % -0071 % Notes -0072 % ----- -0073 % The models in the "models" structure should have named the metabolites in -0074 % the same manner, have their reversible reactions in the same direction -0075 % (run sortModel), and use the same compartment names. To avoid keeping -0076 % unneccesary old genes, the models should not have "or"-relations in their -0077 % grRules (use expandModel). -0078 % -0079 % The resulting draft model contains only reactions associated with -0080 % orthologous genes. The old (original) genes involved in "and" relations -0081 % in grRules without any orthologs are still included in the draft model as -0082 % OLD_MODELID_geneName. +0059 % complexPolicy : char +0060 % what happens to an "and"-linked subunit without an ortholog: +0061 % "flag" keeps the reaction with the subunit renamed +0062 % OLD_MODELID_geneName, for a curator to review; "keep" drops just that +0063 % subunit; "drop" drops the whole reaction (default "flag"). +0064 % keepGeneFree : logical +0065 % also transfer the template reactions that have no grRule (spontaneous, +0066 % transport, exchange, artificial). By default they are dropped, because +0067 % nothing supports them in the new organism. Set it when the template is +0068 % a curated reference whose gene-free reactions belong in every derived +0069 % model. With several templates, a gene-free reaction comes in once per +0070 % template (default false). +0071 % preserveNotes : logical +0072 % keep each transferred reaction's rxnNotes, rxnReferences and +0073 % rxnConfidenceScores from the template. By default the notes say the +0074 % reaction was included by getModelFromHomology and the confidence score +0075 % is 2 (default false). +0076 % +0077 % Returns +0078 % ------- +0079 % draftModel : struct +0080 % a model structure for the new organism. +0081 % hitGenes : struct +0082 % collect the old and new genes. 0083 % -0084 % "to" and "from" means relative to the new organism. -0085 -0086 hitGenes.oldGenes = []; % collect the old genes from the template model (organism) -0087 hitGenes.newGenes = []; % collect the new genes of the draft model (target organism) -0088 -0089 getModelFor=char(getModelFor); -0090 -0091 p=parseRAVENargs(varargin, {'preferredOrder',[]; 'strictness',1; ... -0092 'onlyGenesInModels',false; 'maxE',10^-30; 'minLen',100; 'minIde',40; ... -0093 'mapNewGenesToOld',true}); -0094 preferredOrder=p.preferredOrder; -0095 if isempty(preferredOrder) -0096 preferredOrder=[]; -0097 else -0098 preferredOrder=convertCharArray(preferredOrder); -0099 [row,col]=size(preferredOrder); -0100 if col>row -0101 preferredOrder=transpose(preferredOrder); -0102 end -0103 end -0104 switch p.strictness -0105 case 2 -0106 bidirectional=false; bestHitsOnly=false; -0107 case 3 -0108 bidirectional=true; bestHitsOnly=true; -0109 otherwise -0110 bidirectional=true; bestHitsOnly=false; -0111 end -0112 onlyGenesInModels=p.onlyGenesInModels; -0113 maxE=p.maxE; -0114 minLen=p.minLen; -0115 minIde=p.minIde; -0116 mapNewGenesToOld=p.mapNewGenesToOld; -0117 -0118 if isfield(models,'S') -0119 models={models}; -0120 end -0121 -0122 %Check that all the information is in the blast structure -0123 modelNames=cell(numel(models),1); -0124 for i=1:numel(models) -0125 modelNames{i}=models{i}.id; -0126 %Gene short names, geneMiriams and proteins are often different -0127 %between species, safer not to include them -0128 if isfield(models{i},'geneShortNames') -0129 models{i}=rmfield(models{i},'geneShortNames'); -0130 end -0131 if isfield(models{i},'geneMiriams') -0132 models{i}=rmfield(models{i},'geneMiriams'); -0133 end -0134 if isfield(models{i},'proteins') -0135 models{i}=rmfield(models{i},'proteins'); -0136 end -0137 %The geneFrom field also loses meaning if the genes are replaced by -0138 %orthologs -0139 if isfield(models{i},'geneFrom') -0140 models{i}=rmfield(models{i},'geneFrom'); -0141 end -0142 end -0143 -0144 %Check that genes do not begin with ( or end with ), as this makes problematic grRules -0145 for i=1:numel(blastStructure) -0146 problemGenes = startsWith(blastStructure(i).fromGenes,'(') | endsWith(blastStructure(i).fromGenes,')'); -0147 if any(problemGenes) -0148 error(['One or multiple gene identifiers from ' blastStructure(i).fromId ... -0149 ' starts with ''('' and/or ends with '')'', which is not allowed']) -0150 end -0151 end -0152 -0153 %Assume for now that all information is there and that it is correct. This -0154 %is important to fix since no further checks are being made! -0155 -0156 %Check whether provided fasta files use the same gene identifiers as -0157 %provided template models -0158 for i=1:numel(blastStructure) -0159 if ~strcmp(blastStructure(i).fromId,getModelFor) -0160 j=strcmpi(blastStructure(i).fromId,modelNames); -0161 if j==0 -0162 error(['While the blastStructure contains sequences from '... -0163 'organismID "%s" (as\nprovided in getBlast), none of '... -0164 'template models have this id (as model.id)'],... -0165 string(blastStructure(i).fromId)); -0166 end -0167 k=sum(ismember(blastStructure(i).fromGenes,models{j}.genes)); -0168 if k<(numel(models{j}.genes)*0.05) -0169 error(['Less than 5%% of the genes in the template model '... -0170 'with model.id "%s"\ncan be found in the blastStructure. '... -0171 'Ensure that the protein FASTA\nused in getBlast and '... -0172 'the template model used in getModelFromHomology\nuse '... -0173 'the same style of gene identifiers'],models{j}.id) -0174 end -0175 end -0176 end -0177 -0178 %Standardize grRules of template models -0179 for i=1:length(models) -0180 fprintf('\nStandardizing grRules of template model with ID "%s" ...',models{i}.id); -0181 [models{i}.grRules,models{i}.rxnGeneMat]=standardizeGrRules(models{i},false); -0182 end -0183 fprintf(' done\n'); -0184 -0185 %Remove all gene matches that are below the cutoffs -0186 for i=1:numel(blastStructure) -0187 indexes=blastStructure(i).evalue<maxE & blastStructure(i).aligLen>=minLen & blastStructure(i).identity>=minIde; %Do it in this direction to lose NaNs -0188 blastStructure(i).fromGenes(~indexes)=[]; -0189 blastStructure(i).toGenes(~indexes)=[]; -0190 blastStructure(i).evalue(~indexes)=[]; -0191 blastStructure(i).identity(~indexes)=[]; -0192 blastStructure(i).aligLen(~indexes)=[]; -0193 blastStructure(i).bitscore(~indexes)=[]; -0194 blastStructure(i).ppos(~indexes)=[]; -0195 end -0196 -0197 %Remove all reactions from the models that have no genes encoding for them. -0198 %Also remove all genes that encode for no reactions. There should not be any -0199 %but there might be mistakes -0200 for i=1:numel(models) -0201 [hasGenes, ~]=find(models{i}.rxnGeneMat); -0202 hasNoGenes=1:numel(models{i}.rxns); -0203 hasNoGenes(hasGenes)=[]; -0204 models{i}=removeReactions(models{i},hasNoGenes,true,true); -0205 end -0206 -0207 %Create a structure that contains all genes used in the blasts in any -0208 %direction for each of the models in "models" and for the new organism. The -0209 %first cell is for the new organism and then according to the preferred -0210 %order. If no such order is supplied, then according to the order in -0211 %"models" -0212 allGenes=cell(numel(models)+1,1); -0213 if isempty(preferredOrder) -0214 useOrder=modelNames; -0215 else -0216 useOrder=preferredOrder; -0217 end -0218 -0219 %Get the corresponding indexes for those models in the "models" structure -0220 useOrderIndexes=zeros(numel(models),1); -0221 for i=1:numel(models) -0222 [~, index]=ismember(models{i}.id,useOrder); -0223 useOrderIndexes(index)=i; -0224 end -0225 -0226 %Remove all genes from the blast structure that have no genes in the models -0227 if onlyGenesInModels==true -0228 modelGenes={}; -0229 for i=1:numel(models) -0230 modelGenes=[modelGenes;models{i}.genes]; -0231 end -0232 for i=1:numel(blastStructure) -0233 %Check to see if it should match the toId or fromId -0234 if strcmpi(blastStructure(i).fromId,getModelFor) -0235 I=ismember(blastStructure(i).toGenes,modelGenes); -0236 else -0237 I=ismember(blastStructure(i).fromGenes,modelGenes); -0238 end -0239 blastStructure(i).fromGenes(~I)=[]; -0240 blastStructure(i).toGenes(~I)=[]; -0241 blastStructure(i).evalue(~I)=[]; -0242 blastStructure(i).identity(~I)=[]; -0243 blastStructure(i).aligLen(~I)=[]; -0244 blastStructure(i).bitscore(~I)=[]; -0245 blastStructure(i).ppos(~I)=[]; -0246 -0247 %Check that no matching in blastStructure is empty. This happens if -0248 %no genes in the models are present in the corresponding sheet -0249 if isempty(blastStructure(i).fromGenes) -0250 EM=['No genes in matching from ' blastStructure(i).fromId ' to ' blastStructure(i).toId ' are present in the corresponding model']; -0251 error('RAVEN:badInput', '%s', EM); -0252 end -0253 end -0254 end -0255 -0256 %If only best orthologs are to be used then all other measurements are -0257 %deleted from the blastStructure. All code after this stays the same. This -0258 %means that preferred order can still matter. The best ortholog is selected -0259 %by bitscore, which unlike E-value does not shift when the search database -0260 %changes size. -0261 if bestHitsOnly -0262 for i=1:numel(blastStructure) -0263 keep=false(numel(blastStructure(i).toGenes),1); -0264 [allFromGenes, ~, I]=unique(blastStructure(i).fromGenes); -0265 -0266 %It would be nice to get rid of this loop -0267 for j=1:numel(allFromGenes) -0268 allMatches=find(I==j); -0269 bestMatches=allMatches(blastStructure(i).bitscore(allMatches)==max(blastStructure(i).bitscore(allMatches))); -0270 -0271 %Keep the best matches -0272 keep(bestMatches)=true; -0273 end -0274 -0275 %Delete all matches that were not best matches -0276 blastStructure(i).fromGenes(~keep)=[]; -0277 blastStructure(i).toGenes(~keep)=[]; -0278 blastStructure(i).evalue(~keep)=[]; -0279 blastStructure(i).identity(~keep)=[]; -0280 blastStructure(i).aligLen(~keep)=[]; -0281 blastStructure(i).bitscore(~keep)=[]; -0282 blastStructure(i).ppos(~keep)=[]; -0283 end -0284 end -0285 -0286 useOrder=[{getModelFor};useOrder]; -0287 -0288 for i=1:numel(blastStructure) -0289 [~, toIndex]=ismember(blastStructure(i).toId,useOrder); -0290 [~, fromIndex]=ismember(blastStructure(i).fromId,useOrder); -0291 -0292 %Add all genes to the corresponding list in allGenes -0293 allGenes{toIndex}=[allGenes{toIndex};blastStructure(i).toGenes]; -0294 allGenes{fromIndex}=[allGenes{fromIndex};blastStructure(i).fromGenes]; -0295 end -0296 -0297 %Keep only the unique gene names -0298 maxOtherGeneNr=0; %Determines the dimension of the connectivity matrixes -0299 for i=1:numel(allGenes) -0300 allGenes{i}=unique(allGenes{i}); -0301 if i>1 -0302 if numel(allGenes{i})>maxOtherGeneNr -0303 maxOtherGeneNr=numel(allGenes{i}); -0304 end -0305 end -0306 end +0084 % Examples +0085 % -------- +0086 % draftModel = getModelFromHomology(models, blastStructure, getModelFor); +0087 % +0088 % Notes +0089 % ----- +0090 % The models in the "models" structure should have named the metabolites in +0091 % the same manner, have their reversible reactions in the same direction +0092 % (run sortModel), and use the same compartment names. To avoid keeping +0093 % unneccesary old genes, the models should not have "or"-relations in their +0094 % grRules (use expandModel). +0095 % +0096 % The resulting draft model contains only reactions associated with +0097 % orthologous genes, plus the gene-free ones with keepGeneFree. With +0098 % complexPolicy "flag", the old (original) genes involved in "and" relations +0099 % in grRules without any orthologs are still included in the draft model as +0100 % OLD_MODELID_geneName. A gene that several template genes map to appears +0101 % once in a grRule. The draft does not keep the version, date or +0102 % descriptive annotation of the templates. +0103 % +0104 % "to" and "from" means relative to the new organism. +0105 +0106 hitGenes.oldGenes = []; % collect the old genes from the template model (organism) +0107 hitGenes.newGenes = []; % collect the new genes of the draft model (target organism) +0108 +0109 getModelFor=char(getModelFor); +0110 +0111 p=parseRAVENargs(varargin, {'preferredOrder',[]; 'strictness',1; ... +0112 'onlyGenesInModels',false; 'maxE',10^-30; 'minLen',100; 'minIde',40; ... +0113 'mapNewGenesToOld',true; 'complexPolicy','flag'; 'keepGeneFree',false; ... +0114 'preserveNotes',false}); +0115 complexPolicy=lower(char(p.complexPolicy)); +0116 if ~ismember(complexPolicy,{'flag','keep','drop'}) +0117 error('complexPolicy must be "flag", "keep" or "drop", not "%s"',complexPolicy); +0118 end +0119 keepGeneFree=p.keepGeneFree; +0120 preserveNotes=p.preserveNotes; +0121 preferredOrder=p.preferredOrder; +0122 if isempty(preferredOrder) +0123 preferredOrder=[]; +0124 else +0125 preferredOrder=convertCharArray(preferredOrder); +0126 [row,col]=size(preferredOrder); +0127 if col>row +0128 preferredOrder=transpose(preferredOrder); +0129 end +0130 end +0131 switch p.strictness +0132 case 2 +0133 bidirectional=false; bestHitsOnly=false; +0134 case 3 +0135 bidirectional=true; bestHitsOnly=true; +0136 otherwise +0137 bidirectional=true; bestHitsOnly=false; +0138 end +0139 onlyGenesInModels=p.onlyGenesInModels; +0140 maxE=p.maxE; +0141 minLen=p.minLen; +0142 minIde=p.minIde; +0143 mapNewGenesToOld=p.mapNewGenesToOld; +0144 +0145 if isfield(models,'S') +0146 models={models}; +0147 end +0148 +0149 %Check that all the information is in the blast structure +0150 modelNames=cell(numel(models),1); +0151 for i=1:numel(models) +0152 modelNames{i}=models{i}.id; +0153 %Gene short names, geneMiriams and proteins are often different +0154 %between species, safer not to include them +0155 if isfield(models{i},'geneShortNames') +0156 models{i}=rmfield(models{i},'geneShortNames'); +0157 end +0158 if isfield(models{i},'geneMiriams') +0159 models{i}=rmfield(models{i},'geneMiriams'); +0160 end +0161 if isfield(models{i},'proteins') +0162 models{i}=rmfield(models{i},'proteins'); +0163 end +0164 %The geneFrom field also loses meaning if the genes are replaced by +0165 %orthologs +0166 if isfield(models{i},'geneFrom') +0167 models{i}=rmfield(models{i},'geneFrom'); +0168 end +0169 end +0170 +0171 %Check that genes do not begin with ( or end with ), as this makes problematic grRules +0172 for i=1:numel(blastStructure) +0173 problemGenes = startsWith(blastStructure(i).fromGenes,'(') | endsWith(blastStructure(i).fromGenes,')'); +0174 if any(problemGenes) +0175 error(['One or multiple gene identifiers from ' blastStructure(i).fromId ... +0176 ' starts with ''('' and/or ends with '')'', which is not allowed']) +0177 end +0178 end +0179 +0180 %Assume for now that all information is there and that it is correct. This +0181 %is important to fix since no further checks are being made! +0182 +0183 %Check whether provided fasta files use the same gene identifiers as +0184 %provided template models +0185 for i=1:numel(blastStructure) +0186 if ~strcmp(blastStructure(i).fromId,getModelFor) +0187 j=strcmpi(blastStructure(i).fromId,modelNames); +0188 if j==0 +0189 error(['While the blastStructure contains sequences from '... +0190 'organismID "%s" (as\nprovided in getBlast), none of '... +0191 'template models have this id (as model.id)'],... +0192 string(blastStructure(i).fromId)); +0193 end +0194 k=sum(ismember(blastStructure(i).fromGenes,models{j}.genes)); +0195 if k<(numel(models{j}.genes)*0.05) +0196 error(['Less than 5%% of the genes in the template model '... +0197 'with model.id "%s"\ncan be found in the blastStructure. '... +0198 'Ensure that the protein FASTA\nused in getBlast and '... +0199 'the template model used in getModelFromHomology\nuse '... +0200 'the same style of gene identifiers'],models{j}.id) +0201 end +0202 end +0203 end +0204 +0205 %Standardize grRules of template models +0206 for i=1:length(models) +0207 fprintf('\nStandardizing grRules of template model with ID "%s" ...',models{i}.id); +0208 [models{i}.grRules,models{i}.rxnGeneMat]=standardizeGrRules(models{i},false); +0209 end +0210 fprintf(' done\n'); +0211 +0212 %Remove all gene matches that are below the cutoffs +0213 for i=1:numel(blastStructure) +0214 indexes=blastStructure(i).evalue<maxE & blastStructure(i).aligLen>=minLen & blastStructure(i).identity>=minIde; %Do it in this direction to lose NaNs +0215 blastStructure(i).fromGenes(~indexes)=[]; +0216 blastStructure(i).toGenes(~indexes)=[]; +0217 blastStructure(i).evalue(~indexes)=[]; +0218 blastStructure(i).identity(~indexes)=[]; +0219 blastStructure(i).aligLen(~indexes)=[]; +0220 blastStructure(i).bitscore(~indexes)=[]; +0221 blastStructure(i).ppos(~indexes)=[]; +0222 end +0223 +0224 %Remove all reactions from the models that have no genes encoding for them, +0225 %unless keepGeneFree. Also remove all genes that encode for no reactions. +0226 %There should not be any but there might be mistakes +0227 geneFreeRxns=cell(numel(models),1); +0228 geneFreeRxns(:)={{}}; +0229 for i=1:numel(models) +0230 [hasGenes, ~]=find(models{i}.rxnGeneMat); +0231 hasNoGenes=1:numel(models{i}.rxns); +0232 hasNoGenes(hasGenes)=[]; +0233 if keepGeneFree +0234 geneFreeRxns{i}=models{i}.rxns(hasNoGenes); +0235 else +0236 models{i}=removeReactions(models{i},hasNoGenes,true,true); +0237 end +0238 end +0239 +0240 %Create a structure that contains all genes used in the blasts in any +0241 %direction for each of the models in "models" and for the new organism. The +0242 %first cell is for the new organism and then according to the preferred +0243 %order. If no such order is supplied, then according to the order in +0244 %"models" +0245 allGenes=cell(numel(models)+1,1); +0246 if isempty(preferredOrder) +0247 useOrder=modelNames; +0248 else +0249 useOrder=preferredOrder; +0250 end +0251 +0252 %Get the corresponding indexes for those models in the "models" structure +0253 useOrderIndexes=zeros(numel(models),1); +0254 for i=1:numel(models) +0255 [~, index]=ismember(models{i}.id,useOrder); +0256 useOrderIndexes(index)=i; +0257 end +0258 +0259 %Remove all genes from the blast structure that have no genes in the models +0260 if onlyGenesInModels==true +0261 modelGenes={}; +0262 for i=1:numel(models) +0263 modelGenes=[modelGenes;models{i}.genes]; +0264 end +0265 for i=1:numel(blastStructure) +0266 %Check to see if it should match the toId or fromId +0267 if strcmpi(blastStructure(i).fromId,getModelFor) +0268 I=ismember(blastStructure(i).toGenes,modelGenes); +0269 else +0270 I=ismember(blastStructure(i).fromGenes,modelGenes); +0271 end +0272 blastStructure(i).fromGenes(~I)=[]; +0273 blastStructure(i).toGenes(~I)=[]; +0274 blastStructure(i).evalue(~I)=[]; +0275 blastStructure(i).identity(~I)=[]; +0276 blastStructure(i).aligLen(~I)=[]; +0277 blastStructure(i).bitscore(~I)=[]; +0278 blastStructure(i).ppos(~I)=[]; +0279 +0280 %Check that no matching in blastStructure is empty. This happens if +0281 %no genes in the models are present in the corresponding sheet +0282 if isempty(blastStructure(i).fromGenes) +0283 EM=['No genes in matching from ' blastStructure(i).fromId ' to ' blastStructure(i).toId ' are present in the corresponding model']; +0284 error('RAVEN:badInput', '%s', EM); +0285 end +0286 end +0287 end +0288 +0289 %If only best orthologs are to be used then all other measurements are +0290 %deleted from the blastStructure. All code after this stays the same. This +0291 %means that preferred order can still matter. The best ortholog is selected +0292 %by bitscore, which unlike E-value does not shift when the search database +0293 %changes size. +0294 if bestHitsOnly +0295 for i=1:numel(blastStructure) +0296 keep=false(numel(blastStructure(i).toGenes),1); +0297 [allFromGenes, ~, I]=unique(blastStructure(i).fromGenes); +0298 +0299 %It would be nice to get rid of this loop +0300 for j=1:numel(allFromGenes) +0301 allMatches=find(I==j); +0302 bestMatches=allMatches(blastStructure(i).bitscore(allMatches)==max(blastStructure(i).bitscore(allMatches))); +0303 +0304 %Keep the best matches +0305 keep(bestMatches)=true; +0306 end 0307 -0308 %Generate a cell array of matrixes that describes how the genes in the new -0309 %organism map to the models. Each cell matches to the corresponding model -0310 %in useOrder (starting at 2 of course). First dimension is gene in new -0311 %organism, second which gene it is in the other organism. The second matrix -0312 %describes how they map back. -0313 -0314 %As it is now, a significant match is indicated by a 1. This could be -0315 %expanded to contain some kind of significance level. The first dimension -0316 %is still the genes in the new model. -0317 -0318 allTo=cell(numel(useOrder)-1,1); -0319 allFrom=cell(numel(useOrder)-1,1); +0308 %Delete all matches that were not best matches +0309 blastStructure(i).fromGenes(~keep)=[]; +0310 blastStructure(i).toGenes(~keep)=[]; +0311 blastStructure(i).evalue(~keep)=[]; +0312 blastStructure(i).identity(~keep)=[]; +0313 blastStructure(i).aligLen(~keep)=[]; +0314 blastStructure(i).bitscore(~keep)=[]; +0315 blastStructure(i).ppos(~keep)=[]; +0316 end +0317 end +0318 +0319 useOrder=[{getModelFor};useOrder]; 0320 -0321 for i=1:numel(useOrder)-1 -0322 allTo{i}=sparse(numel(allGenes{1}),numel(allGenes{i+1})); -0323 allFrom{i}=sparse(numel(allGenes{1}),numel(allGenes{i+1})); -0324 end -0325 -0326 %Fill the matches to other species -0327 for i=1:numel(blastStructure) -0328 if strcmp(blastStructure(i).toId,getModelFor) -0329 %This was "to" the new organism. They should all match so no checks -0330 %are being made -0331 [~, a]=ismember(blastStructure(i).toGenes,allGenes{1}); -0332 [~, fromModel]=ismember(blastStructure(i).fromId,useOrder); -0333 [~, b]=ismember(blastStructure(i).fromGenes,allGenes{fromModel}); -0334 idx = sub2ind(size(allTo{fromModel-1}), a, b); -0335 allTo{fromModel-1}(idx)=1; -0336 else -0337 %This was "from" the new organism -0338 [~, a]=ismember(blastStructure(i).fromGenes,allGenes{1}); -0339 [~, toModel]=ismember(blastStructure(i).toId,useOrder); -0340 [~, b]=ismember(blastStructure(i).toGenes,allGenes{toModel}); -0341 idx = sub2ind(size(allFrom{toModel-1}), a, b); -0342 allFrom{toModel-1}(idx)=1; -0343 end -0344 end -0345 -0346 %Now we have all the gene matches in a convenient way. For all the genes in -0347 %the new organism get the genes that should be included from other -0348 %organisms. If all genes should be included this simply means keep the -0349 %allFrom matrix as it is. If only orthologs which could be mapped in both -0350 %BLAST directions are to be included then only those elements are kept. -0351 -0352 finalMappings=cell(numel(useOrder)-1,1); -0353 if bidirectional -0354 for j=1:numel(allFrom) -0355 finalMappings{j}=allTo{j}~=0 & allFrom{j}~=0; -0356 end -0357 else -0358 if mapNewGenesToOld==true -0359 finalMappings=allFrom; -0360 else -0361 finalMappings=allTo; -0362 end -0363 end -0364 -0365 %Remove all genes from the mapping that are not in the models. This does not -0366 %do much if only genes in the models were used for the original mapping. -0367 %Also simplify the finalMapping and allGenes structures so that they only -0368 %contain mappings that exist -0369 usedNewGenes=false(numel(allGenes{1}),1); -0370 -0371 for i=1:numel(allGenes)-1 -0372 %First remove mappings for those genes that are not in the model -0373 if onlyGenesInModels==false -0374 a=ismember(allGenes{i+1},models{useOrderIndexes(i)}.genes); -0375 finalMappings{i}(:,~a)=false; +0321 for i=1:numel(blastStructure) +0322 [~, toIndex]=ismember(blastStructure(i).toId,useOrder); +0323 [~, fromIndex]=ismember(blastStructure(i).fromId,useOrder); +0324 +0325 %Add all genes to the corresponding list in allGenes +0326 allGenes{toIndex}=[allGenes{toIndex};blastStructure(i).toGenes]; +0327 allGenes{fromIndex}=[allGenes{fromIndex};blastStructure(i).fromGenes]; +0328 end +0329 +0330 %Keep only the unique gene names +0331 maxOtherGeneNr=0; %Determines the dimension of the connectivity matrixes +0332 for i=1:numel(allGenes) +0333 allGenes{i}=unique(allGenes{i}); +0334 if i>1 +0335 if numel(allGenes{i})>maxOtherGeneNr +0336 maxOtherGeneNr=numel(allGenes{i}); +0337 end +0338 end +0339 end +0340 +0341 %Generate a cell array of matrixes that describes how the genes in the new +0342 %organism map to the models. Each cell matches to the corresponding model +0343 %in useOrder (starting at 2 of course). First dimension is gene in new +0344 %organism, second which gene it is in the other organism. The second matrix +0345 %describes how they map back. +0346 +0347 %As it is now, a significant match is indicated by a 1. This could be +0348 %expanded to contain some kind of significance level. The first dimension +0349 %is still the genes in the new model. +0350 +0351 allTo=cell(numel(useOrder)-1,1); +0352 allFrom=cell(numel(useOrder)-1,1); +0353 +0354 for i=1:numel(useOrder)-1 +0355 allTo{i}=sparse(numel(allGenes{1}),numel(allGenes{i+1})); +0356 allFrom{i}=sparse(numel(allGenes{1}),numel(allGenes{i+1})); +0357 end +0358 +0359 %Fill the matches to other species +0360 for i=1:numel(blastStructure) +0361 if strcmp(blastStructure(i).toId,getModelFor) +0362 %This was "to" the new organism. They should all match so no checks +0363 %are being made +0364 [~, a]=ismember(blastStructure(i).toGenes,allGenes{1}); +0365 [~, fromModel]=ismember(blastStructure(i).fromId,useOrder); +0366 [~, b]=ismember(blastStructure(i).fromGenes,allGenes{fromModel}); +0367 idx = sub2ind(size(allTo{fromModel-1}), a, b); +0368 allTo{fromModel-1}(idx)=1; +0369 else +0370 %This was "from" the new organism +0371 [~, a]=ismember(blastStructure(i).fromGenes,allGenes{1}); +0372 [~, toModel]=ismember(blastStructure(i).toId,useOrder); +0373 [~, b]=ismember(blastStructure(i).toGenes,allGenes{toModel}); +0374 idx = sub2ind(size(allFrom{toModel-1}), a, b); +0375 allFrom{toModel-1}(idx)=1; 0376 end -0377 -0378 %Then remove unused ones and simplify -0379 [a, b]=find(finalMappings{i}); -0380 usedGenes=false(numel(allGenes{i+1}),1); -0381 usedNewGenes(a)=true; -0382 usedGenes(b)=true; -0383 finalMappings{i}=finalMappings{i}(:,usedGenes); -0384 allGenes{i+1}=allGenes{i+1}(usedGenes); -0385 end -0386 -0387 %Remove all new genes that have not been mapped to anything -0388 allGenes{1}=allGenes{1}(usedNewGenes); -0389 for i=1:numel(finalMappings) -0390 finalMappings{i}=finalMappings{i}(usedNewGenes,:); -0391 end -0392 -0393 %Now is it time to choose which reactions should be included from which -0394 %models. If there is a preferred order specified then each gene can only -0395 %result in reactions from one model, otherwise they should all be included -0396 -0397 %Start by simplifying the models by removing genes/reactions that are not -0398 %used. This is where it gets weird with complexes, especially "or" -0399 %complexes. In this step only reactions which are encoded by one single -0400 %gene, or where all genes should be deleted, are deleted. The info on the -0401 %full complex is still present in the grRules -0402 -0403 for i=1:numel(models) -0404 a=ismember(models{useOrderIndexes(i)}.genes,allGenes{i+1}); -0405 -0406 %Remove reactions that are not associated to any of the genes in -0407 %allGenes, thereby also keeping complexes where only for one of the -0408 %genes was matched -0409 [rxnsToKeep,~] = find(models{useOrderIndexes(i)}.rxnGeneMat(:,a)); -0410 rxnsToRemove = repmat(1,numel(models{useOrderIndexes(i)}.rxns),1); -0411 rxnsToRemove(rxnsToKeep) = 0; -0412 rxnsToRemove = find(rxnsToRemove); -0413 models{useOrderIndexes(i)}=removeReactions(models{useOrderIndexes(i)},rxnsToRemove,true,true,true); -0414 end -0415 -0416 %Since mergeModels function will be used in the end, the models are -0417 %simplified further by deleting genes/reactions in the order specified by -0418 %preferredOrder. This means that the last model will only contain reactions -0419 %for genes that mapped only to that model -0420 -0421 allUsedGenes=false(numel(allGenes{1}),1); -0422 -0423 if ~isempty(preferredOrder) && numel(models)>1 -0424 [usedGenes, ~]=find(finalMappings{1}); %All that are used in the first model in preferredOrder -0425 allUsedGenes(usedGenes)=true; -0426 for i=2:numel(finalMappings) -0427 [usedGenes, ~]=find(finalMappings{i}); -0428 usedGenes=unique(usedGenes); -0429 a=ismember(usedGenes,find(allUsedGenes)); -0430 -0431 [~, genesToDelete]=find(finalMappings{i}(usedGenes(a),:)); %IMPORTANT! IS it really correct to remove all genes that map? -0432 genesToDelete=unique(genesToDelete); %Maybe not needed, but for clarity if nothing else -0433 -0434 %Remove all the genes that were already found and add the other -0435 %ones to allUsedGenes -0436 models{useOrderIndexes(i)}=removeGenes(models{useOrderIndexes(i)},allGenes{i+1}(genesToDelete),true,true,false); -0437 allUsedGenes(usedGenes)=true; -0438 -0439 %Remove the deleted genes from finalMappings and allGenes. -0440 finalMappings{i}(:,genesToDelete)=[]; -0441 allGenes{i+1}(genesToDelete)=[]; -0442 end -0443 end -0444 -0445 %Now loop through the models and update the gene associations. Genes not -0446 %belonging to the new organism will be renamed as "OLD_MODELID_gene" -0447 for i=1:numel(models) -0448 %Find all the new genes that should be used for this model -0449 [newGenes, oldGenes]=find(finalMappings{i}); -0450 -0451 %Create a new gene list with the genes from the new organism and those -0452 %genes that could not be removed -0453 replaceableGenes=allGenes{i+1}(unique(oldGenes)); -0454 nonReplaceableGenes=setdiff(models{useOrderIndexes(i)}.genes,replaceableGenes); -0455 fullGeneList=[allGenes{1}(unique(newGenes));nonReplaceableGenes]; -0456 -0457 %Just to save some indexing later. This is the LAST index of -0458 %replaceable ones -0459 nonRepStartIndex=numel(unique(newGenes)); -0460 -0461 %Construct a new rxnGeneMat -0462 newRxnGeneMat=sparse(numel(models{useOrderIndexes(i)}.rxns),numel(fullGeneList)); -0463 -0464 %Now update the rxnGeneMat. This is a little tricky and could -0465 %probably be done in a more efficient way, but I just loop through the -0466 %reactions and add them one by one -0467 for j=1:numel(models{useOrderIndexes(i)}.rxns) -0468 %Get the old genes encoding for this reaction -0469 [~, oldGeneIds]=find(models{useOrderIndexes(i)}.rxnGeneMat(j,:)); -0470 -0471 %Tokenize the grRule once; all gene substitutions operate on the -0472 %token list so that no gene ID can partially match another. -0473 tokens=gprTokenize(models{useOrderIndexes(i)}.grRules{j}); -0474 -0475 %Update the matrix for each gene. This includes replacing one gene -0476 %with several new ones if there were several matches -0477 for k=1:numel(oldGeneIds) -0478 %Match the gene to one in the gene list. This is done as a text -0479 %match. Could probably be done better, but the indexing is -0480 %complex -0481 -0482 geneName=models{useOrderIndexes(i)}.genes(oldGeneIds(k)); -0483 -0484 %First search in the mappable genes -0485 mapIndex=find(ismember(allGenes{i+1},geneName)); -0486 -0487 if ~isempty(mapIndex) -0488 % add the old genes -0489 hitGenes.oldGenes = [hitGenes.oldGenes, {geneName}]; -0490 -0491 %Get the new genes for that gene -0492 a=find(finalMappings{i}(:,mapIndex)); -0493 -0494 %Find the positions of these genes in the final gene list -0495 [~, b]=ismember(allGenes{1}(a),fullGeneList); -0496 -0497 %Update the matrix -0498 newRxnGeneMat(j,b)=1; -0499 -0500 %Build repString for hitGenes (display only) -0501 repString=fullGeneList{b(1)}; -0502 if numel(b)>1 -0503 for l=2:numel(b) -0504 repString=[repString ' or ' fullGeneList{b(l)}]; %#ok<AGROW> -0505 end -0506 repString=['(' repString ')']; -0507 end -0508 hitGenes.newGenes = [hitGenes.newGenes, {repString}]; -0509 -0510 %Replace the old gene token with the new gene(s) -0511 tokens=gprReplaceToken(tokens,geneName{1},fullGeneList(b)); -0512 else -0513 %Then search in the non-replaceable genes. There could only -0514 %be one match here -0515 index=find(ismember(nonReplaceableGenes,geneName)); -0516 -0517 %Update the matrix -0518 newRxnGeneMat(j,nonRepStartIndex+index)=1; -0519 -0520 oldName=strcat('OLD_',models{useOrderIndexes(i)}.id,'_',geneName{1}); -0521 tokens=gprReplaceToken(tokens,geneName{1},{oldName}); -0522 end -0523 end +0377 end +0378 +0379 %Now we have all the gene matches in a convenient way. For all the genes in +0380 %the new organism get the genes that should be included from other +0381 %organisms. If all genes should be included this simply means keep the +0382 %allFrom matrix as it is. If only orthologs which could be mapped in both +0383 %BLAST directions are to be included then only those elements are kept. +0384 +0385 finalMappings=cell(numel(useOrder)-1,1); +0386 if bidirectional +0387 for j=1:numel(allFrom) +0388 finalMappings{j}=allTo{j}~=0 & allFrom{j}~=0; +0389 end +0390 else +0391 if mapNewGenesToOld==true +0392 finalMappings=allFrom; +0393 else +0394 finalMappings=allTo; +0395 end +0396 end +0397 +0398 %Remove all genes from the mapping that are not in the models. This does not +0399 %do much if only genes in the models were used for the original mapping. +0400 %Also simplify the finalMapping and allGenes structures so that they only +0401 %contain mappings that exist +0402 usedNewGenes=false(numel(allGenes{1}),1); +0403 +0404 for i=1:numel(allGenes)-1 +0405 %First remove mappings for those genes that are not in the model +0406 if onlyGenesInModels==false +0407 a=ismember(allGenes{i+1},models{useOrderIndexes(i)}.genes); +0408 finalMappings{i}(:,~a)=false; +0409 end +0410 +0411 %Then remove unused ones and simplify +0412 [a, b]=find(finalMappings{i}); +0413 usedGenes=false(numel(allGenes{i+1}),1); +0414 usedNewGenes(a)=true; +0415 usedGenes(b)=true; +0416 finalMappings{i}=finalMappings{i}(:,usedGenes); +0417 allGenes{i+1}=allGenes{i+1}(usedGenes); +0418 end +0419 +0420 %Remove all new genes that have not been mapped to anything +0421 allGenes{1}=allGenes{1}(usedNewGenes); +0422 for i=1:numel(finalMappings) +0423 finalMappings{i}=finalMappings{i}(usedNewGenes,:); +0424 end +0425 +0426 %Now is it time to choose which reactions should be included from which +0427 %models. If there is a preferred order specified then each gene can only +0428 %result in reactions from one model, otherwise they should all be included +0429 +0430 %Start by simplifying the models by removing genes/reactions that are not +0431 %used. This is where it gets weird with complexes, especially "or" +0432 %complexes. In this step only reactions which are encoded by one single +0433 %gene, or where all genes should be deleted, are deleted. The info on the +0434 %full complex is still present in the grRules +0435 +0436 for i=1:numel(models) +0437 a=ismember(models{useOrderIndexes(i)}.genes,allGenes{i+1}); +0438 +0439 %Remove reactions that are not associated to any of the genes in +0440 %allGenes, thereby also keeping complexes where only for one of the +0441 %genes was matched +0442 [rxnsToKeep,~] = find(models{useOrderIndexes(i)}.rxnGeneMat(:,a)); +0443 rxnsToRemove = repmat(1,numel(models{useOrderIndexes(i)}.rxns),1); +0444 rxnsToRemove(rxnsToKeep) = 0; +0445 rxnsToRemove(ismember(models{useOrderIndexes(i)}.rxns,geneFreeRxns{useOrderIndexes(i)})) = 0; +0446 rxnsToRemove = find(rxnsToRemove); +0447 models{useOrderIndexes(i)}=removeReactions(models{useOrderIndexes(i)},rxnsToRemove,true,true,true); +0448 end +0449 +0450 %Since mergeModels function will be used in the end, the models are +0451 %simplified further by deleting genes/reactions in the order specified by +0452 %preferredOrder. This means that the last model will only contain reactions +0453 %for genes that mapped only to that model +0454 +0455 allUsedGenes=false(numel(allGenes{1}),1); +0456 +0457 if ~isempty(preferredOrder) && numel(models)>1 +0458 [usedGenes, ~]=find(finalMappings{1}); %All that are used in the first model in preferredOrder +0459 allUsedGenes(usedGenes)=true; +0460 for i=2:numel(finalMappings) +0461 [usedGenes, ~]=find(finalMappings{i}); +0462 usedGenes=unique(usedGenes); +0463 a=ismember(usedGenes,find(allUsedGenes)); +0464 +0465 [~, genesToDelete]=find(finalMappings{i}(usedGenes(a),:)); %IMPORTANT! IS it really correct to remove all genes that map? +0466 genesToDelete=unique(genesToDelete); %Maybe not needed, but for clarity if nothing else +0467 +0468 %Remove all the genes that were already found and add the other +0469 %ones to allUsedGenes +0470 models{useOrderIndexes(i)}=removeGenes(models{useOrderIndexes(i)},allGenes{i+1}(genesToDelete),true,true,false); +0471 allUsedGenes(usedGenes)=true; +0472 +0473 %Remove the deleted genes from finalMappings and allGenes. +0474 finalMappings{i}(:,genesToDelete)=[]; +0475 allGenes{i+1}(genesToDelete)=[]; +0476 end +0477 end +0478 +0479 %Now loop through the models and update the gene associations. Genes not +0480 %belonging to the new organism will be renamed as "OLD_MODELID_gene" +0481 for i=1:numel(models) +0482 %Find all the new genes that should be used for this model +0483 [newGenes, oldGenes]=find(finalMappings{i}); +0484 +0485 %Create a new gene list with the genes from the new organism and those +0486 %genes that could not be removed +0487 replaceableGenes=allGenes{i+1}(unique(oldGenes)); +0488 nonReplaceableGenes=setdiff(models{useOrderIndexes(i)}.genes,replaceableGenes); +0489 fullGeneList=[allGenes{1}(unique(newGenes));nonReplaceableGenes]; +0490 +0491 %Just to save some indexing later. This is the LAST index of +0492 %replaceable ones +0493 nonRepStartIndex=numel(unique(newGenes)); +0494 +0495 %Construct a new rxnGeneMat +0496 newRxnGeneMat=sparse(numel(models{useOrderIndexes(i)}.rxns),numel(fullGeneList)); +0497 +0498 %Now update the rxnGeneMat. This is a little tricky and could +0499 %probably be done in a more efficient way, but I just loop through the +0500 %reactions and add them one by one +0501 for j=1:numel(models{useOrderIndexes(i)}.rxns) +0502 %Get the old genes encoding for this reaction +0503 [~, oldGeneIds]=find(models{useOrderIndexes(i)}.rxnGeneMat(j,:)); +0504 +0505 %Tokenize the grRule once; all gene substitutions operate on the +0506 %token list so that no gene ID can partially match another. +0507 tokens=gprTokenize(models{useOrderIndexes(i)}.grRules{j}); +0508 +0509 %Update the matrix for each gene. This includes replacing one gene +0510 %with several new ones if there were several matches +0511 for k=1:numel(oldGeneIds) +0512 %Match the gene to one in the gene list. This is done as a text +0513 %match. Could probably be done better, but the indexing is +0514 %complex +0515 +0516 geneName=models{useOrderIndexes(i)}.genes(oldGeneIds(k)); +0517 +0518 %First search in the mappable genes +0519 mapIndex=find(ismember(allGenes{i+1},geneName)); +0520 +0521 if ~isempty(mapIndex) +0522 % add the old genes +0523 hitGenes.oldGenes = [hitGenes.oldGenes, {geneName}]; 0524 -0525 models{useOrderIndexes(i)}.grRules{j}=gprBuildRule(tokens); -0526 end -0527 -0528 %Add the new list of genes -0529 models{useOrderIndexes(i)}.rxnGeneMat=newRxnGeneMat; -0530 if ~isempty(nonReplaceableGenes) -0531 models{useOrderIndexes(i)}.genes=[allGenes{1}(unique(newGenes));strcat('OLD_',models{useOrderIndexes(i)}.id,'_',nonReplaceableGenes)]; -0532 else -0533 models{useOrderIndexes(i)}.genes=allGenes{1}(unique(newGenes)); -0534 end -0535 if isfield(models{useOrderIndexes(i)},'geneComps') -0536 geneComps=models{useOrderIndexes(i)}.geneComps(1); -0537 models{useOrderIndexes(i)}.geneComps=zeros(numel(models{useOrderIndexes(i)}.genes),1); -0538 %Assume that all genes are in the same compartment, and this -0539 %compartment is specified for the first gene -0540 models{useOrderIndexes(i)}.geneComps(:)=geneComps; -0541 end -0542 end +0525 %Get the new genes for that gene +0526 a=find(finalMappings{i}(:,mapIndex)); +0527 +0528 %Find the positions of these genes in the final gene list +0529 [~, b]=ismember(allGenes{1}(a),fullGeneList); +0530 +0531 %Update the matrix +0532 newRxnGeneMat(j,b)=1; +0533 +0534 %Build repString for hitGenes (display only) +0535 repString=fullGeneList{b(1)}; +0536 if numel(b)>1 +0537 for l=2:numel(b) +0538 repString=[repString ' or ' fullGeneList{b(l)}]; %#ok<AGROW> +0539 end +0540 repString=['(' repString ')']; +0541 end +0542 hitGenes.newGenes = [hitGenes.newGenes, {repString}]; 0543 -0544 %Now merge the models. All information should be correct except for "or" -0545 %complexes -0546 draftModel=mergeModels(models,'metNames'); -0547 -0548 %Remove OLD_ genes that appear only in OR branches (they were added for -0549 %AND-complex genes with no orthologs; in OR branches they are redundant). -0550 %Uses token-level removal to avoid partial-match hazards. -0551 for oldI=1:numel(draftModel.grRules) -0552 if ~contains(draftModel.grRules{oldI},'OLD_') -0553 continue -0554 end -0555 tkns=gprTokenize(draftModel.grRules{oldI}); -0556 again=true; -0557 while again -0558 again=false; -0559 for kk=1:numel(tkns) -0560 if startsWith(tkns{kk},'OLD_') -0561 if kk>1 && strcmp(tkns{kk-1},'or') -0562 tkns(kk-1:kk)=[]; -0563 again=true; break -0564 elseif kk<numel(tkns) && strcmp(tkns{kk+1},'or') -0565 tkns(kk:kk+1)=[]; -0566 again=true; break -0567 end -0568 end -0569 end -0570 end -0571 draftModel.grRules{oldI}=gprBuildRule(tkns); -0572 end -0573 -0574 %Change name of the resulting model -0575 draftModel.id=getModelFor; -0576 name='Generated by getModelFromHomology using '; -0577 for i=1:numel(models) -0578 if i<numel(models) -0579 name=[name models{i}.id ', ']; -0580 else -0581 name=[name models{i}.id]; -0582 end -0583 end -0584 draftModel.name=name; -0585 draftModel.rxnNotes=cell(length(draftModel.rxns),1); -0586 draftModel.rxnNotes(:)={'Included by getModelFromHomology'}; -0587 draftModel.rxnConfidenceScores=NaN(length(draftModel.rxns),1); -0588 draftModel.rxnConfidenceScores(:)=2; -0589 draftModel=deleteUnusedGenes(draftModel,0); -0590 %Standardize grRules and notify if problematic grRules are found -0591 [draftModel.grRules,draftModel.rxnGeneMat]=standardizeGrRules(draftModel,false); -0592 draftModel=deleteUnusedGenes(draftModel,false); -0593 end -0594 -0595 -0596 function tokens=gprTokenize(rule) -0597 % Split a GPR rule into tokens: gene IDs, "and", "or", "(", ")". -0598 % Brackets are separated from adjacent text by whitespace before splitting. -0599 if isempty(rule) -0600 tokens={}; -0601 return -0602 end -0603 r=regexprep(rule,'([\(\)])',' $1 '); -0604 r=regexprep(r,'\s+',' '); -0605 tokens=strsplit(strtrim(r),' '); -0606 tokens=tokens(~cellfun(@isempty,tokens)); -0607 end -0608 -0609 -0610 function rule=gprBuildRule(tokens) -0611 % Reconstruct a GPR rule string from a token list. -0612 if isempty(tokens) -0613 rule=''; -0614 return -0615 end -0616 rule=strjoin(tokens,' '); -0617 rule=strrep(rule,'( ','('); -0618 rule=strrep(rule,' )',')'); -0619 end -0620 -0621 -0622 function tokens=gprReplaceToken(tokens,oldGene,newGenes) -0623 % Replace each occurrence of token oldGene with the gene(s) in newGenes. -0624 % newGenes is a cell array; multiple genes are wrapped in parentheses and -0625 % joined with " or ". -0626 k=1; -0627 while k<=numel(tokens) -0628 if strcmp(tokens{k},oldGene) -0629 if numel(newGenes)==1 -0630 tokens{k}=newGenes{1}; -0631 k=k+1; -0632 else -0633 seq={'('}; -0634 for g=1:numel(newGenes) -0635 seq{end+1}=newGenes{g}; %#ok<AGROW> -0636 if g<numel(newGenes) -0637 seq{end+1}='or'; %#ok<AGROW> -0638 end -0639 end -0640 seq{end+1}=')'; -0641 tokens=[tokens(1:k-1),seq,tokens(k+1:end)]; -0642 k=k+numel(seq); -0643 end -0644 else -0645 k=k+1; -0646 end -0647 end -0648 end

    +0544 %Replace the old gene token with the new gene(s) +0545 tokens=gprReplaceToken(tokens,geneName{1},fullGeneList(b)); +0546 else +0547 %Then search in the non-replaceable genes. There could only +0548 %be one match here +0549 index=find(ismember(nonReplaceableGenes,geneName)); +0550 +0551 %Update the matrix +0552 newRxnGeneMat(j,nonRepStartIndex+index)=1; +0553 +0554 oldName=strcat('OLD_',models{useOrderIndexes(i)}.id,'_',geneName{1}); +0555 tokens=gprReplaceToken(tokens,geneName{1},{oldName}); +0556 end +0557 end +0558 +0559 models{useOrderIndexes(i)}.grRules{j}=gprBuildRule(tokens); +0560 end +0561 +0562 %Add the new list of genes +0563 models{useOrderIndexes(i)}.rxnGeneMat=newRxnGeneMat; +0564 if ~isempty(nonReplaceableGenes) +0565 models{useOrderIndexes(i)}.genes=[allGenes{1}(unique(newGenes));strcat('OLD_',models{useOrderIndexes(i)}.id,'_',nonReplaceableGenes)]; +0566 else +0567 models{useOrderIndexes(i)}.genes=allGenes{1}(unique(newGenes)); +0568 end +0569 if isfield(models{useOrderIndexes(i)},'geneComps') +0570 geneComps=models{useOrderIndexes(i)}.geneComps(1); +0571 models{useOrderIndexes(i)}.geneComps=zeros(numel(models{useOrderIndexes(i)}.genes),1); +0572 %Assume that all genes are in the same compartment, and this +0573 %compartment is specified for the first gene +0574 models{useOrderIndexes(i)}.geneComps(:)=geneComps; +0575 end +0576 end +0577 +0578 %Now merge the models. All information should be correct except for "or" +0579 %complexes +0580 draftModel=mergeModels(models,'metNames'); +0581 +0582 %Remove OLD_ genes that appear only in OR branches (they were added for +0583 %AND-complex genes with no orthologs; in OR branches they are redundant). +0584 %Uses token-level removal to avoid partial-match hazards. +0585 for oldI=1:numel(draftModel.grRules) +0586 if ~contains(draftModel.grRules{oldI},'OLD_') +0587 continue +0588 end +0589 tkns=gprTokenize(draftModel.grRules{oldI}); +0590 again=true; +0591 while again +0592 again=false; +0593 for kk=1:numel(tkns) +0594 if startsWith(tkns{kk},'OLD_') +0595 if kk>1 && strcmp(tkns{kk-1},'or') +0596 tkns(kk-1:kk)=[]; +0597 again=true; break +0598 elseif kk<numel(tkns) && strcmp(tkns{kk+1},'or') +0599 tkns(kk:kk+1)=[]; +0600 again=true; break +0601 end +0602 end +0603 end +0604 end +0605 draftModel.grRules{oldI}=gprBuildRule(tkns); +0606 end +0607 +0608 %Apply the complex policy to the OLD_ genes left in "and" relations, and +0609 %write each grRule without repeated genes or redundant brackets +0610 toRemove=false(numel(draftModel.rxns),1); +0611 for j=1:numel(draftModel.grRules) +0612 rule=draftModel.grRules{j}; +0613 if isempty(rule) +0614 continue +0615 end +0616 if contains(rule,'OLD_') && strcmp(complexPolicy,'drop') +0617 toRemove(j)=true; +0618 elseif contains(rule,'OLD_') && strcmp(complexPolicy,'keep') +0619 draftModel.grRules{j}=gprSimplify(rule,'OLD_'); +0620 toRemove(j)=isempty(draftModel.grRules{j}); +0621 else +0622 draftModel.grRules{j}=gprSimplify(rule,''); +0623 end +0624 end +0625 if any(toRemove) +0626 draftModel=removeReactions(draftModel,draftModel.rxns(toRemove),true,true,true); +0627 end +0628 +0629 %Change name of the resulting model +0630 draftModel.id=getModelFor; +0631 name='Generated by getModelFromHomology using '; +0632 for i=1:numel(models) +0633 if i<numel(models) +0634 name=[name models{i}.id ', ']; +0635 else +0636 name=[name models{i}.id]; +0637 end +0638 end +0639 draftModel.name=name; +0640 %The draft is a new model: the templates' version, date and description are +0641 %not its own +0642 draftModel=rmfield(draftModel,intersect({'version','date','description'},fieldnames(draftModel))); +0643 if isfield(draftModel,'annotation') +0644 descriptive={'taxonomy','sourceUrl','note','givenName','familyName','authors','email','organization'}; +0645 draftModel.annotation=rmfield(draftModel.annotation,intersect(descriptive,fieldnames(draftModel.annotation))); +0646 end +0647 if ~preserveNotes || ~isfield(draftModel,'rxnNotes') +0648 draftModel.rxnNotes=cell(length(draftModel.rxns),1); +0649 draftModel.rxnNotes(:)={'Included by getModelFromHomology'}; +0650 end +0651 if ~preserveNotes || ~isfield(draftModel,'rxnConfidenceScores') +0652 draftModel.rxnConfidenceScores=NaN(length(draftModel.rxns),1); +0653 draftModel.rxnConfidenceScores(:)=2; +0654 end +0655 draftModel=deleteUnusedGenes(draftModel,0); +0656 %Standardize grRules and notify if problematic grRules are found +0657 [draftModel.grRules,draftModel.rxnGeneMat]=standardizeGrRules(draftModel,false); +0658 draftModel=deleteUnusedGenes(draftModel,false); +0659 end +0660 +0661 +0662 function tokens=gprTokenize(rule) +0663 % Split a GPR rule into tokens: gene IDs, "and", "or", "(", ")". +0664 % Brackets are separated from adjacent text by whitespace before splitting. +0665 if isempty(rule) +0666 tokens={}; +0667 return +0668 end +0669 r=regexprep(rule,'([\(\)])',' $1 '); +0670 r=regexprep(r,'\s+',' '); +0671 tokens=strsplit(strtrim(r),' '); +0672 tokens=tokens(~cellfun(@isempty,tokens)); +0673 end +0674 +0675 +0676 function rule=gprBuildRule(tokens) +0677 % Reconstruct a GPR rule string from a token list. +0678 if isempty(tokens) +0679 rule=''; +0680 return +0681 end +0682 rule=strjoin(tokens,' '); +0683 rule=strrep(rule,'( ','('); +0684 rule=strrep(rule,' )',')'); +0685 end +0686 +0687 +0688 function tokens=gprReplaceToken(tokens,oldGene,newGenes) +0689 % Replace each occurrence of token oldGene with the gene(s) in newGenes. +0690 % newGenes is a cell array; multiple genes are wrapped in parentheses and +0691 % joined with " or ". +0692 k=1; +0693 while k<=numel(tokens) +0694 if strcmp(tokens{k},oldGene) +0695 if numel(newGenes)==1 +0696 tokens{k}=newGenes{1}; +0697 k=k+1; +0698 else +0699 seq={'('}; +0700 for g=1:numel(newGenes) +0701 seq{end+1}=newGenes{g}; %#ok<AGROW> +0702 if g<numel(newGenes) +0703 seq{end+1}='or'; %#ok<AGROW> +0704 end +0705 end +0706 seq{end+1}=')'; +0707 tokens=[tokens(1:k-1),seq,tokens(k+1:end)]; +0708 k=k+numel(seq); +0709 end +0710 else +0711 k=k+1; +0712 end +0713 end +0714 end +0715 +0716 +0717 function rule=gprSimplify(rule,removePrefix) +0718 % Rewrite a grRule as a tree: genes starting with removePrefix (if any) are +0719 % dropped from their group, groups of the same operator are flattened, +0720 % repeated genes or groups are removed, and brackets appear only around a +0721 % group of the other operator. A rule with no gene left becomes ''. +0722 tokens=gprTokenize(rule); +0723 if isempty(tokens) +0724 rule=''; +0725 return +0726 end +0727 [node,~]=gprParseOr(tokens,1); +0728 node=gprPrune(node,removePrefix); +0729 if isempty(node) +0730 rule=''; +0731 else +0732 rule=gprRender(node,''); +0733 end +0734 end +0735 +0736 +0737 function [node,pos]=gprParseOr(tokens,pos) +0738 [node,pos]=gprParseAnd(tokens,pos); +0739 kids={node}; +0740 while pos<=numel(tokens) && strcmpi(tokens{pos},'or') +0741 [node,pos]=gprParseAnd(tokens,pos+1); +0742 kids{end+1}=node; %#ok<AGROW> +0743 end +0744 if numel(kids)>1 +0745 node=struct('op','or','gene','','kids',{kids}); +0746 end +0747 end +0748 +0749 +0750 function [node,pos]=gprParseAnd(tokens,pos) +0751 [node,pos]=gprParseAtom(tokens,pos); +0752 kids={node}; +0753 while pos<=numel(tokens) && strcmpi(tokens{pos},'and') +0754 [node,pos]=gprParseAtom(tokens,pos+1); +0755 kids{end+1}=node; %#ok<AGROW> +0756 end +0757 if numel(kids)>1 +0758 node=struct('op','and','gene','','kids',{kids}); +0759 end +0760 end +0761 +0762 +0763 function [node,pos]=gprParseAtom(tokens,pos) +0764 if pos>numel(tokens) +0765 error('Malformed grRule: it ends with an operator'); +0766 end +0767 if strcmp(tokens{pos},'(') +0768 [node,pos]=gprParseOr(tokens,pos+1); +0769 if pos>numel(tokens) || ~strcmp(tokens{pos},')') +0770 error('Malformed grRule: unbalanced brackets'); +0771 end +0772 pos=pos+1; +0773 else +0774 node=struct('op','gene','gene',tokens{pos},'kids',{{}}); +0775 pos=pos+1; +0776 end +0777 end +0778 +0779 +0780 function node=gprPrune(node,removePrefix) +0781 if strcmp(node.op,'gene') +0782 if ~isempty(removePrefix) && startsWith(node.gene,removePrefix) +0783 node=[]; +0784 end +0785 return +0786 end +0787 kids={}; +0788 seen={}; +0789 for k=1:numel(node.kids) +0790 kid=gprPrune(node.kids{k},removePrefix); +0791 if isempty(kid) +0792 continue +0793 end +0794 if strcmp(kid.op,node.op) +0795 parts=kid.kids; +0796 else +0797 parts={kid}; +0798 end +0799 for m=1:numel(parts) +0800 key=gprRender(parts{m},node.op); +0801 if ~ismember(key,seen) +0802 seen{end+1}=key; %#ok<AGROW> +0803 kids{end+1}=parts{m}; %#ok<AGROW> +0804 end +0805 end +0806 end +0807 if isempty(kids) +0808 node=[]; +0809 elseif numel(kids)==1 +0810 node=kids{1}; +0811 else +0812 node.kids=kids; +0813 end +0814 end +0815 +0816 +0817 function rule=gprRender(node,parentOp) +0818 if strcmp(node.op,'gene') +0819 rule=node.gene; +0820 return +0821 end +0822 parts=cellfun(@(k) gprRender(k,node.op),node.kids,'UniformOutput',false); +0823 rule=strjoin(parts,[' ' node.op ' ']); +0824 if ~isempty(parentOp) && ~strcmp(parentOp,node.op) +0825 rule=['(' rule ')']; +0826 end +0827 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/doc/testing/function_tests/tReconstruction.html b/doc/testing/function_tests/tReconstruction.html index bcf564f2..4b8681f6 100644 --- a/doc/testing/function_tests/tReconstruction.html +++ b/doc/testing/function_tests/tReconstruction.html @@ -41,7 +41,7 @@

    CROSS-REFERENCE INFORMATION ^
 
 <h2><a name=SUBFUNCTIONS ^

    +
  • function guessCompositionRuns(testCase)
  • function guessCompositionAccountsForStoichiometricCoefficient(testCase)
  • function makeFakeBlastStructureReturnsStruct(testCase)
  • function getModelFromHomologyBuildsDraft(testCase)
  • function getModelFromHomologyComplexPolicyAndOptions(testCase)
  • function getModelFromHomologyRejectsUnknownComplexPolicy(testCase)
  • function getBlastRunsWhenAvailable(testCase)
  • function getDiamondRunsWhenAvailable(testCase)
  • function getKEGGModelForOrganismNeedsData(testCase)
  • function getModelFromKEGGNeedsData(testCase)
  • function buildGlobalGPRJoinsGenesThroughSharedKO(testCase)
  • function getPhylDistNeedsData(testCase)
  • function model = toyModel()
  • SOURCE CODE ^

    0001 classdef tReconstruction < RavenTestCase
    @@ -86,84 +86,135 @@ 

    SOURCE CODE ^'struct'); 0041 end 0042 -0043 function getBlastRunsWhenAvailable(testCase) -0044 fa1 = fullfile(testCase.ravenRoot,'testing','function_tests','test_data','human_galactosidases.fa'); -0045 fa2 = fullfile(testCase.ravenRoot,'testing','function_tests','test_data','yeast_galactosidases.fa'); -0046 testCase.assumeDependency(exist(fa1,'file')==2, 'test FASTA files'); -0047 ok = true; -0048 try, evalc('bs = getBlast(''human'', fa1, {''yeast''}, {fa2});'); catch, ok = false; end -0049 testCase.assumeTrue(ok, 'BLAST+ binary not functional in this environment'); -0050 testCase.verifyClass(bs, 'struct'); -0051 end -0052 -0053 function getDiamondRunsWhenAvailable(testCase) -0054 fa1 = fullfile(testCase.ravenRoot,'testing','function_tests','test_data','human_galactosidases.fa'); -0055 fa2 = fullfile(testCase.ravenRoot,'testing','function_tests','test_data','yeast_galactosidases.fa'); -0056 testCase.assumeDependency(exist(fa1,'file')==2, 'test FASTA files'); -0057 ok = true; -0058 try, evalc('bs = getDiamond(''human'', fa1, {''yeast''}, {fa2});'); catch, ok = false; end -0059 testCase.assumeTrue(ok, 'DIAMOND binary not functional in this environment'); -0060 testCase.verifyClass(bs, 'struct'); -0061 end -0062 -0063 function getKEGGModelForOrganismNeedsData(testCase) -0064 testCase.assumeFail('Requires keggModel.mat and an HMM library from raven-data.'); -0065 end -0066 -0067 function getModelFromKEGGNeedsData(testCase) -0068 matFile = fullfile(testCase.ravenRoot, 'reconstruction', 'kegg', 'keggModel.mat'); -0069 testCase.assumeFalse(exist(matFile, 'file') == 2, ... -0070 'keggModel.mat is present; skipping the build-from-artefacts path.'); -0071 testCase.assumeFail('Downloads and assembles the full KEGG artefact set from raven-data; not run automatically.'); -0072 end -0073 -0074 function buildGlobalGPRJoinsGenesThroughSharedKO(testCase) -0075 % Offline unit test of the join at the core of buildGlobalKEGGModel: -0076 % two reactions sharing a KO, a KO used by two organisms' genes, and -0077 % a reaction with no KO at all (should end up with no genes). -0078 rxns = {'R1'; 'R2'; 'R3'}; -0079 koReaction = table({'K1'; 'K1'; 'K2'}, {'R1'; 'R2'; 'R2'}, ... -0080 'VariableNames', {'ko', 'reaction'}); -0081 organismGeneKO = table({'a'; 'a'; 'b'}, {'g1'; 'g2'; 'g1'}, {'K1'; 'K2'; 'K1'}, ... -0082 'VariableNames', {'organism', 'gene', 'ko'}); -0083 [genes, rxnGeneMat] = buildGlobalGPR(rxns, koReaction, organismGeneKO); -0084 testCase.verifyEqual(genes, {'a:g1'; 'a:g2'; 'b:g1'}); -0085 expected = [1 0 1; 1 1 1; 0 0 0]; -0086 testCase.verifyEqual(full(rxnGeneMat), expected); -0087 end -0088 -0089 function getPhylDistNeedsData(testCase) -0090 distFile = fullfile(testCase.ravenRoot, 'reconstruction', 'kegg', 'keggPhylDist.mat'); -0091 testCase.assumeFalse(exist(distFile, 'file') == 2, ... -0092 'keggPhylDist.mat is present; skipping error-path test.'); -0093 testCase.verifyError(@() getPhylDist(), 'getPhylDist:noData'); -0094 end -0095 -0096 end -0097 -0098 methods (Static) -0099 function model = toyModel() -0100 % Single reaction: A -> 2 B, one compartment. -0101 model = struct(); -0102 model.id = 'toy'; -0103 model.name = 'toy'; -0104 model.mets = {'m1'; 'm2'}; -0105 model.metNames = {'A'; 'B'}; -0106 model.comps = {'c'}; -0107 model.compNames = {'cytosol'}; -0108 model.metComps = [1; 1]; -0109 model.metFormulas = {''; ''}; -0110 model.rxns = {'R1'}; -0111 model.rxnNames = {'R1'}; -0112 model.S = sparse([-1; 2]); -0113 model.lb = 0; -0114 model.ub = 1000; -0115 model.rev = 0; -0116 model.c = 0; -0117 model.b = zeros(2, 1); -0118 end -0119 end -0120 end

    +0043 function getModelFromHomologyComplexPolicyAndOptions(testCase) +0044 % A small template: a complex, an isozyme pair, a gene-free +0045 % reaction, and two template genes that map to one new gene. +0046 t = struct(); +0047 t.id = 'tmpl'; t.name = 'tmpl'; +0048 t.mets = {'a_c';'b_c';'d_c'}; t.metNames = {'A';'B';'D'}; +0049 t.comps = {'c'}; t.compNames = {'cytosol'}; t.metComps = [1;1;1]; +0050 t.rxns = {'Rcplx';'Riso';'Rfree';'Rdup'}; +0051 t.rxnNames = t.rxns; +0052 t.S = sparse([-1 0 0 -1; 0 -1 0 1; 1 1 -1 0]); +0053 t.lb = [0;0;0;0]; t.ub = [1000;1000;1000;1000]; t.c = [0;0;0;0]; t.b = [0;0;0]; +0054 t.rev = [0;0;0;0]; +0055 t.grRules = {'tg1 and tg2'; 'tg3 or tg4'; ''; 'tg3 or tg4'}; +0056 t.genes = {'tg1';'tg2';'tg3';'tg4'}; +0057 t.rxnGeneMat = sparse([1 1 0 0; 0 0 1 1; 0 0 0 0; 0 0 1 1]); +0058 t.rxnNotes = {'n1';'n2';'n3';'n4'}; +0059 t.rxnConfidenceScores = [4;4;4;4]; +0060 t.version = '9.9.9'; +0061 % makeFakeBlastStructure wants at least 10 pairs; the extra ones +0062 % are genes the template does not have +0063 pairs = [{'tg1','ng1'; 'tg3','ng3'; 'tg4','ng3'}; ... +0064 [strcat('x', string(1:7))', strcat('nx', string(1:7))']]; +0065 pairs = cellstr(pairs); +0066 bs = makeFakeBlastStructure(pairs, 'tmpl', 'new'); +0067 evalc('flag = getModelFromHomology({t}, bs, ''new'', ''minLen'', 0, ''minIde'', 0);'); +0068 evalc('keep = getModelFromHomology({t}, bs, ''new'', ''minLen'', 0, ''minIde'', 0, ''complexPolicy'', ''keep'', ''keepGeneFree'', true, ''preserveNotes'', true);'); +0069 evalc('drop = getModelFromHomology({t}, bs, ''new'', ''minLen'', 0, ''minIde'', 0, ''complexPolicy'', ''drop'');'); +0070 % flag: unmapped subunit kept as OLD_; gene-free reaction dropped +0071 testCase.verifyTrue(contains(flag.grRules{strcmp(flag.rxns,'Rcplx')}, 'OLD_tmpl_tg2')); +0072 testCase.verifyFalse(ismember('Rfree', flag.rxns)); +0073 % two template genes mapping to one new gene appear once +0074 testCase.verifyEqual(flag.grRules{strcmp(flag.rxns,'Riso')}, 'ng3'); +0075 % keep: subunit dropped, gene-free kept, template notes kept +0076 testCase.verifyEqual(keep.grRules{strcmp(keep.rxns,'Rcplx')}, 'ng1'); +0077 testCase.verifyTrue(ismember('Rfree', keep.rxns)); +0078 testCase.verifyEqual(keep.rxnNotes{strcmp(keep.rxns,'Rcplx')}, 'n1'); +0079 testCase.verifyEqual(keep.rxnConfidenceScores(strcmp(keep.rxns,'Rcplx')), 4); +0080 % drop: the incomplete complex is removed +0081 testCase.verifyFalse(ismember('Rcplx', drop.rxns)); +0082 testCase.verifyTrue(ismember('Riso', drop.rxns)); +0083 % the template version is not inherited +0084 testCase.verifyFalse(isfield(flag, 'version') && strcmp(flag.version, '9.9.9')); +0085 testCase.verifyEqual(flag.rxnNotes{1}, 'Included by getModelFromHomology'); +0086 end +0087 +0088 function getModelFromHomologyRejectsUnknownComplexPolicy(testCase) +0089 src = testCase.model; src.id = 'srcModel'; +0090 bs = makeFakeBlastStructure([src.genes(1:10), strcat('t_', src.genes(1:10))], 'srcModel', 'tgtOrg'); +0091 testCase.verifyError(@() getModelFromHomology({src}, bs, 'tgtOrg', 'complexPolicy', 'maybe'), ?MException); +0092 end +0093 +0094 function getBlastRunsWhenAvailable(testCase) +0095 fa1 = fullfile(testCase.ravenRoot,'testing','function_tests','test_data','human_galactosidases.fa'); +0096 fa2 = fullfile(testCase.ravenRoot,'testing','function_tests','test_data','yeast_galactosidases.fa'); +0097 testCase.assumeDependency(exist(fa1,'file')==2, 'test FASTA files'); +0098 ok = true; +0099 try, evalc('bs = getBlast(''human'', fa1, {''yeast''}, {fa2});'); catch, ok = false; end +0100 testCase.assumeTrue(ok, 'BLAST+ binary not functional in this environment'); +0101 testCase.verifyClass(bs, 'struct'); +0102 end +0103 +0104 function getDiamondRunsWhenAvailable(testCase) +0105 fa1 = fullfile(testCase.ravenRoot,'testing','function_tests','test_data','human_galactosidases.fa'); +0106 fa2 = fullfile(testCase.ravenRoot,'testing','function_tests','test_data','yeast_galactosidases.fa'); +0107 testCase.assumeDependency(exist(fa1,'file')==2, 'test FASTA files'); +0108 ok = true; +0109 try, evalc('bs = getDiamond(''human'', fa1, {''yeast''}, {fa2});'); catch, ok = false; end +0110 testCase.assumeTrue(ok, 'DIAMOND binary not functional in this environment'); +0111 testCase.verifyClass(bs, 'struct'); +0112 end +0113 +0114 function getKEGGModelForOrganismNeedsData(testCase) +0115 testCase.assumeFail('Requires keggModel.mat and an HMM library from raven-data.'); +0116 end +0117 +0118 function getModelFromKEGGNeedsData(testCase) +0119 matFile = fullfile(testCase.ravenRoot, 'reconstruction', 'kegg', 'keggModel.mat'); +0120 testCase.assumeFalse(exist(matFile, 'file') == 2, ... +0121 'keggModel.mat is present; skipping the build-from-artefacts path.'); +0122 testCase.assumeFail('Downloads and assembles the full KEGG artefact set from raven-data; not run automatically.'); +0123 end +0124 +0125 function buildGlobalGPRJoinsGenesThroughSharedKO(testCase) +0126 % Offline unit test of the join at the core of buildGlobalKEGGModel: +0127 % two reactions sharing a KO, a KO used by two organisms' genes, and +0128 % a reaction with no KO at all (should end up with no genes). +0129 rxns = {'R1'; 'R2'; 'R3'}; +0130 koReaction = table({'K1'; 'K1'; 'K2'}, {'R1'; 'R2'; 'R2'}, ... +0131 'VariableNames', {'ko', 'reaction'}); +0132 organismGeneKO = table({'a'; 'a'; 'b'}, {'g1'; 'g2'; 'g1'}, {'K1'; 'K2'; 'K1'}, ... +0133 'VariableNames', {'organism', 'gene', 'ko'}); +0134 [genes, rxnGeneMat] = buildGlobalGPR(rxns, koReaction, organismGeneKO); +0135 testCase.verifyEqual(genes, {'a:g1'; 'a:g2'; 'b:g1'}); +0136 expected = [1 0 1; 1 1 1; 0 0 0]; +0137 testCase.verifyEqual(full(rxnGeneMat), expected); +0138 end +0139 +0140 function getPhylDistNeedsData(testCase) +0141 distFile = fullfile(testCase.ravenRoot, 'reconstruction', 'kegg', 'keggPhylDist.mat'); +0142 testCase.assumeFalse(exist(distFile, 'file') == 2, ... +0143 'keggPhylDist.mat is present; skipping error-path test.'); +0144 testCase.verifyError(@() getPhylDist(), 'getPhylDist:noData'); +0145 end +0146 +0147 end +0148 +0149 methods (Static) +0150 function model = toyModel() +0151 % Single reaction: A -> 2 B, one compartment. +0152 model = struct(); +0153 model.id = 'toy'; +0154 model.name = 'toy'; +0155 model.mets = {'m1'; 'm2'}; +0156 model.metNames = {'A'; 'B'}; +0157 model.comps = {'c'}; +0158 model.compNames = {'cytosol'}; +0159 model.metComps = [1; 1]; +0160 model.metFormulas = {''; ''}; +0161 model.rxns = {'R1'}; +0162 model.rxnNames = {'R1'}; +0163 model.S = sparse([-1; 2]); +0164 model.lb = 0; +0165 model.ub = 1000; +0166 model.rev = 0; +0167 model.c = 0; +0168 model.b = zeros(2, 1); +0169 end +0170 end +0171 end
    Generated by m2html © 2005
    \ No newline at end of file From d7db8b179ae30646afbfe8ea13a7bb2d5dc58c71 Mon Sep 17 00:00:00 2001 From: Eduard Kerkhoven Date: Wed, 7 Oct 2026 21:35:56 +0200 Subject: [PATCH 09/48] fix: prefix whole gene ids in grRules in addIdentifierPrefix (SysBioChalmers/RAVEN#758) * fix: prefix whole gene ids in grRules in addIdentifierPrefix Gene ids with a dash, dot or colon (5-HT1A) got a prefix at every word start in grRules but one prefix in model.genes, so exportModel failed on models with such genes. * Update documentation Original-commit: https://github.com/SysBioChalmers/RAVEN/commit/63ca4cbe5c2b3d244d4657e8dac15ff172396419 --- conversion/addIdentifierPrefix.m | 22 ++++++++++--- doc/conversion/addIdentifierPrefix.html | 42 +++++++++++++++++-------- testing/function_tests/tConversion.m | 11 +++++++ 3 files changed, 58 insertions(+), 17 deletions(-) diff --git a/conversion/addIdentifierPrefix.m b/conversion/addIdentifierPrefix.m index 3f0629da..cf6b205f 100644 --- a/conversion/addIdentifierPrefix.m +++ b/conversion/addIdentifierPrefix.m @@ -60,10 +60,8 @@ currField = strcat(currPrefix, currField); hasChanged(toChangeIdx(i)) = true; - if strcmp(currName,'genes') - model.grRules = regexprep(model.grRules, '(\<[0-9_a-zA-Z])', 'G_$1'); - model.grRules = regexprep(model.grRules, ' G_or ', ' or '); - model.grRules = regexprep(model.grRules, ' G_and ', ' and '); + if strcmp(currName,'genes') && isfield(model,'grRules') + model.grRules = cellfun(@(r) prefixGenes(r,currPrefix), model.grRules, 'UniformOutput', false); end model.(currName) = currField; end @@ -72,3 +70,19 @@ hasChanged = modelFields(hasChanged,:); hasChanged = append('model.', hasChanged(:,1), ' (', hasChanged(:,2), ' prefix)'); end + + +function rule = prefixGenes(rule, prefix) +% Prefix every gene in a grRule, leaving "and", "or" and brackets alone. A +% gene is everything between spaces and brackets, so ids that contain +% "-", "." or ":" get one prefix, not one per word. +[tokens, separators] = regexp(rule, '[^\s()]+', 'match', 'split'); +out = separators{1}; +for i = 1:numel(tokens) + if ~any(strcmpi(tokens{i}, {'and','or'})) + tokens{i} = [prefix tokens{i}]; + end + out = [out tokens{i} separators{i+1}]; %#ok +end +rule = out; +end diff --git a/doc/conversion/addIdentifierPrefix.html b/doc/conversion/addIdentifierPrefix.html index 24266779..4c7b682d 100644 --- a/doc/conversion/addIdentifierPrefix.html +++ b/doc/conversion/addIdentifierPrefix.html @@ -72,7 +72,9 @@

    CROSS-REFERENCE INFORMATION ^
 </ul>
 <!-- crossreference -->
 
-
+<h2><a name=SUBFUNCTIONS ^

    +

    SOURCE CODE ^

    +0063 if strcmp(currName,'genes') && isfield(model,'grRules') +0064 model.grRules = cellfun(@(r) prefixGenes(r,currPrefix), model.grRules, 'UniformOutput', false); +0065 end +0066 model.(currName) = currField; +0067 end +0068 end +0069 +0070 hasChanged = modelFields(hasChanged,:); +0071 hasChanged = append('model.', hasChanged(:,1), ' (', hasChanged(:,2), ' prefix)'); +0072 end +0073 +0074 +0075 function rule = prefixGenes(rule, prefix) +0076 % Prefix every gene in a grRule, leaving "and", "or" and brackets alone. A +0077 % gene is everything between spaces and brackets, so ids that contain +0078 % "-", "." or ":" get one prefix, not one per word. +0079 [tokens, separators] = regexp(rule, '[^\s()]+', 'match', 'split'); +0080 out = separators{1}; +0081 for i = 1:numel(tokens) +0082 if ~any(strcmpi(tokens{i}, {'and','or'})) +0083 tokens{i} = [prefix tokens{i}]; +0084 end +0085 out = [out tokens{i} separators{i+1}]; %#ok<AGROW> +0086 end +0087 rule = out; +0088 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/testing/function_tests/tConversion.m b/testing/function_tests/tConversion.m index 53ed7d5a..1c2d4195 100644 --- a/testing/function_tests/tConversion.m +++ b/testing/function_tests/tConversion.m @@ -12,6 +12,17 @@ function identifierPrefixRoundTrip(testCase) testCase.verifyEqual(mr.genes, testCase.model.genes); end + function identifierPrefixKeepsGenesWithPunctuationWhole(testCase) + % A gene id with "-", "." or ":" gets one prefix in grRules, the + % same one it gets in model.genes, so the two still match. + m = testCase.model; + m.genes = [m.genes; {'5-HT1A'; 'b2:1'}]; + m.grRules{1} = '5-HT1A and (b2:1 or 5-HT1A)'; + [mp, ~] = addIdentifierPrefix(m, 'fields', {'genes'}); + testCase.verifyEqual(mp.grRules{1}, 'G_5-HT1A and (G_b2:1 or G_5-HT1A)'); + testCase.verifyTrue(all(ismember(getGenesFromGrRules(mp.grRules(1)), mp.genes))); + end + function removeIdentifierPrefixSkipsMissingFields(testCase) % A model missing an optional field (genes, metNames, rxnNames, % id) must not make the default field list throw; it should From 1366b4410f4f8d790a12ca9cc8896d67fe126ef4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 7 Oct 2026 19:39:03 +0000 Subject: [PATCH 10/48] doc: regenerate HTML documentation for d7db8b1 [skip ci] Original-commit: https://github.com/SysBioChalmers/RAVEN/commit/00bbd234d53defeca469e2856aae6b91181cd56e --- doc/testing/function_tests/tConversion.html | 135 +++++++++++--------- 1 file changed, 73 insertions(+), 62 deletions(-) diff --git a/doc/testing/function_tests/tConversion.html b/doc/testing/function_tests/tConversion.html index 0cad470b..154e7667 100644 --- a/doc/testing/function_tests/tConversion.html +++ b/doc/testing/function_tests/tConversion.html @@ -41,7 +41,7 @@

    CROSS-REFERENCE INFORMATION ^
 
 <h2><a name=SUBFUNCTIONS ^

    +
  • function identifierPrefixRoundTrip(testCase)
  • function identifierPrefixKeepsGenesWithPunctuationWhole(testCase)
  • function removeIdentifierPrefixSkipsMissingFields(testCase)
  • function ravenCobraWrapperMarksCobra(testCase)
  • function ravenCobraWrapperRoundTrip(testCase)
  • function ravenCobraWrapperGeneFieldsUseGeneMiriams(testCase)
  • function ravenCobraWrapperEscapesRegexGeneNames(testCase)
  • function standardizeFieldOrderPreservesFields(testCase)
  • function standardizeFieldOrderIsIdempotent(testCase)
  • SOURCE CODE ^

    0001 classdef tConversion < RavenTestCase
    @@ -58,69 +58,80 @@ 

    SOURCE CODE ^end 0014 -0015 function removeIdentifierPrefixSkipsMissingFields(testCase) -0016 % A model missing an optional field (genes, metNames, rxnNames, -0017 % id) must not make the default field list throw; it should -0018 % just be skipped. -0019 m = rmfield(testCase.model, {'genes','metNames','rxnNames','id'}); -0020 m.rxns = strcat('R_', m.rxns); -0021 m2 = removeIdentifierPrefix(m); -0022 testCase.verifyFalse(any(startsWith(m2.rxns, 'R_'))); -0023 end -0024 -0025 function ravenCobraWrapperMarksCobra(testCase) -0026 cobra = ravenCobraWrapper(testCase.model); -0027 testCase.verifyTrue(isfield(cobra, 'rules')); % COBRA-only field -0028 end -0029 -0030 function ravenCobraWrapperRoundTrip(testCase) -0031 cobra = ravenCobraWrapper(testCase.model); -0032 back = ravenCobraWrapper(cobra); -0033 testCase.verifyEqual(back.rxns, testCase.model.rxns); -0034 testCase.verifyNumElements(back.mets, numel(testCase.model.mets)); -0035 testCase.verifyEqual(size(back.S), size(testCase.model.S)); -0036 end -0037 -0038 function ravenCobraWrapperGeneFieldsUseGeneMiriams(testCase) -0039 % Gene annotation fields must come from geneMiriams, not from -0040 % whatever extractMiriam last returned for metMiriams. -0041 m = testCase.model; -0042 m.metMiriams = cell(numel(m.mets),1); -0043 m.metMiriams{1}.name = {'hmdb'}; -0044 m.metMiriams{1}.value = {'HMDB00001'}; -0045 m.geneMiriams = cell(numel(m.genes),1); -0046 m.geneMiriams{1}.name = {'ncbigene'}; -0047 m.geneMiriams{1}.value = {'12345'}; -0048 evalc('newModel = ravenCobraWrapper(m);'); -0049 testCase.verifyEqual(numel(newModel.geneEntrezID), numel(newModel.genes)); -0050 testCase.verifyEqual(newModel.geneEntrezID{1}, '12345'); -0051 end -0052 -0053 function ravenCobraWrapperEscapesRegexGeneNames(testCase) -0054 % A gene id containing a regex metacharacter ('.') must be -0055 % matched literally in grRules, not as a wildcard that could -0056 % also match an unrelated gene id (e.g. 'G.1' vs 'GX1'). -0057 m = testCase.model; -0058 m.genes{1} = 'G.1'; -0059 m.genes{2} = 'GX1'; -0060 m.grRules{3} = 'GX1'; -0061 evalc('cobra = ravenCobraWrapper(m);'); -0062 testCase.verifyEqual(cobra.rules{3}, 'x(2)'); -0063 end -0064 -0065 function standardizeFieldOrderPreservesFields(testCase) -0066 m2 = standardizeModelFieldOrder(testCase.model); -0067 testCase.verifyEqual(sort(fieldnames(m2)), sort(fieldnames(testCase.model))); -0068 end -0069 -0070 function standardizeFieldOrderIsIdempotent(testCase) -0071 m2 = standardizeModelFieldOrder(testCase.model); -0072 m3 = standardizeModelFieldOrder(m2); -0073 testCase.verifyEqual(fieldnames(m3), fieldnames(m2)); +0015 function identifierPrefixKeepsGenesWithPunctuationWhole(testCase) +0016 % A gene id with "-", "." or ":" gets one prefix in grRules, the +0017 % same one it gets in model.genes, so the two still match. +0018 m = testCase.model; +0019 m.genes = [m.genes; {'5-HT1A'; 'b2:1'}]; +0020 m.grRules{1} = '5-HT1A and (b2:1 or 5-HT1A)'; +0021 [mp, ~] = addIdentifierPrefix(m, 'fields', {'genes'}); +0022 testCase.verifyEqual(mp.grRules{1}, 'G_5-HT1A and (G_b2:1 or G_5-HT1A)'); +0023 testCase.verifyTrue(all(ismember(getGenesFromGrRules(mp.grRules(1)), mp.genes))); +0024 end +0025 +0026 function removeIdentifierPrefixSkipsMissingFields(testCase) +0027 % A model missing an optional field (genes, metNames, rxnNames, +0028 % id) must not make the default field list throw; it should +0029 % just be skipped. +0030 m = rmfield(testCase.model, {'genes','metNames','rxnNames','id'}); +0031 m.rxns = strcat('R_', m.rxns); +0032 m2 = removeIdentifierPrefix(m); +0033 testCase.verifyFalse(any(startsWith(m2.rxns, 'R_'))); +0034 end +0035 +0036 function ravenCobraWrapperMarksCobra(testCase) +0037 cobra = ravenCobraWrapper(testCase.model); +0038 testCase.verifyTrue(isfield(cobra, 'rules')); % COBRA-only field +0039 end +0040 +0041 function ravenCobraWrapperRoundTrip(testCase) +0042 cobra = ravenCobraWrapper(testCase.model); +0043 back = ravenCobraWrapper(cobra); +0044 testCase.verifyEqual(back.rxns, testCase.model.rxns); +0045 testCase.verifyNumElements(back.mets, numel(testCase.model.mets)); +0046 testCase.verifyEqual(size(back.S), size(testCase.model.S)); +0047 end +0048 +0049 function ravenCobraWrapperGeneFieldsUseGeneMiriams(testCase) +0050 % Gene annotation fields must come from geneMiriams, not from +0051 % whatever extractMiriam last returned for metMiriams. +0052 m = testCase.model; +0053 m.metMiriams = cell(numel(m.mets),1); +0054 m.metMiriams{1}.name = {'hmdb'}; +0055 m.metMiriams{1}.value = {'HMDB00001'}; +0056 m.geneMiriams = cell(numel(m.genes),1); +0057 m.geneMiriams{1}.name = {'ncbigene'}; +0058 m.geneMiriams{1}.value = {'12345'}; +0059 evalc('newModel = ravenCobraWrapper(m);'); +0060 testCase.verifyEqual(numel(newModel.geneEntrezID), numel(newModel.genes)); +0061 testCase.verifyEqual(newModel.geneEntrezID{1}, '12345'); +0062 end +0063 +0064 function ravenCobraWrapperEscapesRegexGeneNames(testCase) +0065 % A gene id containing a regex metacharacter ('.') must be +0066 % matched literally in grRules, not as a wildcard that could +0067 % also match an unrelated gene id (e.g. 'G.1' vs 'GX1'). +0068 m = testCase.model; +0069 m.genes{1} = 'G.1'; +0070 m.genes{2} = 'GX1'; +0071 m.grRules{3} = 'GX1'; +0072 evalc('cobra = ravenCobraWrapper(m);'); +0073 testCase.verifyEqual(cobra.rules{3}, 'x(2)'); 0074 end 0075 -0076 end -0077 end

    +0076 function standardizeFieldOrderPreservesFields(testCase) +0077 m2 = standardizeModelFieldOrder(testCase.model); +0078 testCase.verifyEqual(sort(fieldnames(m2)), sort(fieldnames(testCase.model))); +0079 end +0080 +0081 function standardizeFieldOrderIsIdempotent(testCase) +0082 m2 = standardizeModelFieldOrder(testCase.model); +0083 m3 = standardizeModelFieldOrder(m2); +0084 testCase.verifyEqual(fieldnames(m3), fieldnames(m2)); +0085 end +0086 +0087 end +0088 end
    Generated by m2html © 2005
    \ No newline at end of file From 3c349cf170f48b0d02e8b2e83bd09c7f93cfe941 Mon Sep 17 00:00:00 2001 From: ae-tafur Date: Sat, 10 Oct 2026 09:45:06 -0500 Subject: [PATCH 11/48] refactor: reorganize repository into src/, tests/, docs/ and tutorials/ Original-commit: https://github.com/SysBioChalmers/RAVEN/commit/50b47d18516362f4e1fdc68a56ae9dc9f48296ff --- .github/CONTRIBUTING.md | 4 +-- .github/scripts/assemble_offline_software.py | 6 ++-- .github/workflows/documentation.yml | 14 ++++---- .github/workflows/release-bundle.yml | 4 +-- .github/workflows/tests.yml | 2 +- .gitignore | 16 ++++----- README.md | 4 +-- _config.yml | 4 +-- checkRaven.m | 8 ++--- {doc => docs/html}/INIT/INITStepDesc.html | 0 {doc => docs/html}/INIT/ftINIT.html | 0 .../html}/INIT/ftINITInternalAlg.html | 0 {doc => docs/html}/INIT/getINITSteps.html | 0 {doc => docs/html}/INIT/groupRxnScores.html | 0 {doc => docs/html}/INIT/index.html | 0 {doc => docs/html}/INIT/mergeLinear.html | 0 {doc => docs/html}/INIT/parseHPA.html | 0 {doc => docs/html}/INIT/parseHPArna.html | 0 {doc => docs/html}/INIT/prepINITModel.html | 0 .../html}/INIT/rescaleModelForINIT.html | 0 {doc => docs/html}/INIT/scoreModel.html | 0 .../html}/INIT/tINIT/getINITModel.html | 0 {doc => docs/html}/INIT/tINIT/index.html | 0 {doc => docs/html}/INIT/tINIT/runINIT.html | 0 {doc => docs/html}/alpha.png | Bin {doc => docs/html}/analysis/FSEOF.html | 0 .../html}/analysis/analyzeSampling.html | 0 .../html}/analysis/compareFluxes.html | 0 .../html}/analysis/findGeneDeletions.html | 0 .../html}/analysis/getAllSubGraphs.html | 0 .../html}/analysis/getAllowedBounds.html | 0 .../html}/analysis/getEssentialRxns.html | 0 {doc => docs/html}/analysis/getFluxZ.html | 0 .../html}/analysis/getMinimalMedium.html | 0 {doc => docs/html}/analysis/haveFlux.html | 0 {doc => docs/html}/analysis/index.html | 0 .../html}/analysis/randomSampling.html | 0 .../html}/analysis/reporterMetabolites.html | 0 .../html}/analysis/runDynamicFBA.html | 0 .../analysis/runPhenotypePhasePlane.html | 0 .../html}/analysis/runProductionEnvelope.html | 0 .../html}/analysis/runRobustnessAnalysis.html | 0 .../html}/analysis/runSimpleOptKnock.html | 0 {doc => docs/html}/analysis/sampleACHR.html | 0 {doc => docs/html}/analysis/sampleCHRR.html | 0 .../html}/analysis/sampleChebyshevCenter.html | 0 .../html}/analysis/sampleMaxVolEllipse.html | 0 .../html}/analysis/sampleWarmupPoints.html | 0 .../html}/analysis/traceFluxPath.html | 0 {doc => docs/html}/analysis/walkFluxes.html | 0 .../html}/annotation/assignSBOterms.html | 0 {doc => docs/html}/annotation/editMiriam.html | 0 .../html}/annotation/extractMiriam.html | 0 {doc => docs/html}/annotation/index.html | 0 .../html}/annotation/loadDeltaGCSV.html | 0 .../html}/annotation/saveDeltaGCSV.html | 0 {doc => docs/html}/biomass/fitParameters.html | 0 .../html}/biomass/getBiomassFractions.html | 0 {doc => docs/html}/biomass/index.html | 0 .../html}/biomass/scaleBiomassFraction.html | 0 .../biomass/scaleBiomassPseudoreaction.html | 0 {doc => docs/html}/biomass/setGAM.html | 0 {doc => docs/html}/c++.png | Bin {doc => docs/html}/c.png | Bin {doc => docs/html}/checkInstallation.html | 0 {doc => docs/html}/checkRaven.html | 0 .../comparison/compareMultipleModels.html | 0 {doc => docs/html}/comparison/diffModels.html | 0 {doc => docs/html}/comparison/index.html | 0 .../html}/conditions/applyCondition.html | 0 {doc => docs/html}/conditions/index.html | 0 .../html}/conversion/addIdentifierPrefix.html | 0 {doc => docs/html}/conversion/index.html | 0 .../html}/conversion/ravenCobraWrapper.html | 0 .../conversion/removeIdentifierPrefix.html | 0 .../standardizeModelFieldOrder.html | 0 .../html}/curation/curateModelFromTables.html | 0 .../html}/curation/downloadGenomeData.html | 0 {doc => docs/html}/curation/getGeneData.html | 0 {doc => docs/html}/curation/index.html | 0 .../curation/processProteinFastaFile.html | 0 .../html}/curation/renameModelGenes.html | 0 {doc => docs/html}/demoicon.gif | Bin {doc => docs/html}/down.png | Bin {doc => docs/html}/doxysearch.php | 0 {doc => docs/html}/fortran.png | Bin .../html}/gapfilling/canExchange.html | 0 .../html}/gapfilling/checkProduction.html | 0 {doc => docs/html}/gapfilling/fillGaps.html | 0 .../html}/gapfilling/findLeakMetabolite.html | 0 {doc => docs/html}/gapfilling/fitTasks.html | 0 .../html}/gapfilling/ftINITFillGaps.html | 0 .../html}/gapfilling/gapFillFastCore.html | 0 .../html}/gapfilling/gapFillFastLP.html | 0 .../html}/gapfilling/gapFillMILP.html | 0 .../html}/gapfilling/gapFillSwiftCore.html | 0 .../html}/gapfilling/gapFillTopological.html | 0 {doc => docs/html}/gapfilling/gapReport.html | 0 {doc => docs/html}/gapfilling/index.html | 0 {doc => docs/html}/hp.png | Bin {doc => docs/html}/index.html | 0 .../installation/addRavenToUserPath.html | 0 .../installation/checkFunctionUniqueness.html | 0 .../installation/downloadRavenBinaries.html | 0 .../installation/fetchRavenDataAsset.html | 0 .../html}/installation/findRAVENroot.html | 0 {doc => docs/html}/installation/index.html | 0 .../installation/removeRavenFromPath.html | 0 .../installation/updateDocumentation.html | 0 {doc => docs/html}/io/checkFileExistence.html | 0 {doc => docs/html}/io/cleanSheet.html | 0 {doc => docs/html}/io/exportForGit.html | 0 {doc => docs/html}/io/exportModel.html | 0 .../html}/io/exportToExcelFormat.html | 0 {doc => docs/html}/io/importModel.html | 0 {doc => docs/html}/io/index.html | 0 {doc => docs/html}/io/parseYAML.html | 0 {doc => docs/html}/io/readFasta.html | 0 {doc => docs/html}/io/readYAMLmodel.html | 0 {doc => docs/html}/io/writeExcel.html | 0 {doc => docs/html}/io/writeFasta.html | 0 {doc => docs/html}/io/writeYAMLmodel.html | 0 {doc => docs/html}/left.png | Bin {doc => docs/html}/linux.png | Bin .../localization/assignCompartments.html | 0 .../localization/defaultCompartmentMap.html | 0 .../html}/localization/getUniProtScores.html | 0 {doc => docs/html}/localization/index.html | 0 .../html}/localization/parseScores.html | 0 .../localization/predictLocalization.html | 0 {doc => docs/html}/m2html.css | 0 .../html}/manipulation/addExchangeRxns.html | 0 .../html}/manipulation/addGenesRaven.html | 0 {doc => docs/html}/manipulation/addMets.html | 0 {doc => docs/html}/manipulation/addRxns.html | 0 .../html}/manipulation/addRxnsGenesMets.html | 0 .../html}/manipulation/addTransport.html | 0 .../html}/manipulation/changeGrRules.html | 0 .../html}/manipulation/changeRxns.html | 0 .../html}/manipulation/closeModel.html | 0 .../html}/manipulation/contractModel.html | 0 .../html}/manipulation/convertToIrrev.html | 0 .../html}/manipulation/copyToComps.html | 0 .../html}/manipulation/deleteUnusedGenes.html | 0 .../html}/manipulation/expandModel.html | 0 .../html}/manipulation/findDuplicateRxns.html | 0 .../manipulation/findPotentialErrors.html | 0 .../html}/manipulation/generateNewIds.html | 0 {doc => docs/html}/manipulation/index.html | 0 .../html}/manipulation/mergeCompartments.html | 0 .../html}/manipulation/mergeModels.html | 0 .../html}/manipulation/permuteModel.html | 0 .../html}/manipulation/removeBadRxns.html | 0 .../html}/manipulation/removeGenes.html | 0 .../manipulation/removeLowScoreGenes.html | 0 .../html}/manipulation/removeMets.html | 0 .../html}/manipulation/removeReactions.html | 0 .../html}/manipulation/replaceMets.html | 0 .../html}/manipulation/reverseRxns.html | 0 .../html}/manipulation/setExchangeBounds.html | 0 {doc => docs/html}/manipulation/setParam.html | 0 .../html}/manipulation/simplifyModel.html | 0 .../html}/manipulation/sortIdentifiers.html | 0 .../html}/manipulation/sortModel.html | 0 .../manipulation/standardizeGrRules.html | 0 {doc => docs/html}/matlabicon.gif | Bin {doc => docs/html}/mex.png | Bin {doc => docs/html}/pcode.png | Bin {doc => docs/html}/queries/buildEquation.html | 0 .../html}/queries/checkModelStruct.html | 0 .../html}/queries/constructEquations.html | 0 {doc => docs/html}/queries/constructS.html | 0 .../html}/queries/getAllRxnsFromGenes.html | 0 .../html}/queries/getElementalBalance.html | 0 .../html}/queries/getExchangeRxns.html | 0 .../html}/queries/getGenesFromGrRules.html | 0 {doc => docs/html}/queries/getIndexes.html | 0 {doc => docs/html}/queries/getRxnsInComp.html | 0 .../html}/queries/getTransportRxns.html | 0 {doc => docs/html}/queries/index.html | 0 {doc => docs/html}/queries/modelSummary.html | 0 {doc => docs/html}/queries/parseFormulas.html | 0 {doc => docs/html}/queries/parseRxnEqu.html | 0 {doc => docs/html}/queries/printFluxes.html | 0 .../html}/queries/printModelStats.html | 0 .../reconstruction/guessComposition.html | 0 .../reconstruction/homology/getBlast.html | 0 .../reconstruction/homology/getDiamond.html | 0 .../homology/getModelFromHomology.html | 0 .../html}/reconstruction/homology/index.html | 0 .../homology/makeFakeBlastStructure.html | 0 {doc => docs/html}/reconstruction/index.html | 0 .../reconstruction/kegg/buildGlobalGPR.html | 0 .../kegg/buildGlobalKEGGModel.html | 0 .../kegg/getKEGGModelForOrganism.html | 0 .../reconstruction/kegg/getModelFromKEGG.html | 0 .../reconstruction/kegg/getPhylDist.html | 0 .../html}/reconstruction/kegg/index.html | 0 .../reconstruction/kegg/keggDataVersion.html | 0 .../reconstruction/kegg/readKEGGTable.html | 0 {doc => docs/html}/right.png | Bin {doc => docs/html}/sgi.png | Bin {doc => docs/html}/simulinkicon.gif | Bin {doc => docs/html}/solaris.png | Bin {doc => docs/html}/solver/checkSolution.html | 0 {doc => docs/html}/solver/getMinNrFluxes.html | 0 {doc => docs/html}/solver/index.html | 0 {doc => docs/html}/solver/optimizeProb.html | 0 {doc => docs/html}/solver/setRavenSolver.html | 0 {doc => docs/html}/solver/solveLP.html | 0 .../solver/splitProbForConditioning.html | 0 {doc => docs/html}/tasks/checkTasks.html | 0 {doc => docs/html}/tasks/index.html | 0 {doc => docs/html}/tasks/parseTaskList.html | 0 .../testing/function_tests/RavenTestCase.html | 0 .../html}/testing/function_tests/index.html | 0 .../private/getExprForRxnScore.html | 0 .../testing/function_tests/private/index.html | 0 .../testing/function_tests/tAnalysis.html | 0 .../testing/function_tests/tAnnotation.html | 0 .../function_tests/tAssignCompartments.html | 0 .../testing/function_tests/tBinaries.html | 0 .../testing/function_tests/tBiomass.html | 0 .../testing/function_tests/tComparison.html | 0 .../testing/function_tests/tConditions.html | 0 .../testing/function_tests/tConversion.html | 0 .../testing/function_tests/tCuration.html | 0 .../testing/function_tests/tGapfilling.html | 0 .../testing/function_tests/tGenomeData.html | 0 .../html}/testing/function_tests/tINIT.html | 0 .../html}/testing/function_tests/tIO.html | 0 .../testing/function_tests/tInstallation.html | 0 .../testing/function_tests/tLocalization.html | 0 .../testing/function_tests/tManipulation.html | 0 .../testing/function_tests/tNamedArgs.html | 0 .../function_tests/tParseRAVENargs.html | 0 .../testing/function_tests/tQueries.html | 0 .../function_tests/tReconstruction.html | 0 .../testing/function_tests/tSampling.html | 0 .../html}/testing/function_tests/tSolver.html | 0 .../html}/testing/function_tests/tSyntax.html | 0 .../html}/testing/function_tests/tTasks.html | 0 .../html}/testing/function_tests/tUtils.html | 0 {doc => docs/html}/tutorial/index.html | 0 {doc => docs/html}/tutorial/tutorial1.html | 0 {doc => docs/html}/tutorial/tutorial2.html | 0 .../html}/tutorial/tutorial2_solutions.html | 0 {doc => docs/html}/tutorial/tutorial3.html | 0 .../html}/tutorial/tutorial3_solutions.html | 0 {doc => docs/html}/tutorial/tutorial4.html | 0 .../html}/tutorial/tutorial4_solutions.html | 0 {doc => docs/html}/tutorial/tutorial5.html | 0 {doc => docs/html}/tutorial/tutorial7.html | 0 {doc => docs/html}/up.png | Bin .../html}/utils/convertCharArray.html | 0 .../html}/utils/deprecationWarning.html | 0 {doc => docs/html}/utils/grRuleToDNF.html | 0 {doc => docs/html}/utils/grRuleToString.html | 0 {doc => docs/html}/utils/index.html | 0 {doc => docs/html}/utils/isDnfGrRule.html | 0 .../html}/utils/legacyMethodNotice.html | 0 .../html}/utils/parallelWorkersRAVEN.html | 0 {doc => docs/html}/utils/parseGrRule.html | 0 {doc => docs/html}/utils/parseRAVENargs.html | 0 {doc => docs/html}/utils/printOrange.html | 0 {doc => docs/html}/utils/progressReport.html | 0 {doc => docs/html}/utils/ravenList.html | 0 .../html}/utils/ravenModelFields.html | 0 {doc => docs/html}/utils/runRAVENtests.html | 0 .../html}/utils/setRavenProgress.html | 0 {doc => docs/html}/windows.png | Bin {readme => docs/readme}/GPL3.MD | 0 .../readme}/RAVEN_structure_fields.csv | 0 {readme => docs/readme}/changelog.MD | 0 RAVEN.png => img/RAVEN.png | Bin {INIT => src/INIT}/INITStepDesc.m | 0 {INIT => src/INIT}/ftINIT.m | 0 {INIT => src/INIT}/ftINITInternalAlg.m | 0 {INIT => src/INIT}/getINITSteps.m | 0 {INIT => src/INIT}/groupRxnScores.m | 0 {INIT => src/INIT}/mergeLinear.m | 0 {INIT => src/INIT}/parseHPA.m | 0 {INIT => src/INIT}/parseHPArna.m | 0 {INIT => src/INIT}/prepINITModel.m | 0 {INIT => src/INIT}/rescaleModelForINIT.m | 0 {INIT => src/INIT}/scoreModel.m | 0 {INIT => src/INIT}/tINIT/README.md | 0 {INIT => src/INIT}/tINIT/getINITModel.m | 0 {INIT => src/INIT}/tINIT/runINIT.m | 0 {analysis => src/analysis}/FSEOF.m | 0 {analysis => src/analysis}/analyzeSampling.m | 0 {analysis => src/analysis}/compareFluxes.m | 0 .../analysis}/findGeneDeletions.m | 0 {analysis => src/analysis}/getAllSubGraphs.m | 0 {analysis => src/analysis}/getAllowedBounds.m | 0 {analysis => src/analysis}/getEssentialRxns.m | 0 {analysis => src/analysis}/getFluxZ.m | 0 {analysis => src/analysis}/getMinimalMedium.m | 0 {analysis => src/analysis}/haveFlux.m | 0 {analysis => src/analysis}/randomSampling.m | 0 .../analysis}/reporterMetabolites.m | 0 {analysis => src/analysis}/runDynamicFBA.m | 0 .../analysis}/runPhenotypePhasePlane.m | 0 .../analysis}/runProductionEnvelope.m | 0 .../analysis}/runRobustnessAnalysis.m | 0 .../analysis}/runSimpleOptKnock.m | 0 {analysis => src/analysis}/sampleACHR.m | 0 {analysis => src/analysis}/sampleCHRR.m | 0 .../analysis}/sampleChebyshevCenter.m | 0 .../analysis}/sampleMaxVolEllipse.m | 0 .../analysis}/sampleWarmupPoints.m | 0 {analysis => src/analysis}/traceFluxPath.m | 0 {analysis => src/analysis}/walkFluxes.m | 0 .../annotation}/assignSBOterms.m | 0 {annotation => src/annotation}/editMiriam.m | 0 .../annotation}/extractMiriam.m | 0 .../annotation}/loadDeltaGCSV.m | 0 .../annotation}/saveDeltaGCSV.m | 0 {biomass => src/biomass}/fitParameters.m | 0 .../biomass}/getBiomassFractions.m | 0 .../biomass}/scaleBiomassFraction.m | 0 .../biomass}/scaleBiomassPseudoreaction.m | 0 {biomass => src/biomass}/setGAM.m | 0 .../comparison}/compareMultipleModels.m | 0 {comparison => src/comparison}/diffModels.m | 0 .../conditions}/applyCondition.m | 0 .../conversion}/COBRA_structure_fields.csv | 0 .../conversion}/addIdentifierPrefix.m | 0 .../conversion}/cobraNamespaces.csv | 0 .../conversion}/orderRavenFields.csv | 0 .../conversion}/ravenCobraWrapper.m | 4 +-- .../conversion}/removeIdentifierPrefix.m | 0 .../conversion}/standardizeModelFieldOrder.m | 4 +-- .../curation}/curateModelFromTables.m | 0 .../curation}/downloadGenomeData.m | 0 {curation => src/curation}/getGeneData.m | 0 .../curation}/processProteinFastaFile.m | 0 {curation => src/curation}/renameModelGenes.m | 0 {deprecated => src/deprecated}/canConsume.m | 0 {deprecated => src/deprecated}/canProduce.m | 0 .../deprecated}/consumeSomething.m | 0 .../deprecated}/makeSomething.m | 0 {gapfilling => src/gapfilling}/canExchange.m | 0 .../gapfilling}/checkProduction.m | 0 {gapfilling => src/gapfilling}/fillGaps.m | 0 .../gapfilling}/findLeakMetabolite.m | 0 {gapfilling => src/gapfilling}/fitTasks.m | 0 .../gapfilling}/ftINITFillGaps.m | 0 .../gapfilling}/gapFillFastCore.m | 0 .../gapfilling}/gapFillFastLP.m | 0 {gapfilling => src/gapfilling}/gapFillMILP.m | 0 .../gapfilling}/gapFillSwiftCore.m | 0 .../gapfilling}/gapFillTopological.m | 0 {gapfilling => src/gapfilling}/gapReport.m | 0 .../installation}/addRavenToUserPath.m | 0 .../installation}/checkFunctionUniqueness.m | 0 .../installation}/downloadRavenBinaries.m | 8 ++--- .../installation}/fetchRavenDataAsset.m | 0 .../installation}/findRAVENroot.m | 8 ++--- .../installation}/removeRavenFromPath.m | 0 .../installation}/updateDocumentation.m | 33 +++++++++--------- {io => src/io}/checkFileExistence.m | 0 {io => src/io}/cleanSheet.m | 0 {io => src/io}/exportForGit.m | 0 {io => src/io}/exportModel.m | 0 {io => src/io}/exportToExcelFormat.m | 0 {io => src/io}/importModel.m | 0 {io => src/io}/parseYAML.m | 0 {io => src/io}/readFasta.m | 0 {io => src/io}/readYAMLmodel.m | 0 {io => src/io}/writeExcel.m | 0 {io => src/io}/writeFasta.m | 0 {io => src/io}/writeYAMLmodel.m | 0 .../localization}/assignCompartments.m | 0 .../localization}/defaultCompartmentMap.m | 0 .../localization}/getUniProtScores.m | 0 .../localization}/parseScores.m | 0 .../localization}/predictLocalization.m | 0 .../manipulation}/addExchangeRxns.m | 0 .../manipulation}/addGenesRaven.m | 0 {manipulation => src/manipulation}/addMets.m | 0 {manipulation => src/manipulation}/addRxns.m | 0 .../manipulation}/addRxnsGenesMets.m | 0 .../manipulation}/addTransport.m | 0 .../manipulation}/changeGrRules.m | 0 .../manipulation}/changeRxns.m | 0 .../manipulation}/closeModel.m | 0 .../manipulation}/contractModel.m | 0 .../manipulation}/convertToIrrev.m | 0 .../manipulation}/copyToComps.m | 0 .../manipulation}/deleteUnusedGenes.m | 0 .../manipulation}/expandModel.m | 0 .../manipulation}/findDuplicateRxns.m | 0 .../manipulation}/findPotentialErrors.m | 0 .../manipulation}/generateNewIds.m | 0 .../manipulation}/mergeCompartments.m | 0 .../manipulation}/mergeModels.m | 0 .../manipulation}/permuteModel.m | 0 .../manipulation}/removeBadRxns.m | 0 .../manipulation}/removeGenes.m | 0 .../manipulation}/removeLowScoreGenes.m | 0 .../manipulation}/removeMets.m | 0 .../manipulation}/removeReactions.m | 0 .../manipulation}/replaceMets.m | 0 .../manipulation}/reverseRxns.m | 0 .../manipulation}/setExchangeBounds.m | 0 {manipulation => src/manipulation}/setParam.m | 0 .../manipulation}/simplifyModel.m | 0 .../manipulation}/sortIdentifiers.m | 0 .../manipulation}/sortModel.m | 0 .../manipulation}/standardizeGrRules.m | 0 {queries => src/queries}/buildEquation.m | 0 {queries => src/queries}/checkModelStruct.m | 0 {queries => src/queries}/constructEquations.m | 0 {queries => src/queries}/constructS.m | 0 .../queries}/getAllRxnsFromGenes.m | 0 .../queries}/getElementalBalance.m | 0 {queries => src/queries}/getExchangeRxns.m | 0 .../queries}/getGenesFromGrRules.m | 0 {queries => src/queries}/getIndexes.m | 0 {queries => src/queries}/getRxnsInComp.m | 0 {queries => src/queries}/getTransportRxns.m | 0 {queries => src/queries}/modelSummary.m | 0 {queries => src/queries}/parseFormulas.m | 0 {queries => src/queries}/parseRxnEqu.m | 0 {queries => src/queries}/printFluxes.m | 0 {queries => src/queries}/printModelStats.m | 0 .../reconstruction}/guessComposition.m | 0 .../reconstruction}/homology/getBlast.m | 10 +++--- .../reconstruction}/homology/getDiamond.m | 10 +++--- .../homology/getModelFromHomology.m | 0 .../homology/makeFakeBlastStructure.m | 0 .../reconstruction}/kegg/KEGG_VERSION.md | 0 .../reconstruction}/kegg/buildGlobalGPR.m | 0 .../kegg/buildGlobalKEGGModel.m | 2 +- .../kegg/getKEGGModelForOrganism.m | 6 ++-- .../reconstruction}/kegg/getModelFromKEGG.m | 2 +- .../reconstruction}/kegg/getPhylDist.m | 2 +- .../reconstruction}/kegg/keggDataVersion.m | 2 +- .../reconstruction}/kegg/keggPhylDist.mat | Bin .../reconstruction}/kegg/readKEGGTable.m | 0 {software => src/software}/GLPKmex/COPYING | 0 {software => src/software}/GLPKmex/README.md | 0 {software => src/software}/GLPKmex/glpk.m | 0 .../software}/GLPKmex/glpkcc.mexa64 | Bin .../software}/GLPKmex/glpkcc.mexmaca64 | Bin .../software}/GLPKmex/glpkcc.mexmaci64 | Bin .../software}/GLPKmex/glpkcc.mexw64 | Bin {software => src/software}/blast+/.keep | 0 {software => src/software}/diamond/.keep | 0 {software => src/software}/hmmer/.keep | 0 .../software}/libSBML/CheckAndConvert.m | 0 .../libSBML/ConvertFormulaToMathML.m | 0 .../software}/libSBML/OutputSBML_RAVEN.m | 0 .../software}/libSBML/OutputSBML_RAVEN.mexa64 | Bin .../libSBML/OutputSBML_RAVEN.mexmaca64 | Bin .../libSBML/OutputSBML_RAVEN.mexmaci64 | Bin .../software}/libSBML/OutputSBML_RAVEN.mexw64 | Bin {software => src/software}/libSBML/README.md | 0 .../software}/libSBML/TranslateSBML_RAVEN.m | 0 .../libSBML/TranslateSBML_RAVEN.mexa64 | Bin .../libSBML/TranslateSBML_RAVEN.mexmaca64 | Bin .../libSBML/TranslateSBML_RAVEN.mexmaci64 | Bin .../libSBML/TranslateSBML_RAVEN.mexw64 | Bin .../software}/libSBML/VERSION.txt | 0 .../software}/libSBML/addLevelVersion.m | 0 .../software}/libSBML/applyUserValidation.m | 0 .../software}/libSBML/getDefaultValues.m | 0 .../software}/libSBML/getStructure.m | 0 .../software}/libSBML/getStructureEnum.m | 0 .../libSBML/getStructureFieldnames.m | 0 .../software}/libSBML/getValueType.m | 0 .../software}/libSBML/isSBML_Model.m | 0 {software => src/software}/libSBML/isoctave.m | 0 .../software}/m2html/@template/char.m | 0 .../software}/m2html/@template/display.m | 0 .../software}/m2html/@template/get.m | 0 .../software}/m2html/@template/parse.m | 0 .../m2html/@template/private/finish.m | 0 .../m2html/@template/private/loadtpl.m | 0 .../m2html/@template/private/subst.m | 0 .../software}/m2html/@template/set.m | 0 .../software}/m2html/@template/template.m | 0 {software => src/software}/m2html/Changelog | 0 {software => src/software}/m2html/Contents.m | 0 {software => src/software}/m2html/GPL | 0 {software => src/software}/m2html/INSTALL | 0 {software => src/software}/m2html/LICENSE | 0 {software => src/software}/m2html/README | 0 {software => src/software}/m2html/TODO | 0 {software => src/software}/m2html/m2html.m | 0 {software => src/software}/m2html/mdot.m | 0 .../software}/m2html/private/doxyread.m | 0 .../software}/m2html/private/doxysearch.m | 0 .../software}/m2html/private/doxywrite.m | 0 .../m2html/private/m2htmltoolbarimages.mat | Bin .../software}/m2html/private/mexexts.m | 0 .../software}/m2html/private/mfileparse.m | 0 .../software}/m2html/private/openfile.m | 0 .../software}/m2html/private/searchindex.m | 0 .../software}/m2html/private/splitcode.m | 0 .../software}/m2html/private/strtok.m | 0 .../software}/m2html/templates/blue/Thumbs.db | Bin .../software}/m2html/templates/blue/alpha.png | Bin .../software}/m2html/templates/blue/c++.png | Bin .../software}/m2html/templates/blue/c.png | Bin .../m2html/templates/blue/demoicon.gif | Bin .../software}/m2html/templates/blue/down.png | Bin .../m2html/templates/blue/doxysearch.php | 0 .../m2html/templates/blue/fortran.png | Bin .../software}/m2html/templates/blue/graph.tpl | 0 .../software}/m2html/templates/blue/hp.png | Bin .../software}/m2html/templates/blue/left.png | Bin .../software}/m2html/templates/blue/linux.png | Bin .../m2html/templates/blue/m2html.css | 0 .../m2html/templates/blue/master.tpl | 0 .../m2html/templates/blue/matlabicon.gif | Bin .../software}/m2html/templates/blue/mdir.tpl | 0 .../software}/m2html/templates/blue/mex.png | Bin .../software}/m2html/templates/blue/mfile.tpl | 0 .../software}/m2html/templates/blue/pcode.png | Bin .../software}/m2html/templates/blue/right.png | Bin .../m2html/templates/blue/search.tpl | 0 .../software}/m2html/templates/blue/sgi.png | Bin .../m2html/templates/blue/simulinkicon.gif | Bin .../m2html/templates/blue/solaris.png | Bin .../software}/m2html/templates/blue/todo.tpl | 0 .../software}/m2html/templates/blue/up.png | Bin .../m2html/templates/blue/windows.png | Bin {software => src/software}/scip/.keep | 0 {software => src/software}/versions.txt | 0 {solver => src/solver}/checkSolution.m | 0 {solver => src/solver}/getMinNrFluxes.m | 0 {solver => src/solver}/optimizeProb.m | 2 +- {solver => src/solver}/setRavenSolver.m | 8 ++--- {solver => src/solver}/solveLP.m | 0 .../solver}/splitProbForConditioning.m | 0 {tasks => src/tasks}/checkTasks.m | 0 {tasks => src/tasks}/parseTaskList.m | 0 {utils => src/utils}/convertCharArray.m | 0 {utils => src/utils}/deprecationWarning.m | 0 {utils => src/utils}/grRuleToDNF.m | 0 {utils => src/utils}/grRuleToString.m | 0 {utils => src/utils}/isDnfGrRule.m | 0 {utils => src/utils}/legacyMethodNotice.m | 0 {utils => src/utils}/parallelWorkersRAVEN.m | 0 {utils => src/utils}/parseGrRule.m | 0 {utils => src/utils}/parseRAVENargs.m | 0 {utils => src/utils}/printOrange.m | 0 {utils => src/utils}/progressReport.m | 0 {utils => src/utils}/ravenList.m | 0 {utils => src/utils}/ravenModelFields.m | 0 {utils => src/utils}/runRAVENtests.m | 2 +- {utils => src/utils}/setRavenProgress.m | 0 {testing => tests}/function_tests/README.md | 4 +-- .../function_tests/RavenTestCase.m | 2 +- .../private/getExprForRxnScore.m | 0 {testing => tests}/function_tests/tAnalysis.m | 0 .../function_tests/tAnnotation.m | 0 .../function_tests/tAssignCompartments.m | 0 {testing => tests}/function_tests/tBinaries.m | 2 +- {testing => tests}/function_tests/tBiomass.m | 0 .../function_tests/tComparison.m | 0 .../function_tests/tConditions.m | 0 .../function_tests/tConversion.m | 0 {testing => tests}/function_tests/tCuration.m | 0 .../function_tests/tGapfilling.m | 0 .../function_tests/tGenomeData.m | 2 +- {testing => tests}/function_tests/tINIT.m | 0 .../function_tests/tINITtestInfo/01.svg | 0 .../function_tests/tINITtestInfo/02.svg | 0 .../function_tests/tINITtestInfo/03.svg | 0 .../function_tests/tINITtestInfo/04.svg | 0 .../function_tests/tINITtestInfo/05.svg | 0 .../function_tests/tINITtestInfo/06.svg | 0 .../function_tests/tINITtestInfo/07.svg | 0 .../function_tests/tINITtestInfo/README.md | 0 {testing => tests}/function_tests/tIO.m | 14 ++++---- .../function_tests/tInstallation.m | 2 +- .../function_tests/tLocalization.m | 0 .../function_tests/tManipulation.m | 2 +- .../function_tests/tNamedArgs.m | 0 .../function_tests/tParseRAVENargs.m | 0 {testing => tests}/function_tests/tQueries.m | 0 .../function_tests/tReconstruction.m | 12 +++---- {testing => tests}/function_tests/tSampling.m | 0 {testing => tests}/function_tests/tSolver.m | 0 {testing => tests}/function_tests/tSyntax.m | 0 {testing => tests}/function_tests/tTasks.m | 4 +-- {testing => tests}/function_tests/tUtils.m | 0 .../test_data/ecoli_textbook.mat | Bin .../test_data/genomeData/euk.gff | 0 .../test_data/genomeData/euk_protein.faa | 0 .../test_data/genomeData/prok.gff | 0 .../test_data/human_galactosidases.fa | 0 .../test_data/importModelEdgeCases.xml | 0 .../test_data/importModelSpecialCompName.xml | 0 .../function_tests/test_data/test_tasks.txt | 0 .../function_tests/test_data/test_tasks.xls | Bin .../test_data/yeast_galactosidases.fa | 0 .../biomassConditions_template.tsv | 0 {tutorial => tutorials/data}/empty.mat | Bin {tutorial => tutorials/data}/empty.xml | 0 {tutorial => tutorials/data}/empty.yml | 0 .../data/models}/iAL1006 v1.00.xml | 0 {tutorial => tutorials/data/models}/small.yml | 0 .../data/models}/smallYeast.yml | 0 .../data/models}/smallYeastBad.yml | 0 .../data/omics}/expression.txt | 0 {tutorial => tutorials/data/sequences}/sce.fa | 0 {tutorial => tutorials}/tutorial1.m | 2 +- {tutorial => tutorials}/tutorial2.m | 0 {tutorial => tutorials}/tutorial2_solutions.m | 0 {tutorial => tutorials}/tutorial3.m | 0 {tutorial => tutorials}/tutorial3_solutions.m | 0 {tutorial => tutorials}/tutorial4.m | 0 {tutorial => tutorials}/tutorial4_solutions.m | 0 {tutorial => tutorials}/tutorial5.m | 0 {tutorial => tutorials}/tutorial7.m | 0 619 files changed, 106 insertions(+), 105 deletions(-) rename {doc => docs/html}/INIT/INITStepDesc.html (100%) rename {doc => docs/html}/INIT/ftINIT.html (100%) rename {doc => docs/html}/INIT/ftINITInternalAlg.html (100%) rename {doc => docs/html}/INIT/getINITSteps.html (100%) rename {doc => docs/html}/INIT/groupRxnScores.html (100%) rename {doc => docs/html}/INIT/index.html (100%) rename {doc => docs/html}/INIT/mergeLinear.html (100%) rename {doc => docs/html}/INIT/parseHPA.html (100%) rename {doc => docs/html}/INIT/parseHPArna.html (100%) rename {doc => docs/html}/INIT/prepINITModel.html (100%) rename {doc => docs/html}/INIT/rescaleModelForINIT.html (100%) rename {doc => docs/html}/INIT/scoreModel.html (100%) rename {doc => docs/html}/INIT/tINIT/getINITModel.html (100%) rename {doc => docs/html}/INIT/tINIT/index.html (100%) rename {doc => docs/html}/INIT/tINIT/runINIT.html (100%) rename {doc => docs/html}/alpha.png (100%) rename {doc => docs/html}/analysis/FSEOF.html (100%) rename {doc => docs/html}/analysis/analyzeSampling.html (100%) rename {doc => docs/html}/analysis/compareFluxes.html (100%) rename {doc => docs/html}/analysis/findGeneDeletions.html (100%) rename {doc => docs/html}/analysis/getAllSubGraphs.html (100%) rename {doc => docs/html}/analysis/getAllowedBounds.html (100%) rename {doc => docs/html}/analysis/getEssentialRxns.html (100%) rename {doc => docs/html}/analysis/getFluxZ.html (100%) rename {doc => docs/html}/analysis/getMinimalMedium.html (100%) rename {doc => docs/html}/analysis/haveFlux.html (100%) rename {doc => docs/html}/analysis/index.html (100%) rename {doc => docs/html}/analysis/randomSampling.html (100%) rename {doc => docs/html}/analysis/reporterMetabolites.html (100%) rename {doc => docs/html}/analysis/runDynamicFBA.html (100%) rename {doc => docs/html}/analysis/runPhenotypePhasePlane.html (100%) rename {doc => docs/html}/analysis/runProductionEnvelope.html (100%) rename {doc => docs/html}/analysis/runRobustnessAnalysis.html (100%) rename {doc => docs/html}/analysis/runSimpleOptKnock.html (100%) rename {doc => docs/html}/analysis/sampleACHR.html (100%) rename {doc => docs/html}/analysis/sampleCHRR.html (100%) rename {doc => docs/html}/analysis/sampleChebyshevCenter.html (100%) rename {doc => docs/html}/analysis/sampleMaxVolEllipse.html (100%) rename {doc => docs/html}/analysis/sampleWarmupPoints.html (100%) rename {doc => docs/html}/analysis/traceFluxPath.html (100%) rename {doc => docs/html}/analysis/walkFluxes.html (100%) rename {doc => docs/html}/annotation/assignSBOterms.html (100%) rename {doc => docs/html}/annotation/editMiriam.html (100%) rename {doc => docs/html}/annotation/extractMiriam.html (100%) rename {doc => docs/html}/annotation/index.html (100%) rename {doc => docs/html}/annotation/loadDeltaGCSV.html (100%) rename {doc => docs/html}/annotation/saveDeltaGCSV.html (100%) rename {doc => docs/html}/biomass/fitParameters.html (100%) rename {doc => docs/html}/biomass/getBiomassFractions.html (100%) rename {doc => docs/html}/biomass/index.html (100%) rename {doc => docs/html}/biomass/scaleBiomassFraction.html (100%) rename {doc => docs/html}/biomass/scaleBiomassPseudoreaction.html (100%) rename {doc => docs/html}/biomass/setGAM.html (100%) rename {doc => docs/html}/c++.png (100%) rename {doc => docs/html}/c.png (100%) rename {doc => docs/html}/checkInstallation.html (100%) rename {doc => docs/html}/checkRaven.html (100%) rename {doc => docs/html}/comparison/compareMultipleModels.html (100%) rename {doc => docs/html}/comparison/diffModels.html (100%) rename {doc => docs/html}/comparison/index.html (100%) rename {doc => docs/html}/conditions/applyCondition.html (100%) rename {doc => docs/html}/conditions/index.html (100%) rename {doc => docs/html}/conversion/addIdentifierPrefix.html (100%) rename {doc => docs/html}/conversion/index.html (100%) rename {doc => docs/html}/conversion/ravenCobraWrapper.html (100%) rename {doc => docs/html}/conversion/removeIdentifierPrefix.html (100%) rename {doc => docs/html}/conversion/standardizeModelFieldOrder.html (100%) rename {doc => docs/html}/curation/curateModelFromTables.html (100%) rename {doc => docs/html}/curation/downloadGenomeData.html (100%) rename {doc => docs/html}/curation/getGeneData.html (100%) rename {doc => docs/html}/curation/index.html (100%) rename {doc => docs/html}/curation/processProteinFastaFile.html (100%) rename {doc => docs/html}/curation/renameModelGenes.html (100%) rename {doc => docs/html}/demoicon.gif (100%) rename {doc => docs/html}/down.png (100%) rename {doc => docs/html}/doxysearch.php (100%) rename {doc => docs/html}/fortran.png (100%) rename {doc => docs/html}/gapfilling/canExchange.html (100%) rename {doc => docs/html}/gapfilling/checkProduction.html (100%) rename {doc => docs/html}/gapfilling/fillGaps.html (100%) rename {doc => docs/html}/gapfilling/findLeakMetabolite.html (100%) rename {doc => docs/html}/gapfilling/fitTasks.html (100%) rename {doc => docs/html}/gapfilling/ftINITFillGaps.html (100%) rename {doc => docs/html}/gapfilling/gapFillFastCore.html (100%) rename {doc => docs/html}/gapfilling/gapFillFastLP.html (100%) rename {doc => docs/html}/gapfilling/gapFillMILP.html (100%) rename {doc => docs/html}/gapfilling/gapFillSwiftCore.html (100%) rename {doc => docs/html}/gapfilling/gapFillTopological.html (100%) rename {doc => docs/html}/gapfilling/gapReport.html (100%) rename {doc => docs/html}/gapfilling/index.html (100%) rename {doc => docs/html}/hp.png (100%) rename {doc => docs/html}/index.html (100%) rename {doc => docs/html}/installation/addRavenToUserPath.html (100%) rename {doc => docs/html}/installation/checkFunctionUniqueness.html (100%) rename {doc => docs/html}/installation/downloadRavenBinaries.html (100%) rename {doc => docs/html}/installation/fetchRavenDataAsset.html (100%) rename {doc => docs/html}/installation/findRAVENroot.html (100%) rename {doc => docs/html}/installation/index.html (100%) rename {doc => docs/html}/installation/removeRavenFromPath.html (100%) rename {doc => docs/html}/installation/updateDocumentation.html (100%) rename {doc => docs/html}/io/checkFileExistence.html (100%) rename {doc => docs/html}/io/cleanSheet.html (100%) rename {doc => docs/html}/io/exportForGit.html (100%) rename {doc => docs/html}/io/exportModel.html (100%) rename {doc => docs/html}/io/exportToExcelFormat.html (100%) rename {doc => docs/html}/io/importModel.html (100%) rename {doc => docs/html}/io/index.html (100%) rename {doc => docs/html}/io/parseYAML.html (100%) rename {doc => docs/html}/io/readFasta.html (100%) rename {doc => docs/html}/io/readYAMLmodel.html (100%) rename {doc => docs/html}/io/writeExcel.html (100%) rename {doc => docs/html}/io/writeFasta.html (100%) rename {doc => docs/html}/io/writeYAMLmodel.html (100%) rename {doc => docs/html}/left.png (100%) rename {doc => docs/html}/linux.png (100%) rename {doc => docs/html}/localization/assignCompartments.html (100%) rename {doc => docs/html}/localization/defaultCompartmentMap.html (100%) rename {doc => docs/html}/localization/getUniProtScores.html (100%) rename {doc => docs/html}/localization/index.html (100%) rename {doc => docs/html}/localization/parseScores.html (100%) rename {doc => docs/html}/localization/predictLocalization.html (100%) rename {doc => docs/html}/m2html.css (100%) rename {doc => docs/html}/manipulation/addExchangeRxns.html (100%) rename {doc => docs/html}/manipulation/addGenesRaven.html (100%) rename {doc => docs/html}/manipulation/addMets.html (100%) rename {doc => docs/html}/manipulation/addRxns.html (100%) rename {doc => docs/html}/manipulation/addRxnsGenesMets.html (100%) rename {doc => docs/html}/manipulation/addTransport.html (100%) rename {doc => docs/html}/manipulation/changeGrRules.html (100%) rename {doc => docs/html}/manipulation/changeRxns.html (100%) rename {doc => docs/html}/manipulation/closeModel.html (100%) rename {doc => docs/html}/manipulation/contractModel.html (100%) rename {doc => docs/html}/manipulation/convertToIrrev.html (100%) rename {doc => docs/html}/manipulation/copyToComps.html (100%) rename {doc => docs/html}/manipulation/deleteUnusedGenes.html (100%) rename {doc => docs/html}/manipulation/expandModel.html (100%) rename {doc => docs/html}/manipulation/findDuplicateRxns.html (100%) rename {doc => docs/html}/manipulation/findPotentialErrors.html (100%) rename {doc => docs/html}/manipulation/generateNewIds.html (100%) rename {doc => docs/html}/manipulation/index.html (100%) rename {doc => docs/html}/manipulation/mergeCompartments.html (100%) rename {doc => docs/html}/manipulation/mergeModels.html (100%) rename {doc => docs/html}/manipulation/permuteModel.html (100%) rename {doc => docs/html}/manipulation/removeBadRxns.html (100%) rename {doc => docs/html}/manipulation/removeGenes.html (100%) rename {doc => docs/html}/manipulation/removeLowScoreGenes.html (100%) rename {doc => docs/html}/manipulation/removeMets.html (100%) rename {doc => docs/html}/manipulation/removeReactions.html (100%) rename {doc => docs/html}/manipulation/replaceMets.html (100%) rename {doc => docs/html}/manipulation/reverseRxns.html (100%) rename {doc => docs/html}/manipulation/setExchangeBounds.html (100%) rename {doc => docs/html}/manipulation/setParam.html (100%) rename {doc => docs/html}/manipulation/simplifyModel.html (100%) rename {doc => docs/html}/manipulation/sortIdentifiers.html (100%) rename {doc => docs/html}/manipulation/sortModel.html (100%) rename {doc => docs/html}/manipulation/standardizeGrRules.html (100%) rename {doc => docs/html}/matlabicon.gif (100%) rename {doc => docs/html}/mex.png (100%) rename {doc => docs/html}/pcode.png (100%) rename {doc => docs/html}/queries/buildEquation.html (100%) rename {doc => docs/html}/queries/checkModelStruct.html (100%) rename {doc => docs/html}/queries/constructEquations.html (100%) rename {doc => docs/html}/queries/constructS.html (100%) rename {doc => docs/html}/queries/getAllRxnsFromGenes.html (100%) rename {doc => docs/html}/queries/getElementalBalance.html (100%) rename {doc => docs/html}/queries/getExchangeRxns.html (100%) rename {doc => docs/html}/queries/getGenesFromGrRules.html (100%) rename {doc => docs/html}/queries/getIndexes.html (100%) rename {doc => docs/html}/queries/getRxnsInComp.html (100%) rename {doc => docs/html}/queries/getTransportRxns.html (100%) rename {doc => docs/html}/queries/index.html (100%) rename {doc => docs/html}/queries/modelSummary.html (100%) rename {doc => docs/html}/queries/parseFormulas.html (100%) rename {doc => docs/html}/queries/parseRxnEqu.html (100%) rename {doc => docs/html}/queries/printFluxes.html (100%) rename {doc => docs/html}/queries/printModelStats.html (100%) rename {doc => docs/html}/reconstruction/guessComposition.html (100%) rename {doc => docs/html}/reconstruction/homology/getBlast.html (100%) rename {doc => docs/html}/reconstruction/homology/getDiamond.html (100%) rename {doc => docs/html}/reconstruction/homology/getModelFromHomology.html (100%) rename {doc => docs/html}/reconstruction/homology/index.html (100%) rename {doc => docs/html}/reconstruction/homology/makeFakeBlastStructure.html (100%) rename {doc => docs/html}/reconstruction/index.html (100%) rename {doc => docs/html}/reconstruction/kegg/buildGlobalGPR.html (100%) rename {doc => docs/html}/reconstruction/kegg/buildGlobalKEGGModel.html (100%) rename {doc => docs/html}/reconstruction/kegg/getKEGGModelForOrganism.html (100%) rename {doc => docs/html}/reconstruction/kegg/getModelFromKEGG.html (100%) rename {doc => docs/html}/reconstruction/kegg/getPhylDist.html (100%) rename {doc => docs/html}/reconstruction/kegg/index.html (100%) rename {doc => docs/html}/reconstruction/kegg/keggDataVersion.html (100%) rename {doc => docs/html}/reconstruction/kegg/readKEGGTable.html (100%) rename {doc => docs/html}/right.png (100%) rename {doc => docs/html}/sgi.png (100%) rename {doc => docs/html}/simulinkicon.gif (100%) rename {doc => docs/html}/solaris.png (100%) rename {doc => docs/html}/solver/checkSolution.html (100%) rename {doc => docs/html}/solver/getMinNrFluxes.html (100%) rename {doc => docs/html}/solver/index.html (100%) rename {doc => docs/html}/solver/optimizeProb.html (100%) rename {doc => docs/html}/solver/setRavenSolver.html (100%) rename {doc => docs/html}/solver/solveLP.html (100%) rename {doc => docs/html}/solver/splitProbForConditioning.html (100%) rename {doc => docs/html}/tasks/checkTasks.html (100%) rename {doc => docs/html}/tasks/index.html (100%) rename {doc => docs/html}/tasks/parseTaskList.html (100%) rename {doc => docs/html}/testing/function_tests/RavenTestCase.html (100%) rename {doc => docs/html}/testing/function_tests/index.html (100%) rename {doc => docs/html}/testing/function_tests/private/getExprForRxnScore.html (100%) rename {doc => docs/html}/testing/function_tests/private/index.html (100%) rename {doc => docs/html}/testing/function_tests/tAnalysis.html (100%) rename {doc => docs/html}/testing/function_tests/tAnnotation.html (100%) rename {doc => docs/html}/testing/function_tests/tAssignCompartments.html (100%) rename {doc => docs/html}/testing/function_tests/tBinaries.html (100%) rename {doc => docs/html}/testing/function_tests/tBiomass.html (100%) rename {doc => docs/html}/testing/function_tests/tComparison.html (100%) rename {doc => docs/html}/testing/function_tests/tConditions.html (100%) rename {doc => docs/html}/testing/function_tests/tConversion.html (100%) rename {doc => docs/html}/testing/function_tests/tCuration.html (100%) rename {doc => docs/html}/testing/function_tests/tGapfilling.html (100%) rename {doc => docs/html}/testing/function_tests/tGenomeData.html (100%) rename {doc => docs/html}/testing/function_tests/tINIT.html (100%) rename {doc => docs/html}/testing/function_tests/tIO.html (100%) rename {doc => docs/html}/testing/function_tests/tInstallation.html (100%) rename {doc => docs/html}/testing/function_tests/tLocalization.html (100%) rename {doc => docs/html}/testing/function_tests/tManipulation.html (100%) rename {doc => docs/html}/testing/function_tests/tNamedArgs.html (100%) rename {doc => docs/html}/testing/function_tests/tParseRAVENargs.html (100%) rename {doc => docs/html}/testing/function_tests/tQueries.html (100%) rename {doc => docs/html}/testing/function_tests/tReconstruction.html (100%) rename {doc => docs/html}/testing/function_tests/tSampling.html (100%) rename {doc => docs/html}/testing/function_tests/tSolver.html (100%) rename {doc => docs/html}/testing/function_tests/tSyntax.html (100%) rename {doc => docs/html}/testing/function_tests/tTasks.html (100%) rename {doc => docs/html}/testing/function_tests/tUtils.html (100%) rename {doc => docs/html}/tutorial/index.html (100%) rename {doc => docs/html}/tutorial/tutorial1.html (100%) rename {doc => docs/html}/tutorial/tutorial2.html (100%) rename {doc => docs/html}/tutorial/tutorial2_solutions.html (100%) rename {doc => docs/html}/tutorial/tutorial3.html (100%) rename {doc => docs/html}/tutorial/tutorial3_solutions.html (100%) rename {doc => docs/html}/tutorial/tutorial4.html (100%) rename {doc => docs/html}/tutorial/tutorial4_solutions.html (100%) rename {doc => docs/html}/tutorial/tutorial5.html (100%) rename {doc => docs/html}/tutorial/tutorial7.html (100%) rename {doc => docs/html}/up.png (100%) rename {doc => docs/html}/utils/convertCharArray.html (100%) rename {doc => docs/html}/utils/deprecationWarning.html (100%) rename {doc => docs/html}/utils/grRuleToDNF.html (100%) rename {doc => docs/html}/utils/grRuleToString.html (100%) rename {doc => docs/html}/utils/index.html (100%) rename {doc => docs/html}/utils/isDnfGrRule.html (100%) rename {doc => docs/html}/utils/legacyMethodNotice.html (100%) rename {doc => docs/html}/utils/parallelWorkersRAVEN.html (100%) rename {doc => docs/html}/utils/parseGrRule.html (100%) rename {doc => docs/html}/utils/parseRAVENargs.html (100%) rename {doc => docs/html}/utils/printOrange.html (100%) rename {doc => docs/html}/utils/progressReport.html (100%) rename {doc => docs/html}/utils/ravenList.html (100%) rename {doc => docs/html}/utils/ravenModelFields.html (100%) rename {doc => docs/html}/utils/runRAVENtests.html (100%) rename {doc => docs/html}/utils/setRavenProgress.html (100%) rename {doc => docs/html}/windows.png (100%) rename {readme => docs/readme}/GPL3.MD (100%) rename {readme => docs/readme}/RAVEN_structure_fields.csv (100%) rename {readme => docs/readme}/changelog.MD (100%) rename RAVEN.png => img/RAVEN.png (100%) rename {INIT => src/INIT}/INITStepDesc.m (100%) rename {INIT => src/INIT}/ftINIT.m (100%) rename {INIT => src/INIT}/ftINITInternalAlg.m (100%) rename {INIT => src/INIT}/getINITSteps.m (100%) rename {INIT => src/INIT}/groupRxnScores.m (100%) rename {INIT => src/INIT}/mergeLinear.m (100%) rename {INIT => src/INIT}/parseHPA.m (100%) rename {INIT => src/INIT}/parseHPArna.m (100%) rename {INIT => src/INIT}/prepINITModel.m (100%) rename {INIT => src/INIT}/rescaleModelForINIT.m (100%) rename {INIT => src/INIT}/scoreModel.m (100%) rename {INIT => src/INIT}/tINIT/README.md (100%) rename {INIT => src/INIT}/tINIT/getINITModel.m (100%) rename {INIT => src/INIT}/tINIT/runINIT.m (100%) rename {analysis => src/analysis}/FSEOF.m (100%) rename {analysis => src/analysis}/analyzeSampling.m (100%) rename {analysis => src/analysis}/compareFluxes.m (100%) rename {analysis => src/analysis}/findGeneDeletions.m (100%) rename {analysis => src/analysis}/getAllSubGraphs.m (100%) rename {analysis => src/analysis}/getAllowedBounds.m (100%) rename {analysis => src/analysis}/getEssentialRxns.m (100%) rename {analysis => src/analysis}/getFluxZ.m (100%) rename {analysis => src/analysis}/getMinimalMedium.m (100%) rename {analysis => src/analysis}/haveFlux.m (100%) rename {analysis => src/analysis}/randomSampling.m (100%) rename {analysis => src/analysis}/reporterMetabolites.m (100%) rename {analysis => src/analysis}/runDynamicFBA.m (100%) rename {analysis => src/analysis}/runPhenotypePhasePlane.m (100%) rename {analysis => src/analysis}/runProductionEnvelope.m (100%) rename {analysis => src/analysis}/runRobustnessAnalysis.m (100%) rename {analysis => src/analysis}/runSimpleOptKnock.m (100%) rename {analysis => src/analysis}/sampleACHR.m (100%) rename {analysis => src/analysis}/sampleCHRR.m (100%) rename {analysis => src/analysis}/sampleChebyshevCenter.m (100%) rename {analysis => src/analysis}/sampleMaxVolEllipse.m (100%) rename {analysis => src/analysis}/sampleWarmupPoints.m (100%) rename {analysis => src/analysis}/traceFluxPath.m (100%) rename {analysis => src/analysis}/walkFluxes.m (100%) rename {annotation => src/annotation}/assignSBOterms.m (100%) rename {annotation => src/annotation}/editMiriam.m (100%) rename {annotation => src/annotation}/extractMiriam.m (100%) rename {annotation => src/annotation}/loadDeltaGCSV.m (100%) rename {annotation => src/annotation}/saveDeltaGCSV.m (100%) rename {biomass => src/biomass}/fitParameters.m (100%) rename {biomass => src/biomass}/getBiomassFractions.m (100%) rename {biomass => src/biomass}/scaleBiomassFraction.m (100%) rename {biomass => src/biomass}/scaleBiomassPseudoreaction.m (100%) rename {biomass => src/biomass}/setGAM.m (100%) rename {comparison => src/comparison}/compareMultipleModels.m (100%) rename {comparison => src/comparison}/diffModels.m (100%) rename {conditions => src/conditions}/applyCondition.m (100%) rename {conversion => src/conversion}/COBRA_structure_fields.csv (100%) rename {conversion => src/conversion}/addIdentifierPrefix.m (100%) rename {conversion => src/conversion}/cobraNamespaces.csv (100%) rename {conversion => src/conversion}/orderRavenFields.csv (100%) rename {conversion => src/conversion}/ravenCobraWrapper.m (98%) rename {conversion => src/conversion}/removeIdentifierPrefix.m (100%) rename {conversion => src/conversion}/standardizeModelFieldOrder.m (85%) rename {curation => src/curation}/curateModelFromTables.m (100%) rename {curation => src/curation}/downloadGenomeData.m (100%) rename {curation => src/curation}/getGeneData.m (100%) rename {curation => src/curation}/processProteinFastaFile.m (100%) rename {curation => src/curation}/renameModelGenes.m (100%) rename {deprecated => src/deprecated}/canConsume.m (100%) rename {deprecated => src/deprecated}/canProduce.m (100%) rename {deprecated => src/deprecated}/consumeSomething.m (100%) rename {deprecated => src/deprecated}/makeSomething.m (100%) rename {gapfilling => src/gapfilling}/canExchange.m (100%) rename {gapfilling => src/gapfilling}/checkProduction.m (100%) rename {gapfilling => src/gapfilling}/fillGaps.m (100%) rename {gapfilling => src/gapfilling}/findLeakMetabolite.m (100%) rename {gapfilling => src/gapfilling}/fitTasks.m (100%) rename {gapfilling => src/gapfilling}/ftINITFillGaps.m (100%) rename {gapfilling => src/gapfilling}/gapFillFastCore.m (100%) rename {gapfilling => src/gapfilling}/gapFillFastLP.m (100%) rename {gapfilling => src/gapfilling}/gapFillMILP.m (100%) rename {gapfilling => src/gapfilling}/gapFillSwiftCore.m (100%) rename {gapfilling => src/gapfilling}/gapFillTopological.m (100%) rename {gapfilling => src/gapfilling}/gapReport.m (100%) rename {installation => src/installation}/addRavenToUserPath.m (100%) rename {installation => src/installation}/checkFunctionUniqueness.m (100%) rename {installation => src/installation}/downloadRavenBinaries.m (92%) rename {installation => src/installation}/fetchRavenDataAsset.m (100%) rename {installation => src/installation}/findRAVENroot.m (87%) rename {installation => src/installation}/removeRavenFromPath.m (100%) rename {installation => src/installation}/updateDocumentation.m (62%) rename {io => src/io}/checkFileExistence.m (100%) rename {io => src/io}/cleanSheet.m (100%) rename {io => src/io}/exportForGit.m (100%) rename {io => src/io}/exportModel.m (100%) rename {io => src/io}/exportToExcelFormat.m (100%) rename {io => src/io}/importModel.m (100%) rename {io => src/io}/parseYAML.m (100%) rename {io => src/io}/readFasta.m (100%) rename {io => src/io}/readYAMLmodel.m (100%) rename {io => src/io}/writeExcel.m (100%) rename {io => src/io}/writeFasta.m (100%) rename {io => src/io}/writeYAMLmodel.m (100%) rename {localization => src/localization}/assignCompartments.m (100%) rename {localization => src/localization}/defaultCompartmentMap.m (100%) rename {localization => src/localization}/getUniProtScores.m (100%) rename {localization => src/localization}/parseScores.m (100%) rename {localization => src/localization}/predictLocalization.m (100%) rename {manipulation => src/manipulation}/addExchangeRxns.m (100%) rename {manipulation => src/manipulation}/addGenesRaven.m (100%) rename {manipulation => src/manipulation}/addMets.m (100%) rename {manipulation => src/manipulation}/addRxns.m (100%) rename {manipulation => src/manipulation}/addRxnsGenesMets.m (100%) rename {manipulation => src/manipulation}/addTransport.m (100%) rename {manipulation => src/manipulation}/changeGrRules.m (100%) rename {manipulation => src/manipulation}/changeRxns.m (100%) rename {manipulation => src/manipulation}/closeModel.m (100%) rename {manipulation => src/manipulation}/contractModel.m (100%) rename {manipulation => src/manipulation}/convertToIrrev.m (100%) rename {manipulation => src/manipulation}/copyToComps.m (100%) rename {manipulation => src/manipulation}/deleteUnusedGenes.m (100%) rename {manipulation => src/manipulation}/expandModel.m (100%) rename {manipulation => src/manipulation}/findDuplicateRxns.m (100%) rename {manipulation => src/manipulation}/findPotentialErrors.m (100%) rename {manipulation => src/manipulation}/generateNewIds.m (100%) rename {manipulation => src/manipulation}/mergeCompartments.m (100%) rename {manipulation => src/manipulation}/mergeModels.m (100%) rename {manipulation => src/manipulation}/permuteModel.m (100%) rename {manipulation => src/manipulation}/removeBadRxns.m (100%) rename {manipulation => src/manipulation}/removeGenes.m (100%) rename {manipulation => src/manipulation}/removeLowScoreGenes.m (100%) rename {manipulation => src/manipulation}/removeMets.m (100%) rename {manipulation => src/manipulation}/removeReactions.m (100%) rename {manipulation => src/manipulation}/replaceMets.m (100%) rename {manipulation => src/manipulation}/reverseRxns.m (100%) rename {manipulation => src/manipulation}/setExchangeBounds.m (100%) rename {manipulation => src/manipulation}/setParam.m (100%) rename {manipulation => src/manipulation}/simplifyModel.m (100%) rename {manipulation => src/manipulation}/sortIdentifiers.m (100%) rename {manipulation => src/manipulation}/sortModel.m (100%) rename {manipulation => src/manipulation}/standardizeGrRules.m (100%) rename {queries => src/queries}/buildEquation.m (100%) rename {queries => src/queries}/checkModelStruct.m (100%) rename {queries => src/queries}/constructEquations.m (100%) rename {queries => src/queries}/constructS.m (100%) rename {queries => src/queries}/getAllRxnsFromGenes.m (100%) rename {queries => src/queries}/getElementalBalance.m (100%) rename {queries => src/queries}/getExchangeRxns.m (100%) rename {queries => src/queries}/getGenesFromGrRules.m (100%) rename {queries => src/queries}/getIndexes.m (100%) rename {queries => src/queries}/getRxnsInComp.m (100%) rename {queries => src/queries}/getTransportRxns.m (100%) rename {queries => src/queries}/modelSummary.m (100%) rename {queries => src/queries}/parseFormulas.m (100%) rename {queries => src/queries}/parseRxnEqu.m (100%) rename {queries => src/queries}/printFluxes.m (100%) rename {queries => src/queries}/printModelStats.m (100%) rename {reconstruction => src/reconstruction}/guessComposition.m (100%) rename {reconstruction => src/reconstruction}/homology/getBlast.m (87%) rename {reconstruction => src/reconstruction}/homology/getDiamond.m (85%) rename {reconstruction => src/reconstruction}/homology/getModelFromHomology.m (100%) rename {reconstruction => src/reconstruction}/homology/makeFakeBlastStructure.m (100%) rename {reconstruction => src/reconstruction}/kegg/KEGG_VERSION.md (100%) rename {reconstruction => src/reconstruction}/kegg/buildGlobalGPR.m (100%) rename {reconstruction => src/reconstruction}/kegg/buildGlobalKEGGModel.m (97%) rename {reconstruction => src/reconstruction}/kegg/getKEGGModelForOrganism.m (98%) rename {reconstruction => src/reconstruction}/kegg/getModelFromKEGG.m (97%) rename {reconstruction => src/reconstruction}/kegg/getPhylDist.m (94%) rename {reconstruction => src/reconstruction}/kegg/keggDataVersion.m (89%) rename {reconstruction => src/reconstruction}/kegg/keggPhylDist.mat (100%) rename {reconstruction => src/reconstruction}/kegg/readKEGGTable.m (100%) rename {software => src/software}/GLPKmex/COPYING (100%) rename {software => src/software}/GLPKmex/README.md (100%) rename {software => src/software}/GLPKmex/glpk.m (100%) rename {software => src/software}/GLPKmex/glpkcc.mexa64 (100%) rename {software => src/software}/GLPKmex/glpkcc.mexmaca64 (100%) rename {software => src/software}/GLPKmex/glpkcc.mexmaci64 (100%) rename {software => src/software}/GLPKmex/glpkcc.mexw64 (100%) rename {software => src/software}/blast+/.keep (100%) rename {software => src/software}/diamond/.keep (100%) rename {software => src/software}/hmmer/.keep (100%) rename {software => src/software}/libSBML/CheckAndConvert.m (100%) rename {software => src/software}/libSBML/ConvertFormulaToMathML.m (100%) rename {software => src/software}/libSBML/OutputSBML_RAVEN.m (100%) rename {software => src/software}/libSBML/OutputSBML_RAVEN.mexa64 (100%) mode change 100644 => 100755 rename {software => src/software}/libSBML/OutputSBML_RAVEN.mexmaca64 (100%) mode change 100644 => 100755 rename {software => src/software}/libSBML/OutputSBML_RAVEN.mexmaci64 (100%) mode change 100644 => 100755 rename {software => src/software}/libSBML/OutputSBML_RAVEN.mexw64 (100%) rename {software => src/software}/libSBML/README.md (100%) rename {software => src/software}/libSBML/TranslateSBML_RAVEN.m (100%) rename {software => src/software}/libSBML/TranslateSBML_RAVEN.mexa64 (100%) mode change 100644 => 100755 rename {software => src/software}/libSBML/TranslateSBML_RAVEN.mexmaca64 (100%) mode change 100644 => 100755 rename {software => src/software}/libSBML/TranslateSBML_RAVEN.mexmaci64 (100%) mode change 100644 => 100755 rename {software => src/software}/libSBML/TranslateSBML_RAVEN.mexw64 (100%) rename {software => src/software}/libSBML/VERSION.txt (100%) rename {software => src/software}/libSBML/addLevelVersion.m (100%) rename {software => src/software}/libSBML/applyUserValidation.m (100%) rename {software => src/software}/libSBML/getDefaultValues.m (100%) rename {software => src/software}/libSBML/getStructure.m (100%) rename {software => src/software}/libSBML/getStructureEnum.m (100%) rename {software => src/software}/libSBML/getStructureFieldnames.m (100%) rename {software => src/software}/libSBML/getValueType.m (100%) rename {software => src/software}/libSBML/isSBML_Model.m (100%) rename {software => src/software}/libSBML/isoctave.m (100%) rename {software => src/software}/m2html/@template/char.m (100%) rename {software => src/software}/m2html/@template/display.m (100%) rename {software => src/software}/m2html/@template/get.m (100%) rename {software => src/software}/m2html/@template/parse.m (100%) rename {software => src/software}/m2html/@template/private/finish.m (100%) rename {software => src/software}/m2html/@template/private/loadtpl.m (100%) rename {software => src/software}/m2html/@template/private/subst.m (100%) rename {software => src/software}/m2html/@template/set.m (100%) rename {software => src/software}/m2html/@template/template.m (100%) rename {software => src/software}/m2html/Changelog (100%) rename {software => src/software}/m2html/Contents.m (100%) rename {software => src/software}/m2html/GPL (100%) rename {software => src/software}/m2html/INSTALL (100%) rename {software => src/software}/m2html/LICENSE (100%) rename {software => src/software}/m2html/README (100%) rename {software => src/software}/m2html/TODO (100%) rename {software => src/software}/m2html/m2html.m (100%) rename {software => src/software}/m2html/mdot.m (100%) rename {software => src/software}/m2html/private/doxyread.m (100%) rename {software => src/software}/m2html/private/doxysearch.m (100%) rename {software => src/software}/m2html/private/doxywrite.m (100%) rename {software => src/software}/m2html/private/m2htmltoolbarimages.mat (100%) rename {software => src/software}/m2html/private/mexexts.m (100%) rename {software => src/software}/m2html/private/mfileparse.m (100%) rename {software => src/software}/m2html/private/openfile.m (100%) rename {software => src/software}/m2html/private/searchindex.m (100%) rename {software => src/software}/m2html/private/splitcode.m (100%) rename {software => src/software}/m2html/private/strtok.m (100%) rename {software => src/software}/m2html/templates/blue/Thumbs.db (100%) rename {software => src/software}/m2html/templates/blue/alpha.png (100%) rename {software => src/software}/m2html/templates/blue/c++.png (100%) rename {software => src/software}/m2html/templates/blue/c.png (100%) rename {software => src/software}/m2html/templates/blue/demoicon.gif (100%) rename {software => src/software}/m2html/templates/blue/down.png (100%) rename {software => src/software}/m2html/templates/blue/doxysearch.php (100%) rename {software => src/software}/m2html/templates/blue/fortran.png (100%) rename {software => src/software}/m2html/templates/blue/graph.tpl (100%) rename {software => src/software}/m2html/templates/blue/hp.png (100%) rename {software => src/software}/m2html/templates/blue/left.png (100%) rename {software => src/software}/m2html/templates/blue/linux.png (100%) rename {software => src/software}/m2html/templates/blue/m2html.css (100%) rename {software => src/software}/m2html/templates/blue/master.tpl (100%) rename {software => src/software}/m2html/templates/blue/matlabicon.gif (100%) rename {software => src/software}/m2html/templates/blue/mdir.tpl (100%) rename {software => src/software}/m2html/templates/blue/mex.png (100%) rename {software => src/software}/m2html/templates/blue/mfile.tpl (100%) rename {software => src/software}/m2html/templates/blue/pcode.png (100%) rename {software => src/software}/m2html/templates/blue/right.png (100%) rename {software => src/software}/m2html/templates/blue/search.tpl (100%) rename {software => src/software}/m2html/templates/blue/sgi.png (100%) rename {software => src/software}/m2html/templates/blue/simulinkicon.gif (100%) rename {software => src/software}/m2html/templates/blue/solaris.png (100%) rename {software => src/software}/m2html/templates/blue/todo.tpl (100%) rename {software => src/software}/m2html/templates/blue/up.png (100%) rename {software => src/software}/m2html/templates/blue/windows.png (100%) rename {software => src/software}/scip/.keep (100%) rename {software => src/software}/versions.txt (100%) rename {solver => src/solver}/checkSolution.m (100%) rename {solver => src/solver}/getMinNrFluxes.m (100%) rename {solver => src/solver}/optimizeProb.m (99%) rename {solver => src/solver}/setRavenSolver.m (81%) rename {solver => src/solver}/solveLP.m (100%) rename {solver => src/solver}/splitProbForConditioning.m (100%) rename {tasks => src/tasks}/checkTasks.m (100%) rename {tasks => src/tasks}/parseTaskList.m (100%) rename {utils => src/utils}/convertCharArray.m (100%) rename {utils => src/utils}/deprecationWarning.m (100%) rename {utils => src/utils}/grRuleToDNF.m (100%) rename {utils => src/utils}/grRuleToString.m (100%) rename {utils => src/utils}/isDnfGrRule.m (100%) rename {utils => src/utils}/legacyMethodNotice.m (100%) rename {utils => src/utils}/parallelWorkersRAVEN.m (100%) rename {utils => src/utils}/parseGrRule.m (100%) rename {utils => src/utils}/parseRAVENargs.m (100%) rename {utils => src/utils}/printOrange.m (100%) rename {utils => src/utils}/progressReport.m (100%) rename {utils => src/utils}/ravenList.m (100%) rename {utils => src/utils}/ravenModelFields.m (100%) rename {utils => src/utils}/runRAVENtests.m (93%) rename {utils => src/utils}/setRavenProgress.m (100%) rename {testing => tests}/function_tests/README.md (91%) rename {testing => tests}/function_tests/RavenTestCase.m (98%) rename {testing => tests}/function_tests/private/getExprForRxnScore.m (100%) rename {testing => tests}/function_tests/tAnalysis.m (100%) rename {testing => tests}/function_tests/tAnnotation.m (100%) rename {testing => tests}/function_tests/tAssignCompartments.m (100%) rename {testing => tests}/function_tests/tBinaries.m (95%) rename {testing => tests}/function_tests/tBiomass.m (100%) rename {testing => tests}/function_tests/tComparison.m (100%) rename {testing => tests}/function_tests/tConditions.m (100%) rename {testing => tests}/function_tests/tConversion.m (100%) rename {testing => tests}/function_tests/tCuration.m (100%) rename {testing => tests}/function_tests/tGapfilling.m (100%) rename {testing => tests}/function_tests/tGenomeData.m (98%) rename {testing => tests}/function_tests/tINIT.m (100%) rename {testing => tests}/function_tests/tINITtestInfo/01.svg (100%) rename {testing => tests}/function_tests/tINITtestInfo/02.svg (100%) rename {testing => tests}/function_tests/tINITtestInfo/03.svg (100%) rename {testing => tests}/function_tests/tINITtestInfo/04.svg (100%) rename {testing => tests}/function_tests/tINITtestInfo/05.svg (100%) rename {testing => tests}/function_tests/tINITtestInfo/06.svg (100%) rename {testing => tests}/function_tests/tINITtestInfo/07.svg (100%) rename {testing => tests}/function_tests/tINITtestInfo/README.md (100%) rename {testing => tests}/function_tests/tIO.m (96%) rename {testing => tests}/function_tests/tInstallation.m (96%) rename {testing => tests}/function_tests/tLocalization.m (100%) rename {testing => tests}/function_tests/tManipulation.m (99%) rename {testing => tests}/function_tests/tNamedArgs.m (100%) rename {testing => tests}/function_tests/tParseRAVENargs.m (100%) rename {testing => tests}/function_tests/tQueries.m (100%) rename {testing => tests}/function_tests/tReconstruction.m (93%) rename {testing => tests}/function_tests/tSampling.m (100%) rename {testing => tests}/function_tests/tSolver.m (100%) rename {testing => tests}/function_tests/tSyntax.m (100%) rename {testing => tests}/function_tests/tTasks.m (95%) rename {testing => tests}/function_tests/tUtils.m (100%) rename {testing => tests}/function_tests/test_data/ecoli_textbook.mat (100%) rename {testing => tests}/function_tests/test_data/genomeData/euk.gff (100%) rename {testing => tests}/function_tests/test_data/genomeData/euk_protein.faa (100%) rename {testing => tests}/function_tests/test_data/genomeData/prok.gff (100%) rename {testing => tests}/function_tests/test_data/human_galactosidases.fa (100%) rename {testing => tests}/function_tests/test_data/importModelEdgeCases.xml (100%) rename {testing => tests}/function_tests/test_data/importModelSpecialCompName.xml (100%) rename {testing => tests}/function_tests/test_data/test_tasks.txt (100%) rename {testing => tests}/function_tests/test_data/test_tasks.xls (100%) rename {testing => tests}/function_tests/test_data/yeast_galactosidases.fa (100%) rename {tutorial => tutorials/data/cell_analysis}/biomassConditions_template.tsv (100%) rename {tutorial => tutorials/data}/empty.mat (100%) rename {tutorial => tutorials/data}/empty.xml (100%) rename {tutorial => tutorials/data}/empty.yml (100%) rename {tutorial => tutorials/data/models}/iAL1006 v1.00.xml (100%) rename {tutorial => tutorials/data/models}/small.yml (100%) rename {tutorial => tutorials/data/models}/smallYeast.yml (100%) rename {tutorial => tutorials/data/models}/smallYeastBad.yml (100%) rename {tutorial => tutorials/data/omics}/expression.txt (100%) rename {tutorial => tutorials/data/sequences}/sce.fa (100%) rename {tutorial => tutorials}/tutorial1.m (99%) rename {tutorial => tutorials}/tutorial2.m (100%) rename {tutorial => tutorials}/tutorial2_solutions.m (100%) rename {tutorial => tutorials}/tutorial3.m (100%) rename {tutorial => tutorials}/tutorial3_solutions.m (100%) rename {tutorial => tutorials}/tutorial4.m (100%) rename {tutorial => tutorials}/tutorial4_solutions.m (100%) rename {tutorial => tutorials}/tutorial5.m (100%) rename {tutorial => tutorials}/tutorial7.m (100%) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 308c1f82..4da17e51 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -30,8 +30,8 @@ A more detailed explanation or comments can be left in the commit description. * Once the newer version for any external software (BLAST+, DIAMOND, HMMER) is available, identify the newest version which is simultaneously available for macOS, Unix/Linux and Windows * Create a separate branch from the `devel` branch and name it e.g. `chore/updateBinaries` * Commit the changes for each program and operating system separately, e.g. `chore: update HMMER (Win) to 3.2.1` -* As soon as all binaries for particular program are updated through commits, update the corresponding license file, if available and place it in e.g. `software/blast+` directory. -* Update the version number in `RAVENdir/software/version.txt` file. +* As soon as all binaries for particular program are updated through commits, update the corresponding license file, if available and place it in e.g. `src/software/blast+` directory. +* Update the version number in `RAVENdir/src/software/version.txt` file. * Do some testing to ensure that the new binaries are working correctly. Upon successful tests, create a Pull Request to the `devel` branch. ### Pull Requests diff --git a/.github/scripts/assemble_offline_software.py b/.github/scripts/assemble_offline_software.py index 0d0384d1..fb40c454 100644 --- a/.github/scripts/assemble_offline_software.py +++ b/.github/scripts/assemble_offline_software.py @@ -1,8 +1,8 @@ #!/usr/bin/env python -"""Populate RAVEN's software/ with every platform's external binaries. +"""Populate RAVEN's src/software/ with every platform's external binaries. Downloads the per-platform binary ZIPs from raven-data and lays them out under -``/software//`` using RAVEN's suffix scheme — bare name = Linux, +``/src/software//`` using RAVEN's suffix scheme — bare name = Linux, ``.mac`` = macOS, ``.exe`` = Windows. With all platforms present in one tree, the whole RAVEN checkout can be zipped into a single **OS-independent** offline distribution (see the release-bundle workflow). @@ -50,7 +50,7 @@ def _extract(zip_bytes: bytes, dest: Path, rename: dict[str, str]) -> None: def assemble(raven: Path) -> None: - soft = raven / "software" + soft = raven / "src" / "software" for subdir, bundle, vmap, exes in CLI_TOOLS: dest = soft / subdir for plat, suffix in PLATFORMS.items(): diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 3c7183aa..c280ae19 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -1,8 +1,8 @@ name: Documentation -# Regenerate doc/ from the function help texts and commit the result. +# Regenerate docs/html/ from the function help texts and commit the result. # -# doc/ is generated output that is nevertheless tracked, so it drifts as soon +# docs/html/ is generated output that is nevertheless tracked, so it drifts as soon # as anyone edits a docstring or adds/removes a function without remembering to # run updateDocumentation. Running it here instead means nobody has to. # @@ -34,7 +34,7 @@ concurrency: jobs: update-documentation: - name: Regenerate doc/ + name: Regenerate docs/html/ runs-on: ubuntu-latest timeout-minutes: 20 @@ -71,11 +71,11 @@ jobs: - name: Commit regenerated documentation run: | - # updateDocumentation deletes and rebuilds doc/, so stage first: + # updateDocumentation deletes and rebuilds docs/html/, so stage first: # a plain "git diff" would not see added or removed pages. - git add -A doc - if git diff --cached --quiet -- doc; then - echo "doc/ is already up to date." + git add -A docs/html + if git diff --cached --quiet -- docs/html; then + echo "docs/html/ is already up to date." exit 0 fi git config user.name "github-actions[bot]" diff --git a/.github/workflows/release-bundle.yml b/.github/workflows/release-bundle.yml index fdcf3a5c..1ec31488 100644 --- a/.github/workflows/release-bundle.yml +++ b/.github/workflows/release-bundle.yml @@ -1,7 +1,7 @@ name: Offline binary bundle # Build the OS-independent offline RAVEN distribution: the full repo with -# software/ populated with every platform's external binaries, so an air-gapped +# src/software/ populated with every platform's external binaries, so an air-gapped # user downloads one ZIP and runs RAVEN on any OS without fetching anything. # Attached automatically to each published release; also runnable on demand # against an existing tag. @@ -37,7 +37,7 @@ jobs: with: python-version: '3.12' - - name: Populate software/ with every platform's binaries + - name: Populate src/software/ with every platform's binaries run: python .github/scripts/assemble_offline_software.py - name: Build RAVEN--full.zip diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index bef69b5b..8d4001f7 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -46,7 +46,7 @@ jobs: if ~isfolder(resultsDir); mkdir(resultsDir); end import matlab.unittest.TestRunner import matlab.unittest.plugins.XMLPlugin - cd(fullfile('testing', 'function_tests')); + cd(fullfile('tests', 'function_tests')); files = string({dir('*.m').name}); files(files == "RavenTestCase.m") = []; files(files == "tBinaries.m") = []; % run separately by the binary-tests job (Linux + macOS) diff --git a/.gitignore b/.gitignore index 956c5b58..cb63dd10 100644 --- a/.gitignore +++ b/.gitignore @@ -38,15 +38,15 @@ Temporary Items # first use (see buildGlobalKEGGModel); none of them belong in the repo. The # glob (rather than a version-pinned name) keeps this working across a # KEGG_VERSION.md bump without needing its own update. -reconstruction/kegg/keggModel.mat -reconstruction/kegg/kegg*_core.tar.gz -reconstruction/kegg/kegg*_core/ +src/reconstruction/kegg/keggModel.mat +src/reconstruction/kegg/kegg*_core.tar.gz +src/reconstruction/kegg/kegg*_core/ #software -software/scip/ -software/blast+/ -software/diamond/ -software/hmmer/ -software/WoLFPSORT/ +src/software/scip/ +src/software/blast+/ +src/software/diamond/ +src/software/hmmer/ +src/software/WoLFPSORT/ !.keep #doc diff --git a/README.md b/README.md index 26e597fd..aed0e32c 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ - + [![Current release](https://img.shields.io/github/release/SysBioChalmers/RAVEN/all.svg)](https://GitHub.com/SysBioChalmers/RAVEN/releases/) [![GitHub Discussions](https://img.shields.io/github/discussions-search?query=repo%3Asysbiochalmers%2raven&label=GitHub%20Discussions)](https://github.com/SysBioChalmers/RAVEN/discussions) @@ -17,7 +17,7 @@ For context-specific model extraction, use `ftINIT`; `getINITModel` and `runINIT ## Documentation -The information about downloading, installing and developing RAVEN is included in the [Wiki](https://github.com/SysBioChalmers/RAVEN/wiki). The source code documentation is also available [online](http://sysbiochalmers.github.io/RAVEN/doc/). +The information about downloading, installing and developing RAVEN is included in the [Wiki](https://github.com/SysBioChalmers/RAVEN/wiki). The source code documentation is also available [online](http://sysbiochalmers.github.io/RAVEN/docs/html/). Full user documentation, including a migration guide for users upgrading from RAVEN 2 to RAVEN 3, is published at [raven-docs](https://raven-docs.readthedocs.io/en/latest/). diff --git a/_config.yml b/_config.yml index 0add0be6..13d66dce 100644 --- a/_config.yml +++ b/_config.yml @@ -1,4 +1,4 @@ theme: jekyll-theme-minimal -logo: RAVEN.png +logo: img/RAVEN.png show_downloads: true -keep_files: ["doc/"] +keep_files: ["docs/html/"] diff --git a/checkRaven.m b/checkRaven.m index aa462a86..fbb67cd8 100755 --- a/checkRaven.m +++ b/checkRaven.m @@ -166,7 +166,7 @@ fprintf(myStr(' > Checking libSBML version',40)) model = []; try - evalc('model = importModel(fullfile(ravenDir,''tutorial'',''empty.xml''));'); + evalc('model = importModel(fullfile(ravenDir,''tutorials'',''data'',''empty.xml''));'); try libSBMLver=OutputSBML_RAVEN; % Only works in libSBML 5.17.0+ fprintf([libSBMLver.libSBML_version_string '\n']); @@ -193,7 +193,7 @@ end reportCheck(' > Export SBML format', @() exportModel(model, fullfile(tmpDir,'model.xml'))); -reportCheck(' > Import YAML format', @() readYAMLmodel(fullfile(ravenDir,'tutorial','empty.yml'))); +reportCheck(' > Import YAML format', @() readYAMLmodel(fullfile(ravenDir,'tutorials','data','empty.yml'))); reportCheck(' > Export YAML format', @() writeYAMLmodel(model, fullfile(tmpDir,'model.yml'))); reportCheck(' > Export Excel format', @() exportToExcelFormat(model, fullfile(tmpDir,'model.xlsx'))); @@ -300,7 +300,7 @@ function testBinary(ravenDir, tool, binName, versionArg) else binEnd = ''; end -cmd = ['"' fullfile(ravenDir,'software',tool,[binName binEnd]) '" ' versionArg]; +cmd = ['"' fullfile(ravenDir,'src','software',tool,[binName binEnd]) '" ' versionArg]; [status,~] = system(cmd); if status ~= 0 error('%s did not execute (exit status %d)', binName, status); @@ -339,7 +339,7 @@ function testBinary(ravenDir, tool, binName, versionArg) status = 0; % No need to run on Windows return; end -binDir = fullfile(ravenDir,'software'); +binDir = fullfile(ravenDir,'src','software'); % blast+/diamond/hmmer may be present from an on-demand download % (downloadRavenBinaries already chmods those) or from the offline bundle; diff --git a/doc/INIT/INITStepDesc.html b/docs/html/INIT/INITStepDesc.html similarity index 100% rename from doc/INIT/INITStepDesc.html rename to docs/html/INIT/INITStepDesc.html diff --git a/doc/INIT/ftINIT.html b/docs/html/INIT/ftINIT.html similarity index 100% rename from doc/INIT/ftINIT.html rename to docs/html/INIT/ftINIT.html diff --git a/doc/INIT/ftINITInternalAlg.html b/docs/html/INIT/ftINITInternalAlg.html similarity index 100% rename from doc/INIT/ftINITInternalAlg.html rename to docs/html/INIT/ftINITInternalAlg.html diff --git a/doc/INIT/getINITSteps.html b/docs/html/INIT/getINITSteps.html similarity index 100% rename from doc/INIT/getINITSteps.html rename to docs/html/INIT/getINITSteps.html diff --git a/doc/INIT/groupRxnScores.html b/docs/html/INIT/groupRxnScores.html similarity index 100% rename from doc/INIT/groupRxnScores.html rename to docs/html/INIT/groupRxnScores.html diff --git a/doc/INIT/index.html b/docs/html/INIT/index.html similarity index 100% rename from doc/INIT/index.html rename to docs/html/INIT/index.html diff --git a/doc/INIT/mergeLinear.html b/docs/html/INIT/mergeLinear.html similarity index 100% rename from doc/INIT/mergeLinear.html rename to docs/html/INIT/mergeLinear.html diff --git a/doc/INIT/parseHPA.html b/docs/html/INIT/parseHPA.html similarity index 100% rename from doc/INIT/parseHPA.html rename to docs/html/INIT/parseHPA.html diff --git a/doc/INIT/parseHPArna.html b/docs/html/INIT/parseHPArna.html similarity index 100% rename from doc/INIT/parseHPArna.html rename to docs/html/INIT/parseHPArna.html diff --git a/doc/INIT/prepINITModel.html b/docs/html/INIT/prepINITModel.html similarity index 100% rename from doc/INIT/prepINITModel.html rename to docs/html/INIT/prepINITModel.html diff --git a/doc/INIT/rescaleModelForINIT.html b/docs/html/INIT/rescaleModelForINIT.html similarity index 100% rename from doc/INIT/rescaleModelForINIT.html rename to docs/html/INIT/rescaleModelForINIT.html diff --git a/doc/INIT/scoreModel.html b/docs/html/INIT/scoreModel.html similarity index 100% rename from doc/INIT/scoreModel.html rename to docs/html/INIT/scoreModel.html diff --git a/doc/INIT/tINIT/getINITModel.html b/docs/html/INIT/tINIT/getINITModel.html similarity index 100% rename from doc/INIT/tINIT/getINITModel.html rename to docs/html/INIT/tINIT/getINITModel.html diff --git a/doc/INIT/tINIT/index.html b/docs/html/INIT/tINIT/index.html similarity index 100% rename from doc/INIT/tINIT/index.html rename to docs/html/INIT/tINIT/index.html diff --git a/doc/INIT/tINIT/runINIT.html b/docs/html/INIT/tINIT/runINIT.html similarity index 100% rename from doc/INIT/tINIT/runINIT.html rename to docs/html/INIT/tINIT/runINIT.html diff --git a/doc/alpha.png b/docs/html/alpha.png similarity index 100% rename from doc/alpha.png rename to docs/html/alpha.png diff --git a/doc/analysis/FSEOF.html b/docs/html/analysis/FSEOF.html similarity index 100% rename from doc/analysis/FSEOF.html rename to docs/html/analysis/FSEOF.html diff --git a/doc/analysis/analyzeSampling.html b/docs/html/analysis/analyzeSampling.html similarity index 100% rename from doc/analysis/analyzeSampling.html rename to docs/html/analysis/analyzeSampling.html diff --git a/doc/analysis/compareFluxes.html b/docs/html/analysis/compareFluxes.html similarity index 100% rename from doc/analysis/compareFluxes.html rename to docs/html/analysis/compareFluxes.html diff --git a/doc/analysis/findGeneDeletions.html b/docs/html/analysis/findGeneDeletions.html similarity index 100% rename from doc/analysis/findGeneDeletions.html rename to docs/html/analysis/findGeneDeletions.html diff --git a/doc/analysis/getAllSubGraphs.html b/docs/html/analysis/getAllSubGraphs.html similarity index 100% rename from doc/analysis/getAllSubGraphs.html rename to docs/html/analysis/getAllSubGraphs.html diff --git a/doc/analysis/getAllowedBounds.html b/docs/html/analysis/getAllowedBounds.html similarity index 100% rename from doc/analysis/getAllowedBounds.html rename to docs/html/analysis/getAllowedBounds.html diff --git a/doc/analysis/getEssentialRxns.html b/docs/html/analysis/getEssentialRxns.html similarity index 100% rename from doc/analysis/getEssentialRxns.html rename to docs/html/analysis/getEssentialRxns.html diff --git a/doc/analysis/getFluxZ.html b/docs/html/analysis/getFluxZ.html similarity index 100% rename from doc/analysis/getFluxZ.html rename to docs/html/analysis/getFluxZ.html diff --git a/doc/analysis/getMinimalMedium.html b/docs/html/analysis/getMinimalMedium.html similarity index 100% rename from doc/analysis/getMinimalMedium.html rename to docs/html/analysis/getMinimalMedium.html diff --git a/doc/analysis/haveFlux.html b/docs/html/analysis/haveFlux.html similarity index 100% rename from doc/analysis/haveFlux.html rename to docs/html/analysis/haveFlux.html diff --git a/doc/analysis/index.html b/docs/html/analysis/index.html similarity index 100% rename from doc/analysis/index.html rename to docs/html/analysis/index.html diff --git a/doc/analysis/randomSampling.html b/docs/html/analysis/randomSampling.html similarity index 100% rename from doc/analysis/randomSampling.html rename to docs/html/analysis/randomSampling.html diff --git a/doc/analysis/reporterMetabolites.html b/docs/html/analysis/reporterMetabolites.html similarity index 100% rename from doc/analysis/reporterMetabolites.html rename to docs/html/analysis/reporterMetabolites.html diff --git a/doc/analysis/runDynamicFBA.html b/docs/html/analysis/runDynamicFBA.html similarity index 100% rename from doc/analysis/runDynamicFBA.html rename to docs/html/analysis/runDynamicFBA.html diff --git a/doc/analysis/runPhenotypePhasePlane.html b/docs/html/analysis/runPhenotypePhasePlane.html similarity index 100% rename from doc/analysis/runPhenotypePhasePlane.html rename to docs/html/analysis/runPhenotypePhasePlane.html diff --git a/doc/analysis/runProductionEnvelope.html b/docs/html/analysis/runProductionEnvelope.html similarity index 100% rename from doc/analysis/runProductionEnvelope.html rename to docs/html/analysis/runProductionEnvelope.html diff --git a/doc/analysis/runRobustnessAnalysis.html b/docs/html/analysis/runRobustnessAnalysis.html similarity index 100% rename from doc/analysis/runRobustnessAnalysis.html rename to docs/html/analysis/runRobustnessAnalysis.html diff --git a/doc/analysis/runSimpleOptKnock.html b/docs/html/analysis/runSimpleOptKnock.html similarity index 100% rename from doc/analysis/runSimpleOptKnock.html rename to docs/html/analysis/runSimpleOptKnock.html diff --git a/doc/analysis/sampleACHR.html b/docs/html/analysis/sampleACHR.html similarity index 100% rename from doc/analysis/sampleACHR.html rename to docs/html/analysis/sampleACHR.html diff --git a/doc/analysis/sampleCHRR.html b/docs/html/analysis/sampleCHRR.html similarity index 100% rename from doc/analysis/sampleCHRR.html rename to docs/html/analysis/sampleCHRR.html diff --git a/doc/analysis/sampleChebyshevCenter.html b/docs/html/analysis/sampleChebyshevCenter.html similarity index 100% rename from doc/analysis/sampleChebyshevCenter.html rename to docs/html/analysis/sampleChebyshevCenter.html diff --git a/doc/analysis/sampleMaxVolEllipse.html b/docs/html/analysis/sampleMaxVolEllipse.html similarity index 100% rename from doc/analysis/sampleMaxVolEllipse.html rename to docs/html/analysis/sampleMaxVolEllipse.html diff --git a/doc/analysis/sampleWarmupPoints.html b/docs/html/analysis/sampleWarmupPoints.html similarity index 100% rename from doc/analysis/sampleWarmupPoints.html rename to docs/html/analysis/sampleWarmupPoints.html diff --git a/doc/analysis/traceFluxPath.html b/docs/html/analysis/traceFluxPath.html similarity index 100% rename from doc/analysis/traceFluxPath.html rename to docs/html/analysis/traceFluxPath.html diff --git a/doc/analysis/walkFluxes.html b/docs/html/analysis/walkFluxes.html similarity index 100% rename from doc/analysis/walkFluxes.html rename to docs/html/analysis/walkFluxes.html diff --git a/doc/annotation/assignSBOterms.html b/docs/html/annotation/assignSBOterms.html similarity index 100% rename from doc/annotation/assignSBOterms.html rename to docs/html/annotation/assignSBOterms.html diff --git a/doc/annotation/editMiriam.html b/docs/html/annotation/editMiriam.html similarity index 100% rename from doc/annotation/editMiriam.html rename to docs/html/annotation/editMiriam.html diff --git a/doc/annotation/extractMiriam.html b/docs/html/annotation/extractMiriam.html similarity index 100% rename from doc/annotation/extractMiriam.html rename to docs/html/annotation/extractMiriam.html diff --git a/doc/annotation/index.html b/docs/html/annotation/index.html similarity index 100% rename from doc/annotation/index.html rename to docs/html/annotation/index.html diff --git a/doc/annotation/loadDeltaGCSV.html b/docs/html/annotation/loadDeltaGCSV.html similarity index 100% rename from doc/annotation/loadDeltaGCSV.html rename to docs/html/annotation/loadDeltaGCSV.html diff --git a/doc/annotation/saveDeltaGCSV.html b/docs/html/annotation/saveDeltaGCSV.html similarity index 100% rename from doc/annotation/saveDeltaGCSV.html rename to docs/html/annotation/saveDeltaGCSV.html diff --git a/doc/biomass/fitParameters.html b/docs/html/biomass/fitParameters.html similarity index 100% rename from doc/biomass/fitParameters.html rename to docs/html/biomass/fitParameters.html diff --git a/doc/biomass/getBiomassFractions.html b/docs/html/biomass/getBiomassFractions.html similarity index 100% rename from doc/biomass/getBiomassFractions.html rename to docs/html/biomass/getBiomassFractions.html diff --git a/doc/biomass/index.html b/docs/html/biomass/index.html similarity index 100% rename from doc/biomass/index.html rename to docs/html/biomass/index.html diff --git a/doc/biomass/scaleBiomassFraction.html b/docs/html/biomass/scaleBiomassFraction.html similarity index 100% rename from doc/biomass/scaleBiomassFraction.html rename to docs/html/biomass/scaleBiomassFraction.html diff --git a/doc/biomass/scaleBiomassPseudoreaction.html b/docs/html/biomass/scaleBiomassPseudoreaction.html similarity index 100% rename from doc/biomass/scaleBiomassPseudoreaction.html rename to docs/html/biomass/scaleBiomassPseudoreaction.html diff --git a/doc/biomass/setGAM.html b/docs/html/biomass/setGAM.html similarity index 100% rename from doc/biomass/setGAM.html rename to docs/html/biomass/setGAM.html diff --git a/doc/c++.png b/docs/html/c++.png similarity index 100% rename from doc/c++.png rename to docs/html/c++.png diff --git a/doc/c.png b/docs/html/c.png similarity index 100% rename from doc/c.png rename to docs/html/c.png diff --git a/doc/checkInstallation.html b/docs/html/checkInstallation.html similarity index 100% rename from doc/checkInstallation.html rename to docs/html/checkInstallation.html diff --git a/doc/checkRaven.html b/docs/html/checkRaven.html similarity index 100% rename from doc/checkRaven.html rename to docs/html/checkRaven.html diff --git a/doc/comparison/compareMultipleModels.html b/docs/html/comparison/compareMultipleModels.html similarity index 100% rename from doc/comparison/compareMultipleModels.html rename to docs/html/comparison/compareMultipleModels.html diff --git a/doc/comparison/diffModels.html b/docs/html/comparison/diffModels.html similarity index 100% rename from doc/comparison/diffModels.html rename to docs/html/comparison/diffModels.html diff --git a/doc/comparison/index.html b/docs/html/comparison/index.html similarity index 100% rename from doc/comparison/index.html rename to docs/html/comparison/index.html diff --git a/doc/conditions/applyCondition.html b/docs/html/conditions/applyCondition.html similarity index 100% rename from doc/conditions/applyCondition.html rename to docs/html/conditions/applyCondition.html diff --git a/doc/conditions/index.html b/docs/html/conditions/index.html similarity index 100% rename from doc/conditions/index.html rename to docs/html/conditions/index.html diff --git a/doc/conversion/addIdentifierPrefix.html b/docs/html/conversion/addIdentifierPrefix.html similarity index 100% rename from doc/conversion/addIdentifierPrefix.html rename to docs/html/conversion/addIdentifierPrefix.html diff --git a/doc/conversion/index.html b/docs/html/conversion/index.html similarity index 100% rename from doc/conversion/index.html rename to docs/html/conversion/index.html diff --git a/doc/conversion/ravenCobraWrapper.html b/docs/html/conversion/ravenCobraWrapper.html similarity index 100% rename from doc/conversion/ravenCobraWrapper.html rename to docs/html/conversion/ravenCobraWrapper.html diff --git a/doc/conversion/removeIdentifierPrefix.html b/docs/html/conversion/removeIdentifierPrefix.html similarity index 100% rename from doc/conversion/removeIdentifierPrefix.html rename to docs/html/conversion/removeIdentifierPrefix.html diff --git a/doc/conversion/standardizeModelFieldOrder.html b/docs/html/conversion/standardizeModelFieldOrder.html similarity index 100% rename from doc/conversion/standardizeModelFieldOrder.html rename to docs/html/conversion/standardizeModelFieldOrder.html diff --git a/doc/curation/curateModelFromTables.html b/docs/html/curation/curateModelFromTables.html similarity index 100% rename from doc/curation/curateModelFromTables.html rename to docs/html/curation/curateModelFromTables.html diff --git a/doc/curation/downloadGenomeData.html b/docs/html/curation/downloadGenomeData.html similarity index 100% rename from doc/curation/downloadGenomeData.html rename to docs/html/curation/downloadGenomeData.html diff --git a/doc/curation/getGeneData.html b/docs/html/curation/getGeneData.html similarity index 100% rename from doc/curation/getGeneData.html rename to docs/html/curation/getGeneData.html diff --git a/doc/curation/index.html b/docs/html/curation/index.html similarity index 100% rename from doc/curation/index.html rename to docs/html/curation/index.html diff --git a/doc/curation/processProteinFastaFile.html b/docs/html/curation/processProteinFastaFile.html similarity index 100% rename from doc/curation/processProteinFastaFile.html rename to docs/html/curation/processProteinFastaFile.html diff --git a/doc/curation/renameModelGenes.html b/docs/html/curation/renameModelGenes.html similarity index 100% rename from doc/curation/renameModelGenes.html rename to docs/html/curation/renameModelGenes.html diff --git a/doc/demoicon.gif b/docs/html/demoicon.gif similarity index 100% rename from doc/demoicon.gif rename to docs/html/demoicon.gif diff --git a/doc/down.png b/docs/html/down.png similarity index 100% rename from doc/down.png rename to docs/html/down.png diff --git a/doc/doxysearch.php b/docs/html/doxysearch.php similarity index 100% rename from doc/doxysearch.php rename to docs/html/doxysearch.php diff --git a/doc/fortran.png b/docs/html/fortran.png similarity index 100% rename from doc/fortran.png rename to docs/html/fortran.png diff --git a/doc/gapfilling/canExchange.html b/docs/html/gapfilling/canExchange.html similarity index 100% rename from doc/gapfilling/canExchange.html rename to docs/html/gapfilling/canExchange.html diff --git a/doc/gapfilling/checkProduction.html b/docs/html/gapfilling/checkProduction.html similarity index 100% rename from doc/gapfilling/checkProduction.html rename to docs/html/gapfilling/checkProduction.html diff --git a/doc/gapfilling/fillGaps.html b/docs/html/gapfilling/fillGaps.html similarity index 100% rename from doc/gapfilling/fillGaps.html rename to docs/html/gapfilling/fillGaps.html diff --git a/doc/gapfilling/findLeakMetabolite.html b/docs/html/gapfilling/findLeakMetabolite.html similarity index 100% rename from doc/gapfilling/findLeakMetabolite.html rename to docs/html/gapfilling/findLeakMetabolite.html diff --git a/doc/gapfilling/fitTasks.html b/docs/html/gapfilling/fitTasks.html similarity index 100% rename from doc/gapfilling/fitTasks.html rename to docs/html/gapfilling/fitTasks.html diff --git a/doc/gapfilling/ftINITFillGaps.html b/docs/html/gapfilling/ftINITFillGaps.html similarity index 100% rename from doc/gapfilling/ftINITFillGaps.html rename to docs/html/gapfilling/ftINITFillGaps.html diff --git a/doc/gapfilling/gapFillFastCore.html b/docs/html/gapfilling/gapFillFastCore.html similarity index 100% rename from doc/gapfilling/gapFillFastCore.html rename to docs/html/gapfilling/gapFillFastCore.html diff --git a/doc/gapfilling/gapFillFastLP.html b/docs/html/gapfilling/gapFillFastLP.html similarity index 100% rename from doc/gapfilling/gapFillFastLP.html rename to docs/html/gapfilling/gapFillFastLP.html diff --git a/doc/gapfilling/gapFillMILP.html b/docs/html/gapfilling/gapFillMILP.html similarity index 100% rename from doc/gapfilling/gapFillMILP.html rename to docs/html/gapfilling/gapFillMILP.html diff --git a/doc/gapfilling/gapFillSwiftCore.html b/docs/html/gapfilling/gapFillSwiftCore.html similarity index 100% rename from doc/gapfilling/gapFillSwiftCore.html rename to docs/html/gapfilling/gapFillSwiftCore.html diff --git a/doc/gapfilling/gapFillTopological.html b/docs/html/gapfilling/gapFillTopological.html similarity index 100% rename from doc/gapfilling/gapFillTopological.html rename to docs/html/gapfilling/gapFillTopological.html diff --git a/doc/gapfilling/gapReport.html b/docs/html/gapfilling/gapReport.html similarity index 100% rename from doc/gapfilling/gapReport.html rename to docs/html/gapfilling/gapReport.html diff --git a/doc/gapfilling/index.html b/docs/html/gapfilling/index.html similarity index 100% rename from doc/gapfilling/index.html rename to docs/html/gapfilling/index.html diff --git a/doc/hp.png b/docs/html/hp.png similarity index 100% rename from doc/hp.png rename to docs/html/hp.png diff --git a/doc/index.html b/docs/html/index.html similarity index 100% rename from doc/index.html rename to docs/html/index.html diff --git a/doc/installation/addRavenToUserPath.html b/docs/html/installation/addRavenToUserPath.html similarity index 100% rename from doc/installation/addRavenToUserPath.html rename to docs/html/installation/addRavenToUserPath.html diff --git a/doc/installation/checkFunctionUniqueness.html b/docs/html/installation/checkFunctionUniqueness.html similarity index 100% rename from doc/installation/checkFunctionUniqueness.html rename to docs/html/installation/checkFunctionUniqueness.html diff --git a/doc/installation/downloadRavenBinaries.html b/docs/html/installation/downloadRavenBinaries.html similarity index 100% rename from doc/installation/downloadRavenBinaries.html rename to docs/html/installation/downloadRavenBinaries.html diff --git a/doc/installation/fetchRavenDataAsset.html b/docs/html/installation/fetchRavenDataAsset.html similarity index 100% rename from doc/installation/fetchRavenDataAsset.html rename to docs/html/installation/fetchRavenDataAsset.html diff --git a/doc/installation/findRAVENroot.html b/docs/html/installation/findRAVENroot.html similarity index 100% rename from doc/installation/findRAVENroot.html rename to docs/html/installation/findRAVENroot.html diff --git a/doc/installation/index.html b/docs/html/installation/index.html similarity index 100% rename from doc/installation/index.html rename to docs/html/installation/index.html diff --git a/doc/installation/removeRavenFromPath.html b/docs/html/installation/removeRavenFromPath.html similarity index 100% rename from doc/installation/removeRavenFromPath.html rename to docs/html/installation/removeRavenFromPath.html diff --git a/doc/installation/updateDocumentation.html b/docs/html/installation/updateDocumentation.html similarity index 100% rename from doc/installation/updateDocumentation.html rename to docs/html/installation/updateDocumentation.html diff --git a/doc/io/checkFileExistence.html b/docs/html/io/checkFileExistence.html similarity index 100% rename from doc/io/checkFileExistence.html rename to docs/html/io/checkFileExistence.html diff --git a/doc/io/cleanSheet.html b/docs/html/io/cleanSheet.html similarity index 100% rename from doc/io/cleanSheet.html rename to docs/html/io/cleanSheet.html diff --git a/doc/io/exportForGit.html b/docs/html/io/exportForGit.html similarity index 100% rename from doc/io/exportForGit.html rename to docs/html/io/exportForGit.html diff --git a/doc/io/exportModel.html b/docs/html/io/exportModel.html similarity index 100% rename from doc/io/exportModel.html rename to docs/html/io/exportModel.html diff --git a/doc/io/exportToExcelFormat.html b/docs/html/io/exportToExcelFormat.html similarity index 100% rename from doc/io/exportToExcelFormat.html rename to docs/html/io/exportToExcelFormat.html diff --git a/doc/io/importModel.html b/docs/html/io/importModel.html similarity index 100% rename from doc/io/importModel.html rename to docs/html/io/importModel.html diff --git a/doc/io/index.html b/docs/html/io/index.html similarity index 100% rename from doc/io/index.html rename to docs/html/io/index.html diff --git a/doc/io/parseYAML.html b/docs/html/io/parseYAML.html similarity index 100% rename from doc/io/parseYAML.html rename to docs/html/io/parseYAML.html diff --git a/doc/io/readFasta.html b/docs/html/io/readFasta.html similarity index 100% rename from doc/io/readFasta.html rename to docs/html/io/readFasta.html diff --git a/doc/io/readYAMLmodel.html b/docs/html/io/readYAMLmodel.html similarity index 100% rename from doc/io/readYAMLmodel.html rename to docs/html/io/readYAMLmodel.html diff --git a/doc/io/writeExcel.html b/docs/html/io/writeExcel.html similarity index 100% rename from doc/io/writeExcel.html rename to docs/html/io/writeExcel.html diff --git a/doc/io/writeFasta.html b/docs/html/io/writeFasta.html similarity index 100% rename from doc/io/writeFasta.html rename to docs/html/io/writeFasta.html diff --git a/doc/io/writeYAMLmodel.html b/docs/html/io/writeYAMLmodel.html similarity index 100% rename from doc/io/writeYAMLmodel.html rename to docs/html/io/writeYAMLmodel.html diff --git a/doc/left.png b/docs/html/left.png similarity index 100% rename from doc/left.png rename to docs/html/left.png diff --git a/doc/linux.png b/docs/html/linux.png similarity index 100% rename from doc/linux.png rename to docs/html/linux.png diff --git a/doc/localization/assignCompartments.html b/docs/html/localization/assignCompartments.html similarity index 100% rename from doc/localization/assignCompartments.html rename to docs/html/localization/assignCompartments.html diff --git a/doc/localization/defaultCompartmentMap.html b/docs/html/localization/defaultCompartmentMap.html similarity index 100% rename from doc/localization/defaultCompartmentMap.html rename to docs/html/localization/defaultCompartmentMap.html diff --git a/doc/localization/getUniProtScores.html b/docs/html/localization/getUniProtScores.html similarity index 100% rename from doc/localization/getUniProtScores.html rename to docs/html/localization/getUniProtScores.html diff --git a/doc/localization/index.html b/docs/html/localization/index.html similarity index 100% rename from doc/localization/index.html rename to docs/html/localization/index.html diff --git a/doc/localization/parseScores.html b/docs/html/localization/parseScores.html similarity index 100% rename from doc/localization/parseScores.html rename to docs/html/localization/parseScores.html diff --git a/doc/localization/predictLocalization.html b/docs/html/localization/predictLocalization.html similarity index 100% rename from doc/localization/predictLocalization.html rename to docs/html/localization/predictLocalization.html diff --git a/doc/m2html.css b/docs/html/m2html.css similarity index 100% rename from doc/m2html.css rename to docs/html/m2html.css diff --git a/doc/manipulation/addExchangeRxns.html b/docs/html/manipulation/addExchangeRxns.html similarity index 100% rename from doc/manipulation/addExchangeRxns.html rename to docs/html/manipulation/addExchangeRxns.html diff --git a/doc/manipulation/addGenesRaven.html b/docs/html/manipulation/addGenesRaven.html similarity index 100% rename from doc/manipulation/addGenesRaven.html rename to docs/html/manipulation/addGenesRaven.html diff --git a/doc/manipulation/addMets.html b/docs/html/manipulation/addMets.html similarity index 100% rename from doc/manipulation/addMets.html rename to docs/html/manipulation/addMets.html diff --git a/doc/manipulation/addRxns.html b/docs/html/manipulation/addRxns.html similarity index 100% rename from doc/manipulation/addRxns.html rename to docs/html/manipulation/addRxns.html diff --git a/doc/manipulation/addRxnsGenesMets.html b/docs/html/manipulation/addRxnsGenesMets.html similarity index 100% rename from doc/manipulation/addRxnsGenesMets.html rename to docs/html/manipulation/addRxnsGenesMets.html diff --git a/doc/manipulation/addTransport.html b/docs/html/manipulation/addTransport.html similarity index 100% rename from doc/manipulation/addTransport.html rename to docs/html/manipulation/addTransport.html diff --git a/doc/manipulation/changeGrRules.html b/docs/html/manipulation/changeGrRules.html similarity index 100% rename from doc/manipulation/changeGrRules.html rename to docs/html/manipulation/changeGrRules.html diff --git a/doc/manipulation/changeRxns.html b/docs/html/manipulation/changeRxns.html similarity index 100% rename from doc/manipulation/changeRxns.html rename to docs/html/manipulation/changeRxns.html diff --git a/doc/manipulation/closeModel.html b/docs/html/manipulation/closeModel.html similarity index 100% rename from doc/manipulation/closeModel.html rename to docs/html/manipulation/closeModel.html diff --git a/doc/manipulation/contractModel.html b/docs/html/manipulation/contractModel.html similarity index 100% rename from doc/manipulation/contractModel.html rename to docs/html/manipulation/contractModel.html diff --git a/doc/manipulation/convertToIrrev.html b/docs/html/manipulation/convertToIrrev.html similarity index 100% rename from doc/manipulation/convertToIrrev.html rename to docs/html/manipulation/convertToIrrev.html diff --git a/doc/manipulation/copyToComps.html b/docs/html/manipulation/copyToComps.html similarity index 100% rename from doc/manipulation/copyToComps.html rename to docs/html/manipulation/copyToComps.html diff --git a/doc/manipulation/deleteUnusedGenes.html b/docs/html/manipulation/deleteUnusedGenes.html similarity index 100% rename from doc/manipulation/deleteUnusedGenes.html rename to docs/html/manipulation/deleteUnusedGenes.html diff --git a/doc/manipulation/expandModel.html b/docs/html/manipulation/expandModel.html similarity index 100% rename from doc/manipulation/expandModel.html rename to docs/html/manipulation/expandModel.html diff --git a/doc/manipulation/findDuplicateRxns.html b/docs/html/manipulation/findDuplicateRxns.html similarity index 100% rename from doc/manipulation/findDuplicateRxns.html rename to docs/html/manipulation/findDuplicateRxns.html diff --git a/doc/manipulation/findPotentialErrors.html b/docs/html/manipulation/findPotentialErrors.html similarity index 100% rename from doc/manipulation/findPotentialErrors.html rename to docs/html/manipulation/findPotentialErrors.html diff --git a/doc/manipulation/generateNewIds.html b/docs/html/manipulation/generateNewIds.html similarity index 100% rename from doc/manipulation/generateNewIds.html rename to docs/html/manipulation/generateNewIds.html diff --git a/doc/manipulation/index.html b/docs/html/manipulation/index.html similarity index 100% rename from doc/manipulation/index.html rename to docs/html/manipulation/index.html diff --git a/doc/manipulation/mergeCompartments.html b/docs/html/manipulation/mergeCompartments.html similarity index 100% rename from doc/manipulation/mergeCompartments.html rename to docs/html/manipulation/mergeCompartments.html diff --git a/doc/manipulation/mergeModels.html b/docs/html/manipulation/mergeModels.html similarity index 100% rename from doc/manipulation/mergeModels.html rename to docs/html/manipulation/mergeModels.html diff --git a/doc/manipulation/permuteModel.html b/docs/html/manipulation/permuteModel.html similarity index 100% rename from doc/manipulation/permuteModel.html rename to docs/html/manipulation/permuteModel.html diff --git a/doc/manipulation/removeBadRxns.html b/docs/html/manipulation/removeBadRxns.html similarity index 100% rename from doc/manipulation/removeBadRxns.html rename to docs/html/manipulation/removeBadRxns.html diff --git a/doc/manipulation/removeGenes.html b/docs/html/manipulation/removeGenes.html similarity index 100% rename from doc/manipulation/removeGenes.html rename to docs/html/manipulation/removeGenes.html diff --git a/doc/manipulation/removeLowScoreGenes.html b/docs/html/manipulation/removeLowScoreGenes.html similarity index 100% rename from doc/manipulation/removeLowScoreGenes.html rename to docs/html/manipulation/removeLowScoreGenes.html diff --git a/doc/manipulation/removeMets.html b/docs/html/manipulation/removeMets.html similarity index 100% rename from doc/manipulation/removeMets.html rename to docs/html/manipulation/removeMets.html diff --git a/doc/manipulation/removeReactions.html b/docs/html/manipulation/removeReactions.html similarity index 100% rename from doc/manipulation/removeReactions.html rename to docs/html/manipulation/removeReactions.html diff --git a/doc/manipulation/replaceMets.html b/docs/html/manipulation/replaceMets.html similarity index 100% rename from doc/manipulation/replaceMets.html rename to docs/html/manipulation/replaceMets.html diff --git a/doc/manipulation/reverseRxns.html b/docs/html/manipulation/reverseRxns.html similarity index 100% rename from doc/manipulation/reverseRxns.html rename to docs/html/manipulation/reverseRxns.html diff --git a/doc/manipulation/setExchangeBounds.html b/docs/html/manipulation/setExchangeBounds.html similarity index 100% rename from doc/manipulation/setExchangeBounds.html rename to docs/html/manipulation/setExchangeBounds.html diff --git a/doc/manipulation/setParam.html b/docs/html/manipulation/setParam.html similarity index 100% rename from doc/manipulation/setParam.html rename to docs/html/manipulation/setParam.html diff --git a/doc/manipulation/simplifyModel.html b/docs/html/manipulation/simplifyModel.html similarity index 100% rename from doc/manipulation/simplifyModel.html rename to docs/html/manipulation/simplifyModel.html diff --git a/doc/manipulation/sortIdentifiers.html b/docs/html/manipulation/sortIdentifiers.html similarity index 100% rename from doc/manipulation/sortIdentifiers.html rename to docs/html/manipulation/sortIdentifiers.html diff --git a/doc/manipulation/sortModel.html b/docs/html/manipulation/sortModel.html similarity index 100% rename from doc/manipulation/sortModel.html rename to docs/html/manipulation/sortModel.html diff --git a/doc/manipulation/standardizeGrRules.html b/docs/html/manipulation/standardizeGrRules.html similarity index 100% rename from doc/manipulation/standardizeGrRules.html rename to docs/html/manipulation/standardizeGrRules.html diff --git a/doc/matlabicon.gif b/docs/html/matlabicon.gif similarity index 100% rename from doc/matlabicon.gif rename to docs/html/matlabicon.gif diff --git a/doc/mex.png b/docs/html/mex.png similarity index 100% rename from doc/mex.png rename to docs/html/mex.png diff --git a/doc/pcode.png b/docs/html/pcode.png similarity index 100% rename from doc/pcode.png rename to docs/html/pcode.png diff --git a/doc/queries/buildEquation.html b/docs/html/queries/buildEquation.html similarity index 100% rename from doc/queries/buildEquation.html rename to docs/html/queries/buildEquation.html diff --git a/doc/queries/checkModelStruct.html b/docs/html/queries/checkModelStruct.html similarity index 100% rename from doc/queries/checkModelStruct.html rename to docs/html/queries/checkModelStruct.html diff --git a/doc/queries/constructEquations.html b/docs/html/queries/constructEquations.html similarity index 100% rename from doc/queries/constructEquations.html rename to docs/html/queries/constructEquations.html diff --git a/doc/queries/constructS.html b/docs/html/queries/constructS.html similarity index 100% rename from doc/queries/constructS.html rename to docs/html/queries/constructS.html diff --git a/doc/queries/getAllRxnsFromGenes.html b/docs/html/queries/getAllRxnsFromGenes.html similarity index 100% rename from doc/queries/getAllRxnsFromGenes.html rename to docs/html/queries/getAllRxnsFromGenes.html diff --git a/doc/queries/getElementalBalance.html b/docs/html/queries/getElementalBalance.html similarity index 100% rename from doc/queries/getElementalBalance.html rename to docs/html/queries/getElementalBalance.html diff --git a/doc/queries/getExchangeRxns.html b/docs/html/queries/getExchangeRxns.html similarity index 100% rename from doc/queries/getExchangeRxns.html rename to docs/html/queries/getExchangeRxns.html diff --git a/doc/queries/getGenesFromGrRules.html b/docs/html/queries/getGenesFromGrRules.html similarity index 100% rename from doc/queries/getGenesFromGrRules.html rename to docs/html/queries/getGenesFromGrRules.html diff --git a/doc/queries/getIndexes.html b/docs/html/queries/getIndexes.html similarity index 100% rename from doc/queries/getIndexes.html rename to docs/html/queries/getIndexes.html diff --git a/doc/queries/getRxnsInComp.html b/docs/html/queries/getRxnsInComp.html similarity index 100% rename from doc/queries/getRxnsInComp.html rename to docs/html/queries/getRxnsInComp.html diff --git a/doc/queries/getTransportRxns.html b/docs/html/queries/getTransportRxns.html similarity index 100% rename from doc/queries/getTransportRxns.html rename to docs/html/queries/getTransportRxns.html diff --git a/doc/queries/index.html b/docs/html/queries/index.html similarity index 100% rename from doc/queries/index.html rename to docs/html/queries/index.html diff --git a/doc/queries/modelSummary.html b/docs/html/queries/modelSummary.html similarity index 100% rename from doc/queries/modelSummary.html rename to docs/html/queries/modelSummary.html diff --git a/doc/queries/parseFormulas.html b/docs/html/queries/parseFormulas.html similarity index 100% rename from doc/queries/parseFormulas.html rename to docs/html/queries/parseFormulas.html diff --git a/doc/queries/parseRxnEqu.html b/docs/html/queries/parseRxnEqu.html similarity index 100% rename from doc/queries/parseRxnEqu.html rename to docs/html/queries/parseRxnEqu.html diff --git a/doc/queries/printFluxes.html b/docs/html/queries/printFluxes.html similarity index 100% rename from doc/queries/printFluxes.html rename to docs/html/queries/printFluxes.html diff --git a/doc/queries/printModelStats.html b/docs/html/queries/printModelStats.html similarity index 100% rename from doc/queries/printModelStats.html rename to docs/html/queries/printModelStats.html diff --git a/doc/reconstruction/guessComposition.html b/docs/html/reconstruction/guessComposition.html similarity index 100% rename from doc/reconstruction/guessComposition.html rename to docs/html/reconstruction/guessComposition.html diff --git a/doc/reconstruction/homology/getBlast.html b/docs/html/reconstruction/homology/getBlast.html similarity index 100% rename from doc/reconstruction/homology/getBlast.html rename to docs/html/reconstruction/homology/getBlast.html diff --git a/doc/reconstruction/homology/getDiamond.html b/docs/html/reconstruction/homology/getDiamond.html similarity index 100% rename from doc/reconstruction/homology/getDiamond.html rename to docs/html/reconstruction/homology/getDiamond.html diff --git a/doc/reconstruction/homology/getModelFromHomology.html b/docs/html/reconstruction/homology/getModelFromHomology.html similarity index 100% rename from doc/reconstruction/homology/getModelFromHomology.html rename to docs/html/reconstruction/homology/getModelFromHomology.html diff --git a/doc/reconstruction/homology/index.html b/docs/html/reconstruction/homology/index.html similarity index 100% rename from doc/reconstruction/homology/index.html rename to docs/html/reconstruction/homology/index.html diff --git a/doc/reconstruction/homology/makeFakeBlastStructure.html b/docs/html/reconstruction/homology/makeFakeBlastStructure.html similarity index 100% rename from doc/reconstruction/homology/makeFakeBlastStructure.html rename to docs/html/reconstruction/homology/makeFakeBlastStructure.html diff --git a/doc/reconstruction/index.html b/docs/html/reconstruction/index.html similarity index 100% rename from doc/reconstruction/index.html rename to docs/html/reconstruction/index.html diff --git a/doc/reconstruction/kegg/buildGlobalGPR.html b/docs/html/reconstruction/kegg/buildGlobalGPR.html similarity index 100% rename from doc/reconstruction/kegg/buildGlobalGPR.html rename to docs/html/reconstruction/kegg/buildGlobalGPR.html diff --git a/doc/reconstruction/kegg/buildGlobalKEGGModel.html b/docs/html/reconstruction/kegg/buildGlobalKEGGModel.html similarity index 100% rename from doc/reconstruction/kegg/buildGlobalKEGGModel.html rename to docs/html/reconstruction/kegg/buildGlobalKEGGModel.html diff --git a/doc/reconstruction/kegg/getKEGGModelForOrganism.html b/docs/html/reconstruction/kegg/getKEGGModelForOrganism.html similarity index 100% rename from doc/reconstruction/kegg/getKEGGModelForOrganism.html rename to docs/html/reconstruction/kegg/getKEGGModelForOrganism.html diff --git a/doc/reconstruction/kegg/getModelFromKEGG.html b/docs/html/reconstruction/kegg/getModelFromKEGG.html similarity index 100% rename from doc/reconstruction/kegg/getModelFromKEGG.html rename to docs/html/reconstruction/kegg/getModelFromKEGG.html diff --git a/doc/reconstruction/kegg/getPhylDist.html b/docs/html/reconstruction/kegg/getPhylDist.html similarity index 100% rename from doc/reconstruction/kegg/getPhylDist.html rename to docs/html/reconstruction/kegg/getPhylDist.html diff --git a/doc/reconstruction/kegg/index.html b/docs/html/reconstruction/kegg/index.html similarity index 100% rename from doc/reconstruction/kegg/index.html rename to docs/html/reconstruction/kegg/index.html diff --git a/doc/reconstruction/kegg/keggDataVersion.html b/docs/html/reconstruction/kegg/keggDataVersion.html similarity index 100% rename from doc/reconstruction/kegg/keggDataVersion.html rename to docs/html/reconstruction/kegg/keggDataVersion.html diff --git a/doc/reconstruction/kegg/readKEGGTable.html b/docs/html/reconstruction/kegg/readKEGGTable.html similarity index 100% rename from doc/reconstruction/kegg/readKEGGTable.html rename to docs/html/reconstruction/kegg/readKEGGTable.html diff --git a/doc/right.png b/docs/html/right.png similarity index 100% rename from doc/right.png rename to docs/html/right.png diff --git a/doc/sgi.png b/docs/html/sgi.png similarity index 100% rename from doc/sgi.png rename to docs/html/sgi.png diff --git a/doc/simulinkicon.gif b/docs/html/simulinkicon.gif similarity index 100% rename from doc/simulinkicon.gif rename to docs/html/simulinkicon.gif diff --git a/doc/solaris.png b/docs/html/solaris.png similarity index 100% rename from doc/solaris.png rename to docs/html/solaris.png diff --git a/doc/solver/checkSolution.html b/docs/html/solver/checkSolution.html similarity index 100% rename from doc/solver/checkSolution.html rename to docs/html/solver/checkSolution.html diff --git a/doc/solver/getMinNrFluxes.html b/docs/html/solver/getMinNrFluxes.html similarity index 100% rename from doc/solver/getMinNrFluxes.html rename to docs/html/solver/getMinNrFluxes.html diff --git a/doc/solver/index.html b/docs/html/solver/index.html similarity index 100% rename from doc/solver/index.html rename to docs/html/solver/index.html diff --git a/doc/solver/optimizeProb.html b/docs/html/solver/optimizeProb.html similarity index 100% rename from doc/solver/optimizeProb.html rename to docs/html/solver/optimizeProb.html diff --git a/doc/solver/setRavenSolver.html b/docs/html/solver/setRavenSolver.html similarity index 100% rename from doc/solver/setRavenSolver.html rename to docs/html/solver/setRavenSolver.html diff --git a/doc/solver/solveLP.html b/docs/html/solver/solveLP.html similarity index 100% rename from doc/solver/solveLP.html rename to docs/html/solver/solveLP.html diff --git a/doc/solver/splitProbForConditioning.html b/docs/html/solver/splitProbForConditioning.html similarity index 100% rename from doc/solver/splitProbForConditioning.html rename to docs/html/solver/splitProbForConditioning.html diff --git a/doc/tasks/checkTasks.html b/docs/html/tasks/checkTasks.html similarity index 100% rename from doc/tasks/checkTasks.html rename to docs/html/tasks/checkTasks.html diff --git a/doc/tasks/index.html b/docs/html/tasks/index.html similarity index 100% rename from doc/tasks/index.html rename to docs/html/tasks/index.html diff --git a/doc/tasks/parseTaskList.html b/docs/html/tasks/parseTaskList.html similarity index 100% rename from doc/tasks/parseTaskList.html rename to docs/html/tasks/parseTaskList.html diff --git a/doc/testing/function_tests/RavenTestCase.html b/docs/html/testing/function_tests/RavenTestCase.html similarity index 100% rename from doc/testing/function_tests/RavenTestCase.html rename to docs/html/testing/function_tests/RavenTestCase.html diff --git a/doc/testing/function_tests/index.html b/docs/html/testing/function_tests/index.html similarity index 100% rename from doc/testing/function_tests/index.html rename to docs/html/testing/function_tests/index.html diff --git a/doc/testing/function_tests/private/getExprForRxnScore.html b/docs/html/testing/function_tests/private/getExprForRxnScore.html similarity index 100% rename from doc/testing/function_tests/private/getExprForRxnScore.html rename to docs/html/testing/function_tests/private/getExprForRxnScore.html diff --git a/doc/testing/function_tests/private/index.html b/docs/html/testing/function_tests/private/index.html similarity index 100% rename from doc/testing/function_tests/private/index.html rename to docs/html/testing/function_tests/private/index.html diff --git a/doc/testing/function_tests/tAnalysis.html b/docs/html/testing/function_tests/tAnalysis.html similarity index 100% rename from doc/testing/function_tests/tAnalysis.html rename to docs/html/testing/function_tests/tAnalysis.html diff --git a/doc/testing/function_tests/tAnnotation.html b/docs/html/testing/function_tests/tAnnotation.html similarity index 100% rename from doc/testing/function_tests/tAnnotation.html rename to docs/html/testing/function_tests/tAnnotation.html diff --git a/doc/testing/function_tests/tAssignCompartments.html b/docs/html/testing/function_tests/tAssignCompartments.html similarity index 100% rename from doc/testing/function_tests/tAssignCompartments.html rename to docs/html/testing/function_tests/tAssignCompartments.html diff --git a/doc/testing/function_tests/tBinaries.html b/docs/html/testing/function_tests/tBinaries.html similarity index 100% rename from doc/testing/function_tests/tBinaries.html rename to docs/html/testing/function_tests/tBinaries.html diff --git a/doc/testing/function_tests/tBiomass.html b/docs/html/testing/function_tests/tBiomass.html similarity index 100% rename from doc/testing/function_tests/tBiomass.html rename to docs/html/testing/function_tests/tBiomass.html diff --git a/doc/testing/function_tests/tComparison.html b/docs/html/testing/function_tests/tComparison.html similarity index 100% rename from doc/testing/function_tests/tComparison.html rename to docs/html/testing/function_tests/tComparison.html diff --git a/doc/testing/function_tests/tConditions.html b/docs/html/testing/function_tests/tConditions.html similarity index 100% rename from doc/testing/function_tests/tConditions.html rename to docs/html/testing/function_tests/tConditions.html diff --git a/doc/testing/function_tests/tConversion.html b/docs/html/testing/function_tests/tConversion.html similarity index 100% rename from doc/testing/function_tests/tConversion.html rename to docs/html/testing/function_tests/tConversion.html diff --git a/doc/testing/function_tests/tCuration.html b/docs/html/testing/function_tests/tCuration.html similarity index 100% rename from doc/testing/function_tests/tCuration.html rename to docs/html/testing/function_tests/tCuration.html diff --git a/doc/testing/function_tests/tGapfilling.html b/docs/html/testing/function_tests/tGapfilling.html similarity index 100% rename from doc/testing/function_tests/tGapfilling.html rename to docs/html/testing/function_tests/tGapfilling.html diff --git a/doc/testing/function_tests/tGenomeData.html b/docs/html/testing/function_tests/tGenomeData.html similarity index 100% rename from doc/testing/function_tests/tGenomeData.html rename to docs/html/testing/function_tests/tGenomeData.html diff --git a/doc/testing/function_tests/tINIT.html b/docs/html/testing/function_tests/tINIT.html similarity index 100% rename from doc/testing/function_tests/tINIT.html rename to docs/html/testing/function_tests/tINIT.html diff --git a/doc/testing/function_tests/tIO.html b/docs/html/testing/function_tests/tIO.html similarity index 100% rename from doc/testing/function_tests/tIO.html rename to docs/html/testing/function_tests/tIO.html diff --git a/doc/testing/function_tests/tInstallation.html b/docs/html/testing/function_tests/tInstallation.html similarity index 100% rename from doc/testing/function_tests/tInstallation.html rename to docs/html/testing/function_tests/tInstallation.html diff --git a/doc/testing/function_tests/tLocalization.html b/docs/html/testing/function_tests/tLocalization.html similarity index 100% rename from doc/testing/function_tests/tLocalization.html rename to docs/html/testing/function_tests/tLocalization.html diff --git a/doc/testing/function_tests/tManipulation.html b/docs/html/testing/function_tests/tManipulation.html similarity index 100% rename from doc/testing/function_tests/tManipulation.html rename to docs/html/testing/function_tests/tManipulation.html diff --git a/doc/testing/function_tests/tNamedArgs.html b/docs/html/testing/function_tests/tNamedArgs.html similarity index 100% rename from doc/testing/function_tests/tNamedArgs.html rename to docs/html/testing/function_tests/tNamedArgs.html diff --git a/doc/testing/function_tests/tParseRAVENargs.html b/docs/html/testing/function_tests/tParseRAVENargs.html similarity index 100% rename from doc/testing/function_tests/tParseRAVENargs.html rename to docs/html/testing/function_tests/tParseRAVENargs.html diff --git a/doc/testing/function_tests/tQueries.html b/docs/html/testing/function_tests/tQueries.html similarity index 100% rename from doc/testing/function_tests/tQueries.html rename to docs/html/testing/function_tests/tQueries.html diff --git a/doc/testing/function_tests/tReconstruction.html b/docs/html/testing/function_tests/tReconstruction.html similarity index 100% rename from doc/testing/function_tests/tReconstruction.html rename to docs/html/testing/function_tests/tReconstruction.html diff --git a/doc/testing/function_tests/tSampling.html b/docs/html/testing/function_tests/tSampling.html similarity index 100% rename from doc/testing/function_tests/tSampling.html rename to docs/html/testing/function_tests/tSampling.html diff --git a/doc/testing/function_tests/tSolver.html b/docs/html/testing/function_tests/tSolver.html similarity index 100% rename from doc/testing/function_tests/tSolver.html rename to docs/html/testing/function_tests/tSolver.html diff --git a/doc/testing/function_tests/tSyntax.html b/docs/html/testing/function_tests/tSyntax.html similarity index 100% rename from doc/testing/function_tests/tSyntax.html rename to docs/html/testing/function_tests/tSyntax.html diff --git a/doc/testing/function_tests/tTasks.html b/docs/html/testing/function_tests/tTasks.html similarity index 100% rename from doc/testing/function_tests/tTasks.html rename to docs/html/testing/function_tests/tTasks.html diff --git a/doc/testing/function_tests/tUtils.html b/docs/html/testing/function_tests/tUtils.html similarity index 100% rename from doc/testing/function_tests/tUtils.html rename to docs/html/testing/function_tests/tUtils.html diff --git a/doc/tutorial/index.html b/docs/html/tutorial/index.html similarity index 100% rename from doc/tutorial/index.html rename to docs/html/tutorial/index.html diff --git a/doc/tutorial/tutorial1.html b/docs/html/tutorial/tutorial1.html similarity index 100% rename from doc/tutorial/tutorial1.html rename to docs/html/tutorial/tutorial1.html diff --git a/doc/tutorial/tutorial2.html b/docs/html/tutorial/tutorial2.html similarity index 100% rename from doc/tutorial/tutorial2.html rename to docs/html/tutorial/tutorial2.html diff --git a/doc/tutorial/tutorial2_solutions.html b/docs/html/tutorial/tutorial2_solutions.html similarity index 100% rename from doc/tutorial/tutorial2_solutions.html rename to docs/html/tutorial/tutorial2_solutions.html diff --git a/doc/tutorial/tutorial3.html b/docs/html/tutorial/tutorial3.html similarity index 100% rename from doc/tutorial/tutorial3.html rename to docs/html/tutorial/tutorial3.html diff --git a/doc/tutorial/tutorial3_solutions.html b/docs/html/tutorial/tutorial3_solutions.html similarity index 100% rename from doc/tutorial/tutorial3_solutions.html rename to docs/html/tutorial/tutorial3_solutions.html diff --git a/doc/tutorial/tutorial4.html b/docs/html/tutorial/tutorial4.html similarity index 100% rename from doc/tutorial/tutorial4.html rename to docs/html/tutorial/tutorial4.html diff --git a/doc/tutorial/tutorial4_solutions.html b/docs/html/tutorial/tutorial4_solutions.html similarity index 100% rename from doc/tutorial/tutorial4_solutions.html rename to docs/html/tutorial/tutorial4_solutions.html diff --git a/doc/tutorial/tutorial5.html b/docs/html/tutorial/tutorial5.html similarity index 100% rename from doc/tutorial/tutorial5.html rename to docs/html/tutorial/tutorial5.html diff --git a/doc/tutorial/tutorial7.html b/docs/html/tutorial/tutorial7.html similarity index 100% rename from doc/tutorial/tutorial7.html rename to docs/html/tutorial/tutorial7.html diff --git a/doc/up.png b/docs/html/up.png similarity index 100% rename from doc/up.png rename to docs/html/up.png diff --git a/doc/utils/convertCharArray.html b/docs/html/utils/convertCharArray.html similarity index 100% rename from doc/utils/convertCharArray.html rename to docs/html/utils/convertCharArray.html diff --git a/doc/utils/deprecationWarning.html b/docs/html/utils/deprecationWarning.html similarity index 100% rename from doc/utils/deprecationWarning.html rename to docs/html/utils/deprecationWarning.html diff --git a/doc/utils/grRuleToDNF.html b/docs/html/utils/grRuleToDNF.html similarity index 100% rename from doc/utils/grRuleToDNF.html rename to docs/html/utils/grRuleToDNF.html diff --git a/doc/utils/grRuleToString.html b/docs/html/utils/grRuleToString.html similarity index 100% rename from doc/utils/grRuleToString.html rename to docs/html/utils/grRuleToString.html diff --git a/doc/utils/index.html b/docs/html/utils/index.html similarity index 100% rename from doc/utils/index.html rename to docs/html/utils/index.html diff --git a/doc/utils/isDnfGrRule.html b/docs/html/utils/isDnfGrRule.html similarity index 100% rename from doc/utils/isDnfGrRule.html rename to docs/html/utils/isDnfGrRule.html diff --git a/doc/utils/legacyMethodNotice.html b/docs/html/utils/legacyMethodNotice.html similarity index 100% rename from doc/utils/legacyMethodNotice.html rename to docs/html/utils/legacyMethodNotice.html diff --git a/doc/utils/parallelWorkersRAVEN.html b/docs/html/utils/parallelWorkersRAVEN.html similarity index 100% rename from doc/utils/parallelWorkersRAVEN.html rename to docs/html/utils/parallelWorkersRAVEN.html diff --git a/doc/utils/parseGrRule.html b/docs/html/utils/parseGrRule.html similarity index 100% rename from doc/utils/parseGrRule.html rename to docs/html/utils/parseGrRule.html diff --git a/doc/utils/parseRAVENargs.html b/docs/html/utils/parseRAVENargs.html similarity index 100% rename from doc/utils/parseRAVENargs.html rename to docs/html/utils/parseRAVENargs.html diff --git a/doc/utils/printOrange.html b/docs/html/utils/printOrange.html similarity index 100% rename from doc/utils/printOrange.html rename to docs/html/utils/printOrange.html diff --git a/doc/utils/progressReport.html b/docs/html/utils/progressReport.html similarity index 100% rename from doc/utils/progressReport.html rename to docs/html/utils/progressReport.html diff --git a/doc/utils/ravenList.html b/docs/html/utils/ravenList.html similarity index 100% rename from doc/utils/ravenList.html rename to docs/html/utils/ravenList.html diff --git a/doc/utils/ravenModelFields.html b/docs/html/utils/ravenModelFields.html similarity index 100% rename from doc/utils/ravenModelFields.html rename to docs/html/utils/ravenModelFields.html diff --git a/doc/utils/runRAVENtests.html b/docs/html/utils/runRAVENtests.html similarity index 100% rename from doc/utils/runRAVENtests.html rename to docs/html/utils/runRAVENtests.html diff --git a/doc/utils/setRavenProgress.html b/docs/html/utils/setRavenProgress.html similarity index 100% rename from doc/utils/setRavenProgress.html rename to docs/html/utils/setRavenProgress.html diff --git a/doc/windows.png b/docs/html/windows.png similarity index 100% rename from doc/windows.png rename to docs/html/windows.png diff --git a/readme/GPL3.MD b/docs/readme/GPL3.MD similarity index 100% rename from readme/GPL3.MD rename to docs/readme/GPL3.MD diff --git a/readme/RAVEN_structure_fields.csv b/docs/readme/RAVEN_structure_fields.csv similarity index 100% rename from readme/RAVEN_structure_fields.csv rename to docs/readme/RAVEN_structure_fields.csv diff --git a/readme/changelog.MD b/docs/readme/changelog.MD similarity index 100% rename from readme/changelog.MD rename to docs/readme/changelog.MD diff --git a/RAVEN.png b/img/RAVEN.png similarity index 100% rename from RAVEN.png rename to img/RAVEN.png diff --git a/INIT/INITStepDesc.m b/src/INIT/INITStepDesc.m similarity index 100% rename from INIT/INITStepDesc.m rename to src/INIT/INITStepDesc.m diff --git a/INIT/ftINIT.m b/src/INIT/ftINIT.m similarity index 100% rename from INIT/ftINIT.m rename to src/INIT/ftINIT.m diff --git a/INIT/ftINITInternalAlg.m b/src/INIT/ftINITInternalAlg.m similarity index 100% rename from INIT/ftINITInternalAlg.m rename to src/INIT/ftINITInternalAlg.m diff --git a/INIT/getINITSteps.m b/src/INIT/getINITSteps.m similarity index 100% rename from INIT/getINITSteps.m rename to src/INIT/getINITSteps.m diff --git a/INIT/groupRxnScores.m b/src/INIT/groupRxnScores.m similarity index 100% rename from INIT/groupRxnScores.m rename to src/INIT/groupRxnScores.m diff --git a/INIT/mergeLinear.m b/src/INIT/mergeLinear.m similarity index 100% rename from INIT/mergeLinear.m rename to src/INIT/mergeLinear.m diff --git a/INIT/parseHPA.m b/src/INIT/parseHPA.m similarity index 100% rename from INIT/parseHPA.m rename to src/INIT/parseHPA.m diff --git a/INIT/parseHPArna.m b/src/INIT/parseHPArna.m similarity index 100% rename from INIT/parseHPArna.m rename to src/INIT/parseHPArna.m diff --git a/INIT/prepINITModel.m b/src/INIT/prepINITModel.m similarity index 100% rename from INIT/prepINITModel.m rename to src/INIT/prepINITModel.m diff --git a/INIT/rescaleModelForINIT.m b/src/INIT/rescaleModelForINIT.m similarity index 100% rename from INIT/rescaleModelForINIT.m rename to src/INIT/rescaleModelForINIT.m diff --git a/INIT/scoreModel.m b/src/INIT/scoreModel.m similarity index 100% rename from INIT/scoreModel.m rename to src/INIT/scoreModel.m diff --git a/INIT/tINIT/README.md b/src/INIT/tINIT/README.md similarity index 100% rename from INIT/tINIT/README.md rename to src/INIT/tINIT/README.md diff --git a/INIT/tINIT/getINITModel.m b/src/INIT/tINIT/getINITModel.m similarity index 100% rename from INIT/tINIT/getINITModel.m rename to src/INIT/tINIT/getINITModel.m diff --git a/INIT/tINIT/runINIT.m b/src/INIT/tINIT/runINIT.m similarity index 100% rename from INIT/tINIT/runINIT.m rename to src/INIT/tINIT/runINIT.m diff --git a/analysis/FSEOF.m b/src/analysis/FSEOF.m similarity index 100% rename from analysis/FSEOF.m rename to src/analysis/FSEOF.m diff --git a/analysis/analyzeSampling.m b/src/analysis/analyzeSampling.m similarity index 100% rename from analysis/analyzeSampling.m rename to src/analysis/analyzeSampling.m diff --git a/analysis/compareFluxes.m b/src/analysis/compareFluxes.m similarity index 100% rename from analysis/compareFluxes.m rename to src/analysis/compareFluxes.m diff --git a/analysis/findGeneDeletions.m b/src/analysis/findGeneDeletions.m similarity index 100% rename from analysis/findGeneDeletions.m rename to src/analysis/findGeneDeletions.m diff --git a/analysis/getAllSubGraphs.m b/src/analysis/getAllSubGraphs.m similarity index 100% rename from analysis/getAllSubGraphs.m rename to src/analysis/getAllSubGraphs.m diff --git a/analysis/getAllowedBounds.m b/src/analysis/getAllowedBounds.m similarity index 100% rename from analysis/getAllowedBounds.m rename to src/analysis/getAllowedBounds.m diff --git a/analysis/getEssentialRxns.m b/src/analysis/getEssentialRxns.m similarity index 100% rename from analysis/getEssentialRxns.m rename to src/analysis/getEssentialRxns.m diff --git a/analysis/getFluxZ.m b/src/analysis/getFluxZ.m similarity index 100% rename from analysis/getFluxZ.m rename to src/analysis/getFluxZ.m diff --git a/analysis/getMinimalMedium.m b/src/analysis/getMinimalMedium.m similarity index 100% rename from analysis/getMinimalMedium.m rename to src/analysis/getMinimalMedium.m diff --git a/analysis/haveFlux.m b/src/analysis/haveFlux.m similarity index 100% rename from analysis/haveFlux.m rename to src/analysis/haveFlux.m diff --git a/analysis/randomSampling.m b/src/analysis/randomSampling.m similarity index 100% rename from analysis/randomSampling.m rename to src/analysis/randomSampling.m diff --git a/analysis/reporterMetabolites.m b/src/analysis/reporterMetabolites.m similarity index 100% rename from analysis/reporterMetabolites.m rename to src/analysis/reporterMetabolites.m diff --git a/analysis/runDynamicFBA.m b/src/analysis/runDynamicFBA.m similarity index 100% rename from analysis/runDynamicFBA.m rename to src/analysis/runDynamicFBA.m diff --git a/analysis/runPhenotypePhasePlane.m b/src/analysis/runPhenotypePhasePlane.m similarity index 100% rename from analysis/runPhenotypePhasePlane.m rename to src/analysis/runPhenotypePhasePlane.m diff --git a/analysis/runProductionEnvelope.m b/src/analysis/runProductionEnvelope.m similarity index 100% rename from analysis/runProductionEnvelope.m rename to src/analysis/runProductionEnvelope.m diff --git a/analysis/runRobustnessAnalysis.m b/src/analysis/runRobustnessAnalysis.m similarity index 100% rename from analysis/runRobustnessAnalysis.m rename to src/analysis/runRobustnessAnalysis.m diff --git a/analysis/runSimpleOptKnock.m b/src/analysis/runSimpleOptKnock.m similarity index 100% rename from analysis/runSimpleOptKnock.m rename to src/analysis/runSimpleOptKnock.m diff --git a/analysis/sampleACHR.m b/src/analysis/sampleACHR.m similarity index 100% rename from analysis/sampleACHR.m rename to src/analysis/sampleACHR.m diff --git a/analysis/sampleCHRR.m b/src/analysis/sampleCHRR.m similarity index 100% rename from analysis/sampleCHRR.m rename to src/analysis/sampleCHRR.m diff --git a/analysis/sampleChebyshevCenter.m b/src/analysis/sampleChebyshevCenter.m similarity index 100% rename from analysis/sampleChebyshevCenter.m rename to src/analysis/sampleChebyshevCenter.m diff --git a/analysis/sampleMaxVolEllipse.m b/src/analysis/sampleMaxVolEllipse.m similarity index 100% rename from analysis/sampleMaxVolEllipse.m rename to src/analysis/sampleMaxVolEllipse.m diff --git a/analysis/sampleWarmupPoints.m b/src/analysis/sampleWarmupPoints.m similarity index 100% rename from analysis/sampleWarmupPoints.m rename to src/analysis/sampleWarmupPoints.m diff --git a/analysis/traceFluxPath.m b/src/analysis/traceFluxPath.m similarity index 100% rename from analysis/traceFluxPath.m rename to src/analysis/traceFluxPath.m diff --git a/analysis/walkFluxes.m b/src/analysis/walkFluxes.m similarity index 100% rename from analysis/walkFluxes.m rename to src/analysis/walkFluxes.m diff --git a/annotation/assignSBOterms.m b/src/annotation/assignSBOterms.m similarity index 100% rename from annotation/assignSBOterms.m rename to src/annotation/assignSBOterms.m diff --git a/annotation/editMiriam.m b/src/annotation/editMiriam.m similarity index 100% rename from annotation/editMiriam.m rename to src/annotation/editMiriam.m diff --git a/annotation/extractMiriam.m b/src/annotation/extractMiriam.m similarity index 100% rename from annotation/extractMiriam.m rename to src/annotation/extractMiriam.m diff --git a/annotation/loadDeltaGCSV.m b/src/annotation/loadDeltaGCSV.m similarity index 100% rename from annotation/loadDeltaGCSV.m rename to src/annotation/loadDeltaGCSV.m diff --git a/annotation/saveDeltaGCSV.m b/src/annotation/saveDeltaGCSV.m similarity index 100% rename from annotation/saveDeltaGCSV.m rename to src/annotation/saveDeltaGCSV.m diff --git a/biomass/fitParameters.m b/src/biomass/fitParameters.m similarity index 100% rename from biomass/fitParameters.m rename to src/biomass/fitParameters.m diff --git a/biomass/getBiomassFractions.m b/src/biomass/getBiomassFractions.m similarity index 100% rename from biomass/getBiomassFractions.m rename to src/biomass/getBiomassFractions.m diff --git a/biomass/scaleBiomassFraction.m b/src/biomass/scaleBiomassFraction.m similarity index 100% rename from biomass/scaleBiomassFraction.m rename to src/biomass/scaleBiomassFraction.m diff --git a/biomass/scaleBiomassPseudoreaction.m b/src/biomass/scaleBiomassPseudoreaction.m similarity index 100% rename from biomass/scaleBiomassPseudoreaction.m rename to src/biomass/scaleBiomassPseudoreaction.m diff --git a/biomass/setGAM.m b/src/biomass/setGAM.m similarity index 100% rename from biomass/setGAM.m rename to src/biomass/setGAM.m diff --git a/comparison/compareMultipleModels.m b/src/comparison/compareMultipleModels.m similarity index 100% rename from comparison/compareMultipleModels.m rename to src/comparison/compareMultipleModels.m diff --git a/comparison/diffModels.m b/src/comparison/diffModels.m similarity index 100% rename from comparison/diffModels.m rename to src/comparison/diffModels.m diff --git a/conditions/applyCondition.m b/src/conditions/applyCondition.m similarity index 100% rename from conditions/applyCondition.m rename to src/conditions/applyCondition.m diff --git a/conversion/COBRA_structure_fields.csv b/src/conversion/COBRA_structure_fields.csv similarity index 100% rename from conversion/COBRA_structure_fields.csv rename to src/conversion/COBRA_structure_fields.csv diff --git a/conversion/addIdentifierPrefix.m b/src/conversion/addIdentifierPrefix.m similarity index 100% rename from conversion/addIdentifierPrefix.m rename to src/conversion/addIdentifierPrefix.m diff --git a/conversion/cobraNamespaces.csv b/src/conversion/cobraNamespaces.csv similarity index 100% rename from conversion/cobraNamespaces.csv rename to src/conversion/cobraNamespaces.csv diff --git a/conversion/orderRavenFields.csv b/src/conversion/orderRavenFields.csv similarity index 100% rename from conversion/orderRavenFields.csv rename to src/conversion/orderRavenFields.csv diff --git a/conversion/ravenCobraWrapper.m b/src/conversion/ravenCobraWrapper.m similarity index 98% rename from conversion/ravenCobraWrapper.m rename to src/conversion/ravenCobraWrapper.m index 652ad187..f82048df 100755 --- a/conversion/ravenCobraWrapper.m +++ b/src/conversion/ravenCobraWrapper.m @@ -45,7 +45,7 @@ ravenPath=findRAVENroot(); % Load COBRA field information -fid = fopen(fullfile(ravenPath,'conversion','COBRA_structure_fields.csv')); % Taken from https://github.com/opencobra/cobratoolbox/blob/develop/src/base/io/definitions/COBRA_structure_fields.csv +fid = fopen(fullfile(ravenPath,'src','conversion','COBRA_structure_fields.csv')); % Taken from https://github.com/opencobra/cobratoolbox/blob/develop/src/base/io/definitions/COBRA_structure_fields.csv fieldFile = textscan(fid,repmat('%s',1,15),'Delimiter','\t','HeaderLines',1); dbFields = ~cellfun(@isempty,fieldFile{5}); % Only keep fields with database annotations that should be translated to xxxMiriams dbFields = dbFields & ~contains(fieldFile{1},{'metInChIString','metKEGGID','metPubChemID','rxnECNumbers'}); @@ -55,7 +55,7 @@ fclose(fid); % Load conversion between additional COBRA fields and namespaces: -fid = fopen(fullfile(ravenPath,'conversion','cobraNamespaces.csv')); +fid = fopen(fullfile(ravenPath,'src','conversion','cobraNamespaces.csv')); fieldFile = textscan(fid,'%s %s','Delimiter',',','HeaderLines',0); COBRAfields = [COBRAfields; fieldFile{1}]; COBRAnamespace = [COBRAnamespace; fieldFile{2}]; diff --git a/conversion/removeIdentifierPrefix.m b/src/conversion/removeIdentifierPrefix.m similarity index 100% rename from conversion/removeIdentifierPrefix.m rename to src/conversion/removeIdentifierPrefix.m diff --git a/conversion/standardizeModelFieldOrder.m b/src/conversion/standardizeModelFieldOrder.m similarity index 85% rename from conversion/standardizeModelFieldOrder.m rename to src/conversion/standardizeModelFieldOrder.m index b81441f1..a9c3d003 100755 --- a/conversion/standardizeModelFieldOrder.m +++ b/src/conversion/standardizeModelFieldOrder.m @@ -29,12 +29,12 @@ ravenPath=findRAVENroot(); if ~isfield(model,'rules') % Check if model is RAVEN - fid = fopen(fullfile(ravenPath,'conversion','orderRavenFields.csv')); + fid = fopen(fullfile(ravenPath,'src','conversion','orderRavenFields.csv')); fields = textscan(fid,'%s','Delimiter',',','HeaderLines',0); fields = fields{1}; fclose(fid); else % If model is COBRA - fid = fopen(fullfile(ravenPath,'conversion','COBRA_structure_fields.csv')); % Taken from https://github.com/opencobra/cobratoolbox/blob/develop/src/base/io/definitions/COBRA_structure_fields.csv + fid = fopen(fullfile(ravenPath,'src','conversion','COBRA_structure_fields.csv')); % Taken from https://github.com/opencobra/cobratoolbox/blob/develop/src/base/io/definitions/COBRA_structure_fields.csv fields = textscan(fid,repmat('%s',1,15),'Delimiter','\t','HeaderLines',1); fields = fields{1}; fclose(fid); diff --git a/curation/curateModelFromTables.m b/src/curation/curateModelFromTables.m similarity index 100% rename from curation/curateModelFromTables.m rename to src/curation/curateModelFromTables.m diff --git a/curation/downloadGenomeData.m b/src/curation/downloadGenomeData.m similarity index 100% rename from curation/downloadGenomeData.m rename to src/curation/downloadGenomeData.m diff --git a/curation/getGeneData.m b/src/curation/getGeneData.m similarity index 100% rename from curation/getGeneData.m rename to src/curation/getGeneData.m diff --git a/curation/processProteinFastaFile.m b/src/curation/processProteinFastaFile.m similarity index 100% rename from curation/processProteinFastaFile.m rename to src/curation/processProteinFastaFile.m diff --git a/curation/renameModelGenes.m b/src/curation/renameModelGenes.m similarity index 100% rename from curation/renameModelGenes.m rename to src/curation/renameModelGenes.m diff --git a/deprecated/canConsume.m b/src/deprecated/canConsume.m similarity index 100% rename from deprecated/canConsume.m rename to src/deprecated/canConsume.m diff --git a/deprecated/canProduce.m b/src/deprecated/canProduce.m similarity index 100% rename from deprecated/canProduce.m rename to src/deprecated/canProduce.m diff --git a/deprecated/consumeSomething.m b/src/deprecated/consumeSomething.m similarity index 100% rename from deprecated/consumeSomething.m rename to src/deprecated/consumeSomething.m diff --git a/deprecated/makeSomething.m b/src/deprecated/makeSomething.m similarity index 100% rename from deprecated/makeSomething.m rename to src/deprecated/makeSomething.m diff --git a/gapfilling/canExchange.m b/src/gapfilling/canExchange.m similarity index 100% rename from gapfilling/canExchange.m rename to src/gapfilling/canExchange.m diff --git a/gapfilling/checkProduction.m b/src/gapfilling/checkProduction.m similarity index 100% rename from gapfilling/checkProduction.m rename to src/gapfilling/checkProduction.m diff --git a/gapfilling/fillGaps.m b/src/gapfilling/fillGaps.m similarity index 100% rename from gapfilling/fillGaps.m rename to src/gapfilling/fillGaps.m diff --git a/gapfilling/findLeakMetabolite.m b/src/gapfilling/findLeakMetabolite.m similarity index 100% rename from gapfilling/findLeakMetabolite.m rename to src/gapfilling/findLeakMetabolite.m diff --git a/gapfilling/fitTasks.m b/src/gapfilling/fitTasks.m similarity index 100% rename from gapfilling/fitTasks.m rename to src/gapfilling/fitTasks.m diff --git a/gapfilling/ftINITFillGaps.m b/src/gapfilling/ftINITFillGaps.m similarity index 100% rename from gapfilling/ftINITFillGaps.m rename to src/gapfilling/ftINITFillGaps.m diff --git a/gapfilling/gapFillFastCore.m b/src/gapfilling/gapFillFastCore.m similarity index 100% rename from gapfilling/gapFillFastCore.m rename to src/gapfilling/gapFillFastCore.m diff --git a/gapfilling/gapFillFastLP.m b/src/gapfilling/gapFillFastLP.m similarity index 100% rename from gapfilling/gapFillFastLP.m rename to src/gapfilling/gapFillFastLP.m diff --git a/gapfilling/gapFillMILP.m b/src/gapfilling/gapFillMILP.m similarity index 100% rename from gapfilling/gapFillMILP.m rename to src/gapfilling/gapFillMILP.m diff --git a/gapfilling/gapFillSwiftCore.m b/src/gapfilling/gapFillSwiftCore.m similarity index 100% rename from gapfilling/gapFillSwiftCore.m rename to src/gapfilling/gapFillSwiftCore.m diff --git a/gapfilling/gapFillTopological.m b/src/gapfilling/gapFillTopological.m similarity index 100% rename from gapfilling/gapFillTopological.m rename to src/gapfilling/gapFillTopological.m diff --git a/gapfilling/gapReport.m b/src/gapfilling/gapReport.m similarity index 100% rename from gapfilling/gapReport.m rename to src/gapfilling/gapReport.m diff --git a/installation/addRavenToUserPath.m b/src/installation/addRavenToUserPath.m similarity index 100% rename from installation/addRavenToUserPath.m rename to src/installation/addRavenToUserPath.m diff --git a/installation/checkFunctionUniqueness.m b/src/installation/checkFunctionUniqueness.m similarity index 100% rename from installation/checkFunctionUniqueness.m rename to src/installation/checkFunctionUniqueness.m diff --git a/installation/downloadRavenBinaries.m b/src/installation/downloadRavenBinaries.m similarity index 92% rename from installation/downloadRavenBinaries.m rename to src/installation/downloadRavenBinaries.m index 16c8ebf9..f440c8a1 100644 --- a/installation/downloadRavenBinaries.m +++ b/src/installation/downloadRavenBinaries.m @@ -44,13 +44,13 @@ function downloadRavenBinaries(tools) case 'blast+' tag = 'blast-2.17.0'; asset = ['blast-2.17.0-' plat '.zip']; - destDir = fullfile(ravenDir,'software','blast+'); + destDir = fullfile(ravenDir,'src','software','blast+'); sentinel = fullfile(destDir,'blastp'); % bare name on every OS (.exe on Windows) execs = {'blastp','makeblastdb'}; case 'diamond' tag = 'diamond-2.1.17'; asset = ['diamond-2.1.17-' plat '.zip']; - destDir = fullfile(ravenDir,'software','diamond'); + destDir = fullfile(ravenDir,'src','software','diamond'); sentinel = fullfile(destDir,'diamond'); execs = {'diamond'}; case 'hmmer' @@ -61,7 +61,7 @@ function downloadRavenBinaries(tools) tag = 'hmmer-3.4.0'; asset = ['hmmer-3.4.0-' plat '.zip']; end - destDir = fullfile(ravenDir,'software','hmmer'); + destDir = fullfile(ravenDir,'src','software','hmmer'); sentinel = fullfile(destDir,'hmmsearch'); execs = {'hmmsearch'}; otherwise @@ -78,7 +78,7 @@ function downloadRavenBinaries(tools) continue; % already provisioned end - zipPath = fetchRavenDataAsset(fullfile(ravenDir,'software'),tag,asset); + zipPath = fetchRavenDataAsset(fullfile(ravenDir,'src','software'),tag,asset); unzip(zipPath,destDir); delete(zipPath); diff --git a/installation/fetchRavenDataAsset.m b/src/installation/fetchRavenDataAsset.m similarity index 100% rename from installation/fetchRavenDataAsset.m rename to src/installation/fetchRavenDataAsset.m diff --git a/installation/findRAVENroot.m b/src/installation/findRAVENroot.m similarity index 87% rename from installation/findRAVENroot.m rename to src/installation/findRAVENroot.m index e828bf79..02334031 100755 --- a/installation/findRAVENroot.m +++ b/src/installation/findRAVENroot.m @@ -1,7 +1,7 @@ function [ravenPath, prevDir] = findRAVENroot() % findRAVENroot % Finds the root of the RAVEN directory by searching for the path to -% RAVEN.png. Can also record the current directory, in case a function will +% version.txt. Can also record the current directory, in case a function will % use the ravenPath to navigate to a precise folder, and it should return to % the previous directory afterwards. See e.g. optimizeProb calling glpk. % @@ -28,7 +28,7 @@ ravenPath = ''; if ispref('RAVEN','ravenPath') prefPath = getpref('RAVEN','ravenPath'); - if isfile(fullfile(prefPath,'RAVEN.png')) + if isfile(fullfile(prefPath,'version.txt')) ravenPath = prefPath; end end @@ -37,14 +37,14 @@ ravenPath = ST(strcmp({ST.name},'findRAVENroot')).file; rootFound = 0; while rootFound == 0 - isRoot = isfile(fullfile(ravenPath,'RAVEN.png')); + isRoot = isfile(fullfile(ravenPath,'version.txt')); if isRoot rootFound = 1; else ravenPathOld = ravenPath; ravenPath = fileparts(ravenPath); if strcmp(ravenPathOld,ravenPath) - error('Cannot find the RAVEN root directory. Make sure you have not removed the RAVEN.png file from your RAVEN installation.') + error('Cannot find the RAVEN root directory. Make sure you have not removed the version.txt file from your RAVEN installation.') end end end diff --git a/installation/removeRavenFromPath.m b/src/installation/removeRavenFromPath.m similarity index 100% rename from installation/removeRavenFromPath.m rename to src/installation/removeRavenFromPath.m diff --git a/installation/updateDocumentation.m b/src/installation/updateDocumentation.m similarity index 62% rename from installation/updateDocumentation.m rename to src/installation/updateDocumentation.m index 1975dde5..efb7ff9a 100755 --- a/installation/updateDocumentation.m +++ b/src/installation/updateDocumentation.m @@ -1,47 +1,48 @@ function updateDocumentation() % updateDocumentation -% Regenerates the HTML documentation in RAVEN's doc directory from the -% function help texts. The doc directory is deleted and rebuilt, so +% Regenerates the HTML documentation in RAVEN's docs/html directory from the +% function help texts. The docs/html directory is deleted and rebuilt, so % anything hand-edited there is lost. % % The Documentation workflow runs this on every push to a release branch -% and commits the result, so doc stays in step with the sources without +% and commits the result, so docs/html stays in step with the sources without % anyone running it by hand. Run it locally only to preview. % % Usage: updateDocumentation() %Get the RAVEN path ravenDir=findRAVENroot(); +srcDir=fullfile(ravenDir,'src'); %Make sure that RAVEN-provided m2html is used -path(fullfile(ravenDir,'software','m2html'),path); +path(fullfile(ravenDir,'src','software','m2html'),path); %Get a non-redundant list of RAVEN subdirectories containing MATLAB %functions. Absolute paths are not compatible with M2HTML, so convert them %to the relative paths instead. -ravenDirs=dir(fullfile(ravenDir,'**/*.m')); +ravenDirs=dir(fullfile(srcDir,'**/*.m')); ravenDirs=unique({ravenDirs.folder})'; %Get rid of MATLAB functions from external software -ravenDirs(startsWith(ravenDirs,strcat(ravenDir,filesep,'software')))=[]; -ravenDirs(startsWith(ravenDirs,strcat(ravenDir,filesep,'legacy',filesep,'software')))=[]; +ravenDirs(startsWith(ravenDirs,strcat(srcDir,filesep,'software')))=[]; +ravenDirs(startsWith(ravenDirs,strcat(srcDir,filesep,'legacy',filesep,'software')))=[]; %Skip deprecated wrappers: they are on the path so existing scripts keep %working, but documenting them would advertise functions that are on their %way out. Each one names its replacement when called. -ravenDirs(startsWith(ravenDirs,strcat(ravenDir,filesep,'deprecated')))=[]; +ravenDirs(startsWith(ravenDirs,strcat(srcDir,filesep,'deprecated')))=[]; %Remove keggModel.mat if it exists -if exist(fullfile(ravenDir,'reconstruction','kegg','keggModel.mat'), 'file') == 2 - delete(fullfile(ravenDir,'reconstruction','kegg','keggModel.mat')); +if exist(fullfile(ravenDir,'src','reconstruction','kegg','keggModel.mat'), 'file') == 2 + delete(fullfile(ravenDir,'src','reconstruction','kegg','keggModel.mat')); end -%Remove the existing "doc" directory from RAVEN -docDir=fullfile(ravenDir,'doc'); +%Remove the existing "docs/html" directory from RAVEN +docDir=fullfile(ravenDir,'docs','html'); if isfolder(docDir) rmdir(docDir,'s'); end %Make relative path -relStart = numel(ravenDir)+2; +relStart = numel(srcDir)+2; for i=1:numel(ravenDirs) ravenDirs{i,1} = ravenDirs{i,1}(relStart:end); end @@ -58,11 +59,11 @@ function updateDocumentation() end end -%Save the current working directory and go to RAVEN root directory +%Save the current working directory and go to RAVEN source directory originalDir=pwd; -cd(ravenDir); +cd(srcDir); %Generate HTML documentation files for RAVEN MATLAB functions -m2html('mFiles',ravenDirs,'htmldir','doc'); +m2html('mFiles',ravenDirs,'htmldir',docDir); %Go back to the original working directory cd(originalDir); diff --git a/io/checkFileExistence.m b/src/io/checkFileExistence.m similarity index 100% rename from io/checkFileExistence.m rename to src/io/checkFileExistence.m diff --git a/io/cleanSheet.m b/src/io/cleanSheet.m similarity index 100% rename from io/cleanSheet.m rename to src/io/cleanSheet.m diff --git a/io/exportForGit.m b/src/io/exportForGit.m similarity index 100% rename from io/exportForGit.m rename to src/io/exportForGit.m diff --git a/io/exportModel.m b/src/io/exportModel.m similarity index 100% rename from io/exportModel.m rename to src/io/exportModel.m diff --git a/io/exportToExcelFormat.m b/src/io/exportToExcelFormat.m similarity index 100% rename from io/exportToExcelFormat.m rename to src/io/exportToExcelFormat.m diff --git a/io/importModel.m b/src/io/importModel.m similarity index 100% rename from io/importModel.m rename to src/io/importModel.m diff --git a/io/parseYAML.m b/src/io/parseYAML.m similarity index 100% rename from io/parseYAML.m rename to src/io/parseYAML.m diff --git a/io/readFasta.m b/src/io/readFasta.m similarity index 100% rename from io/readFasta.m rename to src/io/readFasta.m diff --git a/io/readYAMLmodel.m b/src/io/readYAMLmodel.m similarity index 100% rename from io/readYAMLmodel.m rename to src/io/readYAMLmodel.m diff --git a/io/writeExcel.m b/src/io/writeExcel.m similarity index 100% rename from io/writeExcel.m rename to src/io/writeExcel.m diff --git a/io/writeFasta.m b/src/io/writeFasta.m similarity index 100% rename from io/writeFasta.m rename to src/io/writeFasta.m diff --git a/io/writeYAMLmodel.m b/src/io/writeYAMLmodel.m similarity index 100% rename from io/writeYAMLmodel.m rename to src/io/writeYAMLmodel.m diff --git a/localization/assignCompartments.m b/src/localization/assignCompartments.m similarity index 100% rename from localization/assignCompartments.m rename to src/localization/assignCompartments.m diff --git a/localization/defaultCompartmentMap.m b/src/localization/defaultCompartmentMap.m similarity index 100% rename from localization/defaultCompartmentMap.m rename to src/localization/defaultCompartmentMap.m diff --git a/localization/getUniProtScores.m b/src/localization/getUniProtScores.m similarity index 100% rename from localization/getUniProtScores.m rename to src/localization/getUniProtScores.m diff --git a/localization/parseScores.m b/src/localization/parseScores.m similarity index 100% rename from localization/parseScores.m rename to src/localization/parseScores.m diff --git a/localization/predictLocalization.m b/src/localization/predictLocalization.m similarity index 100% rename from localization/predictLocalization.m rename to src/localization/predictLocalization.m diff --git a/manipulation/addExchangeRxns.m b/src/manipulation/addExchangeRxns.m similarity index 100% rename from manipulation/addExchangeRxns.m rename to src/manipulation/addExchangeRxns.m diff --git a/manipulation/addGenesRaven.m b/src/manipulation/addGenesRaven.m similarity index 100% rename from manipulation/addGenesRaven.m rename to src/manipulation/addGenesRaven.m diff --git a/manipulation/addMets.m b/src/manipulation/addMets.m similarity index 100% rename from manipulation/addMets.m rename to src/manipulation/addMets.m diff --git a/manipulation/addRxns.m b/src/manipulation/addRxns.m similarity index 100% rename from manipulation/addRxns.m rename to src/manipulation/addRxns.m diff --git a/manipulation/addRxnsGenesMets.m b/src/manipulation/addRxnsGenesMets.m similarity index 100% rename from manipulation/addRxnsGenesMets.m rename to src/manipulation/addRxnsGenesMets.m diff --git a/manipulation/addTransport.m b/src/manipulation/addTransport.m similarity index 100% rename from manipulation/addTransport.m rename to src/manipulation/addTransport.m diff --git a/manipulation/changeGrRules.m b/src/manipulation/changeGrRules.m similarity index 100% rename from manipulation/changeGrRules.m rename to src/manipulation/changeGrRules.m diff --git a/manipulation/changeRxns.m b/src/manipulation/changeRxns.m similarity index 100% rename from manipulation/changeRxns.m rename to src/manipulation/changeRxns.m diff --git a/manipulation/closeModel.m b/src/manipulation/closeModel.m similarity index 100% rename from manipulation/closeModel.m rename to src/manipulation/closeModel.m diff --git a/manipulation/contractModel.m b/src/manipulation/contractModel.m similarity index 100% rename from manipulation/contractModel.m rename to src/manipulation/contractModel.m diff --git a/manipulation/convertToIrrev.m b/src/manipulation/convertToIrrev.m similarity index 100% rename from manipulation/convertToIrrev.m rename to src/manipulation/convertToIrrev.m diff --git a/manipulation/copyToComps.m b/src/manipulation/copyToComps.m similarity index 100% rename from manipulation/copyToComps.m rename to src/manipulation/copyToComps.m diff --git a/manipulation/deleteUnusedGenes.m b/src/manipulation/deleteUnusedGenes.m similarity index 100% rename from manipulation/deleteUnusedGenes.m rename to src/manipulation/deleteUnusedGenes.m diff --git a/manipulation/expandModel.m b/src/manipulation/expandModel.m similarity index 100% rename from manipulation/expandModel.m rename to src/manipulation/expandModel.m diff --git a/manipulation/findDuplicateRxns.m b/src/manipulation/findDuplicateRxns.m similarity index 100% rename from manipulation/findDuplicateRxns.m rename to src/manipulation/findDuplicateRxns.m diff --git a/manipulation/findPotentialErrors.m b/src/manipulation/findPotentialErrors.m similarity index 100% rename from manipulation/findPotentialErrors.m rename to src/manipulation/findPotentialErrors.m diff --git a/manipulation/generateNewIds.m b/src/manipulation/generateNewIds.m similarity index 100% rename from manipulation/generateNewIds.m rename to src/manipulation/generateNewIds.m diff --git a/manipulation/mergeCompartments.m b/src/manipulation/mergeCompartments.m similarity index 100% rename from manipulation/mergeCompartments.m rename to src/manipulation/mergeCompartments.m diff --git a/manipulation/mergeModels.m b/src/manipulation/mergeModels.m similarity index 100% rename from manipulation/mergeModels.m rename to src/manipulation/mergeModels.m diff --git a/manipulation/permuteModel.m b/src/manipulation/permuteModel.m similarity index 100% rename from manipulation/permuteModel.m rename to src/manipulation/permuteModel.m diff --git a/manipulation/removeBadRxns.m b/src/manipulation/removeBadRxns.m similarity index 100% rename from manipulation/removeBadRxns.m rename to src/manipulation/removeBadRxns.m diff --git a/manipulation/removeGenes.m b/src/manipulation/removeGenes.m similarity index 100% rename from manipulation/removeGenes.m rename to src/manipulation/removeGenes.m diff --git a/manipulation/removeLowScoreGenes.m b/src/manipulation/removeLowScoreGenes.m similarity index 100% rename from manipulation/removeLowScoreGenes.m rename to src/manipulation/removeLowScoreGenes.m diff --git a/manipulation/removeMets.m b/src/manipulation/removeMets.m similarity index 100% rename from manipulation/removeMets.m rename to src/manipulation/removeMets.m diff --git a/manipulation/removeReactions.m b/src/manipulation/removeReactions.m similarity index 100% rename from manipulation/removeReactions.m rename to src/manipulation/removeReactions.m diff --git a/manipulation/replaceMets.m b/src/manipulation/replaceMets.m similarity index 100% rename from manipulation/replaceMets.m rename to src/manipulation/replaceMets.m diff --git a/manipulation/reverseRxns.m b/src/manipulation/reverseRxns.m similarity index 100% rename from manipulation/reverseRxns.m rename to src/manipulation/reverseRxns.m diff --git a/manipulation/setExchangeBounds.m b/src/manipulation/setExchangeBounds.m similarity index 100% rename from manipulation/setExchangeBounds.m rename to src/manipulation/setExchangeBounds.m diff --git a/manipulation/setParam.m b/src/manipulation/setParam.m similarity index 100% rename from manipulation/setParam.m rename to src/manipulation/setParam.m diff --git a/manipulation/simplifyModel.m b/src/manipulation/simplifyModel.m similarity index 100% rename from manipulation/simplifyModel.m rename to src/manipulation/simplifyModel.m diff --git a/manipulation/sortIdentifiers.m b/src/manipulation/sortIdentifiers.m similarity index 100% rename from manipulation/sortIdentifiers.m rename to src/manipulation/sortIdentifiers.m diff --git a/manipulation/sortModel.m b/src/manipulation/sortModel.m similarity index 100% rename from manipulation/sortModel.m rename to src/manipulation/sortModel.m diff --git a/manipulation/standardizeGrRules.m b/src/manipulation/standardizeGrRules.m similarity index 100% rename from manipulation/standardizeGrRules.m rename to src/manipulation/standardizeGrRules.m diff --git a/queries/buildEquation.m b/src/queries/buildEquation.m similarity index 100% rename from queries/buildEquation.m rename to src/queries/buildEquation.m diff --git a/queries/checkModelStruct.m b/src/queries/checkModelStruct.m similarity index 100% rename from queries/checkModelStruct.m rename to src/queries/checkModelStruct.m diff --git a/queries/constructEquations.m b/src/queries/constructEquations.m similarity index 100% rename from queries/constructEquations.m rename to src/queries/constructEquations.m diff --git a/queries/constructS.m b/src/queries/constructS.m similarity index 100% rename from queries/constructS.m rename to src/queries/constructS.m diff --git a/queries/getAllRxnsFromGenes.m b/src/queries/getAllRxnsFromGenes.m similarity index 100% rename from queries/getAllRxnsFromGenes.m rename to src/queries/getAllRxnsFromGenes.m diff --git a/queries/getElementalBalance.m b/src/queries/getElementalBalance.m similarity index 100% rename from queries/getElementalBalance.m rename to src/queries/getElementalBalance.m diff --git a/queries/getExchangeRxns.m b/src/queries/getExchangeRxns.m similarity index 100% rename from queries/getExchangeRxns.m rename to src/queries/getExchangeRxns.m diff --git a/queries/getGenesFromGrRules.m b/src/queries/getGenesFromGrRules.m similarity index 100% rename from queries/getGenesFromGrRules.m rename to src/queries/getGenesFromGrRules.m diff --git a/queries/getIndexes.m b/src/queries/getIndexes.m similarity index 100% rename from queries/getIndexes.m rename to src/queries/getIndexes.m diff --git a/queries/getRxnsInComp.m b/src/queries/getRxnsInComp.m similarity index 100% rename from queries/getRxnsInComp.m rename to src/queries/getRxnsInComp.m diff --git a/queries/getTransportRxns.m b/src/queries/getTransportRxns.m similarity index 100% rename from queries/getTransportRxns.m rename to src/queries/getTransportRxns.m diff --git a/queries/modelSummary.m b/src/queries/modelSummary.m similarity index 100% rename from queries/modelSummary.m rename to src/queries/modelSummary.m diff --git a/queries/parseFormulas.m b/src/queries/parseFormulas.m similarity index 100% rename from queries/parseFormulas.m rename to src/queries/parseFormulas.m diff --git a/queries/parseRxnEqu.m b/src/queries/parseRxnEqu.m similarity index 100% rename from queries/parseRxnEqu.m rename to src/queries/parseRxnEqu.m diff --git a/queries/printFluxes.m b/src/queries/printFluxes.m similarity index 100% rename from queries/printFluxes.m rename to src/queries/printFluxes.m diff --git a/queries/printModelStats.m b/src/queries/printModelStats.m similarity index 100% rename from queries/printModelStats.m rename to src/queries/printModelStats.m diff --git a/reconstruction/guessComposition.m b/src/reconstruction/guessComposition.m similarity index 100% rename from reconstruction/guessComposition.m rename to src/reconstruction/guessComposition.m diff --git a/reconstruction/homology/getBlast.m b/src/reconstruction/homology/getBlast.m similarity index 87% rename from reconstruction/homology/getBlast.m rename to src/reconstruction/homology/getBlast.m index a85e00e4..0d6b19e5 100755 --- a/reconstruction/homology/getBlast.m +++ b/src/reconstruction/homology/getBlast.m @@ -109,7 +109,7 @@ end %Fetch the BLAST+ binaries on demand if they are not already present -if ~exist(fullfile(ravenPath,'software','blast+',['blastp' binEnd]),'file') +if ~exist(fullfile(ravenPath,'src','software','blast+',['blastp' binEnd]),'file') downloadRavenBinaries({'blast+'}); end @@ -121,7 +121,7 @@ %Create a database for the new organism and blast each of the refFastaFiles %against it -[status, message]=system(['"' fullfile(ravenPath,'software','blast+',['makeblastdb' binEnd]) '" -in "' fastaFile{1} '" -out "' fullfile(tmpDB, 'tmpDB') '" -dbtype prot']); +[status, message]=system(['"' fullfile(ravenPath,'src','software','blast+',['makeblastdb' binEnd]) '" -in "' fastaFile{1} '" -out "' fullfile(tmpDB, 'tmpDB') '" -dbtype prot']); if developMode blastReport.dbHashes.phr{numel(blastReport.dbHashes.phr)+1}=filemd5(fullfile(tmpDB, 'tmpDB.phr')); blastReport.dbHashes.pot{numel(blastReport.dbHashes.pot)+1}=filemd5(fullfile(tmpDB, 'tmpDB.pot')); @@ -136,7 +136,7 @@ if ~hideVerbose fprintf(['BLASTing "' modelIDs{i} '" against "' organismID{1} '"..\n']); end - [status, message]=system(['"' fullfile(ravenPath,'software','blast+',['blastp' binEnd]) '" -query "' refFastaFiles{i} '" -out "' outFile '_' num2str(i) '" -db "' fullfile(tmpDB, 'tmpDB') '" -evalue 10e-5 -outfmt "10 qseqid sseqid evalue pident length bitscore ppos" -num_threads "' cores '"']); + [status, message]=system(['"' fullfile(ravenPath,'src','software','blast+',['blastp' binEnd]) '" -query "' refFastaFiles{i} '" -out "' outFile '_' num2str(i) '" -db "' fullfile(tmpDB, 'tmpDB') '" -evalue 10e-5 -outfmt "10 qseqid sseqid evalue pident length bitscore ppos" -num_threads "' cores '"']); if developMode blastReport.blastTxtOutput{numel(blastReport.blastTxtOutput)+1}=importdata([outFile '_' num2str(i)]); end @@ -152,11 +152,11 @@ if ~hideVerbose fprintf(['BLASTing "' organismID{1} '" against "' modelIDs{i} '"..\n']); end - [status, message]=system(['"' fullfile(ravenPath,'software','blast+',['makeblastdb' binEnd]) '" -in "' refFastaFiles{i} '" -out "' fullfile(tmpDB, 'tmpDB') '" -dbtype prot']); + [status, message]=system(['"' fullfile(ravenPath,'src','software','blast+',['makeblastdb' binEnd]) '" -in "' refFastaFiles{i} '" -out "' fullfile(tmpDB, 'tmpDB') '" -dbtype prot']); if status~=0 error('makeblastdb did not run successfully, error:\n%s',strip(message)) end - [status, message]=system(['"' fullfile(ravenPath,'software','blast+',['blastp' binEnd]) '" -query "' fastaFile{1} '" -out "' outFile '_r' num2str(i) '" -db "' fullfile(tmpDB, 'tmpDB') '" -evalue 10e-5 -outfmt "10 qseqid sseqid evalue pident length bitscore ppos" -num_threads "' cores '"']); + [status, message]=system(['"' fullfile(ravenPath,'src','software','blast+',['blastp' binEnd]) '" -query "' fastaFile{1} '" -out "' outFile '_r' num2str(i) '" -db "' fullfile(tmpDB, 'tmpDB') '" -evalue 10e-5 -outfmt "10 qseqid sseqid evalue pident length bitscore ppos" -num_threads "' cores '"']); if developMode blastReport.dbHashes.phr{numel(blastReport.dbHashes.phr)+1}=filemd5(fullfile(tmpDB, 'tmpDB.phr')); blastReport.dbHashes.pot{numel(blastReport.dbHashes.pot)+1}=filemd5(fullfile(tmpDB, 'tmpDB.pot')); diff --git a/reconstruction/homology/getDiamond.m b/src/reconstruction/homology/getDiamond.m similarity index 85% rename from reconstruction/homology/getDiamond.m rename to src/reconstruction/homology/getDiamond.m index bea379d5..1e927ddb 100755 --- a/reconstruction/homology/getDiamond.m +++ b/src/reconstruction/homology/getDiamond.m @@ -98,7 +98,7 @@ end %Fetch the DIAMOND binary on demand if it is not already present -if ~exist(fullfile(ravenPath,'software','diamond',['diamond' binEnd]),'file') +if ~exist(fullfile(ravenPath,'src','software','diamond',['diamond' binEnd]),'file') downloadRavenBinaries({'diamond'}); end @@ -110,7 +110,7 @@ %Create a database for the new organism and blast each of the refFastaFiles %against it -[status, message]=system(['"' fullfile(ravenPath,'software','diamond',['diamond' binEnd]) '" makedb --in "' fastaFile{1} '" --db "' fullfile(tmpDB) '"']); +[status, message]=system(['"' fullfile(ravenPath,'src','software','diamond',['diamond' binEnd]) '" makedb --in "' fastaFile{1} '" --db "' fullfile(tmpDB) '"']); if developMode diamondReport.dbHashes{numel(diamondReport.dbHashes)+1} = char(regexp(message,'[a-f0-9]{32}','match')); end @@ -122,7 +122,7 @@ if ~hideVerbose fprintf(['Running DIAMOND blastp with "' modelIDs{i} '" against "' organismID{1} '"..\n']); end - [status, message]=system(['"' fullfile(ravenPath,'software','diamond',['diamond' binEnd]) '" blastp --query "' refFastaFiles{i} '" --out "' outFile '_' num2str(i) '" --db "' fullfile(tmpDB) '" --more-sensitive --outfmt 6 qseqid sseqid evalue pident length bitscore ppos --threads ' cores ]); + [status, message]=system(['"' fullfile(ravenPath,'src','software','diamond',['diamond' binEnd]) '" blastp --query "' refFastaFiles{i} '" --out "' outFile '_' num2str(i) '" --db "' fullfile(tmpDB) '" --more-sensitive --outfmt 6 qseqid sseqid evalue pident length bitscore ppos --threads ' cores ]); if developMode diamondReport.diamondTxtOutput{numel(diamondReport.diamondTxtOutput)+1}=importdata([outFile '_' num2str(i)]); end @@ -138,11 +138,11 @@ if ~hideVerbose fprintf(['Running DIAMOND blastp with "' organismID{1} '" against "' modelIDs{i} '"..\n']); end - [status, message1]=system(['"' fullfile(ravenPath,'software','diamond',['diamond' binEnd]) '" makedb --in "' refFastaFiles{i} '" --db "' fullfile(tmpDB) '"']); + [status, message1]=system(['"' fullfile(ravenPath,'src','software','diamond',['diamond' binEnd]) '" makedb --in "' refFastaFiles{i} '" --db "' fullfile(tmpDB) '"']); if status~=0 error('DIAMOND makedb did not run successfully, error:\n%s',strip(message1)) end - [status, message]=system(['"' fullfile(ravenPath,'software','diamond',['diamond' binEnd]) '" blastp --query "' fastaFile{1} '" --out "' outFile '_r' num2str(i) '" --db "' fullfile(tmpDB) '" --more-sensitive --outfmt 6 qseqid sseqid evalue pident length bitscore ppos --threads ' cores]); + [status, message]=system(['"' fullfile(ravenPath,'src','software','diamond',['diamond' binEnd]) '" blastp --query "' fastaFile{1} '" --out "' outFile '_r' num2str(i) '" --db "' fullfile(tmpDB) '" --more-sensitive --outfmt 6 qseqid sseqid evalue pident length bitscore ppos --threads ' cores]); if developMode diamondReport.dbHashes{numel(diamondReport.dbHashes)+1} = char(regexp(message1,'[a-f0-9]{32}','match')); diamondReport.diamondTxtOutput{numel(diamondReport.diamondTxtOutput)+1}=importdata([outFile '_r' num2str(i)]); diff --git a/reconstruction/homology/getModelFromHomology.m b/src/reconstruction/homology/getModelFromHomology.m similarity index 100% rename from reconstruction/homology/getModelFromHomology.m rename to src/reconstruction/homology/getModelFromHomology.m diff --git a/reconstruction/homology/makeFakeBlastStructure.m b/src/reconstruction/homology/makeFakeBlastStructure.m similarity index 100% rename from reconstruction/homology/makeFakeBlastStructure.m rename to src/reconstruction/homology/makeFakeBlastStructure.m diff --git a/reconstruction/kegg/KEGG_VERSION.md b/src/reconstruction/kegg/KEGG_VERSION.md similarity index 100% rename from reconstruction/kegg/KEGG_VERSION.md rename to src/reconstruction/kegg/KEGG_VERSION.md diff --git a/reconstruction/kegg/buildGlobalGPR.m b/src/reconstruction/kegg/buildGlobalGPR.m similarity index 100% rename from reconstruction/kegg/buildGlobalGPR.m rename to src/reconstruction/kegg/buildGlobalGPR.m diff --git a/reconstruction/kegg/buildGlobalKEGGModel.m b/src/reconstruction/kegg/buildGlobalKEGGModel.m similarity index 97% rename from reconstruction/kegg/buildGlobalKEGGModel.m rename to src/reconstruction/kegg/buildGlobalKEGGModel.m index da6b0a65..e25dd916 100644 --- a/reconstruction/kegg/buildGlobalKEGGModel.m +++ b/src/reconstruction/kegg/buildGlobalKEGGModel.m @@ -26,7 +26,7 @@ % reaction ids carrying the corresponding rxn_flags quality flag. kver=keggDataVersion(); -keggDir=fullfile(ravenPath,'reconstruction','kegg'); +keggDir=fullfile(ravenPath,'src','reconstruction','kegg'); archive=fetchRavenDataAsset(keggDir,kver,[kver '_core.tar.gz']); coreDir=fullfile(keggDir,[kver '_core']); diff --git a/reconstruction/kegg/getKEGGModelForOrganism.m b/src/reconstruction/kegg/getKEGGModelForOrganism.m similarity index 98% rename from reconstruction/kegg/getKEGGModelForOrganism.m rename to src/reconstruction/kegg/getKEGGModelForOrganism.m index 6aa08f9e..b27421ee 100755 --- a/reconstruction/kegg/getKEGGModelForOrganism.m +++ b/src/reconstruction/kegg/getKEGGModelForOrganism.m @@ -295,7 +295,7 @@ %First remove all reactions without genes if keepSpontaneous==true fprintf('Removing non-spontaneous reactions without GPR rules... '); - load(fullfile(ravenPath,'reconstruction','kegg','keggModel.mat'),'isSpontaneous'); + load(fullfile(ravenPath,'src','reconstruction','kegg','keggModel.mat'),'isSpontaneous'); I=~any(model.rxnGeneMat,2)&~ismember(model.rxns,isSpontaneous); spontRxnsWithGenes=model.rxns(any(model.rxnGeneMat,2)&~ismember(model.rxns,isSpontaneous)); else @@ -368,12 +368,12 @@ binEnd=''; end %Fetch the HMMER binary on demand if it is not already present -if ~exist(fullfile(ravenPath,'software','hmmer',['hmmsearch' binEnd]),'file') +if ~exist(fullfile(ravenPath,'src','software','hmmer',['hmmsearch' binEnd]),'file') downloadRavenBinaries({'hmmer'}); end tblFile=[tempname '.tblout']; fprintf('Querying the user-specified FASTA file against the KEGG Orthology specific HMMs... '); -[status, output]=system(['"' fullfile(ravenPath,'software','hmmer',['hmmsearch' binEnd]) '" --cpu "' num2str(cores) '" --tblout "' tblFile '" "' libraryFile '" "' fastaFile '"']); +[status, output]=system(['"' fullfile(ravenPath,'src','software','hmmer',['hmmsearch' binEnd]) '" --cpu "' num2str(cores) '" --tblout "' tblFile '" "' libraryFile '" "' fastaFile '"']); if status~=0 EM=['Error when querying the concatenated HMM library:\n' output]; error('RAVEN:badInput', '%s', EM); diff --git a/reconstruction/kegg/getModelFromKEGG.m b/src/reconstruction/kegg/getModelFromKEGG.m similarity index 97% rename from reconstruction/kegg/getModelFromKEGG.m rename to src/reconstruction/kegg/getModelFromKEGG.m index ca8a3780..f61ba301 100755 --- a/reconstruction/kegg/getModelFromKEGG.m +++ b/src/reconstruction/kegg/getModelFromKEGG.m @@ -49,7 +49,7 @@ keepIncomplete=p.keepIncomplete; keepGeneral=p.keepGeneral; -modelFile=fullfile(ravenPath,'reconstruction','kegg','keggModel.mat'); +modelFile=fullfile(ravenPath,'src','reconstruction','kegg','keggModel.mat'); if isfile(modelFile) fprintf(['Importing the global KEGG model from ' strrep(modelFile,'\','/') '... ']); load(modelFile,'model','KOModel','isSpontaneous','isUndefinedStoich','isIncomplete','isGeneral'); diff --git a/reconstruction/kegg/getPhylDist.m b/src/reconstruction/kegg/getPhylDist.m similarity index 94% rename from reconstruction/kegg/getPhylDist.m rename to src/reconstruction/kegg/getPhylDist.m index 1a8c7244..f4bbbc43 100755 --- a/reconstruction/kegg/getPhylDist.m +++ b/src/reconstruction/kegg/getPhylDist.m @@ -27,7 +27,7 @@ onlyInKingdom=p.onlyInKingdom; ravenPath=findRAVENroot(); -distFile=fullfile(ravenPath,'reconstruction','kegg','keggPhylDist.mat'); +distFile=fullfile(ravenPath,'src','reconstruction','kegg','keggPhylDist.mat'); if ~exist(distFile,'file') error('getPhylDist:noData', ... ['keggPhylDist.mat not found at ' strrep(distFile,'\','/') '.\n' ... diff --git a/reconstruction/kegg/keggDataVersion.m b/src/reconstruction/kegg/keggDataVersion.m similarity index 89% rename from reconstruction/kegg/keggDataVersion.m rename to src/reconstruction/kegg/keggDataVersion.m index f8a8e9d0..7fec3802 100644 --- a/reconstruction/kegg/keggDataVersion.m +++ b/src/reconstruction/kegg/keggDataVersion.m @@ -13,7 +13,7 @@ % the raven-data release tag, e.g. 'kegg118'. ravenPath=findRAVENroot(); -versionFile=fullfile(ravenPath,'reconstruction','kegg','KEGG_VERSION.md'); +versionFile=fullfile(ravenPath,'src','reconstruction','kegg','KEGG_VERSION.md'); fid=fopen(versionFile,'r'); if fid==-1 error('keggDataVersion:fileNotFound','Cannot read %s.',strrep(versionFile,'\','/')) diff --git a/reconstruction/kegg/keggPhylDist.mat b/src/reconstruction/kegg/keggPhylDist.mat similarity index 100% rename from reconstruction/kegg/keggPhylDist.mat rename to src/reconstruction/kegg/keggPhylDist.mat diff --git a/reconstruction/kegg/readKEGGTable.m b/src/reconstruction/kegg/readKEGGTable.m similarity index 100% rename from reconstruction/kegg/readKEGGTable.m rename to src/reconstruction/kegg/readKEGGTable.m diff --git a/software/GLPKmex/COPYING b/src/software/GLPKmex/COPYING similarity index 100% rename from software/GLPKmex/COPYING rename to src/software/GLPKmex/COPYING diff --git a/software/GLPKmex/README.md b/src/software/GLPKmex/README.md similarity index 100% rename from software/GLPKmex/README.md rename to src/software/GLPKmex/README.md diff --git a/software/GLPKmex/glpk.m b/src/software/GLPKmex/glpk.m similarity index 100% rename from software/GLPKmex/glpk.m rename to src/software/GLPKmex/glpk.m diff --git a/software/GLPKmex/glpkcc.mexa64 b/src/software/GLPKmex/glpkcc.mexa64 similarity index 100% rename from software/GLPKmex/glpkcc.mexa64 rename to src/software/GLPKmex/glpkcc.mexa64 diff --git a/software/GLPKmex/glpkcc.mexmaca64 b/src/software/GLPKmex/glpkcc.mexmaca64 similarity index 100% rename from software/GLPKmex/glpkcc.mexmaca64 rename to src/software/GLPKmex/glpkcc.mexmaca64 diff --git a/software/GLPKmex/glpkcc.mexmaci64 b/src/software/GLPKmex/glpkcc.mexmaci64 similarity index 100% rename from software/GLPKmex/glpkcc.mexmaci64 rename to src/software/GLPKmex/glpkcc.mexmaci64 diff --git a/software/GLPKmex/glpkcc.mexw64 b/src/software/GLPKmex/glpkcc.mexw64 similarity index 100% rename from software/GLPKmex/glpkcc.mexw64 rename to src/software/GLPKmex/glpkcc.mexw64 diff --git a/software/blast+/.keep b/src/software/blast+/.keep similarity index 100% rename from software/blast+/.keep rename to src/software/blast+/.keep diff --git a/software/diamond/.keep b/src/software/diamond/.keep similarity index 100% rename from software/diamond/.keep rename to src/software/diamond/.keep diff --git a/software/hmmer/.keep b/src/software/hmmer/.keep similarity index 100% rename from software/hmmer/.keep rename to src/software/hmmer/.keep diff --git a/software/libSBML/CheckAndConvert.m b/src/software/libSBML/CheckAndConvert.m similarity index 100% rename from software/libSBML/CheckAndConvert.m rename to src/software/libSBML/CheckAndConvert.m diff --git a/software/libSBML/ConvertFormulaToMathML.m b/src/software/libSBML/ConvertFormulaToMathML.m similarity index 100% rename from software/libSBML/ConvertFormulaToMathML.m rename to src/software/libSBML/ConvertFormulaToMathML.m diff --git a/software/libSBML/OutputSBML_RAVEN.m b/src/software/libSBML/OutputSBML_RAVEN.m similarity index 100% rename from software/libSBML/OutputSBML_RAVEN.m rename to src/software/libSBML/OutputSBML_RAVEN.m diff --git a/software/libSBML/OutputSBML_RAVEN.mexa64 b/src/software/libSBML/OutputSBML_RAVEN.mexa64 old mode 100644 new mode 100755 similarity index 100% rename from software/libSBML/OutputSBML_RAVEN.mexa64 rename to src/software/libSBML/OutputSBML_RAVEN.mexa64 diff --git a/software/libSBML/OutputSBML_RAVEN.mexmaca64 b/src/software/libSBML/OutputSBML_RAVEN.mexmaca64 old mode 100644 new mode 100755 similarity index 100% rename from software/libSBML/OutputSBML_RAVEN.mexmaca64 rename to src/software/libSBML/OutputSBML_RAVEN.mexmaca64 diff --git a/software/libSBML/OutputSBML_RAVEN.mexmaci64 b/src/software/libSBML/OutputSBML_RAVEN.mexmaci64 old mode 100644 new mode 100755 similarity index 100% rename from software/libSBML/OutputSBML_RAVEN.mexmaci64 rename to src/software/libSBML/OutputSBML_RAVEN.mexmaci64 diff --git a/software/libSBML/OutputSBML_RAVEN.mexw64 b/src/software/libSBML/OutputSBML_RAVEN.mexw64 similarity index 100% rename from software/libSBML/OutputSBML_RAVEN.mexw64 rename to src/software/libSBML/OutputSBML_RAVEN.mexw64 diff --git a/software/libSBML/README.md b/src/software/libSBML/README.md similarity index 100% rename from software/libSBML/README.md rename to src/software/libSBML/README.md diff --git a/software/libSBML/TranslateSBML_RAVEN.m b/src/software/libSBML/TranslateSBML_RAVEN.m similarity index 100% rename from software/libSBML/TranslateSBML_RAVEN.m rename to src/software/libSBML/TranslateSBML_RAVEN.m diff --git a/software/libSBML/TranslateSBML_RAVEN.mexa64 b/src/software/libSBML/TranslateSBML_RAVEN.mexa64 old mode 100644 new mode 100755 similarity index 100% rename from software/libSBML/TranslateSBML_RAVEN.mexa64 rename to src/software/libSBML/TranslateSBML_RAVEN.mexa64 diff --git a/software/libSBML/TranslateSBML_RAVEN.mexmaca64 b/src/software/libSBML/TranslateSBML_RAVEN.mexmaca64 old mode 100644 new mode 100755 similarity index 100% rename from software/libSBML/TranslateSBML_RAVEN.mexmaca64 rename to src/software/libSBML/TranslateSBML_RAVEN.mexmaca64 diff --git a/software/libSBML/TranslateSBML_RAVEN.mexmaci64 b/src/software/libSBML/TranslateSBML_RAVEN.mexmaci64 old mode 100644 new mode 100755 similarity index 100% rename from software/libSBML/TranslateSBML_RAVEN.mexmaci64 rename to src/software/libSBML/TranslateSBML_RAVEN.mexmaci64 diff --git a/software/libSBML/TranslateSBML_RAVEN.mexw64 b/src/software/libSBML/TranslateSBML_RAVEN.mexw64 similarity index 100% rename from software/libSBML/TranslateSBML_RAVEN.mexw64 rename to src/software/libSBML/TranslateSBML_RAVEN.mexw64 diff --git a/software/libSBML/VERSION.txt b/src/software/libSBML/VERSION.txt similarity index 100% rename from software/libSBML/VERSION.txt rename to src/software/libSBML/VERSION.txt diff --git a/software/libSBML/addLevelVersion.m b/src/software/libSBML/addLevelVersion.m similarity index 100% rename from software/libSBML/addLevelVersion.m rename to src/software/libSBML/addLevelVersion.m diff --git a/software/libSBML/applyUserValidation.m b/src/software/libSBML/applyUserValidation.m similarity index 100% rename from software/libSBML/applyUserValidation.m rename to src/software/libSBML/applyUserValidation.m diff --git a/software/libSBML/getDefaultValues.m b/src/software/libSBML/getDefaultValues.m similarity index 100% rename from software/libSBML/getDefaultValues.m rename to src/software/libSBML/getDefaultValues.m diff --git a/software/libSBML/getStructure.m b/src/software/libSBML/getStructure.m similarity index 100% rename from software/libSBML/getStructure.m rename to src/software/libSBML/getStructure.m diff --git a/software/libSBML/getStructureEnum.m b/src/software/libSBML/getStructureEnum.m similarity index 100% rename from software/libSBML/getStructureEnum.m rename to src/software/libSBML/getStructureEnum.m diff --git a/software/libSBML/getStructureFieldnames.m b/src/software/libSBML/getStructureFieldnames.m similarity index 100% rename from software/libSBML/getStructureFieldnames.m rename to src/software/libSBML/getStructureFieldnames.m diff --git a/software/libSBML/getValueType.m b/src/software/libSBML/getValueType.m similarity index 100% rename from software/libSBML/getValueType.m rename to src/software/libSBML/getValueType.m diff --git a/software/libSBML/isSBML_Model.m b/src/software/libSBML/isSBML_Model.m similarity index 100% rename from software/libSBML/isSBML_Model.m rename to src/software/libSBML/isSBML_Model.m diff --git a/software/libSBML/isoctave.m b/src/software/libSBML/isoctave.m similarity index 100% rename from software/libSBML/isoctave.m rename to src/software/libSBML/isoctave.m diff --git a/software/m2html/@template/char.m b/src/software/m2html/@template/char.m similarity index 100% rename from software/m2html/@template/char.m rename to src/software/m2html/@template/char.m diff --git a/software/m2html/@template/display.m b/src/software/m2html/@template/display.m similarity index 100% rename from software/m2html/@template/display.m rename to src/software/m2html/@template/display.m diff --git a/software/m2html/@template/get.m b/src/software/m2html/@template/get.m similarity index 100% rename from software/m2html/@template/get.m rename to src/software/m2html/@template/get.m diff --git a/software/m2html/@template/parse.m b/src/software/m2html/@template/parse.m similarity index 100% rename from software/m2html/@template/parse.m rename to src/software/m2html/@template/parse.m diff --git a/software/m2html/@template/private/finish.m b/src/software/m2html/@template/private/finish.m similarity index 100% rename from software/m2html/@template/private/finish.m rename to src/software/m2html/@template/private/finish.m diff --git a/software/m2html/@template/private/loadtpl.m b/src/software/m2html/@template/private/loadtpl.m similarity index 100% rename from software/m2html/@template/private/loadtpl.m rename to src/software/m2html/@template/private/loadtpl.m diff --git a/software/m2html/@template/private/subst.m b/src/software/m2html/@template/private/subst.m similarity index 100% rename from software/m2html/@template/private/subst.m rename to src/software/m2html/@template/private/subst.m diff --git a/software/m2html/@template/set.m b/src/software/m2html/@template/set.m similarity index 100% rename from software/m2html/@template/set.m rename to src/software/m2html/@template/set.m diff --git a/software/m2html/@template/template.m b/src/software/m2html/@template/template.m similarity index 100% rename from software/m2html/@template/template.m rename to src/software/m2html/@template/template.m diff --git a/software/m2html/Changelog b/src/software/m2html/Changelog similarity index 100% rename from software/m2html/Changelog rename to src/software/m2html/Changelog diff --git a/software/m2html/Contents.m b/src/software/m2html/Contents.m similarity index 100% rename from software/m2html/Contents.m rename to src/software/m2html/Contents.m diff --git a/software/m2html/GPL b/src/software/m2html/GPL similarity index 100% rename from software/m2html/GPL rename to src/software/m2html/GPL diff --git a/software/m2html/INSTALL b/src/software/m2html/INSTALL similarity index 100% rename from software/m2html/INSTALL rename to src/software/m2html/INSTALL diff --git a/software/m2html/LICENSE b/src/software/m2html/LICENSE similarity index 100% rename from software/m2html/LICENSE rename to src/software/m2html/LICENSE diff --git a/software/m2html/README b/src/software/m2html/README similarity index 100% rename from software/m2html/README rename to src/software/m2html/README diff --git a/software/m2html/TODO b/src/software/m2html/TODO similarity index 100% rename from software/m2html/TODO rename to src/software/m2html/TODO diff --git a/software/m2html/m2html.m b/src/software/m2html/m2html.m similarity index 100% rename from software/m2html/m2html.m rename to src/software/m2html/m2html.m diff --git a/software/m2html/mdot.m b/src/software/m2html/mdot.m similarity index 100% rename from software/m2html/mdot.m rename to src/software/m2html/mdot.m diff --git a/software/m2html/private/doxyread.m b/src/software/m2html/private/doxyread.m similarity index 100% rename from software/m2html/private/doxyread.m rename to src/software/m2html/private/doxyread.m diff --git a/software/m2html/private/doxysearch.m b/src/software/m2html/private/doxysearch.m similarity index 100% rename from software/m2html/private/doxysearch.m rename to src/software/m2html/private/doxysearch.m diff --git a/software/m2html/private/doxywrite.m b/src/software/m2html/private/doxywrite.m similarity index 100% rename from software/m2html/private/doxywrite.m rename to src/software/m2html/private/doxywrite.m diff --git a/software/m2html/private/m2htmltoolbarimages.mat b/src/software/m2html/private/m2htmltoolbarimages.mat similarity index 100% rename from software/m2html/private/m2htmltoolbarimages.mat rename to src/software/m2html/private/m2htmltoolbarimages.mat diff --git a/software/m2html/private/mexexts.m b/src/software/m2html/private/mexexts.m similarity index 100% rename from software/m2html/private/mexexts.m rename to src/software/m2html/private/mexexts.m diff --git a/software/m2html/private/mfileparse.m b/src/software/m2html/private/mfileparse.m similarity index 100% rename from software/m2html/private/mfileparse.m rename to src/software/m2html/private/mfileparse.m diff --git a/software/m2html/private/openfile.m b/src/software/m2html/private/openfile.m similarity index 100% rename from software/m2html/private/openfile.m rename to src/software/m2html/private/openfile.m diff --git a/software/m2html/private/searchindex.m b/src/software/m2html/private/searchindex.m similarity index 100% rename from software/m2html/private/searchindex.m rename to src/software/m2html/private/searchindex.m diff --git a/software/m2html/private/splitcode.m b/src/software/m2html/private/splitcode.m similarity index 100% rename from software/m2html/private/splitcode.m rename to src/software/m2html/private/splitcode.m diff --git a/software/m2html/private/strtok.m b/src/software/m2html/private/strtok.m similarity index 100% rename from software/m2html/private/strtok.m rename to src/software/m2html/private/strtok.m diff --git a/software/m2html/templates/blue/Thumbs.db b/src/software/m2html/templates/blue/Thumbs.db similarity index 100% rename from software/m2html/templates/blue/Thumbs.db rename to src/software/m2html/templates/blue/Thumbs.db diff --git a/software/m2html/templates/blue/alpha.png b/src/software/m2html/templates/blue/alpha.png similarity index 100% rename from software/m2html/templates/blue/alpha.png rename to src/software/m2html/templates/blue/alpha.png diff --git a/software/m2html/templates/blue/c++.png b/src/software/m2html/templates/blue/c++.png similarity index 100% rename from software/m2html/templates/blue/c++.png rename to src/software/m2html/templates/blue/c++.png diff --git a/software/m2html/templates/blue/c.png b/src/software/m2html/templates/blue/c.png similarity index 100% rename from software/m2html/templates/blue/c.png rename to src/software/m2html/templates/blue/c.png diff --git a/software/m2html/templates/blue/demoicon.gif b/src/software/m2html/templates/blue/demoicon.gif similarity index 100% rename from software/m2html/templates/blue/demoicon.gif rename to src/software/m2html/templates/blue/demoicon.gif diff --git a/software/m2html/templates/blue/down.png b/src/software/m2html/templates/blue/down.png similarity index 100% rename from software/m2html/templates/blue/down.png rename to src/software/m2html/templates/blue/down.png diff --git a/software/m2html/templates/blue/doxysearch.php b/src/software/m2html/templates/blue/doxysearch.php similarity index 100% rename from software/m2html/templates/blue/doxysearch.php rename to src/software/m2html/templates/blue/doxysearch.php diff --git a/software/m2html/templates/blue/fortran.png b/src/software/m2html/templates/blue/fortran.png similarity index 100% rename from software/m2html/templates/blue/fortran.png rename to src/software/m2html/templates/blue/fortran.png diff --git a/software/m2html/templates/blue/graph.tpl b/src/software/m2html/templates/blue/graph.tpl similarity index 100% rename from software/m2html/templates/blue/graph.tpl rename to src/software/m2html/templates/blue/graph.tpl diff --git a/software/m2html/templates/blue/hp.png b/src/software/m2html/templates/blue/hp.png similarity index 100% rename from software/m2html/templates/blue/hp.png rename to src/software/m2html/templates/blue/hp.png diff --git a/software/m2html/templates/blue/left.png b/src/software/m2html/templates/blue/left.png similarity index 100% rename from software/m2html/templates/blue/left.png rename to src/software/m2html/templates/blue/left.png diff --git a/software/m2html/templates/blue/linux.png b/src/software/m2html/templates/blue/linux.png similarity index 100% rename from software/m2html/templates/blue/linux.png rename to src/software/m2html/templates/blue/linux.png diff --git a/software/m2html/templates/blue/m2html.css b/src/software/m2html/templates/blue/m2html.css similarity index 100% rename from software/m2html/templates/blue/m2html.css rename to src/software/m2html/templates/blue/m2html.css diff --git a/software/m2html/templates/blue/master.tpl b/src/software/m2html/templates/blue/master.tpl similarity index 100% rename from software/m2html/templates/blue/master.tpl rename to src/software/m2html/templates/blue/master.tpl diff --git a/software/m2html/templates/blue/matlabicon.gif b/src/software/m2html/templates/blue/matlabicon.gif similarity index 100% rename from software/m2html/templates/blue/matlabicon.gif rename to src/software/m2html/templates/blue/matlabicon.gif diff --git a/software/m2html/templates/blue/mdir.tpl b/src/software/m2html/templates/blue/mdir.tpl similarity index 100% rename from software/m2html/templates/blue/mdir.tpl rename to src/software/m2html/templates/blue/mdir.tpl diff --git a/software/m2html/templates/blue/mex.png b/src/software/m2html/templates/blue/mex.png similarity index 100% rename from software/m2html/templates/blue/mex.png rename to src/software/m2html/templates/blue/mex.png diff --git a/software/m2html/templates/blue/mfile.tpl b/src/software/m2html/templates/blue/mfile.tpl similarity index 100% rename from software/m2html/templates/blue/mfile.tpl rename to src/software/m2html/templates/blue/mfile.tpl diff --git a/software/m2html/templates/blue/pcode.png b/src/software/m2html/templates/blue/pcode.png similarity index 100% rename from software/m2html/templates/blue/pcode.png rename to src/software/m2html/templates/blue/pcode.png diff --git a/software/m2html/templates/blue/right.png b/src/software/m2html/templates/blue/right.png similarity index 100% rename from software/m2html/templates/blue/right.png rename to src/software/m2html/templates/blue/right.png diff --git a/software/m2html/templates/blue/search.tpl b/src/software/m2html/templates/blue/search.tpl similarity index 100% rename from software/m2html/templates/blue/search.tpl rename to src/software/m2html/templates/blue/search.tpl diff --git a/software/m2html/templates/blue/sgi.png b/src/software/m2html/templates/blue/sgi.png similarity index 100% rename from software/m2html/templates/blue/sgi.png rename to src/software/m2html/templates/blue/sgi.png diff --git a/software/m2html/templates/blue/simulinkicon.gif b/src/software/m2html/templates/blue/simulinkicon.gif similarity index 100% rename from software/m2html/templates/blue/simulinkicon.gif rename to src/software/m2html/templates/blue/simulinkicon.gif diff --git a/software/m2html/templates/blue/solaris.png b/src/software/m2html/templates/blue/solaris.png similarity index 100% rename from software/m2html/templates/blue/solaris.png rename to src/software/m2html/templates/blue/solaris.png diff --git a/software/m2html/templates/blue/todo.tpl b/src/software/m2html/templates/blue/todo.tpl similarity index 100% rename from software/m2html/templates/blue/todo.tpl rename to src/software/m2html/templates/blue/todo.tpl diff --git a/software/m2html/templates/blue/up.png b/src/software/m2html/templates/blue/up.png similarity index 100% rename from software/m2html/templates/blue/up.png rename to src/software/m2html/templates/blue/up.png diff --git a/software/m2html/templates/blue/windows.png b/src/software/m2html/templates/blue/windows.png similarity index 100% rename from software/m2html/templates/blue/windows.png rename to src/software/m2html/templates/blue/windows.png diff --git a/software/scip/.keep b/src/software/scip/.keep similarity index 100% rename from software/scip/.keep rename to src/software/scip/.keep diff --git a/software/versions.txt b/src/software/versions.txt similarity index 100% rename from software/versions.txt rename to src/software/versions.txt diff --git a/solver/checkSolution.m b/src/solver/checkSolution.m similarity index 100% rename from solver/checkSolution.m rename to src/solver/checkSolution.m diff --git a/solver/getMinNrFluxes.m b/src/solver/getMinNrFluxes.m similarity index 100% rename from solver/getMinNrFluxes.m rename to src/solver/getMinNrFluxes.m diff --git a/solver/optimizeProb.m b/src/solver/optimizeProb.m similarity index 99% rename from solver/optimizeProb.m rename to src/solver/optimizeProb.m index 047a39a0..71b12e73 100755 --- a/solver/optimizeProb.m +++ b/src/solver/optimizeProb.m @@ -224,7 +224,7 @@ % Ensure that RAVEN glpk binary is used, return to original % directory afterwards [ravenDir,currDir]=findRAVENroot(); - cd(fullfile(ravenDir,'software','GLPKmex')) + cd(fullfile(ravenDir,'src','software','GLPKmex')) [xopt, fmin, errnum, extra] = glpk(prob.c, prob.A, prob.b, prob.lb, prob.ub, prob.csense, prob.vartype, prob.osense, solverparams); cd(currDir) diff --git a/solver/setRavenSolver.m b/src/solver/setRavenSolver.m similarity index 81% rename from solver/setRavenSolver.m rename to src/solver/setRavenSolver.m index 9288c8df..c50de2a1 100755 --- a/solver/setRavenSolver.m +++ b/src/solver/setRavenSolver.m @@ -43,15 +43,15 @@ function setRavenSolver(solver) end else ravenDir = findRAVENroot(); - if ~exist(fullfile(ravenDir,'software','scip','scip.mexw64'),'file') + if ~exist(fullfile(ravenDir,'src','software','scip','scip.mexw64'),'file') try disp('Downloading and installing RAVEN-provided SCIP MEX binary...') - websave(fullfile(ravenDir,'software','scip_mex_win.zip'),'https://github.com/SysBioChalmers/RAVEN/releases/download/v2.8.6/scip_mex_win.zip'); % Should be updated to release with SCIP functionality + websave(fullfile(ravenDir,'src','software','scip_mex_win.zip'),'https://github.com/SysBioChalmers/RAVEN/releases/download/v2.8.6/scip_mex_win.zip'); % Should be updated to release with SCIP functionality catch error('Unable to download SCIP MEX binary from the RAVEN GitHub page.') end - unzip(fullfile(ravenDir,'software','scip_mex_win.zip'),fullfile(ravenDir,'software','scip')); - delete(fullfile(ravenDir,'software','scip_mex_win.zip')); + unzip(fullfile(ravenDir,'src','software','scip_mex_win.zip'),fullfile(ravenDir,'src','software','scip')); + delete(fullfile(ravenDir,'src','software','scip_mex_win.zip')); end try scip; % The pre-compiled MEX file might fail diff --git a/solver/solveLP.m b/src/solver/solveLP.m similarity index 100% rename from solver/solveLP.m rename to src/solver/solveLP.m diff --git a/solver/splitProbForConditioning.m b/src/solver/splitProbForConditioning.m similarity index 100% rename from solver/splitProbForConditioning.m rename to src/solver/splitProbForConditioning.m diff --git a/tasks/checkTasks.m b/src/tasks/checkTasks.m similarity index 100% rename from tasks/checkTasks.m rename to src/tasks/checkTasks.m diff --git a/tasks/parseTaskList.m b/src/tasks/parseTaskList.m similarity index 100% rename from tasks/parseTaskList.m rename to src/tasks/parseTaskList.m diff --git a/utils/convertCharArray.m b/src/utils/convertCharArray.m similarity index 100% rename from utils/convertCharArray.m rename to src/utils/convertCharArray.m diff --git a/utils/deprecationWarning.m b/src/utils/deprecationWarning.m similarity index 100% rename from utils/deprecationWarning.m rename to src/utils/deprecationWarning.m diff --git a/utils/grRuleToDNF.m b/src/utils/grRuleToDNF.m similarity index 100% rename from utils/grRuleToDNF.m rename to src/utils/grRuleToDNF.m diff --git a/utils/grRuleToString.m b/src/utils/grRuleToString.m similarity index 100% rename from utils/grRuleToString.m rename to src/utils/grRuleToString.m diff --git a/utils/isDnfGrRule.m b/src/utils/isDnfGrRule.m similarity index 100% rename from utils/isDnfGrRule.m rename to src/utils/isDnfGrRule.m diff --git a/utils/legacyMethodNotice.m b/src/utils/legacyMethodNotice.m similarity index 100% rename from utils/legacyMethodNotice.m rename to src/utils/legacyMethodNotice.m diff --git a/utils/parallelWorkersRAVEN.m b/src/utils/parallelWorkersRAVEN.m similarity index 100% rename from utils/parallelWorkersRAVEN.m rename to src/utils/parallelWorkersRAVEN.m diff --git a/utils/parseGrRule.m b/src/utils/parseGrRule.m similarity index 100% rename from utils/parseGrRule.m rename to src/utils/parseGrRule.m diff --git a/utils/parseRAVENargs.m b/src/utils/parseRAVENargs.m similarity index 100% rename from utils/parseRAVENargs.m rename to src/utils/parseRAVENargs.m diff --git a/utils/printOrange.m b/src/utils/printOrange.m similarity index 100% rename from utils/printOrange.m rename to src/utils/printOrange.m diff --git a/utils/progressReport.m b/src/utils/progressReport.m similarity index 100% rename from utils/progressReport.m rename to src/utils/progressReport.m diff --git a/utils/ravenList.m b/src/utils/ravenList.m similarity index 100% rename from utils/ravenList.m rename to src/utils/ravenList.m diff --git a/utils/ravenModelFields.m b/src/utils/ravenModelFields.m similarity index 100% rename from utils/ravenModelFields.m rename to src/utils/ravenModelFields.m diff --git a/utils/runRAVENtests.m b/src/utils/runRAVENtests.m similarity index 93% rename from utils/runRAVENtests.m rename to src/utils/runRAVENtests.m index 4539d939..6801e027 100644 --- a/utils/runRAVENtests.m +++ b/src/utils/runRAVENtests.m @@ -15,7 +15,7 @@ ravenPath = findRAVENroot; curwd = pwd; -cd(fullfile(ravenPath,'testing','function_tests')); +cd(fullfile(ravenPath,'tests','function_tests')); % Collect the concrete test classes only. RavenTestCase is an abstract base % class that the test classes subclass, and runtests cannot build a suite diff --git a/utils/setRavenProgress.m b/src/utils/setRavenProgress.m similarity index 100% rename from utils/setRavenProgress.m rename to src/utils/setRavenProgress.m diff --git a/testing/function_tests/README.md b/tests/function_tests/README.md similarity index 91% rename from testing/function_tests/README.md rename to tests/function_tests/README.md index 67df9ed8..7f15dbff 100644 --- a/testing/function_tests/README.md +++ b/tests/function_tests/README.md @@ -6,8 +6,8 @@ test class per source folder (`tQueries`, `tManipulation`, `tSolver`, …). ## Running ```matlab -results = runtests('testing/function_tests'); % whole suite -results = runtests('testing/function_tests/tIO.m'); % one folder +results = runtests('tests/function_tests'); % whole suite +results = runtests('tests/function_tests/tIO.m'); % one folder ``` A working solver is required for the simulation-based tests; GLPK ships with diff --git a/testing/function_tests/RavenTestCase.m b/tests/function_tests/RavenTestCase.m similarity index 98% rename from testing/function_tests/RavenTestCase.m rename to tests/function_tests/RavenTestCase.m index 57a17d88..f6af4ee7 100644 --- a/testing/function_tests/RavenTestCase.m +++ b/tests/function_tests/RavenTestCase.m @@ -25,7 +25,7 @@ methods (TestClassSetup) function setupRaven(testCase) testCase.ravenRoot = findRAVENroot(); - loaded = load(fullfile(testCase.ravenRoot,'testing','function_tests', ... + loaded = load(fullfile(testCase.ravenRoot,'tests','function_tests', ... 'test_data','ecoli_textbook.mat'),'model'); testCase.modelTemplate = loaded.model; try diff --git a/testing/function_tests/private/getExprForRxnScore.m b/tests/function_tests/private/getExprForRxnScore.m similarity index 100% rename from testing/function_tests/private/getExprForRxnScore.m rename to tests/function_tests/private/getExprForRxnScore.m diff --git a/testing/function_tests/tAnalysis.m b/tests/function_tests/tAnalysis.m similarity index 100% rename from testing/function_tests/tAnalysis.m rename to tests/function_tests/tAnalysis.m diff --git a/testing/function_tests/tAnnotation.m b/tests/function_tests/tAnnotation.m similarity index 100% rename from testing/function_tests/tAnnotation.m rename to tests/function_tests/tAnnotation.m diff --git a/testing/function_tests/tAssignCompartments.m b/tests/function_tests/tAssignCompartments.m similarity index 100% rename from testing/function_tests/tAssignCompartments.m rename to tests/function_tests/tAssignCompartments.m diff --git a/testing/function_tests/tBinaries.m b/tests/function_tests/tBinaries.m similarity index 95% rename from testing/function_tests/tBinaries.m rename to tests/function_tests/tBinaries.m index 2630b0df..89045fab 100644 --- a/testing/function_tests/tBinaries.m +++ b/tests/function_tests/tBinaries.m @@ -28,7 +28,7 @@ function hmmer(testCase) methods (Access = private) function p = binPath(~, tool, name) if ispc; ext = '.exe'; elseif ismac; ext = '.mac'; else; ext = ''; end - p = fullfile(findRAVENroot(),'software',tool,[name ext]); + p = fullfile(findRAVENroot(),'src','software',tool,[name ext]); end function fetchAndRun(testCase, tool, name, versionArg) diff --git a/testing/function_tests/tBiomass.m b/tests/function_tests/tBiomass.m similarity index 100% rename from testing/function_tests/tBiomass.m rename to tests/function_tests/tBiomass.m diff --git a/testing/function_tests/tComparison.m b/tests/function_tests/tComparison.m similarity index 100% rename from testing/function_tests/tComparison.m rename to tests/function_tests/tComparison.m diff --git a/testing/function_tests/tConditions.m b/tests/function_tests/tConditions.m similarity index 100% rename from testing/function_tests/tConditions.m rename to tests/function_tests/tConditions.m diff --git a/testing/function_tests/tConversion.m b/tests/function_tests/tConversion.m similarity index 100% rename from testing/function_tests/tConversion.m rename to tests/function_tests/tConversion.m diff --git a/testing/function_tests/tCuration.m b/tests/function_tests/tCuration.m similarity index 100% rename from testing/function_tests/tCuration.m rename to tests/function_tests/tCuration.m diff --git a/testing/function_tests/tGapfilling.m b/tests/function_tests/tGapfilling.m similarity index 100% rename from testing/function_tests/tGapfilling.m rename to tests/function_tests/tGapfilling.m diff --git a/testing/function_tests/tGenomeData.m b/tests/function_tests/tGenomeData.m similarity index 98% rename from testing/function_tests/tGenomeData.m rename to tests/function_tests/tGenomeData.m index 7ea61779..06855ece 100644 --- a/testing/function_tests/tGenomeData.m +++ b/tests/function_tests/tGenomeData.m @@ -9,7 +9,7 @@ methods (Access = protected) function d = fixtureDir(testCase) - d = fullfile(testCase.ravenRoot,'testing','function_tests', ... + d = fullfile(testCase.ravenRoot,'tests','function_tests', ... 'test_data','genomeData'); end end diff --git a/testing/function_tests/tINIT.m b/tests/function_tests/tINIT.m similarity index 100% rename from testing/function_tests/tINIT.m rename to tests/function_tests/tINIT.m diff --git a/testing/function_tests/tINITtestInfo/01.svg b/tests/function_tests/tINITtestInfo/01.svg similarity index 100% rename from testing/function_tests/tINITtestInfo/01.svg rename to tests/function_tests/tINITtestInfo/01.svg diff --git a/testing/function_tests/tINITtestInfo/02.svg b/tests/function_tests/tINITtestInfo/02.svg similarity index 100% rename from testing/function_tests/tINITtestInfo/02.svg rename to tests/function_tests/tINITtestInfo/02.svg diff --git a/testing/function_tests/tINITtestInfo/03.svg b/tests/function_tests/tINITtestInfo/03.svg similarity index 100% rename from testing/function_tests/tINITtestInfo/03.svg rename to tests/function_tests/tINITtestInfo/03.svg diff --git a/testing/function_tests/tINITtestInfo/04.svg b/tests/function_tests/tINITtestInfo/04.svg similarity index 100% rename from testing/function_tests/tINITtestInfo/04.svg rename to tests/function_tests/tINITtestInfo/04.svg diff --git a/testing/function_tests/tINITtestInfo/05.svg b/tests/function_tests/tINITtestInfo/05.svg similarity index 100% rename from testing/function_tests/tINITtestInfo/05.svg rename to tests/function_tests/tINITtestInfo/05.svg diff --git a/testing/function_tests/tINITtestInfo/06.svg b/tests/function_tests/tINITtestInfo/06.svg similarity index 100% rename from testing/function_tests/tINITtestInfo/06.svg rename to tests/function_tests/tINITtestInfo/06.svg diff --git a/testing/function_tests/tINITtestInfo/07.svg b/tests/function_tests/tINITtestInfo/07.svg similarity index 100% rename from testing/function_tests/tINITtestInfo/07.svg rename to tests/function_tests/tINITtestInfo/07.svg diff --git a/testing/function_tests/tINITtestInfo/README.md b/tests/function_tests/tINITtestInfo/README.md similarity index 100% rename from testing/function_tests/tINITtestInfo/README.md rename to tests/function_tests/tINITtestInfo/README.md diff --git a/testing/function_tests/tIO.m b/tests/function_tests/tIO.m similarity index 96% rename from testing/function_tests/tIO.m rename to tests/function_tests/tIO.m index 53712c11..f26ace24 100644 --- a/testing/function_tests/tIO.m +++ b/tests/function_tests/tIO.m @@ -9,7 +9,7 @@ function exportForGitWritesDependencies(testCase) % Exercises the toolbox-version lookup in exportForGit via the % dependencies.txt it writes - f = fullfile(testCase.ravenRoot,'tutorial','empty.xml'); + f = fullfile(testCase.ravenRoot,'tutorials','data','empty.xml'); evalc('model = importModel(f);'); outDir = tempname; mkdir(outDir); c = onCleanup(@() rmdir(outDir,'s')); @@ -33,7 +33,7 @@ function importModelHandlesSpeciesAndReactionEdgeCases(testCase) % (and disagree with each other); a multi-reaction objective; % a reaction with more than one SUBSYSTEM note; and species % whose charge is genuinely (not just by default) zero. - f = fullfile(testCase.ravenRoot,'testing','function_tests','test_data', ... + f = fullfile(testCase.ravenRoot,'tests','function_tests','test_data', ... 'importModelEdgeCases.xml'); evalc('m = importModel(f);'); @@ -60,20 +60,20 @@ function importModelStripsRegexSpecialCompartmentName(testCase) % A compartment name containing regex metacharacters ("Golgi % (cis)") must still be recognized and stripped from the % metabolite name suffix, not corrupt the matching pattern. - f = fullfile(testCase.ravenRoot,'testing','function_tests','test_data', ... + f = fullfile(testCase.ravenRoot,'tests','function_tests','test_data', ... 'importModelSpecialCompName.xml'); evalc('m = importModel(f);'); testCase.verifyEqual(m.metNames{1}, 'metabolite one'); end function importModelReadsSBML(testCase) - f = fullfile(testCase.ravenRoot,'tutorial','empty.xml'); + f = fullfile(testCase.ravenRoot,'tutorials','data','empty.xml'); evalc('m = importModel(f);'); testCase.verifyTrue(isfield(m, 'rxns')); end function readYAMLmodelReadsYml(testCase) - f = fullfile(testCase.ravenRoot,'tutorial','empty.yml'); + f = fullfile(testCase.ravenRoot,'tutorials','data','empty.yml'); evalc('m = readYAMLmodel(f);'); testCase.verifyTrue(isfield(m, 'rxns')); end @@ -284,7 +284,7 @@ function exportForGitWritesRepo(testCase) end function checkFileExistenceFindsFile(testCase) - f = fullfile(testCase.ravenRoot,'testing','function_tests', ... + f = fullfile(testCase.ravenRoot,'tests','function_tests', ... 'test_data','ecoli_textbook.mat'); out = checkFileExistence(f, 1, false, true); testCase.verifyNotEmpty(out); @@ -296,7 +296,7 @@ function checkFileExistenceAcceptsForwardSlashes(testCase) % built by another tool is likely to use them. Treating such a path % as relative appends the working directory and turns % "C:/data/model.mat" into "C:\somewhere\C:\data\model.mat". - f = fullfile(testCase.ravenRoot,'testing','function_tests', ... + f = fullfile(testCase.ravenRoot,'tests','function_tests', ... 'test_data','ecoli_textbook.mat'); out = checkFileExistence(strrep(f,'\','/'), 1, false, true); testCase.verifyNotEmpty(out); diff --git a/testing/function_tests/tInstallation.m b/tests/function_tests/tInstallation.m similarity index 96% rename from testing/function_tests/tInstallation.m rename to tests/function_tests/tInstallation.m index 8026f44a..611fb5ee 100644 --- a/testing/function_tests/tInstallation.m +++ b/tests/function_tests/tInstallation.m @@ -11,7 +11,7 @@ function findRAVENrootReturnsInstallFolder(testCase) p = findRAVENroot(); testCase.verifyTrue(isfolder(p)); - testCase.verifyTrue(exist(fullfile(p,'installation','findRAVENroot.m'),'file')==2); + testCase.verifyTrue(exist(fullfile(p,'src','installation','findRAVENroot.m'),'file')==2); end function findRAVENrootSurvivesClearAndRepeatCalls(testCase) diff --git a/testing/function_tests/tLocalization.m b/tests/function_tests/tLocalization.m similarity index 100% rename from testing/function_tests/tLocalization.m rename to tests/function_tests/tLocalization.m diff --git a/testing/function_tests/tManipulation.m b/tests/function_tests/tManipulation.m similarity index 99% rename from testing/function_tests/tManipulation.m rename to tests/function_tests/tManipulation.m index 6dae3b3b..6b07eda4 100644 --- a/testing/function_tests/tManipulation.m +++ b/tests/function_tests/tManipulation.m @@ -138,7 +138,7 @@ function addRxnsInvalidStringEqnTypeErrors(testCase) end function addRxnsGenesMetsCopiesFromSource(testCase) - sbmlFile = fullfile(testCase.ravenRoot,'tutorial','empty.xml'); + sbmlFile = fullfile(testCase.ravenRoot,'tutorials','data','empty.xml'); testCase.assumeDependency(exist(sbmlFile,'file')==2, 'tutorial/empty.xml'); evalc('src = importModel(sbmlFile, [], true);'); evalc('m2 = addRxnsGenesMets(testCase.model, src, ''r1'', true);'); diff --git a/testing/function_tests/tNamedArgs.m b/tests/function_tests/tNamedArgs.m similarity index 100% rename from testing/function_tests/tNamedArgs.m rename to tests/function_tests/tNamedArgs.m diff --git a/testing/function_tests/tParseRAVENargs.m b/tests/function_tests/tParseRAVENargs.m similarity index 100% rename from testing/function_tests/tParseRAVENargs.m rename to tests/function_tests/tParseRAVENargs.m diff --git a/testing/function_tests/tQueries.m b/tests/function_tests/tQueries.m similarity index 100% rename from testing/function_tests/tQueries.m rename to tests/function_tests/tQueries.m diff --git a/testing/function_tests/tReconstruction.m b/tests/function_tests/tReconstruction.m similarity index 93% rename from testing/function_tests/tReconstruction.m rename to tests/function_tests/tReconstruction.m index 96e3b277..71a07e5e 100644 --- a/testing/function_tests/tReconstruction.m +++ b/tests/function_tests/tReconstruction.m @@ -92,8 +92,8 @@ function getModelFromHomologyRejectsUnknownComplexPolicy(testCase) end function getBlastRunsWhenAvailable(testCase) - fa1 = fullfile(testCase.ravenRoot,'testing','function_tests','test_data','human_galactosidases.fa'); - fa2 = fullfile(testCase.ravenRoot,'testing','function_tests','test_data','yeast_galactosidases.fa'); + fa1 = fullfile(testCase.ravenRoot,'tests','function_tests','test_data','human_galactosidases.fa'); + fa2 = fullfile(testCase.ravenRoot,'tests','function_tests','test_data','yeast_galactosidases.fa'); testCase.assumeDependency(exist(fa1,'file')==2, 'test FASTA files'); ok = true; try, evalc('bs = getBlast(''human'', fa1, {''yeast''}, {fa2});'); catch, ok = false; end @@ -102,8 +102,8 @@ function getBlastRunsWhenAvailable(testCase) end function getDiamondRunsWhenAvailable(testCase) - fa1 = fullfile(testCase.ravenRoot,'testing','function_tests','test_data','human_galactosidases.fa'); - fa2 = fullfile(testCase.ravenRoot,'testing','function_tests','test_data','yeast_galactosidases.fa'); + fa1 = fullfile(testCase.ravenRoot,'tests','function_tests','test_data','human_galactosidases.fa'); + fa2 = fullfile(testCase.ravenRoot,'tests','function_tests','test_data','yeast_galactosidases.fa'); testCase.assumeDependency(exist(fa1,'file')==2, 'test FASTA files'); ok = true; try, evalc('bs = getDiamond(''human'', fa1, {''yeast''}, {fa2});'); catch, ok = false; end @@ -116,7 +116,7 @@ function getKEGGModelForOrganismNeedsData(testCase) end function getModelFromKEGGNeedsData(testCase) - matFile = fullfile(testCase.ravenRoot, 'reconstruction', 'kegg', 'keggModel.mat'); + matFile = fullfile(testCase.ravenRoot,'src','reconstruction', 'kegg', 'keggModel.mat'); testCase.assumeFalse(exist(matFile, 'file') == 2, ... 'keggModel.mat is present; skipping the build-from-artefacts path.'); testCase.assumeFail('Downloads and assembles the full KEGG artefact set from raven-data; not run automatically.'); @@ -138,7 +138,7 @@ function buildGlobalGPRJoinsGenesThroughSharedKO(testCase) end function getPhylDistNeedsData(testCase) - distFile = fullfile(testCase.ravenRoot, 'reconstruction', 'kegg', 'keggPhylDist.mat'); + distFile = fullfile(testCase.ravenRoot,'src','reconstruction', 'kegg', 'keggPhylDist.mat'); testCase.assumeFalse(exist(distFile, 'file') == 2, ... 'keggPhylDist.mat is present; skipping error-path test.'); testCase.verifyError(@() getPhylDist(), 'getPhylDist:noData'); diff --git a/testing/function_tests/tSampling.m b/tests/function_tests/tSampling.m similarity index 100% rename from testing/function_tests/tSampling.m rename to tests/function_tests/tSampling.m diff --git a/testing/function_tests/tSolver.m b/tests/function_tests/tSolver.m similarity index 100% rename from testing/function_tests/tSolver.m rename to tests/function_tests/tSolver.m diff --git a/testing/function_tests/tSyntax.m b/tests/function_tests/tSyntax.m similarity index 100% rename from testing/function_tests/tSyntax.m rename to tests/function_tests/tSyntax.m diff --git a/testing/function_tests/tTasks.m b/tests/function_tests/tTasks.m similarity index 95% rename from testing/function_tests/tTasks.m rename to tests/function_tests/tTasks.m index 10360c80..3b858ecf 100644 --- a/testing/function_tests/tTasks.m +++ b/tests/function_tests/tTasks.m @@ -18,7 +18,7 @@ function checkTasksFailsWithoutKeyReaction(testCase) end function parseTaskListReturnsStruct(testCase) - taskFile = fullfile(testCase.ravenRoot,'testing','function_tests', ... + taskFile = fullfile(testCase.ravenRoot,'tests','function_tests', ... 'test_data','test_tasks.txt'); testCase.assumeDependency(exist(taskFile,'file')==2, 'test_tasks.txt'); ts = parseTaskList(taskFile); @@ -29,7 +29,7 @@ function parseTaskListReturnsStruct(testCase) function parseTaskListMatchesExpected(testCase) % The text and Excel task definitions must parse to identical % structs with exactly the expected fields and values. - dataDir = fullfile(testCase.ravenRoot,'testing','function_tests','test_data'); + dataDir = fullfile(testCase.ravenRoot,'tests','function_tests','test_data'); txtFile = fullfile(dataDir,'test_tasks.txt'); xlsFile = fullfile(dataDir,'test_tasks.xls'); testCase.assumeDependency(exist(txtFile,'file')==2, 'test_tasks.txt'); diff --git a/testing/function_tests/tUtils.m b/tests/function_tests/tUtils.m similarity index 100% rename from testing/function_tests/tUtils.m rename to tests/function_tests/tUtils.m diff --git a/testing/function_tests/test_data/ecoli_textbook.mat b/tests/function_tests/test_data/ecoli_textbook.mat similarity index 100% rename from testing/function_tests/test_data/ecoli_textbook.mat rename to tests/function_tests/test_data/ecoli_textbook.mat diff --git a/testing/function_tests/test_data/genomeData/euk.gff b/tests/function_tests/test_data/genomeData/euk.gff similarity index 100% rename from testing/function_tests/test_data/genomeData/euk.gff rename to tests/function_tests/test_data/genomeData/euk.gff diff --git a/testing/function_tests/test_data/genomeData/euk_protein.faa b/tests/function_tests/test_data/genomeData/euk_protein.faa similarity index 100% rename from testing/function_tests/test_data/genomeData/euk_protein.faa rename to tests/function_tests/test_data/genomeData/euk_protein.faa diff --git a/testing/function_tests/test_data/genomeData/prok.gff b/tests/function_tests/test_data/genomeData/prok.gff similarity index 100% rename from testing/function_tests/test_data/genomeData/prok.gff rename to tests/function_tests/test_data/genomeData/prok.gff diff --git a/testing/function_tests/test_data/human_galactosidases.fa b/tests/function_tests/test_data/human_galactosidases.fa similarity index 100% rename from testing/function_tests/test_data/human_galactosidases.fa rename to tests/function_tests/test_data/human_galactosidases.fa diff --git a/testing/function_tests/test_data/importModelEdgeCases.xml b/tests/function_tests/test_data/importModelEdgeCases.xml similarity index 100% rename from testing/function_tests/test_data/importModelEdgeCases.xml rename to tests/function_tests/test_data/importModelEdgeCases.xml diff --git a/testing/function_tests/test_data/importModelSpecialCompName.xml b/tests/function_tests/test_data/importModelSpecialCompName.xml similarity index 100% rename from testing/function_tests/test_data/importModelSpecialCompName.xml rename to tests/function_tests/test_data/importModelSpecialCompName.xml diff --git a/testing/function_tests/test_data/test_tasks.txt b/tests/function_tests/test_data/test_tasks.txt similarity index 100% rename from testing/function_tests/test_data/test_tasks.txt rename to tests/function_tests/test_data/test_tasks.txt diff --git a/testing/function_tests/test_data/test_tasks.xls b/tests/function_tests/test_data/test_tasks.xls similarity index 100% rename from testing/function_tests/test_data/test_tasks.xls rename to tests/function_tests/test_data/test_tasks.xls diff --git a/testing/function_tests/test_data/yeast_galactosidases.fa b/tests/function_tests/test_data/yeast_galactosidases.fa similarity index 100% rename from testing/function_tests/test_data/yeast_galactosidases.fa rename to tests/function_tests/test_data/yeast_galactosidases.fa diff --git a/tutorial/biomassConditions_template.tsv b/tutorials/data/cell_analysis/biomassConditions_template.tsv similarity index 100% rename from tutorial/biomassConditions_template.tsv rename to tutorials/data/cell_analysis/biomassConditions_template.tsv diff --git a/tutorial/empty.mat b/tutorials/data/empty.mat similarity index 100% rename from tutorial/empty.mat rename to tutorials/data/empty.mat diff --git a/tutorial/empty.xml b/tutorials/data/empty.xml similarity index 100% rename from tutorial/empty.xml rename to tutorials/data/empty.xml diff --git a/tutorial/empty.yml b/tutorials/data/empty.yml similarity index 100% rename from tutorial/empty.yml rename to tutorials/data/empty.yml diff --git a/tutorial/iAL1006 v1.00.xml b/tutorials/data/models/iAL1006 v1.00.xml similarity index 100% rename from tutorial/iAL1006 v1.00.xml rename to tutorials/data/models/iAL1006 v1.00.xml diff --git a/tutorial/small.yml b/tutorials/data/models/small.yml similarity index 100% rename from tutorial/small.yml rename to tutorials/data/models/small.yml diff --git a/tutorial/smallYeast.yml b/tutorials/data/models/smallYeast.yml similarity index 100% rename from tutorial/smallYeast.yml rename to tutorials/data/models/smallYeast.yml diff --git a/tutorial/smallYeastBad.yml b/tutorials/data/models/smallYeastBad.yml similarity index 100% rename from tutorial/smallYeastBad.yml rename to tutorials/data/models/smallYeastBad.yml diff --git a/tutorial/expression.txt b/tutorials/data/omics/expression.txt similarity index 100% rename from tutorial/expression.txt rename to tutorials/data/omics/expression.txt diff --git a/tutorial/sce.fa b/tutorials/data/sequences/sce.fa similarity index 100% rename from tutorial/sce.fa rename to tutorials/data/sequences/sce.fa diff --git a/tutorial/tutorial1.m b/tutorials/tutorial1.m similarity index 99% rename from tutorial/tutorial1.m rename to tutorials/tutorial1.m index 3d748cd5..99407995 100755 --- a/tutorial/tutorial1.m +++ b/tutorials/tutorial1.m @@ -4,7 +4,7 @@ % perform an optimization through flux balance analysis (FBA). The % resulting fluxes are visualized and exported to a PDF file. % For a more detailed description of the individual functions, see -% [raven_directory]/doc/index.html. +% [raven_directory]/docs/html/index.html. % A GEM for the filamentous fungus Penicillium chrysogenum is used in % this tutorial. The model is provided as the SBML file % "iAL1006 v1.00.xml". diff --git a/tutorial/tutorial2.m b/tutorials/tutorial2.m similarity index 100% rename from tutorial/tutorial2.m rename to tutorials/tutorial2.m diff --git a/tutorial/tutorial2_solutions.m b/tutorials/tutorial2_solutions.m similarity index 100% rename from tutorial/tutorial2_solutions.m rename to tutorials/tutorial2_solutions.m diff --git a/tutorial/tutorial3.m b/tutorials/tutorial3.m similarity index 100% rename from tutorial/tutorial3.m rename to tutorials/tutorial3.m diff --git a/tutorial/tutorial3_solutions.m b/tutorials/tutorial3_solutions.m similarity index 100% rename from tutorial/tutorial3_solutions.m rename to tutorials/tutorial3_solutions.m diff --git a/tutorial/tutorial4.m b/tutorials/tutorial4.m similarity index 100% rename from tutorial/tutorial4.m rename to tutorials/tutorial4.m diff --git a/tutorial/tutorial4_solutions.m b/tutorials/tutorial4_solutions.m similarity index 100% rename from tutorial/tutorial4_solutions.m rename to tutorials/tutorial4_solutions.m diff --git a/tutorial/tutorial5.m b/tutorials/tutorial5.m similarity index 100% rename from tutorial/tutorial5.m rename to tutorials/tutorial5.m diff --git a/tutorial/tutorial7.m b/tutorials/tutorial7.m similarity index 100% rename from tutorial/tutorial7.m rename to tutorials/tutorial7.m From bba312e147de9c4ba8115ef55634808d71b0458e Mon Sep 17 00:00:00 2001 From: ae-tafur Date: Sat, 10 Oct 2026 14:02:40 -0500 Subject: [PATCH 12/48] refactor: organize external/software Original-commit: https://github.com/SysBioChalmers/RAVEN/commit/cf4f303dd771a372ba75739df9be808f27187bd0 --- .gitignore | 10 +++++----- src/{software => external}/GLPKmex/COPYING | 0 src/{software => external}/GLPKmex/README.md | 0 src/{software => external}/GLPKmex/glpk.m | 0 src/{software => external}/GLPKmex/glpkcc.mexa64 | Bin src/{software => external}/GLPKmex/glpkcc.mexmaca64 | Bin src/{software => external}/GLPKmex/glpkcc.mexmaci64 | Bin src/{software => external}/GLPKmex/glpkcc.mexw64 | Bin src/{software => external}/blast+/.keep | 0 src/{software => external}/diamond/.keep | 0 src/{software => external}/hmmer/.keep | 0 .../libSBML/CheckAndConvert.m | 0 .../libSBML/ConvertFormulaToMathML.m | 0 .../libSBML/OutputSBML_RAVEN.m | 0 .../libSBML/OutputSBML_RAVEN.mexa64 | Bin .../libSBML/OutputSBML_RAVEN.mexmaca64 | Bin .../libSBML/OutputSBML_RAVEN.mexmaci64 | Bin .../libSBML/OutputSBML_RAVEN.mexw64 | Bin src/{software => external}/libSBML/README.md | 0 .../libSBML/TranslateSBML_RAVEN.m | 0 .../libSBML/TranslateSBML_RAVEN.mexa64 | Bin .../libSBML/TranslateSBML_RAVEN.mexmaca64 | Bin .../libSBML/TranslateSBML_RAVEN.mexmaci64 | Bin .../libSBML/TranslateSBML_RAVEN.mexw64 | Bin src/{software => external}/libSBML/VERSION.txt | 0 .../libSBML/addLevelVersion.m | 0 .../libSBML/applyUserValidation.m | 0 .../libSBML/getDefaultValues.m | 0 src/{software => external}/libSBML/getStructure.m | 0 .../libSBML/getStructureEnum.m | 0 .../libSBML/getStructureFieldnames.m | 0 src/{software => external}/libSBML/getValueType.m | 0 src/{software => external}/libSBML/isSBML_Model.m | 0 src/{software => external}/libSBML/isoctave.m | 0 src/{software => external}/m2html/@template/char.m | 0 .../m2html/@template/display.m | 0 src/{software => external}/m2html/@template/get.m | 0 src/{software => external}/m2html/@template/parse.m | 0 .../m2html/@template/private/finish.m | 0 .../m2html/@template/private/loadtpl.m | 0 .../m2html/@template/private/subst.m | 0 src/{software => external}/m2html/@template/set.m | 0 .../m2html/@template/template.m | 0 src/{software => external}/m2html/Changelog | 0 src/{software => external}/m2html/Contents.m | 0 src/{software => external}/m2html/GPL | 0 src/{software => external}/m2html/INSTALL | 0 src/{software => external}/m2html/LICENSE | 0 src/{software => external}/m2html/README | 0 src/{software => external}/m2html/TODO | 0 src/{software => external}/m2html/m2html.m | 0 src/{software => external}/m2html/mdot.m | 0 .../m2html/private/doxyread.m | 0 .../m2html/private/doxysearch.m | 0 .../m2html/private/doxywrite.m | 0 .../m2html/private/m2htmltoolbarimages.mat | Bin src/{software => external}/m2html/private/mexexts.m | 0 .../m2html/private/mfileparse.m | 0 .../m2html/private/openfile.m | 0 .../m2html/private/searchindex.m | 0 .../m2html/private/splitcode.m | 0 src/{software => external}/m2html/private/strtok.m | 0 .../m2html/templates/blue/Thumbs.db | Bin .../m2html/templates/blue/alpha.png | Bin .../m2html/templates/blue/c++.png | Bin .../m2html/templates/blue/c.png | Bin .../m2html/templates/blue/demoicon.gif | Bin .../m2html/templates/blue/down.png | Bin .../m2html/templates/blue/doxysearch.php | 0 .../m2html/templates/blue/fortran.png | Bin .../m2html/templates/blue/graph.tpl | 0 .../m2html/templates/blue/hp.png | Bin .../m2html/templates/blue/left.png | Bin .../m2html/templates/blue/linux.png | Bin .../m2html/templates/blue/m2html.css | 0 .../m2html/templates/blue/master.tpl | 0 .../m2html/templates/blue/matlabicon.gif | Bin .../m2html/templates/blue/mdir.tpl | 0 .../m2html/templates/blue/mex.png | Bin .../m2html/templates/blue/mfile.tpl | 0 .../m2html/templates/blue/pcode.png | Bin .../m2html/templates/blue/right.png | Bin .../m2html/templates/blue/search.tpl | 0 .../m2html/templates/blue/sgi.png | Bin .../m2html/templates/blue/simulinkicon.gif | Bin .../m2html/templates/blue/solaris.png | Bin .../m2html/templates/blue/todo.tpl | 0 .../m2html/templates/blue/up.png | Bin .../m2html/templates/blue/windows.png | Bin src/{software => external}/scip/.keep | 0 src/{software => external}/versions.txt | 0 91 files changed, 5 insertions(+), 5 deletions(-) rename src/{software => external}/GLPKmex/COPYING (100%) rename src/{software => external}/GLPKmex/README.md (100%) rename src/{software => external}/GLPKmex/glpk.m (100%) rename src/{software => external}/GLPKmex/glpkcc.mexa64 (100%) rename src/{software => external}/GLPKmex/glpkcc.mexmaca64 (100%) rename src/{software => external}/GLPKmex/glpkcc.mexmaci64 (100%) rename src/{software => external}/GLPKmex/glpkcc.mexw64 (100%) rename src/{software => external}/blast+/.keep (100%) rename src/{software => external}/diamond/.keep (100%) rename src/{software => external}/hmmer/.keep (100%) rename src/{software => external}/libSBML/CheckAndConvert.m (100%) rename src/{software => external}/libSBML/ConvertFormulaToMathML.m (100%) rename src/{software => external}/libSBML/OutputSBML_RAVEN.m (100%) rename src/{software => external}/libSBML/OutputSBML_RAVEN.mexa64 (100%) rename src/{software => external}/libSBML/OutputSBML_RAVEN.mexmaca64 (100%) rename src/{software => external}/libSBML/OutputSBML_RAVEN.mexmaci64 (100%) rename src/{software => external}/libSBML/OutputSBML_RAVEN.mexw64 (100%) rename src/{software => external}/libSBML/README.md (100%) rename src/{software => external}/libSBML/TranslateSBML_RAVEN.m (100%) rename src/{software => external}/libSBML/TranslateSBML_RAVEN.mexa64 (100%) rename src/{software => external}/libSBML/TranslateSBML_RAVEN.mexmaca64 (100%) rename src/{software => external}/libSBML/TranslateSBML_RAVEN.mexmaci64 (100%) rename src/{software => external}/libSBML/TranslateSBML_RAVEN.mexw64 (100%) rename src/{software => external}/libSBML/VERSION.txt (100%) rename src/{software => external}/libSBML/addLevelVersion.m (100%) rename src/{software => external}/libSBML/applyUserValidation.m (100%) rename src/{software => external}/libSBML/getDefaultValues.m (100%) rename src/{software => external}/libSBML/getStructure.m (100%) rename src/{software => external}/libSBML/getStructureEnum.m (100%) rename src/{software => external}/libSBML/getStructureFieldnames.m (100%) rename src/{software => external}/libSBML/getValueType.m (100%) rename src/{software => external}/libSBML/isSBML_Model.m (100%) rename src/{software => external}/libSBML/isoctave.m (100%) rename src/{software => external}/m2html/@template/char.m (100%) rename src/{software => external}/m2html/@template/display.m (100%) rename src/{software => external}/m2html/@template/get.m (100%) rename src/{software => external}/m2html/@template/parse.m (100%) rename src/{software => external}/m2html/@template/private/finish.m (100%) rename src/{software => external}/m2html/@template/private/loadtpl.m (100%) rename src/{software => external}/m2html/@template/private/subst.m (100%) rename src/{software => external}/m2html/@template/set.m (100%) rename src/{software => external}/m2html/@template/template.m (100%) rename src/{software => external}/m2html/Changelog (100%) rename src/{software => external}/m2html/Contents.m (100%) rename src/{software => external}/m2html/GPL (100%) rename src/{software => external}/m2html/INSTALL (100%) rename src/{software => external}/m2html/LICENSE (100%) rename src/{software => external}/m2html/README (100%) rename src/{software => external}/m2html/TODO (100%) rename src/{software => external}/m2html/m2html.m (100%) rename src/{software => external}/m2html/mdot.m (100%) rename src/{software => external}/m2html/private/doxyread.m (100%) rename src/{software => external}/m2html/private/doxysearch.m (100%) rename src/{software => external}/m2html/private/doxywrite.m (100%) rename src/{software => external}/m2html/private/m2htmltoolbarimages.mat (100%) rename src/{software => external}/m2html/private/mexexts.m (100%) rename src/{software => external}/m2html/private/mfileparse.m (100%) rename src/{software => external}/m2html/private/openfile.m (100%) rename src/{software => external}/m2html/private/searchindex.m (100%) rename src/{software => external}/m2html/private/splitcode.m (100%) rename src/{software => external}/m2html/private/strtok.m (100%) rename src/{software => external}/m2html/templates/blue/Thumbs.db (100%) rename src/{software => external}/m2html/templates/blue/alpha.png (100%) rename src/{software => external}/m2html/templates/blue/c++.png (100%) rename src/{software => external}/m2html/templates/blue/c.png (100%) rename src/{software => external}/m2html/templates/blue/demoicon.gif (100%) rename src/{software => external}/m2html/templates/blue/down.png (100%) rename src/{software => external}/m2html/templates/blue/doxysearch.php (100%) rename src/{software => external}/m2html/templates/blue/fortran.png (100%) rename src/{software => external}/m2html/templates/blue/graph.tpl (100%) rename src/{software => external}/m2html/templates/blue/hp.png (100%) rename src/{software => external}/m2html/templates/blue/left.png (100%) rename src/{software => external}/m2html/templates/blue/linux.png (100%) rename src/{software => external}/m2html/templates/blue/m2html.css (100%) rename src/{software => external}/m2html/templates/blue/master.tpl (100%) rename src/{software => external}/m2html/templates/blue/matlabicon.gif (100%) rename src/{software => external}/m2html/templates/blue/mdir.tpl (100%) rename src/{software => external}/m2html/templates/blue/mex.png (100%) rename src/{software => external}/m2html/templates/blue/mfile.tpl (100%) rename src/{software => external}/m2html/templates/blue/pcode.png (100%) rename src/{software => external}/m2html/templates/blue/right.png (100%) rename src/{software => external}/m2html/templates/blue/search.tpl (100%) rename src/{software => external}/m2html/templates/blue/sgi.png (100%) rename src/{software => external}/m2html/templates/blue/simulinkicon.gif (100%) rename src/{software => external}/m2html/templates/blue/solaris.png (100%) rename src/{software => external}/m2html/templates/blue/todo.tpl (100%) rename src/{software => external}/m2html/templates/blue/up.png (100%) rename src/{software => external}/m2html/templates/blue/windows.png (100%) rename src/{software => external}/scip/.keep (100%) rename src/{software => external}/versions.txt (100%) diff --git a/.gitignore b/.gitignore index cb63dd10..d102dd98 100644 --- a/.gitignore +++ b/.gitignore @@ -42,11 +42,11 @@ src/reconstruction/kegg/keggModel.mat src/reconstruction/kegg/kegg*_core.tar.gz src/reconstruction/kegg/kegg*_core/ #software -src/software/scip/ -src/software/blast+/ -src/software/diamond/ -src/software/hmmer/ -src/software/WoLFPSORT/ +src/external/scip/ +src/external/blast+/ +src/external/diamond/ +src/external/hmmer/ +src/external/WoLFPSORT/ !.keep #doc diff --git a/src/software/GLPKmex/COPYING b/src/external/GLPKmex/COPYING similarity index 100% rename from src/software/GLPKmex/COPYING rename to src/external/GLPKmex/COPYING diff --git a/src/software/GLPKmex/README.md b/src/external/GLPKmex/README.md similarity index 100% rename from src/software/GLPKmex/README.md rename to src/external/GLPKmex/README.md diff --git a/src/software/GLPKmex/glpk.m b/src/external/GLPKmex/glpk.m similarity index 100% rename from src/software/GLPKmex/glpk.m rename to src/external/GLPKmex/glpk.m diff --git a/src/software/GLPKmex/glpkcc.mexa64 b/src/external/GLPKmex/glpkcc.mexa64 similarity index 100% rename from src/software/GLPKmex/glpkcc.mexa64 rename to src/external/GLPKmex/glpkcc.mexa64 diff --git a/src/software/GLPKmex/glpkcc.mexmaca64 b/src/external/GLPKmex/glpkcc.mexmaca64 similarity index 100% rename from src/software/GLPKmex/glpkcc.mexmaca64 rename to src/external/GLPKmex/glpkcc.mexmaca64 diff --git a/src/software/GLPKmex/glpkcc.mexmaci64 b/src/external/GLPKmex/glpkcc.mexmaci64 similarity index 100% rename from src/software/GLPKmex/glpkcc.mexmaci64 rename to src/external/GLPKmex/glpkcc.mexmaci64 diff --git a/src/software/GLPKmex/glpkcc.mexw64 b/src/external/GLPKmex/glpkcc.mexw64 similarity index 100% rename from src/software/GLPKmex/glpkcc.mexw64 rename to src/external/GLPKmex/glpkcc.mexw64 diff --git a/src/software/blast+/.keep b/src/external/blast+/.keep similarity index 100% rename from src/software/blast+/.keep rename to src/external/blast+/.keep diff --git a/src/software/diamond/.keep b/src/external/diamond/.keep similarity index 100% rename from src/software/diamond/.keep rename to src/external/diamond/.keep diff --git a/src/software/hmmer/.keep b/src/external/hmmer/.keep similarity index 100% rename from src/software/hmmer/.keep rename to src/external/hmmer/.keep diff --git a/src/software/libSBML/CheckAndConvert.m b/src/external/libSBML/CheckAndConvert.m similarity index 100% rename from src/software/libSBML/CheckAndConvert.m rename to src/external/libSBML/CheckAndConvert.m diff --git a/src/software/libSBML/ConvertFormulaToMathML.m b/src/external/libSBML/ConvertFormulaToMathML.m similarity index 100% rename from src/software/libSBML/ConvertFormulaToMathML.m rename to src/external/libSBML/ConvertFormulaToMathML.m diff --git a/src/software/libSBML/OutputSBML_RAVEN.m b/src/external/libSBML/OutputSBML_RAVEN.m similarity index 100% rename from src/software/libSBML/OutputSBML_RAVEN.m rename to src/external/libSBML/OutputSBML_RAVEN.m diff --git a/src/software/libSBML/OutputSBML_RAVEN.mexa64 b/src/external/libSBML/OutputSBML_RAVEN.mexa64 similarity index 100% rename from src/software/libSBML/OutputSBML_RAVEN.mexa64 rename to src/external/libSBML/OutputSBML_RAVEN.mexa64 diff --git a/src/software/libSBML/OutputSBML_RAVEN.mexmaca64 b/src/external/libSBML/OutputSBML_RAVEN.mexmaca64 similarity index 100% rename from src/software/libSBML/OutputSBML_RAVEN.mexmaca64 rename to src/external/libSBML/OutputSBML_RAVEN.mexmaca64 diff --git a/src/software/libSBML/OutputSBML_RAVEN.mexmaci64 b/src/external/libSBML/OutputSBML_RAVEN.mexmaci64 similarity index 100% rename from src/software/libSBML/OutputSBML_RAVEN.mexmaci64 rename to src/external/libSBML/OutputSBML_RAVEN.mexmaci64 diff --git a/src/software/libSBML/OutputSBML_RAVEN.mexw64 b/src/external/libSBML/OutputSBML_RAVEN.mexw64 similarity index 100% rename from src/software/libSBML/OutputSBML_RAVEN.mexw64 rename to src/external/libSBML/OutputSBML_RAVEN.mexw64 diff --git a/src/software/libSBML/README.md b/src/external/libSBML/README.md similarity index 100% rename from src/software/libSBML/README.md rename to src/external/libSBML/README.md diff --git a/src/software/libSBML/TranslateSBML_RAVEN.m b/src/external/libSBML/TranslateSBML_RAVEN.m similarity index 100% rename from src/software/libSBML/TranslateSBML_RAVEN.m rename to src/external/libSBML/TranslateSBML_RAVEN.m diff --git a/src/software/libSBML/TranslateSBML_RAVEN.mexa64 b/src/external/libSBML/TranslateSBML_RAVEN.mexa64 similarity index 100% rename from src/software/libSBML/TranslateSBML_RAVEN.mexa64 rename to src/external/libSBML/TranslateSBML_RAVEN.mexa64 diff --git a/src/software/libSBML/TranslateSBML_RAVEN.mexmaca64 b/src/external/libSBML/TranslateSBML_RAVEN.mexmaca64 similarity index 100% rename from src/software/libSBML/TranslateSBML_RAVEN.mexmaca64 rename to src/external/libSBML/TranslateSBML_RAVEN.mexmaca64 diff --git a/src/software/libSBML/TranslateSBML_RAVEN.mexmaci64 b/src/external/libSBML/TranslateSBML_RAVEN.mexmaci64 similarity index 100% rename from src/software/libSBML/TranslateSBML_RAVEN.mexmaci64 rename to src/external/libSBML/TranslateSBML_RAVEN.mexmaci64 diff --git a/src/software/libSBML/TranslateSBML_RAVEN.mexw64 b/src/external/libSBML/TranslateSBML_RAVEN.mexw64 similarity index 100% rename from src/software/libSBML/TranslateSBML_RAVEN.mexw64 rename to src/external/libSBML/TranslateSBML_RAVEN.mexw64 diff --git a/src/software/libSBML/VERSION.txt b/src/external/libSBML/VERSION.txt similarity index 100% rename from src/software/libSBML/VERSION.txt rename to src/external/libSBML/VERSION.txt diff --git a/src/software/libSBML/addLevelVersion.m b/src/external/libSBML/addLevelVersion.m similarity index 100% rename from src/software/libSBML/addLevelVersion.m rename to src/external/libSBML/addLevelVersion.m diff --git a/src/software/libSBML/applyUserValidation.m b/src/external/libSBML/applyUserValidation.m similarity index 100% rename from src/software/libSBML/applyUserValidation.m rename to src/external/libSBML/applyUserValidation.m diff --git a/src/software/libSBML/getDefaultValues.m b/src/external/libSBML/getDefaultValues.m similarity index 100% rename from src/software/libSBML/getDefaultValues.m rename to src/external/libSBML/getDefaultValues.m diff --git a/src/software/libSBML/getStructure.m b/src/external/libSBML/getStructure.m similarity index 100% rename from src/software/libSBML/getStructure.m rename to src/external/libSBML/getStructure.m diff --git a/src/software/libSBML/getStructureEnum.m b/src/external/libSBML/getStructureEnum.m similarity index 100% rename from src/software/libSBML/getStructureEnum.m rename to src/external/libSBML/getStructureEnum.m diff --git a/src/software/libSBML/getStructureFieldnames.m b/src/external/libSBML/getStructureFieldnames.m similarity index 100% rename from src/software/libSBML/getStructureFieldnames.m rename to src/external/libSBML/getStructureFieldnames.m diff --git a/src/software/libSBML/getValueType.m b/src/external/libSBML/getValueType.m similarity index 100% rename from src/software/libSBML/getValueType.m rename to src/external/libSBML/getValueType.m diff --git a/src/software/libSBML/isSBML_Model.m b/src/external/libSBML/isSBML_Model.m similarity index 100% rename from src/software/libSBML/isSBML_Model.m rename to src/external/libSBML/isSBML_Model.m diff --git a/src/software/libSBML/isoctave.m b/src/external/libSBML/isoctave.m similarity index 100% rename from src/software/libSBML/isoctave.m rename to src/external/libSBML/isoctave.m diff --git a/src/software/m2html/@template/char.m b/src/external/m2html/@template/char.m similarity index 100% rename from src/software/m2html/@template/char.m rename to src/external/m2html/@template/char.m diff --git a/src/software/m2html/@template/display.m b/src/external/m2html/@template/display.m similarity index 100% rename from src/software/m2html/@template/display.m rename to src/external/m2html/@template/display.m diff --git a/src/software/m2html/@template/get.m b/src/external/m2html/@template/get.m similarity index 100% rename from src/software/m2html/@template/get.m rename to src/external/m2html/@template/get.m diff --git a/src/software/m2html/@template/parse.m b/src/external/m2html/@template/parse.m similarity index 100% rename from src/software/m2html/@template/parse.m rename to src/external/m2html/@template/parse.m diff --git a/src/software/m2html/@template/private/finish.m b/src/external/m2html/@template/private/finish.m similarity index 100% rename from src/software/m2html/@template/private/finish.m rename to src/external/m2html/@template/private/finish.m diff --git a/src/software/m2html/@template/private/loadtpl.m b/src/external/m2html/@template/private/loadtpl.m similarity index 100% rename from src/software/m2html/@template/private/loadtpl.m rename to src/external/m2html/@template/private/loadtpl.m diff --git a/src/software/m2html/@template/private/subst.m b/src/external/m2html/@template/private/subst.m similarity index 100% rename from src/software/m2html/@template/private/subst.m rename to src/external/m2html/@template/private/subst.m diff --git a/src/software/m2html/@template/set.m b/src/external/m2html/@template/set.m similarity index 100% rename from src/software/m2html/@template/set.m rename to src/external/m2html/@template/set.m diff --git a/src/software/m2html/@template/template.m b/src/external/m2html/@template/template.m similarity index 100% rename from src/software/m2html/@template/template.m rename to src/external/m2html/@template/template.m diff --git a/src/software/m2html/Changelog b/src/external/m2html/Changelog similarity index 100% rename from src/software/m2html/Changelog rename to src/external/m2html/Changelog diff --git a/src/software/m2html/Contents.m b/src/external/m2html/Contents.m similarity index 100% rename from src/software/m2html/Contents.m rename to src/external/m2html/Contents.m diff --git a/src/software/m2html/GPL b/src/external/m2html/GPL similarity index 100% rename from src/software/m2html/GPL rename to src/external/m2html/GPL diff --git a/src/software/m2html/INSTALL b/src/external/m2html/INSTALL similarity index 100% rename from src/software/m2html/INSTALL rename to src/external/m2html/INSTALL diff --git a/src/software/m2html/LICENSE b/src/external/m2html/LICENSE similarity index 100% rename from src/software/m2html/LICENSE rename to src/external/m2html/LICENSE diff --git a/src/software/m2html/README b/src/external/m2html/README similarity index 100% rename from src/software/m2html/README rename to src/external/m2html/README diff --git a/src/software/m2html/TODO b/src/external/m2html/TODO similarity index 100% rename from src/software/m2html/TODO rename to src/external/m2html/TODO diff --git a/src/software/m2html/m2html.m b/src/external/m2html/m2html.m similarity index 100% rename from src/software/m2html/m2html.m rename to src/external/m2html/m2html.m diff --git a/src/software/m2html/mdot.m b/src/external/m2html/mdot.m similarity index 100% rename from src/software/m2html/mdot.m rename to src/external/m2html/mdot.m diff --git a/src/software/m2html/private/doxyread.m b/src/external/m2html/private/doxyread.m similarity index 100% rename from src/software/m2html/private/doxyread.m rename to src/external/m2html/private/doxyread.m diff --git a/src/software/m2html/private/doxysearch.m b/src/external/m2html/private/doxysearch.m similarity index 100% rename from src/software/m2html/private/doxysearch.m rename to src/external/m2html/private/doxysearch.m diff --git a/src/software/m2html/private/doxywrite.m b/src/external/m2html/private/doxywrite.m similarity index 100% rename from src/software/m2html/private/doxywrite.m rename to src/external/m2html/private/doxywrite.m diff --git a/src/software/m2html/private/m2htmltoolbarimages.mat b/src/external/m2html/private/m2htmltoolbarimages.mat similarity index 100% rename from src/software/m2html/private/m2htmltoolbarimages.mat rename to src/external/m2html/private/m2htmltoolbarimages.mat diff --git a/src/software/m2html/private/mexexts.m b/src/external/m2html/private/mexexts.m similarity index 100% rename from src/software/m2html/private/mexexts.m rename to src/external/m2html/private/mexexts.m diff --git a/src/software/m2html/private/mfileparse.m b/src/external/m2html/private/mfileparse.m similarity index 100% rename from src/software/m2html/private/mfileparse.m rename to src/external/m2html/private/mfileparse.m diff --git a/src/software/m2html/private/openfile.m b/src/external/m2html/private/openfile.m similarity index 100% rename from src/software/m2html/private/openfile.m rename to src/external/m2html/private/openfile.m diff --git a/src/software/m2html/private/searchindex.m b/src/external/m2html/private/searchindex.m similarity index 100% rename from src/software/m2html/private/searchindex.m rename to src/external/m2html/private/searchindex.m diff --git a/src/software/m2html/private/splitcode.m b/src/external/m2html/private/splitcode.m similarity index 100% rename from src/software/m2html/private/splitcode.m rename to src/external/m2html/private/splitcode.m diff --git a/src/software/m2html/private/strtok.m b/src/external/m2html/private/strtok.m similarity index 100% rename from src/software/m2html/private/strtok.m rename to src/external/m2html/private/strtok.m diff --git a/src/software/m2html/templates/blue/Thumbs.db b/src/external/m2html/templates/blue/Thumbs.db similarity index 100% rename from src/software/m2html/templates/blue/Thumbs.db rename to src/external/m2html/templates/blue/Thumbs.db diff --git a/src/software/m2html/templates/blue/alpha.png b/src/external/m2html/templates/blue/alpha.png similarity index 100% rename from src/software/m2html/templates/blue/alpha.png rename to src/external/m2html/templates/blue/alpha.png diff --git a/src/software/m2html/templates/blue/c++.png b/src/external/m2html/templates/blue/c++.png similarity index 100% rename from src/software/m2html/templates/blue/c++.png rename to src/external/m2html/templates/blue/c++.png diff --git a/src/software/m2html/templates/blue/c.png b/src/external/m2html/templates/blue/c.png similarity index 100% rename from src/software/m2html/templates/blue/c.png rename to src/external/m2html/templates/blue/c.png diff --git a/src/software/m2html/templates/blue/demoicon.gif b/src/external/m2html/templates/blue/demoicon.gif similarity index 100% rename from src/software/m2html/templates/blue/demoicon.gif rename to src/external/m2html/templates/blue/demoicon.gif diff --git a/src/software/m2html/templates/blue/down.png b/src/external/m2html/templates/blue/down.png similarity index 100% rename from src/software/m2html/templates/blue/down.png rename to src/external/m2html/templates/blue/down.png diff --git a/src/software/m2html/templates/blue/doxysearch.php b/src/external/m2html/templates/blue/doxysearch.php similarity index 100% rename from src/software/m2html/templates/blue/doxysearch.php rename to src/external/m2html/templates/blue/doxysearch.php diff --git a/src/software/m2html/templates/blue/fortran.png b/src/external/m2html/templates/blue/fortran.png similarity index 100% rename from src/software/m2html/templates/blue/fortran.png rename to src/external/m2html/templates/blue/fortran.png diff --git a/src/software/m2html/templates/blue/graph.tpl b/src/external/m2html/templates/blue/graph.tpl similarity index 100% rename from src/software/m2html/templates/blue/graph.tpl rename to src/external/m2html/templates/blue/graph.tpl diff --git a/src/software/m2html/templates/blue/hp.png b/src/external/m2html/templates/blue/hp.png similarity index 100% rename from src/software/m2html/templates/blue/hp.png rename to src/external/m2html/templates/blue/hp.png diff --git a/src/software/m2html/templates/blue/left.png b/src/external/m2html/templates/blue/left.png similarity index 100% rename from src/software/m2html/templates/blue/left.png rename to src/external/m2html/templates/blue/left.png diff --git a/src/software/m2html/templates/blue/linux.png b/src/external/m2html/templates/blue/linux.png similarity index 100% rename from src/software/m2html/templates/blue/linux.png rename to src/external/m2html/templates/blue/linux.png diff --git a/src/software/m2html/templates/blue/m2html.css b/src/external/m2html/templates/blue/m2html.css similarity index 100% rename from src/software/m2html/templates/blue/m2html.css rename to src/external/m2html/templates/blue/m2html.css diff --git a/src/software/m2html/templates/blue/master.tpl b/src/external/m2html/templates/blue/master.tpl similarity index 100% rename from src/software/m2html/templates/blue/master.tpl rename to src/external/m2html/templates/blue/master.tpl diff --git a/src/software/m2html/templates/blue/matlabicon.gif b/src/external/m2html/templates/blue/matlabicon.gif similarity index 100% rename from src/software/m2html/templates/blue/matlabicon.gif rename to src/external/m2html/templates/blue/matlabicon.gif diff --git a/src/software/m2html/templates/blue/mdir.tpl b/src/external/m2html/templates/blue/mdir.tpl similarity index 100% rename from src/software/m2html/templates/blue/mdir.tpl rename to src/external/m2html/templates/blue/mdir.tpl diff --git a/src/software/m2html/templates/blue/mex.png b/src/external/m2html/templates/blue/mex.png similarity index 100% rename from src/software/m2html/templates/blue/mex.png rename to src/external/m2html/templates/blue/mex.png diff --git a/src/software/m2html/templates/blue/mfile.tpl b/src/external/m2html/templates/blue/mfile.tpl similarity index 100% rename from src/software/m2html/templates/blue/mfile.tpl rename to src/external/m2html/templates/blue/mfile.tpl diff --git a/src/software/m2html/templates/blue/pcode.png b/src/external/m2html/templates/blue/pcode.png similarity index 100% rename from src/software/m2html/templates/blue/pcode.png rename to src/external/m2html/templates/blue/pcode.png diff --git a/src/software/m2html/templates/blue/right.png b/src/external/m2html/templates/blue/right.png similarity index 100% rename from src/software/m2html/templates/blue/right.png rename to src/external/m2html/templates/blue/right.png diff --git a/src/software/m2html/templates/blue/search.tpl b/src/external/m2html/templates/blue/search.tpl similarity index 100% rename from src/software/m2html/templates/blue/search.tpl rename to src/external/m2html/templates/blue/search.tpl diff --git a/src/software/m2html/templates/blue/sgi.png b/src/external/m2html/templates/blue/sgi.png similarity index 100% rename from src/software/m2html/templates/blue/sgi.png rename to src/external/m2html/templates/blue/sgi.png diff --git a/src/software/m2html/templates/blue/simulinkicon.gif b/src/external/m2html/templates/blue/simulinkicon.gif similarity index 100% rename from src/software/m2html/templates/blue/simulinkicon.gif rename to src/external/m2html/templates/blue/simulinkicon.gif diff --git a/src/software/m2html/templates/blue/solaris.png b/src/external/m2html/templates/blue/solaris.png similarity index 100% rename from src/software/m2html/templates/blue/solaris.png rename to src/external/m2html/templates/blue/solaris.png diff --git a/src/software/m2html/templates/blue/todo.tpl b/src/external/m2html/templates/blue/todo.tpl similarity index 100% rename from src/software/m2html/templates/blue/todo.tpl rename to src/external/m2html/templates/blue/todo.tpl diff --git a/src/software/m2html/templates/blue/up.png b/src/external/m2html/templates/blue/up.png similarity index 100% rename from src/software/m2html/templates/blue/up.png rename to src/external/m2html/templates/blue/up.png diff --git a/src/software/m2html/templates/blue/windows.png b/src/external/m2html/templates/blue/windows.png similarity index 100% rename from src/software/m2html/templates/blue/windows.png rename to src/external/m2html/templates/blue/windows.png diff --git a/src/software/scip/.keep b/src/external/scip/.keep similarity index 100% rename from src/software/scip/.keep rename to src/external/scip/.keep diff --git a/src/software/versions.txt b/src/external/versions.txt similarity index 100% rename from src/software/versions.txt rename to src/external/versions.txt From 3d10e1c76d8d862d691a460968b7bccc7bc96ffe Mon Sep 17 00:00:00 2001 From: ae-tafur Date: Sat, 10 Oct 2026 14:20:19 -0500 Subject: [PATCH 13/48] fix: set path after reorg to external directory Original-commit: https://github.com/SysBioChalmers/RAVEN/commit/2677d51d655c53333110ccb3143327e3187325a5 --- .github/CONTRIBUTING.md | 4 ++-- .github/scripts/assemble_offline_software.py | 6 +++--- .github/workflows/release-bundle.yml | 4 ++-- checkRaven.m | 4 ++-- src/installation/downloadRavenBinaries.m | 8 ++++---- src/installation/updateDocumentation.m | 5 ++--- src/reconstruction/homology/getBlast.m | 10 +++++----- src/reconstruction/homology/getDiamond.m | 10 +++++----- src/reconstruction/kegg/getKEGGModelForOrganism.m | 4 ++-- src/solver/optimizeProb.m | 2 +- src/solver/setRavenSolver.m | 8 ++++---- tests/function_tests/tBinaries.m | 2 +- tests/function_tests/tSyntax.m | 2 +- 13 files changed, 34 insertions(+), 35 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 4da17e51..3f97e46d 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -30,8 +30,8 @@ A more detailed explanation or comments can be left in the commit description. * Once the newer version for any external software (BLAST+, DIAMOND, HMMER) is available, identify the newest version which is simultaneously available for macOS, Unix/Linux and Windows * Create a separate branch from the `devel` branch and name it e.g. `chore/updateBinaries` * Commit the changes for each program and operating system separately, e.g. `chore: update HMMER (Win) to 3.2.1` -* As soon as all binaries for particular program are updated through commits, update the corresponding license file, if available and place it in e.g. `src/software/blast+` directory. -* Update the version number in `RAVENdir/src/software/version.txt` file. +* As soon as all binaries for particular program are updated through commits, update the corresponding license file, if available and place it in e.g. `src/external/blast+` directory. +* Update the version number in `RAVENdir/src/external/version.txt` file. * Do some testing to ensure that the new binaries are working correctly. Upon successful tests, create a Pull Request to the `devel` branch. ### Pull Requests diff --git a/.github/scripts/assemble_offline_software.py b/.github/scripts/assemble_offline_software.py index fb40c454..4193fcf2 100644 --- a/.github/scripts/assemble_offline_software.py +++ b/.github/scripts/assemble_offline_software.py @@ -1,8 +1,8 @@ #!/usr/bin/env python -"""Populate RAVEN's src/software/ with every platform's external binaries. +"""Populate RAVEN's src/external/ with every platform's external binaries. Downloads the per-platform binary ZIPs from raven-data and lays them out under -``/src/software//`` using RAVEN's suffix scheme — bare name = Linux, +``/src/external//`` using RAVEN's suffix scheme — bare name = Linux, ``.mac`` = macOS, ``.exe`` = Windows. With all platforms present in one tree, the whole RAVEN checkout can be zipped into a single **OS-independent** offline distribution (see the release-bundle workflow). @@ -50,7 +50,7 @@ def _extract(zip_bytes: bytes, dest: Path, rename: dict[str, str]) -> None: def assemble(raven: Path) -> None: - soft = raven / "src" / "software" + soft = raven / "src" / "external" for subdir, bundle, vmap, exes in CLI_TOOLS: dest = soft / subdir for plat, suffix in PLATFORMS.items(): diff --git a/.github/workflows/release-bundle.yml b/.github/workflows/release-bundle.yml index 1ec31488..418a379f 100644 --- a/.github/workflows/release-bundle.yml +++ b/.github/workflows/release-bundle.yml @@ -1,7 +1,7 @@ name: Offline binary bundle # Build the OS-independent offline RAVEN distribution: the full repo with -# src/software/ populated with every platform's external binaries, so an air-gapped +# src/external/ populated with every platform's external binaries, so an air-gapped # user downloads one ZIP and runs RAVEN on any OS without fetching anything. # Attached automatically to each published release; also runnable on demand # against an existing tag. @@ -37,7 +37,7 @@ jobs: with: python-version: '3.12' - - name: Populate src/software/ with every platform's binaries + - name: Populate src/external/ with every platform's binaries run: python .github/scripts/assemble_offline_software.py - name: Build RAVEN--full.zip diff --git a/checkRaven.m b/checkRaven.m index fbb67cd8..be767099 100755 --- a/checkRaven.m +++ b/checkRaven.m @@ -300,7 +300,7 @@ function testBinary(ravenDir, tool, binName, versionArg) else binEnd = ''; end -cmd = ['"' fullfile(ravenDir,'src','software',tool,[binName binEnd]) '" ' versionArg]; +cmd = ['"' fullfile(ravenDir,'src','external',tool,[binName binEnd]) '" ' versionArg]; [status,~] = system(cmd); if status ~= 0 error('%s did not execute (exit status %d)', binName, status); @@ -339,7 +339,7 @@ function testBinary(ravenDir, tool, binName, versionArg) status = 0; % No need to run on Windows return; end -binDir = fullfile(ravenDir,'src','software'); +binDir = fullfile(ravenDir,'src','external'); % blast+/diamond/hmmer may be present from an on-demand download % (downloadRavenBinaries already chmods those) or from the offline bundle; diff --git a/src/installation/downloadRavenBinaries.m b/src/installation/downloadRavenBinaries.m index f440c8a1..0aea8760 100644 --- a/src/installation/downloadRavenBinaries.m +++ b/src/installation/downloadRavenBinaries.m @@ -44,13 +44,13 @@ function downloadRavenBinaries(tools) case 'blast+' tag = 'blast-2.17.0'; asset = ['blast-2.17.0-' plat '.zip']; - destDir = fullfile(ravenDir,'src','software','blast+'); + destDir = fullfile(ravenDir,'src','external','blast+'); sentinel = fullfile(destDir,'blastp'); % bare name on every OS (.exe on Windows) execs = {'blastp','makeblastdb'}; case 'diamond' tag = 'diamond-2.1.17'; asset = ['diamond-2.1.17-' plat '.zip']; - destDir = fullfile(ravenDir,'src','software','diamond'); + destDir = fullfile(ravenDir,'src','external','diamond'); sentinel = fullfile(destDir,'diamond'); execs = {'diamond'}; case 'hmmer' @@ -61,7 +61,7 @@ function downloadRavenBinaries(tools) tag = 'hmmer-3.4.0'; asset = ['hmmer-3.4.0-' plat '.zip']; end - destDir = fullfile(ravenDir,'src','software','hmmer'); + destDir = fullfile(ravenDir,'src','external','hmmer'); sentinel = fullfile(destDir,'hmmsearch'); execs = {'hmmsearch'}; otherwise @@ -78,7 +78,7 @@ function downloadRavenBinaries(tools) continue; % already provisioned end - zipPath = fetchRavenDataAsset(fullfile(ravenDir,'src','software'),tag,asset); + zipPath = fetchRavenDataAsset(fullfile(ravenDir,'src','external'),tag,asset); unzip(zipPath,destDir); delete(zipPath); diff --git a/src/installation/updateDocumentation.m b/src/installation/updateDocumentation.m index efb7ff9a..97892506 100755 --- a/src/installation/updateDocumentation.m +++ b/src/installation/updateDocumentation.m @@ -14,7 +14,7 @@ function updateDocumentation() ravenDir=findRAVENroot(); srcDir=fullfile(ravenDir,'src'); %Make sure that RAVEN-provided m2html is used -path(fullfile(ravenDir,'src','software','m2html'),path); +path(fullfile(ravenDir,'src','external','m2html'),path); %Get a non-redundant list of RAVEN subdirectories containing MATLAB %functions. Absolute paths are not compatible with M2HTML, so convert them %to the relative paths instead. @@ -22,8 +22,7 @@ function updateDocumentation() ravenDirs=unique({ravenDirs.folder})'; %Get rid of MATLAB functions from external software -ravenDirs(startsWith(ravenDirs,strcat(srcDir,filesep,'software')))=[]; -ravenDirs(startsWith(ravenDirs,strcat(srcDir,filesep,'legacy',filesep,'software')))=[]; +ravenDirs(startsWith(ravenDirs,strcat(srcDir,filesep,'external')))=[]; %Skip deprecated wrappers: they are on the path so existing scripts keep %working, but documenting them would advertise functions that are on their diff --git a/src/reconstruction/homology/getBlast.m b/src/reconstruction/homology/getBlast.m index 0d6b19e5..e74775da 100755 --- a/src/reconstruction/homology/getBlast.m +++ b/src/reconstruction/homology/getBlast.m @@ -109,7 +109,7 @@ end %Fetch the BLAST+ binaries on demand if they are not already present -if ~exist(fullfile(ravenPath,'src','software','blast+',['blastp' binEnd]),'file') +if ~exist(fullfile(ravenPath,'src','external','blast+',['blastp' binEnd]),'file') downloadRavenBinaries({'blast+'}); end @@ -121,7 +121,7 @@ %Create a database for the new organism and blast each of the refFastaFiles %against it -[status, message]=system(['"' fullfile(ravenPath,'src','software','blast+',['makeblastdb' binEnd]) '" -in "' fastaFile{1} '" -out "' fullfile(tmpDB, 'tmpDB') '" -dbtype prot']); +[status, message]=system(['"' fullfile(ravenPath,'src','external','blast+',['makeblastdb' binEnd]) '" -in "' fastaFile{1} '" -out "' fullfile(tmpDB, 'tmpDB') '" -dbtype prot']); if developMode blastReport.dbHashes.phr{numel(blastReport.dbHashes.phr)+1}=filemd5(fullfile(tmpDB, 'tmpDB.phr')); blastReport.dbHashes.pot{numel(blastReport.dbHashes.pot)+1}=filemd5(fullfile(tmpDB, 'tmpDB.pot')); @@ -136,7 +136,7 @@ if ~hideVerbose fprintf(['BLASTing "' modelIDs{i} '" against "' organismID{1} '"..\n']); end - [status, message]=system(['"' fullfile(ravenPath,'src','software','blast+',['blastp' binEnd]) '" -query "' refFastaFiles{i} '" -out "' outFile '_' num2str(i) '" -db "' fullfile(tmpDB, 'tmpDB') '" -evalue 10e-5 -outfmt "10 qseqid sseqid evalue pident length bitscore ppos" -num_threads "' cores '"']); + [status, message]=system(['"' fullfile(ravenPath,'src','external','blast+',['blastp' binEnd]) '" -query "' refFastaFiles{i} '" -out "' outFile '_' num2str(i) '" -db "' fullfile(tmpDB, 'tmpDB') '" -evalue 10e-5 -outfmt "10 qseqid sseqid evalue pident length bitscore ppos" -num_threads "' cores '"']); if developMode blastReport.blastTxtOutput{numel(blastReport.blastTxtOutput)+1}=importdata([outFile '_' num2str(i)]); end @@ -152,11 +152,11 @@ if ~hideVerbose fprintf(['BLASTing "' organismID{1} '" against "' modelIDs{i} '"..\n']); end - [status, message]=system(['"' fullfile(ravenPath,'src','software','blast+',['makeblastdb' binEnd]) '" -in "' refFastaFiles{i} '" -out "' fullfile(tmpDB, 'tmpDB') '" -dbtype prot']); + [status, message]=system(['"' fullfile(ravenPath,'src','external','blast+',['makeblastdb' binEnd]) '" -in "' refFastaFiles{i} '" -out "' fullfile(tmpDB, 'tmpDB') '" -dbtype prot']); if status~=0 error('makeblastdb did not run successfully, error:\n%s',strip(message)) end - [status, message]=system(['"' fullfile(ravenPath,'src','software','blast+',['blastp' binEnd]) '" -query "' fastaFile{1} '" -out "' outFile '_r' num2str(i) '" -db "' fullfile(tmpDB, 'tmpDB') '" -evalue 10e-5 -outfmt "10 qseqid sseqid evalue pident length bitscore ppos" -num_threads "' cores '"']); + [status, message]=system(['"' fullfile(ravenPath,'src','external','blast+',['blastp' binEnd]) '" -query "' fastaFile{1} '" -out "' outFile '_r' num2str(i) '" -db "' fullfile(tmpDB, 'tmpDB') '" -evalue 10e-5 -outfmt "10 qseqid sseqid evalue pident length bitscore ppos" -num_threads "' cores '"']); if developMode blastReport.dbHashes.phr{numel(blastReport.dbHashes.phr)+1}=filemd5(fullfile(tmpDB, 'tmpDB.phr')); blastReport.dbHashes.pot{numel(blastReport.dbHashes.pot)+1}=filemd5(fullfile(tmpDB, 'tmpDB.pot')); diff --git a/src/reconstruction/homology/getDiamond.m b/src/reconstruction/homology/getDiamond.m index 1e927ddb..84f93fbe 100755 --- a/src/reconstruction/homology/getDiamond.m +++ b/src/reconstruction/homology/getDiamond.m @@ -98,7 +98,7 @@ end %Fetch the DIAMOND binary on demand if it is not already present -if ~exist(fullfile(ravenPath,'src','software','diamond',['diamond' binEnd]),'file') +if ~exist(fullfile(ravenPath,'src','external','diamond',['diamond' binEnd]),'file') downloadRavenBinaries({'diamond'}); end @@ -110,7 +110,7 @@ %Create a database for the new organism and blast each of the refFastaFiles %against it -[status, message]=system(['"' fullfile(ravenPath,'src','software','diamond',['diamond' binEnd]) '" makedb --in "' fastaFile{1} '" --db "' fullfile(tmpDB) '"']); +[status, message]=system(['"' fullfile(ravenPath,'src','external','diamond',['diamond' binEnd]) '" makedb --in "' fastaFile{1} '" --db "' fullfile(tmpDB) '"']); if developMode diamondReport.dbHashes{numel(diamondReport.dbHashes)+1} = char(regexp(message,'[a-f0-9]{32}','match')); end @@ -122,7 +122,7 @@ if ~hideVerbose fprintf(['Running DIAMOND blastp with "' modelIDs{i} '" against "' organismID{1} '"..\n']); end - [status, message]=system(['"' fullfile(ravenPath,'src','software','diamond',['diamond' binEnd]) '" blastp --query "' refFastaFiles{i} '" --out "' outFile '_' num2str(i) '" --db "' fullfile(tmpDB) '" --more-sensitive --outfmt 6 qseqid sseqid evalue pident length bitscore ppos --threads ' cores ]); + [status, message]=system(['"' fullfile(ravenPath,'src','external','diamond',['diamond' binEnd]) '" blastp --query "' refFastaFiles{i} '" --out "' outFile '_' num2str(i) '" --db "' fullfile(tmpDB) '" --more-sensitive --outfmt 6 qseqid sseqid evalue pident length bitscore ppos --threads ' cores ]); if developMode diamondReport.diamondTxtOutput{numel(diamondReport.diamondTxtOutput)+1}=importdata([outFile '_' num2str(i)]); end @@ -138,11 +138,11 @@ if ~hideVerbose fprintf(['Running DIAMOND blastp with "' organismID{1} '" against "' modelIDs{i} '"..\n']); end - [status, message1]=system(['"' fullfile(ravenPath,'src','software','diamond',['diamond' binEnd]) '" makedb --in "' refFastaFiles{i} '" --db "' fullfile(tmpDB) '"']); + [status, message1]=system(['"' fullfile(ravenPath,'src','external','diamond',['diamond' binEnd]) '" makedb --in "' refFastaFiles{i} '" --db "' fullfile(tmpDB) '"']); if status~=0 error('DIAMOND makedb did not run successfully, error:\n%s',strip(message1)) end - [status, message]=system(['"' fullfile(ravenPath,'src','software','diamond',['diamond' binEnd]) '" blastp --query "' fastaFile{1} '" --out "' outFile '_r' num2str(i) '" --db "' fullfile(tmpDB) '" --more-sensitive --outfmt 6 qseqid sseqid evalue pident length bitscore ppos --threads ' cores]); + [status, message]=system(['"' fullfile(ravenPath,'src','external','diamond',['diamond' binEnd]) '" blastp --query "' fastaFile{1} '" --out "' outFile '_r' num2str(i) '" --db "' fullfile(tmpDB) '" --more-sensitive --outfmt 6 qseqid sseqid evalue pident length bitscore ppos --threads ' cores]); if developMode diamondReport.dbHashes{numel(diamondReport.dbHashes)+1} = char(regexp(message1,'[a-f0-9]{32}','match')); diamondReport.diamondTxtOutput{numel(diamondReport.diamondTxtOutput)+1}=importdata([outFile '_r' num2str(i)]); diff --git a/src/reconstruction/kegg/getKEGGModelForOrganism.m b/src/reconstruction/kegg/getKEGGModelForOrganism.m index b27421ee..8f6dfa5e 100755 --- a/src/reconstruction/kegg/getKEGGModelForOrganism.m +++ b/src/reconstruction/kegg/getKEGGModelForOrganism.m @@ -368,12 +368,12 @@ binEnd=''; end %Fetch the HMMER binary on demand if it is not already present -if ~exist(fullfile(ravenPath,'src','software','hmmer',['hmmsearch' binEnd]),'file') +if ~exist(fullfile(ravenPath,'src','external','hmmer',['hmmsearch' binEnd]),'file') downloadRavenBinaries({'hmmer'}); end tblFile=[tempname '.tblout']; fprintf('Querying the user-specified FASTA file against the KEGG Orthology specific HMMs... '); -[status, output]=system(['"' fullfile(ravenPath,'src','software','hmmer',['hmmsearch' binEnd]) '" --cpu "' num2str(cores) '" --tblout "' tblFile '" "' libraryFile '" "' fastaFile '"']); +[status, output]=system(['"' fullfile(ravenPath,'src','external','hmmer',['hmmsearch' binEnd]) '" --cpu "' num2str(cores) '" --tblout "' tblFile '" "' libraryFile '" "' fastaFile '"']); if status~=0 EM=['Error when querying the concatenated HMM library:\n' output]; error('RAVEN:badInput', '%s', EM); diff --git a/src/solver/optimizeProb.m b/src/solver/optimizeProb.m index 71b12e73..de278ad1 100755 --- a/src/solver/optimizeProb.m +++ b/src/solver/optimizeProb.m @@ -224,7 +224,7 @@ % Ensure that RAVEN glpk binary is used, return to original % directory afterwards [ravenDir,currDir]=findRAVENroot(); - cd(fullfile(ravenDir,'src','software','GLPKmex')) + cd(fullfile(ravenDir,'src','external','GLPKmex')) [xopt, fmin, errnum, extra] = glpk(prob.c, prob.A, prob.b, prob.lb, prob.ub, prob.csense, prob.vartype, prob.osense, solverparams); cd(currDir) diff --git a/src/solver/setRavenSolver.m b/src/solver/setRavenSolver.m index c50de2a1..0adf3663 100755 --- a/src/solver/setRavenSolver.m +++ b/src/solver/setRavenSolver.m @@ -43,15 +43,15 @@ function setRavenSolver(solver) end else ravenDir = findRAVENroot(); - if ~exist(fullfile(ravenDir,'src','software','scip','scip.mexw64'),'file') + if ~exist(fullfile(ravenDir,'src','external','scip','scip.mexw64'),'file') try disp('Downloading and installing RAVEN-provided SCIP MEX binary...') - websave(fullfile(ravenDir,'src','software','scip_mex_win.zip'),'https://github.com/SysBioChalmers/RAVEN/releases/download/v2.8.6/scip_mex_win.zip'); % Should be updated to release with SCIP functionality + websave(fullfile(ravenDir,'src','external','scip_mex_win.zip'),'https://github.com/SysBioChalmers/RAVEN/releases/download/v2.8.6/scip_mex_win.zip'); % Should be updated to release with SCIP functionality catch error('Unable to download SCIP MEX binary from the RAVEN GitHub page.') end - unzip(fullfile(ravenDir,'src','software','scip_mex_win.zip'),fullfile(ravenDir,'src','software','scip')); - delete(fullfile(ravenDir,'src','software','scip_mex_win.zip')); + unzip(fullfile(ravenDir,'src','external','scip_mex_win.zip'),fullfile(ravenDir,'src','external','scip')); + delete(fullfile(ravenDir,'src','external','scip_mex_win.zip')); end try scip; % The pre-compiled MEX file might fail diff --git a/tests/function_tests/tBinaries.m b/tests/function_tests/tBinaries.m index 89045fab..23e5f29f 100644 --- a/tests/function_tests/tBinaries.m +++ b/tests/function_tests/tBinaries.m @@ -28,7 +28,7 @@ function hmmer(testCase) methods (Access = private) function p = binPath(~, tool, name) if ispc; ext = '.exe'; elseif ismac; ext = '.mac'; else; ext = ''; end - p = fullfile(findRAVENroot(),'src','software',tool,[name ext]); + p = fullfile(findRAVENroot(),'src','external',tool,[name ext]); end function fetchAndRun(testCase, tool, name, versionArg) diff --git a/tests/function_tests/tSyntax.m b/tests/function_tests/tSyntax.m index 69c2ebf7..66904f78 100644 --- a/tests/function_tests/tSyntax.m +++ b/tests/function_tests/tSyntax.m @@ -16,7 +16,7 @@ function allSourceFilesParse(testCase) % software/ is vendored third-party code (GLPKmex, libSBML). It is % not ours to fix, so a syntax complaint there is not a RAVEN bug. - paths = paths(~contains(paths, [filesep 'software' filesep])); + paths = paths(~contains(paths, [filesep 'external' filesep])); testCase.assertNotEmpty(paths, 'Found no .m files to check.'); From 743c2dde9962079857715e951c0db5722e5a7b6a Mon Sep 17 00:00:00 2001 From: Eduard Kerkhoven Date: Sat, 17 Oct 2026 07:32:07 +0200 Subject: [PATCH 14/48] Point repository, documentation and version-check links to CompMetEng (#6) --- .github/CONTRIBUTING.md | 2 +- .github/issue_template.md | 2 +- .github/pull_request_template.md | 2 +- README.md | 14 +++++++------- checkRaven.m | 8 ++++---- conversion/standardizeModelFieldOrder.m | 2 +- docs/v3_review.md | 4 ++-- tutorial/tutorial1.m | 2 +- tutorial/tutorial2.m | 2 +- tutorial/tutorial2_solutions.m | 2 +- tutorial/tutorial3.m | 2 +- tutorial/tutorial3_solutions.m | 2 +- tutorial/tutorial4.m | 2 +- tutorial/tutorial4_solutions.m | 2 +- tutorial/tutorial5.m | 2 +- tutorial/tutorial7.m | 2 +- 16 files changed, 26 insertions(+), 26 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 308c1f82..8cdcf209 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -41,7 +41,7 @@ A more detailed explanation or comments can be left in the commit description. * A merge with the mainbranch invokes a new release (see [versioning](#versioning)). ### Versioning -RAVEN Toolbox follows [semantic versioning](https://semver.org/). After each successful Pull Request (PR) to `main`, the `version.txt` file should be updated in a separate PR. When this PR is merged, a new [release](https://github.com/SysBioChalmers/RAVEN/releases) should be specified, with a list detailing the most significant changes since the previous release. +RAVEN Toolbox follows [semantic versioning](https://semver.org/). After each successful Pull Request (PR) to `main`, the `version.txt` file should be updated in a separate PR. When this PR is merged, a new [release](https://github.com/CompMetEng/RAVEN/releases) should be specified, with a list detailing the most significant changes since the previous release. Ensure that the documentation is also updated (in `devel`) before creating a PR to `main`. It can be updated by running `updateDocumentation()`. diff --git a/.github/issue_template.md b/.github/issue_template.md index d31135e1..48b4ee61 100644 --- a/.github/issue_template.md +++ b/.github/issue_template.md @@ -26,4 +26,4 @@ etc. etc. **I hereby confirm that I have:** -- [ ] Checked that a similar issue does not [already exist](https://github.com/SysBioChalmers/RAVEN/issues?q=is%3Aissue) or is already [discussed](https://github.com/SysBioChalmers/RAVEN/discussions). +- [ ] Checked that a similar issue does not [already exist](https://github.com/CompMetEng/RAVEN/issues?q=is%3Aissue) or is already [discussed](https://github.com/CompMetEng/RAVEN/discussions). diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 35742302..c04f8144 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -11,4 +11,4 @@ - [ ] This PR has `develop` as target branch, and will be resolved with a *squash-merge*. -- [ ] This PR has `main` as target branch, and will be resolved as descriped [here](https://github.com/SysBioChalmers/RAVEN/wiki/Development-policy#make-a-new-release). +- [ ] This PR has `main` as target branch, and will be resolved as described [here](https://github.com/CompMetEng/RAVEN/blob/develop/.github/CONTRIBUTING.md#versioning). diff --git a/README.md b/README.md index 26e597fd..fe1385cf 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ -[![Current release](https://img.shields.io/github/release/SysBioChalmers/RAVEN/all.svg)](https://GitHub.com/SysBioChalmers/RAVEN/releases/) -[![GitHub Discussions](https://img.shields.io/github/discussions-search?query=repo%3Asysbiochalmers%2raven&label=GitHub%20Discussions)](https://github.com/SysBioChalmers/RAVEN/discussions) +[![Current release](https://img.shields.io/github/release/CompMetEng/RAVEN/all.svg)](https://github.com/CompMetEng/RAVEN/releases/) +[![GitHub Discussions](https://img.shields.io/github/discussions-search?query=repo%3ACompMetEng%2FRAVEN&label=GitHub%20Discussions)](https://github.com/CompMetEng/RAVEN/discussions) [![Zenodo](https://zenodo.org/badge/DOI/10.5281/zenodo.3689518.svg)](https://doi.org/10.5281/zenodo.3689518) [![MATLAB File Exchange](https://www.mathworks.com/matlabcentral/images/matlab-file-exchange.svg)](https://se.mathworks.com/matlabcentral/fileexchange/112330-raven-toolbox) @@ -9,15 +9,15 @@ The **RAVEN** (Reconstruction, Analysis and Visualization of Metabolic Networks) Toolbox is a software suite for MATLAB that allows for semi-automated reconstruction of genome-scale models (GEMs). It makes use of published models and/or the KEGG database, coupled with extensive gap-filling and quality control features. The software suite also contains methods for visualizing simulation results and omics data, as well as a range of methods for performing simulations and analyzing the results. The software is a useful tool for system-wide data analysis in a metabolic context and for streamlined reconstruction of metabolic networks based on protein homology. -The latest stable release is **RAVEN 2.11.3**, on the `main` branch. +The latest stable release is **RAVEN 2.11.4**, maintained in [SysBioChalmers/RAVEN](https://github.com/SysBioChalmers/RAVEN). -> **This `develop3` branch is where RAVEN 3 is under active development**, with the upcoming **3.0.0b1** beta as its first release, targeting a final **3.0.0**. RAVEN 3 is a deliberate breaking release: the codebase has been reorganized into functional folders, optional function arguments can now be passed positionally, by name, or both, external binaries and KEGG reference data are fetched on demand instead of bundled in the repository, several MATLAB toolbox dependencies were dropped, and a number of low-value or duplicate functions were removed alongside new capabilities. Until 3.0.0 is tagged, this branch can still change. See the [migration guide](#documentation) for the full list of changes for users upgrading from RAVEN 2. +> **This repository is where RAVEN 3 is under active development**, with the upcoming **3.0.0b1** beta as its first release, targeting a final **3.0.0**. RAVEN 3 is a deliberate breaking release: the codebase has been reorganized into functional folders, optional function arguments can now be passed positionally, by name, or both, external binaries and KEGG reference data are fetched on demand instead of bundled in the repository, several MATLAB toolbox dependencies were dropped, and a number of low-value or duplicate functions were removed alongside new capabilities. Until 3.0.0 is tagged, the code can still change. See the [migration guide](#documentation) for the full list of changes for users upgrading from RAVEN 2. For context-specific model extraction, use `ftINIT`; `getINITModel` and `runINIT` are the original tINIT implementation, kept for reproducing models built with it. ## Documentation -The information about downloading, installing and developing RAVEN is included in the [Wiki](https://github.com/SysBioChalmers/RAVEN/wiki). The source code documentation is also available [online](http://sysbiochalmers.github.io/RAVEN/doc/). +Installation instructions, a user guide and the API reference are published at [raven-docs](https://raven-docs.readthedocs.io/en/latest/). Full user documentation, including a migration guide for users upgrading from RAVEN 2 to RAVEN 3, is published at [raven-docs](https://raven-docs.readthedocs.io/en/latest/). @@ -34,8 +34,8 @@ If you use ftINIT in your scientific work, please cite: For crediting supporting work, please cite doi:[10.1002/msb.145122](http://msb.embopress.org/content/10/3/721) (`tInit`); doi:[10.1371/journal.pcbi.1000859](http://journals.plos.org/ploscompbiol/article?id=10.1371/journal.pcbi.1000859) (`randomsampling`). For crediting RAVEN 1, cite doi:[10.1371/journal.pcbi.1002980](http://journals.plos.org/ploscompbiol/article?id=10.1371/journal.pcbi.1002980). For more details, see [wiki#cite-us](https://github.com/SysBioChalmers/RAVEN/wiki#cite-us). ## Contact Us -Use [GitHub Discussions](https://github.com/SysBioChalmers/RAVEN/discussions) for support, to ask questions or leave comments. +Use [GitHub Discussions](https://github.com/CompMetEng/RAVEN/discussions) for support, to ask questions or leave comments. ## Contributing -Contributions are always welcome! Please read the [Contributor guidelines](https://github.com/SysBioChalmers/RAVEN/blob/main/.github/CONTRIBUTING.md) to get started. RAVEN 2.x maintenance happens on the `develop` branch; RAVEN 3 development happens on `develop3`. +Contributions are always welcome! Please read the [Contributor guidelines](https://github.com/CompMetEng/RAVEN/blob/develop/.github/CONTRIBUTING.md) to get started. RAVEN 3 development happens on the `develop` branch of this repository; RAVEN 2.x is maintained in [SysBioChalmers/RAVEN](https://github.com/SysBioChalmers/RAVEN). diff --git a/checkRaven.m b/checkRaven.m index aa462a86..e65111b8 100755 --- a/checkRaven.m +++ b/checkRaven.m @@ -28,7 +28,7 @@ % 1: as MATLAB Add-On % 2: neither of the above, direct download of ZIP file % This matches the installations mentioned in the wiki: -% https://github.com/SysBioChalmers/RAVEN/wiki/Installation +% https://raven-docs.readthedocs.io/en/latest/installation/raven.html % 0 = advanced / 1 = easy / 2 = medium % % Usage: [currVer, installType] = checkRaven(developMode) @@ -82,7 +82,7 @@ if ~versionOnly fprintf([currVer '\n']); try - newVer=strtrim(webread('https://raw.githubusercontent.com/SysBioChalmers/RAVEN/main/version.txt')); + newVer=strtrim(webread('https://raw.githubusercontent.com/CompMetEng/RAVEN/main/version.txt')); newVerNum=str2double(strsplit(newVer,'.')); currVerNum=str2double(strsplit(currVer,'.')); for i=1:3 @@ -95,10 +95,10 @@ printOrange(' to get the latest RAVEN release\n'); case 1 printOrange(myStr(' Instructions on how to upgrade',40)) - fprintf('here\n'); + fprintf('here\n'); case 2 printOrange(myStr(' Instructions on how to upgrade',40)) - fprintf('here\n'); + fprintf('here\n'); end break elseif i==3 diff --git a/conversion/standardizeModelFieldOrder.m b/conversion/standardizeModelFieldOrder.m index b81441f1..c0b63639 100755 --- a/conversion/standardizeModelFieldOrder.m +++ b/conversion/standardizeModelFieldOrder.m @@ -2,7 +2,7 @@ % standardizeModelFieldOrder Order RAVEN model structure fields. % % Orders fields of a RAVEN model structure as specified at -% https://github.com/SysBioChalmers/RAVEN/wiki/RAVEN-Model-Structure +% https://raven-docs.readthedocs.io/en/latest/guide/model-structure.html % % The model fields themselves are not changed, only the order is modified. % For changing model fields between RAVEN and COBRA format, use diff --git a/docs/v3_review.md b/docs/v3_review.md index 691c4568..82d9a4e0 100644 --- a/docs/v3_review.md +++ b/docs/v3_review.md @@ -70,12 +70,12 @@ So the review does not repeat completed work. v3 already: as a new `UniProt` table column (§6.3). - Removed `followFluxes` (SysBioChalmers/RAVEN#725): a strict subset of `printFluxes`'s cutoff filtering plus `followChanged`'s single-reference-flux comparison, with nothing of - its own (SysBioChalmers/raven-gecko-parity#46). + its own (CompMetEng/raven-gecko-parity#46). - Removed `exportToTabDelimited` and the `exportToExcelFormat` bare-path fallback that called it (SysBioChalmers/RAVEN#727): the fallback silently switched output format depending on whether `fileName` looked like a path, contradicting `exportToExcelFormat`'s own error message ("only xlsx format is supported" as of RAVEN 1.9) whenever a full filename *was* - given (SysBioChalmers/raven-gecko-parity#55). + given (CompMetEng/raven-gecko-parity#55). --- diff --git a/tutorial/tutorial1.m b/tutorial/tutorial1.m index 3d748cd5..abcc7db7 100755 --- a/tutorial/tutorial1.m +++ b/tutorial/tutorial1.m @@ -9,7 +9,7 @@ % this tutorial. The model is provided as the SBML file % "iAL1006 v1.00.xml". % See Tutorial 1 on the RAVEN wiki for more details: -% https://github.com/SysBioChalmers/RAVEN/wiki/Tutorials +% https://raven-docs.readthedocs.io/en/latest/tutorials/ %Import the model from SBML. This function performs a number of checks %regarding the model structure (such as for incorrectly written equations diff --git a/tutorial/tutorial2.m b/tutorial/tutorial2.m index 5df2143e..77bf8acf 100755 --- a/tutorial/tutorial2.m +++ b/tutorial/tutorial2.m @@ -4,7 +4,7 @@ % stoichiometric modelling. It is shown how to build a simple model from % scratch, set parameters and perform simple simulations. % See Tutorial 2 on the RAVEN wiki for more details: -% https://github.com/SysBioChalmers/RAVEN/wiki/Tutorials +% https://raven-docs.readthedocs.io/en/latest/tutorials/ %Import the model into a RAVEN model structure smallModel=importModel('empty.xml'); diff --git a/tutorial/tutorial2_solutions.m b/tutorial/tutorial2_solutions.m index 4038575f..a9ea35a4 100755 --- a/tutorial/tutorial2_solutions.m +++ b/tutorial/tutorial2_solutions.m @@ -1,7 +1,7 @@ % tutorial2_solutions % This script contains the solutions for Tutorial 2, see Tutorial 2 on % the RAVEN wiki for more details: -% https://github.com/SysBioChalmers/RAVEN/wiki/Tutorials +% https://raven-docs.readthedocs.io/en/latest/tutorials/ %Import the model into a RAVEN model structure smallModel=readYAMLmodel('small.yml'); diff --git a/tutorial/tutorial3.m b/tutorial/tutorial3.m index 399ed42b..85637f3b 100755 --- a/tutorial/tutorial3.m +++ b/tutorial/tutorial3.m @@ -4,7 +4,7 @@ % data. A simplified model of yeast metabolism is used in this approach % as an example. % See Tutorial 3 on the RAVEN wiki for more details: -% https://github.com/SysBioChalmers/RAVEN/wiki/Tutorials +% https://raven-docs.readthedocs.io/en/latest/tutorials/ % % It is assumed that the user has already completed Tutorial 2 diff --git a/tutorial/tutorial3_solutions.m b/tutorial/tutorial3_solutions.m index 1451360b..5e2f5e7a 100755 --- a/tutorial/tutorial3_solutions.m +++ b/tutorial/tutorial3_solutions.m @@ -1,7 +1,7 @@ % tutorial3_solutions % This script contains the solutions for Tutorial 3, see Tutorial 3 on % the RAVEN wiki for more details: -% https://github.com/SysBioChalmers/RAVEN/wiki/Tutorials +% https://raven-docs.readthedocs.io/en/latest/tutorials/ % All the parameters are set in this script, rather than modifying the % Excel model file. diff --git a/tutorial/tutorial4.m b/tutorial/tutorial4.m index 8f00ba88..20b29dcb 100755 --- a/tutorial/tutorial4.m +++ b/tutorial/tutorial4.m @@ -1,7 +1,7 @@ % tutorial4 % This script contains the list of functions necessary for running % Tutorial 4, see Tutorial 4 on the RAVEN wiki for more details: -% https://github.com/SysBioChalmers/RAVEN/wiki/Tutorials +% https://raven-docs.readthedocs.io/en/latest/tutorials/ % Several key stages may be missing, try to fill these gaps before % checking the solutions in tutorial4_solutions. It is assumed that the % user is somewhat familiar with linear programming. diff --git a/tutorial/tutorial4_solutions.m b/tutorial/tutorial4_solutions.m index 19db2a2c..55b4d00f 100755 --- a/tutorial/tutorial4_solutions.m +++ b/tutorial/tutorial4_solutions.m @@ -1,7 +1,7 @@ % tutorial4_solutions % This script contains the solutions for Tutorial 4, see Tutorial 4 on % the RAVEN wiki for more details: -% https://github.com/SysBioChalmers/RAVEN/wiki/Tutorials +% https://raven-docs.readthedocs.io/en/latest/tutorials/ % % NOTE: Many of these changes are easier to do in the Excel sheet. They % are done here in code just to avoid having several model files. diff --git a/tutorial/tutorial5.m b/tutorial/tutorial5.m index 8764181d..5f1a985d 100755 --- a/tutorial/tutorial5.m +++ b/tutorial/tutorial5.m @@ -6,7 +6,7 @@ % purpose is to serve as a scaffold to reconstruct a GEM for any % organism. % This refers to Tutorial 5 on the RAVEN wiki: -% https://github.com/SysBioChalmers/RAVEN/wiki/Tutorials +% https://raven-docs.readthedocs.io/en/latest/tutorials/ % % Start by downloading trained Hidden Markov Models for eukaryotes. This can % be done automatically or manually from the RAVEN Wiki in its GitHub diff --git a/tutorial/tutorial7.m b/tutorial/tutorial7.m index 77cfad99..9a3f02f4 100644 --- a/tutorial/tutorial7.m +++ b/tutorial/tutorial7.m @@ -6,7 +6,7 @@ % measured values (with one component left to absorb whatever is left, % so the total stays at 1 g/gDW), and setGAM to apply a % growth-associated maintenance value. See Tutorial 7 on the RAVEN wiki -% for more details: https://github.com/SysBioChalmers/RAVEN/wiki/Tutorials +% for more details: https://raven-docs.readthedocs.io/en/latest/tutorials/ % % A small hand-built model is used instead of a real GEM, so the % pseudoreaction layout is visible in full below. Real GEMs lay this out From 58d2dcbd1b45298a27f1b372a0581f6ae4ef1071 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 17 Oct 2026 05:34:15 +0000 Subject: [PATCH 15/48] doc: regenerate HTML documentation for 743c2dd [skip ci] --- doc/checkRaven.html | 10 +++++----- doc/conversion/standardizeModelFieldOrder.html | 4 ++-- doc/tutorial/tutorial1.html | 4 ++-- doc/tutorial/tutorial2.html | 4 ++-- doc/tutorial/tutorial2_solutions.html | 4 ++-- doc/tutorial/tutorial3.html | 4 ++-- doc/tutorial/tutorial3_solutions.html | 4 ++-- doc/tutorial/tutorial4.html | 4 ++-- doc/tutorial/tutorial4_solutions.html | 4 ++-- doc/tutorial/tutorial5.html | 4 ++-- doc/tutorial/tutorial7.html | 4 ++-- 11 files changed, 25 insertions(+), 25 deletions(-) diff --git a/doc/checkRaven.html b/doc/checkRaven.html index dd37d42b..1bc45c60 100644 --- a/doc/checkRaven.html +++ b/doc/checkRaven.html @@ -56,7 +56,7 @@

    DESCRIPTION ^SOURCE CODE ^% 1: as MATLAB Add-On 0029 % 2: neither of the above, direct download of ZIP file 0030 % This matches the installations mentioned in the wiki: -0031 % https://github.com/SysBioChalmers/RAVEN/wiki/Installation +0031 % https://raven-docs.readthedocs.io/en/latest/installation/raven.html 0032 % 0 = advanced / 1 = easy / 2 = medium 0033 % 0034 % Usage: [currVer, installType] = checkRaven(developMode) @@ -160,7 +160,7 @@

    SOURCE CODE ^if ~versionOnly 0083 fprintf([currVer '\n']); 0084 try -0085 newVer=strtrim(webread('https://raw.githubusercontent.com/SysBioChalmers/RAVEN/main/version.txt')); +0085 newVer=strtrim(webread('https://raw.githubusercontent.com/CompMetEng/RAVEN/main/version.txt')); 0086 newVerNum=str2double(strsplit(newVer,'.')); 0087 currVerNum=str2double(strsplit(currVer,'.')); 0088 for i=1:3 @@ -173,10 +173,10 @@

    SOURCE CODE ^printOrange(' to get the latest RAVEN release\n'); 0096 case 1 0097 printOrange(myStr(' Instructions on how to upgrade',40)) -0098 fprintf('<a href="https://github.com/SysBioChalmers/RAVEN/wiki/Installation#upgrade-raven-after-easy-installation">here</a>\n'); +0098 fprintf('<a href="https://raven-docs.readthedocs.io/en/latest/installation/raven.html#upgrade">here</a>\n'); 0099 case 2 0100 printOrange(myStr(' Instructions on how to upgrade',40)) -0101 fprintf('<a href="https://github.com/SysBioChalmers/RAVEN/wiki/Installation#upgrade-raven-after-medium-installation">here</a>\n'); +0101 fprintf('<a href="https://raven-docs.readthedocs.io/en/latest/installation/raven.html#upgrade">here</a>\n'); 0102 end 0103 break 0104 elseif i==3 diff --git a/doc/conversion/standardizeModelFieldOrder.html b/doc/conversion/standardizeModelFieldOrder.html index 45c3897f..303ab7ef 100644 --- a/doc/conversion/standardizeModelFieldOrder.html +++ b/doc/conversion/standardizeModelFieldOrder.html @@ -30,7 +30,7 @@

    DESCRIPTION ^
     standardizeModelFieldOrder  Order RAVEN model structure fields.
     
      Orders fields of a RAVEN model structure as specified at
    - https://github.com/SysBioChalmers/RAVEN/wiki/RAVEN-Model-Structure
    + https://raven-docs.readthedocs.io/en/latest/guide/model-structure.html
     
      The model fields themselves are not changed, only the order is modified.
      For changing model fields between RAVEN and COBRA format, use
    @@ -73,7 +73,7 @@ 

    SOURCE CODE ^% standardizeModelFieldOrder Order RAVEN model structure fields. 0003 % 0004 % Orders fields of a RAVEN model structure as specified at -0005 % https://github.com/SysBioChalmers/RAVEN/wiki/RAVEN-Model-Structure +0005 % https://raven-docs.readthedocs.io/en/latest/guide/model-structure.html 0006 % 0007 % The model fields themselves are not changed, only the order is modified. 0008 % For changing model fields between RAVEN and COBRA format, use diff --git a/doc/tutorial/tutorial1.html b/doc/tutorial/tutorial1.html index 6ce769e5..ba2a793a 100644 --- a/doc/tutorial/tutorial1.html +++ b/doc/tutorial/tutorial1.html @@ -38,7 +38,7 @@

    DESCRIPTION ^CROSS-REFERENCE INFORMATION ^

    @@ -64,7 +64,7 @@

    SOURCE CODE ^% this tutorial. The model is provided as the SBML file 0010 % "iAL1006 v1.00.xml". 0011 % See Tutorial 1 on the RAVEN wiki for more details: -0012 % https://github.com/SysBioChalmers/RAVEN/wiki/Tutorials +0012 % https://raven-docs.readthedocs.io/en/latest/tutorials/ 0013 0014 %Import the model from SBML. This function performs a number of checks 0015 %regarding the model structure (such as for incorrectly written equations diff --git a/doc/tutorial/tutorial2.html b/doc/tutorial/tutorial2.html index 2835eb9e..0d28c451 100644 --- a/doc/tutorial/tutorial2.html +++ b/doc/tutorial/tutorial2.html @@ -33,7 +33,7 @@

    DESCRIPTION ^CROSS-REFERENCE INFORMATION ^

    @@ -54,7 +54,7 @@

    SOURCE CODE ^% stoichiometric modelling. It is shown how to build a simple model from 0005 % scratch, set parameters and perform simple simulations. 0006 % See Tutorial 2 on the RAVEN wiki for more details: -0007 % https://github.com/SysBioChalmers/RAVEN/wiki/Tutorials +0007 % https://raven-docs.readthedocs.io/en/latest/tutorials/ 0008 0009 %Import the model into a RAVEN model structure 0010 smallModel=importModel('empty.xml'); diff --git a/doc/tutorial/tutorial2_solutions.html b/doc/tutorial/tutorial2_solutions.html index c5ded337..a9407206 100644 --- a/doc/tutorial/tutorial2_solutions.html +++ b/doc/tutorial/tutorial2_solutions.html @@ -30,7 +30,7 @@

    DESCRIPTION ^
     tutorial2_solutions
        This script contains the solutions for Tutorial 2, see Tutorial 2 on
        the RAVEN wiki for more details:
    -   https://github.com/SysBioChalmers/RAVEN/wiki/Tutorials
    + https://raven-docs.readthedocs.io/en/latest/tutorials/

    CROSS-REFERENCE INFORMATION ^

    @@ -48,7 +48,7 @@

    SOURCE CODE ^
    0001 % tutorial2_solutions
     0002 %   This script contains the solutions for Tutorial 2, see Tutorial 2 on
     0003 %   the RAVEN wiki for more details:
    -0004 %   https://github.com/SysBioChalmers/RAVEN/wiki/Tutorials
    +0004 %   https://raven-docs.readthedocs.io/en/latest/tutorials/
     0005 
     0006 %Import the model into a RAVEN model structure
     0007 smallModel=readYAMLmodel('small.yml');
    diff --git a/doc/tutorial/tutorial3.html b/doc/tutorial/tutorial3.html
    index 1bc632d6..3c3dfdab 100644
    --- a/doc/tutorial/tutorial3.html
    +++ b/doc/tutorial/tutorial3.html
    @@ -33,7 +33,7 @@ 

    DESCRIPTION ^SOURCE CODE ^% data. A simplified model of yeast metabolism is used in this approach 0005 % as an example. 0006 % See Tutorial 3 on the RAVEN wiki for more details: -0007 % https://github.com/SysBioChalmers/RAVEN/wiki/Tutorials +0007 % https://raven-docs.readthedocs.io/en/latest/tutorials/ 0008 % 0009 % It is assumed that the user has already completed Tutorial 2 0010 diff --git a/doc/tutorial/tutorial3_solutions.html b/doc/tutorial/tutorial3_solutions.html index f7a58c1a..b3ac081a 100644 --- a/doc/tutorial/tutorial3_solutions.html +++ b/doc/tutorial/tutorial3_solutions.html @@ -30,7 +30,7 @@

    DESCRIPTION ^
     tutorial3_solutions
        This script contains the solutions for Tutorial 3, see Tutorial 3 on
        the RAVEN wiki for more details:
    -   https://github.com/SysBioChalmers/RAVEN/wiki/Tutorials
    +   https://raven-docs.readthedocs.io/en/latest/tutorials/
        All the parameters are set in this script, rather than modifying the
        Excel model file.
    @@ -50,7 +50,7 @@

    SOURCE CODE ^
    0001 % tutorial3_solutions
     0002 %   This script contains the solutions for Tutorial 3, see Tutorial 3 on
     0003 %   the RAVEN wiki for more details:
    -0004 %   https://github.com/SysBioChalmers/RAVEN/wiki/Tutorials
    +0004 %   https://raven-docs.readthedocs.io/en/latest/tutorials/
     0005 %   All the parameters are set in this script, rather than modifying the
     0006 %   Excel model file.
     0007 
    diff --git a/doc/tutorial/tutorial4.html b/doc/tutorial/tutorial4.html
    index 5197b2a3..2da3b58c 100644
    --- a/doc/tutorial/tutorial4.html
    +++ b/doc/tutorial/tutorial4.html
    @@ -30,7 +30,7 @@ 

    DESCRIPTION ^
     tutorial4
        This script contains the list of functions necessary for running
        Tutorial 4, see Tutorial 4 on the RAVEN wiki for more details:
    -   https://github.com/SysBioChalmers/RAVEN/wiki/Tutorials
    +   https://raven-docs.readthedocs.io/en/latest/tutorials/
        Several key stages may be missing, try to fill these gaps before
        checking the solutions in tutorial4_solutions. It is assumed that the
        user is somewhat familiar with linear programming.
    @@ -53,7 +53,7 @@ 

    SOURCE CODE ^
    0001 % tutorial4
     0002 %   This script contains the list of functions necessary for running
     0003 %   Tutorial 4, see Tutorial 4 on the RAVEN wiki for more details:
    -0004 %   https://github.com/SysBioChalmers/RAVEN/wiki/Tutorials
    +0004 %   https://raven-docs.readthedocs.io/en/latest/tutorials/
     0005 %   Several key stages may be missing, try to fill these gaps before
     0006 %   checking the solutions in tutorial4_solutions. It is assumed that the
     0007 %   user is somewhat familiar with linear programming.
    diff --git a/doc/tutorial/tutorial4_solutions.html b/doc/tutorial/tutorial4_solutions.html
    index 17391e87..3e746cc8 100644
    --- a/doc/tutorial/tutorial4_solutions.html
    +++ b/doc/tutorial/tutorial4_solutions.html
    @@ -30,7 +30,7 @@ 

    DESCRIPTION ^
     tutorial4_solutions
        This script contains the solutions for Tutorial 4, see Tutorial 4 on
        the RAVEN wiki for more details:
    -   https://github.com/SysBioChalmers/RAVEN/wiki/Tutorials
    +   https://raven-docs.readthedocs.io/en/latest/tutorials/
     
        NOTE: Many of these changes are easier to do in the Excel sheet. They
        are done here in code just to avoid having several model files.
    @@ -51,7 +51,7 @@

    SOURCE CODE ^
    0001 % tutorial4_solutions
     0002 %   This script contains the solutions for Tutorial 4, see Tutorial 4 on
     0003 %   the RAVEN wiki for more details:
    -0004 %   https://github.com/SysBioChalmers/RAVEN/wiki/Tutorials
    +0004 %   https://raven-docs.readthedocs.io/en/latest/tutorials/
     0005 %
     0006 %   NOTE: Many of these changes are easier to do in the Excel sheet. They
     0007 %   are done here in code just to avoid having several model files.
    diff --git a/doc/tutorial/tutorial5.html b/doc/tutorial/tutorial5.html
    index 711fd5cd..b6f84795 100644
    --- a/doc/tutorial/tutorial5.html
    +++ b/doc/tutorial/tutorial5.html
    @@ -35,7 +35,7 @@ 

    DESCRIPTION ^SOURCE CODE ^% purpose is to serve as a scaffold to reconstruct a GEM for any 0007 % organism. 0008 % This refers to Tutorial 5 on the RAVEN wiki: -0009 % https://github.com/SysBioChalmers/RAVEN/wiki/Tutorials +0009 % https://raven-docs.readthedocs.io/en/latest/tutorials/ 0010 % 0011 % Start by downloading trained Hidden Markov Models for eukaryotes. This can 0012 % be done automatically or manually from the RAVEN Wiki in its GitHub diff --git a/doc/tutorial/tutorial7.html b/doc/tutorial/tutorial7.html index a1085ae9..dbdf6b4e 100644 --- a/doc/tutorial/tutorial7.html +++ b/doc/tutorial/tutorial7.html @@ -35,7 +35,7 @@

    DESCRIPTION ^SOURCE CODE ^% measured values (with one component left to absorb whatever is left, 0007 % so the total stays at 1 g/gDW), and setGAM to apply a 0008 % growth-associated maintenance value. See Tutorial 7 on the RAVEN wiki -0009 % for more details: https://github.com/SysBioChalmers/RAVEN/wiki/Tutorials +0009 % for more details: https://raven-docs.readthedocs.io/en/latest/tutorials/ 0010 % 0011 % A small hand-built model is used instead of a real GEM, so the 0012 % pseudoreaction layout is visible in full below. Real GEMs lay this out From 54ee5cc2a5fcf553807a3aa9de87c4f6e747ce39 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 17 Oct 2026 05:49:07 +0000 Subject: [PATCH 16/48] doc: regenerate HTML documentation for 204a3d0 [skip ci] --- docs/html/checkInstallation.html | 82 -- docs/html/checkRaven.html | 456 -------- docs/html/conversion/ravenCobraWrapper.html | 4 +- .../standardizeModelFieldOrder.html | 4 +- docs/html/index.html | 72 +- .../installation/downloadRavenBinaries.html | 8 +- docs/html/installation/findRAVENroot.html | 10 +- .../installation/updateDocumentation.html | 52 +- .../reconstruction/homology/getBlast.html | 10 +- .../reconstruction/homology/getDiamond.html | 10 +- .../kegg/buildGlobalKEGGModel.html | 2 +- .../kegg/getKEGGModelForOrganism.html | 6 +- .../reconstruction/kegg/getModelFromKEGG.html | 2 +- .../html/reconstruction/kegg/getPhylDist.html | 2 +- .../reconstruction/kegg/keggDataVersion.html | 2 +- docs/html/solver/optimizeProb.html | 2 +- docs/html/solver/setRavenSolver.html | 8 +- .../testing/function_tests/RavenTestCase.html | 171 --- docs/html/testing/function_tests/index.html | 31 - .../private/getExprForRxnScore.html | 114 -- .../testing/function_tests/private/index.html | 29 - .../testing/function_tests/tAnalysis.html | 401 ------- .../testing/function_tests/tAnnotation.html | 110 -- .../function_tests/tAssignCompartments.html | 113 -- .../testing/function_tests/tBinaries.html | 94 -- .../html/testing/function_tests/tBiomass.html | 180 --- .../testing/function_tests/tComparison.html | 156 --- .../testing/function_tests/tConditions.html | 141 --- .../testing/function_tests/tConversion.html | 137 --- .../testing/function_tests/tCuration.html | 145 --- .../testing/function_tests/tGapfilling.html | 488 -------- .../testing/function_tests/tGenomeData.html | 145 --- docs/html/testing/function_tests/tINIT.html | 997 ---------------- docs/html/testing/function_tests/tIO.html | 378 ------- .../testing/function_tests/tInstallation.html | 114 -- .../testing/function_tests/tLocalization.html | 188 --- .../testing/function_tests/tManipulation.html | 1005 ----------------- .../testing/function_tests/tNamedArgs.html | 98 -- .../function_tests/tParseRAVENargs.html | 113 -- .../html/testing/function_tests/tQueries.html | 491 -------- .../function_tests/tReconstruction.html | 220 ---- .../testing/function_tests/tSampling.html | 235 ---- docs/html/testing/function_tests/tSolver.html | 121 -- docs/html/testing/function_tests/tSyntax.html | 97 -- docs/html/testing/function_tests/tTasks.html | 140 --- docs/html/testing/function_tests/tUtils.html | 248 ---- docs/html/tutorial/index.html | 31 - docs/html/tutorial/tutorial1.html | 183 --- docs/html/tutorial/tutorial2.html | 75 -- docs/html/tutorial/tutorial2_solutions.html | 69 -- docs/html/tutorial/tutorial3.html | 112 -- docs/html/tutorial/tutorial3_solutions.html | 137 --- docs/html/tutorial/tutorial4.html | 79 -- docs/html/tutorial/tutorial4_solutions.html | 137 --- docs/html/tutorial/tutorial5.html | 254 ----- docs/html/tutorial/tutorial7.html | 209 ---- docs/html/utils/runRAVENtests.html | 2 +- 57 files changed, 118 insertions(+), 8802 deletions(-) delete mode 100644 docs/html/checkInstallation.html delete mode 100644 docs/html/checkRaven.html delete mode 100644 docs/html/testing/function_tests/RavenTestCase.html delete mode 100644 docs/html/testing/function_tests/index.html delete mode 100644 docs/html/testing/function_tests/private/getExprForRxnScore.html delete mode 100644 docs/html/testing/function_tests/private/index.html delete mode 100644 docs/html/testing/function_tests/tAnalysis.html delete mode 100644 docs/html/testing/function_tests/tAnnotation.html delete mode 100644 docs/html/testing/function_tests/tAssignCompartments.html delete mode 100644 docs/html/testing/function_tests/tBinaries.html delete mode 100644 docs/html/testing/function_tests/tBiomass.html delete mode 100644 docs/html/testing/function_tests/tComparison.html delete mode 100644 docs/html/testing/function_tests/tConditions.html delete mode 100644 docs/html/testing/function_tests/tConversion.html delete mode 100644 docs/html/testing/function_tests/tCuration.html delete mode 100644 docs/html/testing/function_tests/tGapfilling.html delete mode 100644 docs/html/testing/function_tests/tGenomeData.html delete mode 100644 docs/html/testing/function_tests/tINIT.html delete mode 100644 docs/html/testing/function_tests/tIO.html delete mode 100644 docs/html/testing/function_tests/tInstallation.html delete mode 100644 docs/html/testing/function_tests/tLocalization.html delete mode 100644 docs/html/testing/function_tests/tManipulation.html delete mode 100644 docs/html/testing/function_tests/tNamedArgs.html delete mode 100644 docs/html/testing/function_tests/tParseRAVENargs.html delete mode 100644 docs/html/testing/function_tests/tQueries.html delete mode 100644 docs/html/testing/function_tests/tReconstruction.html delete mode 100644 docs/html/testing/function_tests/tSampling.html delete mode 100644 docs/html/testing/function_tests/tSolver.html delete mode 100644 docs/html/testing/function_tests/tSyntax.html delete mode 100644 docs/html/testing/function_tests/tTasks.html delete mode 100644 docs/html/testing/function_tests/tUtils.html delete mode 100644 docs/html/tutorial/index.html delete mode 100644 docs/html/tutorial/tutorial1.html delete mode 100644 docs/html/tutorial/tutorial2.html delete mode 100644 docs/html/tutorial/tutorial2_solutions.html delete mode 100644 docs/html/tutorial/tutorial3.html delete mode 100644 docs/html/tutorial/tutorial3_solutions.html delete mode 100644 docs/html/tutorial/tutorial4.html delete mode 100644 docs/html/tutorial/tutorial4_solutions.html delete mode 100644 docs/html/tutorial/tutorial5.html delete mode 100644 docs/html/tutorial/tutorial7.html diff --git a/docs/html/checkInstallation.html b/docs/html/checkInstallation.html deleted file mode 100644 index 0bff4cd6..00000000 --- a/docs/html/checkInstallation.html +++ /dev/null @@ -1,82 +0,0 @@ - - - - Description of checkInstallation - - - - - - - - - -
    Home > . > checkInstallation.m
    - - - -

    checkInstallation -

    - -

    PURPOSE ^

    -
    checkInstallation DEPRECATED. Use checkRaven instead.
    - -

    SYNOPSIS ^

    -
    function [currVer, installType] = checkInstallation(developMode, checkBinaries)
    - -

    DESCRIPTION ^

    -
     checkInstallation  DEPRECATED. Use checkRaven instead.
    -
    -   Forwards all arguments and outputs to checkRaven.
    -
    -   NOTE: this function is run before RAVEN has been added to the MATLAB
    -   path, so it must not call any other RAVEN functions until checkRaven has
    -   added RAVEN to the path itself. It cannot use the shared
    -   utils/deprecationWarning for the same reason.
    -
    - Usage: [currVer, installType] = checkInstallation(developMode, checkBinaries)
    - - -

    CROSS-REFERENCE INFORMATION ^

    -This function calls: -
      -
    -This function is called by: -
      -
    - - - - -

    SOURCE CODE ^

    -
    0001 function [currVer, installType] = checkInstallation(developMode, checkBinaries)
    -0002 % checkInstallation  DEPRECATED. Use checkRaven instead.
    -0003 %
    -0004 %   Forwards all arguments and outputs to checkRaven.
    -0005 %
    -0006 %   NOTE: this function is run before RAVEN has been added to the MATLAB
    -0007 %   path, so it must not call any other RAVEN functions until checkRaven has
    -0008 %   added RAVEN to the path itself. It cannot use the shared
    -0009 %   utils/deprecationWarning for the same reason.
    -0010 %
    -0011 % Usage: [currVer, installType] = checkInstallation(developMode, checkBinaries)
    -0012 
    -0013 warning('RAVEN:deprecated', ...
    -0014     'checkInstallation is deprecated and will be removed in the next major release. Use checkRaven instead.')
    -0015 if nargin < 1
    -0016     developMode = false;
    -0017 end
    -0018 if nargin < 2
    -0019     checkBinaries = true;
    -0020 end
    -0021 if nargout > 0
    -0022     [currVer, installType] = checkRaven(developMode, checkBinaries);
    -0023 else
    -0024     checkRaven(developMode, checkBinaries);
    -0025 end
    -0026 end
    -
    Generated by m2html © 2005
    - - \ No newline at end of file diff --git a/docs/html/checkRaven.html b/docs/html/checkRaven.html deleted file mode 100644 index 1bc45c60..00000000 --- a/docs/html/checkRaven.html +++ /dev/null @@ -1,456 +0,0 @@ - - - - Description of checkRaven - - - - - - - - - -
    Home > . > checkRaven.m
    - - - -

    checkRaven -

    - -

    PURPOSE ^

    -
    checkRaven
    - -

    SYNOPSIS ^

    -
    function [currVer, installType] = checkRaven(developMode, checkBinaries)
    - -

    DESCRIPTION ^

    -
     checkRaven
    -   Checks that all necessary functions are installed and working. It also
    -   checks whether there are any functions
    -   with overlapping names between RAVEN and other toolboxes or
    -   user-defined functions, which are accessible from MATLAB pathlist
    -
    -   NOTE: this function is run before RAVEN has been added to the MATLAB
    -   path, so it must not call any other RAVEN functions until it has added
    -   RAVEN to the path itself. Its arguments are therefore parsed directly
    -   rather than via parseRAVENargs.
    -
    - Parameters
    - ----------
    -   developMode     logical indicating development mode, which includes
    -                   testing of binaries that are required to update KEGG
    -                   HMMs (default false). If 'versionOnly' is
    -                   specified, only the version is reported as currVer, no
    -                   further installation or tests are performed.
    -   checkBinaries   logical whether non-developMode binaries should be
    -                   checked for functionality. If false, it overwrites
    -                   developMode. Default true.
    -
    - Output:
    -   currVer         current RAVEN version
    -   installType     how RAVEN is installed
    -                   0:  via git (as .git folder is found)
    -                   1:  as MATLAB Add-On
    -                   2:  neither of the above, direct download of ZIP file
    -                   This matches the installations mentioned in the wiki:
    -                   https://raven-docs.readthedocs.io/en/latest/installation/raven.html
    -                   0 = advanced / 1 = easy / 2 = medium
    -
    - Usage: [currVer, installType] = checkRaven(developMode)
    - - -

    CROSS-REFERENCE INFORMATION ^

    -This function calls: -
      -
    -This function is called by: -
      -
    - - -

    SUBFUNCTIONS ^

    - - -

    SOURCE CODE ^

    -
    0001 function [currVer, installType] = checkRaven(developMode, checkBinaries)
    -0002 % checkRaven
    -0003 %   Checks that all necessary functions are installed and working. It also
    -0004 %   checks whether there are any functions
    -0005 %   with overlapping names between RAVEN and other toolboxes or
    -0006 %   user-defined functions, which are accessible from MATLAB pathlist
    -0007 %
    -0008 %   NOTE: this function is run before RAVEN has been added to the MATLAB
    -0009 %   path, so it must not call any other RAVEN functions until it has added
    -0010 %   RAVEN to the path itself. Its arguments are therefore parsed directly
    -0011 %   rather than via parseRAVENargs.
    -0012 %
    -0013 % Parameters
    -0014 % ----------
    -0015 %   developMode     logical indicating development mode, which includes
    -0016 %                   testing of binaries that are required to update KEGG
    -0017 %                   HMMs (default false). If 'versionOnly' is
    -0018 %                   specified, only the version is reported as currVer, no
    -0019 %                   further installation or tests are performed.
    -0020 %   checkBinaries   logical whether non-developMode binaries should be
    -0021 %                   checked for functionality. If false, it overwrites
    -0022 %                   developMode. Default true.
    -0023 %
    -0024 % Output:
    -0025 %   currVer         current RAVEN version
    -0026 %   installType     how RAVEN is installed
    -0027 %                   0:  via git (as .git folder is found)
    -0028 %                   1:  as MATLAB Add-On
    -0029 %                   2:  neither of the above, direct download of ZIP file
    -0030 %                   This matches the installations mentioned in the wiki:
    -0031 %                   https://raven-docs.readthedocs.io/en/latest/installation/raven.html
    -0032 %                   0 = advanced / 1 = easy / 2 = medium
    -0033 %
    -0034 % Usage: [currVer, installType] = checkRaven(developMode)
    -0035 
    -0036 if nargin < 1 || isempty(developMode)
    -0037     developMode = false;
    -0038 end
    -0039 if nargin < 2 || isempty(checkBinaries)
    -0040     checkBinaries = true;
    -0041 end
    -0042 
    -0043 if ischar(developMode) && strcmp(developMode,'versionOnly')
    -0044     versionOnly = true;
    -0045 else
    -0046     versionOnly = false;
    -0047 end
    -0048 
    -0049 %Get the RAVEN path. checkRaven.m sits in the RAVEN root.
    -0050 [ST, I]=dbstack('-completenames');
    -0051 [ravenDir,~,~]=fileparts(ST(I).file);
    -0052 
    -0053 installType = 2; % If neither git nor add-on, then ZIP was downloaded
    -0054 addList = matlab.addons.installedAddons;
    -0055 if isfolder(fullfile(ravenDir,'.git'))
    -0056     installType = 0;
    -0057 elseif any(strcmp(addList.Name,'RAVEN Toolbox'))
    -0058     installType = 1;
    -0059 end
    -0060 
    -0061 % Do not print first few lines if only version should be reported
    -0062 if ~versionOnly
    -0063     fprintf('\n*** THE RAVEN TOOLBOX ***\n\n');
    -0064     %Print the RAVEN version if it is not the development version
    -0065     fprintf(myStr(' > Installation type',40));
    -0066     switch installType
    -0067         case 0
    -0068             fprintf('Advanced (via git)\n');
    -0069         case 1
    -0070             fprintf('Easy (as MATLAB Add-On)\n');
    -0071         case 2
    -0072             fprintf('Medium (as downloaded ZIP file)\n');
    -0073     end
    -0074     fprintf(myStr(' > Installing from location',40));
    -0075     fprintf('%s\n',ravenDir)
    -0076     fprintf(myStr(' > Checking RAVEN release',40));
    -0077 end
    -0078                        
    -0079 if exist(fullfile(ravenDir,'version.txt'), 'file') == 2
    -0080     currVer = fgetl(fopen(fullfile(ravenDir,'version.txt')));
    -0081     fclose('all');
    -0082     if ~versionOnly
    -0083         fprintf([currVer '\n']);
    -0084         try
    -0085             newVer=strtrim(webread('https://raw.githubusercontent.com/CompMetEng/RAVEN/main/version.txt'));
    -0086             newVerNum=str2double(strsplit(newVer,'.'));
    -0087             currVerNum=str2double(strsplit(currVer,'.'));
    -0088             for i=1:3
    -0089                 if currVerNum(i)<newVerNum(i)
    -0090                     fprintf(myStr('   > Latest RAVEN release available',40))
    -0091                     printOrange([newVer,'\n'])
    -0092                     switch installType
    -0093                         case 0
    -0094                             printOrange('     Run git pull in your favourite git client\n')
    -0095                             printOrange('     to get the latest RAVEN release\n');
    -0096                         case 1
    -0097                             printOrange(myStr('     Instructions on how to upgrade',40))
    -0098                             fprintf('<a href="https://raven-docs.readthedocs.io/en/latest/installation/raven.html#upgrade">here</a>\n');
    -0099                         case 2
    -0100                             printOrange(myStr('     Instructions on how to upgrade',40))
    -0101                             fprintf('<a href="https://raven-docs.readthedocs.io/en/latest/installation/raven.html#upgrade">here</a>\n');                            
    -0102                     end
    -0103                     break
    -0104                 elseif i==3
    -0105                     fprintf('   > You are running the latest RAVEN release\n');
    -0106                 end
    -0107             end
    -0108         catch
    -0109             fprintf(myStr('   > Checking for latest RAVEN release',40))
    -0110             printOrange('Fail\n');
    -0111             printOrange('     Cannot reach GitHub for release info\n');
    -0112         end
    -0113     end
    -0114 else
    -0115     currVer = 'develop';
    -0116     if ~versionOnly; fprintf('DEVELOPMENT\n'); end
    -0117 end
    -0118 if strcmp(developMode,'versionOnly')
    -0119     return;
    -0120 end
    -0121 
    -0122 fprintf(myStr(' > Checking MATLAB release',40))
    -0123 fprintf([version('-release') '\n'])
    -0124 fprintf(myStr(' > Checking system architecture',40))
    -0125 fprintf([computer('arch'),'\n'])
    -0126 
    -0127 fprintf(myStr(' > Set RAVEN in MATLAB path',40))
    -0128 subpath=regexp(genpath(ravenDir),pathsep,'split'); %List all subdirectories
    -0129 pathsToKeep=cellfun(@(x) ~contains(x,'.git'),subpath) & cellfun(@(x) ~contains(x,'doc'),subpath);
    -0130 try
    -0131     addpath(strjoin(subpath(pathsToKeep),pathsep));
    -0132     fprintf('Pass\n');
    -0133     fprintf(myStr(' > Save MATLAB path',40))
    -0134     try
    -0135         savepath
    -0136         fprintf('Pass\n')   
    -0137     catch
    -0138         printOrange('Fail\n')
    -0139         fprintf(['   You might have to rerun checkRaven again\n'...
    -0140                  '   next time you start up MATLAB\n'])        
    -0141     end
    -0142 catch
    -0143     printOrange('Fail\n')
    -0144 end
    -0145 fprintf(myStr(' > Store RAVEN path as MATLAB pref',40))
    -0146 try
    -0147     setpref('RAVEN','ravenPath',ravenDir);
    -0148     clear('findRAVENroot'); % drop its cached path, in case one is already set for this session
    -0149     fprintf('Pass\n');
    -0150 catch
    -0151     printOrange('Fail\n')
    -0152 end
    -0153 
    -0154 if isunix
    -0155     fprintf(myStr('   > Make binaries executable',40))
    -0156     status = makeBinaryExecutable(ravenDir);
    -0157     if status == 0
    -0158         fprintf('Pass\n')
    -0159     else
    -0160         printOrange('Fail\n')
    -0161     end
    -0162 end
    -0163 
    -0164 %Check the model import and export formats
    -0165 fprintf('\n=== Model import and export ===\n');
    -0166 fprintf(myStr(' > Checking libSBML version',40))
    -0167 model = [];
    -0168 try
    -0169     evalc('model = importModel(fullfile(ravenDir,''tutorial'',''empty.xml''));');
    -0170     try
    -0171         libSBMLver=OutputSBML_RAVEN; % Only works in libSBML 5.17.0+
    -0172         fprintf([libSBMLver.libSBML_version_string '\n']);
    -0173     catch
    -0174         printOrange('Fail\n')
    -0175         fprintf('   An older libSBML version was found, update to version 5.17.0 or higher for a significant improvement of model import\n');
    -0176     end
    -0177 catch
    -0178     printOrange('Fail\n')
    -0179     fprintf('   Download libSBML from http://sbml.org/Software/libSBML/Downloading_libSBML and add to MATLAB path\n');
    -0180 end
    -0181 
    -0182 % Import and export the small "empty" model directly, which is much faster
    -0183 % than running the full importExportTests test case. A temporary folder
    -0184 % holds the exported files and is removed afterwards.
    -0185 fprintf(' > Checking model import and export\n')
    -0186 tmpDir = tempname; mkdir(tmpDir);
    -0187 
    -0188 fprintf(myStr('   > Import SBML format',40))
    -0189 if ~isempty(model)
    -0190     fprintf('Pass\n')
    -0191 else
    -0192     printOrange('Fail\n')
    -0193 end
    -0194 
    -0195 reportCheck('   > Export SBML format', @() exportModel(model, fullfile(tmpDir,'model.xml')));
    -0196 reportCheck('   > Import YAML format', @() readYAMLmodel(fullfile(ravenDir,'tutorial','empty.yml')));
    -0197 reportCheck('   > Export YAML format', @() writeYAMLmodel(model, fullfile(tmpDir,'model.yml')));
    -0198 
    -0199 reportCheck('   > Export Excel format', @() exportToExcelFormat(model, fullfile(tmpDir,'model.xlsx')));
    -0200 
    -0201 rmdir(tmpDir,'s');
    -0202 
    -0203 fprintf('\n=== Model solvers ===\n');
    -0204 fprintf(' > Checking for LP solvers\n')
    -0205 
    -0206 % Solve a minimal feasible LP with each solver to verify it is functional.
    -0207 % This is much faster than running the full solverTests test case.
    -0208 testModel.rxns = {'r1';'r2'};
    -0209 testModel.mets = {'a'};
    -0210 testModel.S    = sparse([1 -1]);
    -0211 testModel.lb   = [0; 0];
    -0212 testModel.ub   = [1; 1000];
    -0213 testModel.rev  = [0; 0];
    -0214 testModel.c    = [0; 1];
    -0215 testModel.b    = 0;
    -0216 
    -0217 solvers  = {'glpk','gurobi','scip','cobra'};
    -0218 solverOK = false(1,numel(solvers));
    -0219 for i = 1:numel(solvers)
    -0220     solverOK(i) = reportCheck(['   > ' solvers{i}], @() testSolver(solvers{i}, testModel));
    -0221 end
    -0222 
    -0223 % Keep the current solver if it is still functional, otherwise pick the best
    -0224 % available one. Order of preference: gurobi > glpk > scip > cobra
    -0225 fprintf(myStr(' > Set RAVEN solver',40))
    -0226 oldSolver = '';
    -0227 try oldSolver = getpref('RAVEN','solver'); catch; end
    -0228 solverIdx = find(strcmp(oldSolver, solvers));
    -0229 if ~isempty(solverIdx) && solverOK(solverIdx)
    -0230     fprintf([oldSolver '\n'])
    -0231     setRavenSolver(oldSolver);
    -0232 elseif solverOK(2)
    -0233     fprintf('gurobi\n')
    -0234     setRavenSolver('gurobi');
    -0235 elseif solverOK(1)
    -0236     fprintf('glpk\n')
    -0237     setRavenSolver('glpk');
    -0238 elseif solverOK(3)
    -0239     fprintf('scip\n')
    -0240     setRavenSolver('scip');
    -0241 elseif solverOK(4)
    -0242     fprintf('cobra\n')
    -0243     setRavenSolver('cobra');
    -0244 else
    -0245     fprintf('None, no functional solvers\n')
    -0246     fprintf('    The glpk should always be working, check your RAVEN installation to make sure all files are present\n')
    -0247 end
    -0248 
    -0249 fprintf('\n=== Essential binary executables ===\n');
    -0250 if ~checkBinaries
    -0251     printOrange('    Skipping check of binary executables\n')
    -0252 else
    -0253     % The BLAST+/DIAMOND/HMMER binaries are downloaded on demand from
    -0254     % raven-data; fetch any that are missing for this platform before
    -0255     % checking. This doubles as the up-front "prefetch" for users who want
    -0256     % everything ready after installation.
    -0257     try
    -0258         downloadRavenBinaries({'blast+','diamond','hmmer'});
    -0259     catch ME
    -0260         printOrange(['    Could not download binaries from raven-data: ' ME.message '\n' ...
    -0261             '    (offline? fetch the "*-binaries" RAVEN release for an offline install)\n']);
    -0262     end
    -0263     % Run each bundled binary with a version/help flag to confirm it
    -0264     % executes on this system, rather than running the full test cases.
    -0265     if ~reportCheck(' > Checking BLAST+', @() testBinary(ravenDir,'blast+','blastp','-version'))
    -0266         fprintf('   This is essential to run getBlast()\n')
    -0267     end
    -0268     if ~reportCheck(' > Checking DIAMOND', @() testBinary(ravenDir,'diamond','diamond','version'))
    -0269         fprintf('   This is essential to run getDiamond()\n')
    -0270     end
    -0271     if ~reportCheck(' > Checking HMMER', @() testBinary(ravenDir,'hmmer','hmmsearch','-h'))
    -0272         fprintf(['   This is essential to run getKEGGModelFromHomology()\n'...
    -0273             '   when using a FASTA file as input\n'])
    -0274     end
    -0275 end
    -0276 
    -0277 fprintf('\n=== Compatibility ===\n');
    -0278 fprintf(myStr(' > Checking function uniqueness',40))
    -0279 checkFunctionUniqueness();
    -0280 
    -0281 fprintf('\n*** checkRaven complete ***\n');
    -0282 end
    -0283 
    -0284 function testSolver(solver, model)
    -0285 % Set the RAVEN solver and solve a trivial LP; error if it does not return
    -0286 % an optimal solution.
    -0287 setRavenSolver(solver);
    -0288 sol = solveLP(model, 0);
    -0289 if ~(isfield(sol,'stat') && sol.stat == 1)
    -0290     error('Solver %s did not return an optimal solution', solver);
    -0291 end
    -0292 end
    -0293 
    -0294 function testBinary(ravenDir, tool, binName, versionArg)
    -0295 % Run a bundled binary with versionArg to confirm it executes; error if not.
    -0296 if ispc
    -0297     binEnd = '.exe'; % native Windows builds (raven-data windows-x86_64 ZIPs)
    -0298 elseif ismac
    -0299     binEnd = '.mac';
    -0300 else
    -0301     binEnd = '';
    -0302 end
    -0303 cmd = ['"' fullfile(ravenDir,'software',tool,[binName binEnd]) '" ' versionArg];
    -0304 [status,~] = system(cmd);
    -0305 if status ~= 0
    -0306     error('%s did not execute (exit status %d)', binName, status);
    -0307 end
    -0308 end
    -0309 
    -0310 function ok = reportCheck(label, fcn)
    -0311 % Print label, run fcn (a function handle) with its output suppressed, and
    -0312 % report Pass/Fail. Returns true on success.
    -0313 assert(isa(fcn,'function_handle'));
    -0314 fprintf(myStr(label,40));
    -0315 try
    -0316     evalc('fcn()');
    -0317     ok = true;
    -0318     fprintf('Pass\n');
    -0319 catch
    -0320     ok = false;
    -0321     printOrange('Fail\n');
    -0322 end
    -0323 end
    -0324 
    -0325 function str = myStr(InputStr,len)
    -0326 str=InputStr;
    -0327 lenDiff = len - length(str);
    -0328 if lenDiff < 0
    -0329     warning('String too long');
    -0330 else
    -0331     str = [str blanks(lenDiff)];
    -0332 end
    -0333 end
    -0334 
    -0335 function status = makeBinaryExecutable(ravenDir)
    -0336 % This function is required to run when RAVEN is downloaded as MATLAB
    -0337 % Add-On, in which case the file permissions are not correctly set
    -0338 if ispc
    -0339     status = 0; % No need to run on Windows
    -0340     return;
    -0341 end
    -0342 binDir = fullfile(ravenDir,'software');
    -0343 
    -0344 % blast+/diamond/hmmer may be present from an on-demand download
    -0345 % (downloadRavenBinaries already chmods those) or from the offline bundle;
    -0346 % chmod whichever unix binaries are present. Missing entries are skipped below.
    -0347 binList = {fullfile(binDir,'blast+','blastp');                      fullfile(binDir,'blast+','blastp.mac');
    -0348            fullfile(binDir,'blast+','makeblastdb');                 fullfile(binDir,'blast+','makeblastdb.mac');
    -0349            fullfile(binDir,'diamond','diamond');                    fullfile(binDir,'diamond','diamond.mac');
    -0350            fullfile(binDir,'hmmer','hmmsearch');                    fullfile(binDir,'hmmer','hmmsearch.mac');
    -0351            fullfile(binDir,'GLPKmex','glpkcc.mexa64');              fullfile(binDir,'GLPKmex','glpkcc.mexmaci64');              fullfile(binDir,'GLPKmex','glpkcc.mexmaca64');
    -0352            fullfile(binDir,'libSBML','TranslateSBML_RAVEN.mexa64'); fullfile(binDir,'libSBML','TranslateSBML_RAVEN.mexmaci64'); fullfile(binDir,'libSBML','TranslateSBML_RAVEN.mexmaca64');
    -0353            fullfile(binDir,'libSBML','OutputSBML_RAVEN.mexa64');    fullfile(binDir,'libSBML','OutputSBML_RAVEN.mexmaci64');    fullfile(binDir,'libSBML','OutputSBML_RAVEN.mexmaca64');};
    -0354 
    -0355 status = 0;
    -0356 for i=1:numel(binList)
    -0357     if ~isfile(binList{i}); continue; end % skip binaries not present on this platform
    -0358     [status,cmdout] = system(['chmod +x "' binList{i} '"']);
    -0359     if status ~= 0
    -0360         warning('Failed to make %s executable: %s ',binList{i},strip(cmdout))
    -0361     end
    -0362 end
    -0363 end
    -0364 
    -0365 function printOrange(stringToPrint)
    -0366 % printOrange
    -0367 %   Duplicate of RAVEN/core/printOrange is also kept here, as this function
    -0368 %   should be able to run before adding RAVEN to the MATLAB path.
    -0369 try useDesktop = usejava('desktop'); catch, useDesktop = false; end
    -0370 if useDesktop
    -0371     fprintf(['[\b' stringToPrint,']\b'])
    -0372 else
    -0373     fprintf(stringToPrint)
    -0374 end
    -0375 end
    -
    Generated by m2html © 2005
    - - \ No newline at end of file diff --git a/docs/html/conversion/ravenCobraWrapper.html b/docs/html/conversion/ravenCobraWrapper.html index 4a2bde36..feebf91b 100644 --- a/docs/html/conversion/ravenCobraWrapper.html +++ b/docs/html/conversion/ravenCobraWrapper.html @@ -126,7 +126,7 @@

    SOURCE CODE ^% Load COBRA field information -0048 fid = fopen(fullfile(ravenPath,'conversion','COBRA_structure_fields.csv')); % Taken from https://github.com/opencobra/cobratoolbox/blob/develop/src/base/io/definitions/COBRA_structure_fields.csv +0048 fid = fopen(fullfile(ravenPath,'src','conversion','COBRA_structure_fields.csv')); % Taken from https://github.com/opencobra/cobratoolbox/blob/develop/src/base/io/definitions/COBRA_structure_fields.csv 0049 fieldFile = textscan(fid,repmat('%s',1,15),'Delimiter','\t','HeaderLines',1); 0050 dbFields = ~cellfun(@isempty,fieldFile{5}); % Only keep fields with database annotations that should be translated to xxxMiriams 0051 dbFields = dbFields & ~contains(fieldFile{1},{'metInChIString','metKEGGID','metPubChemID','rxnECNumbers'}); @@ -136,7 +136,7 @@

    SOURCE CODE ^% Load conversion between additional COBRA fields and namespaces: -0058 fid = fopen(fullfile(ravenPath,'conversion','cobraNamespaces.csv')); +0058 fid = fopen(fullfile(ravenPath,'src','conversion','cobraNamespaces.csv')); 0059 fieldFile = textscan(fid,'%s %s','Delimiter',',','HeaderLines',0); 0060 COBRAfields = [COBRAfields; fieldFile{1}]; 0061 COBRAnamespace = [COBRAnamespace; fieldFile{2}]; diff --git a/docs/html/conversion/standardizeModelFieldOrder.html b/docs/html/conversion/standardizeModelFieldOrder.html index 303ab7ef..bd9e1bae 100644 --- a/docs/html/conversion/standardizeModelFieldOrder.html +++ b/docs/html/conversion/standardizeModelFieldOrder.html @@ -100,12 +100,12 @@

    SOURCE CODE ^if ~isfield(model,'rules') % Check if model is RAVEN -0032 fid = fopen(fullfile(ravenPath,'conversion','orderRavenFields.csv')); +0032 fid = fopen(fullfile(ravenPath,'src','conversion','orderRavenFields.csv')); 0033 fields = textscan(fid,'%s','Delimiter',',','HeaderLines',0); 0034 fields = fields{1}; 0035 fclose(fid); 0036 else % If model is COBRA -0037 fid = fopen(fullfile(ravenPath,'conversion','COBRA_structure_fields.csv')); % Taken from https://github.com/opencobra/cobratoolbox/blob/develop/src/base/io/definitions/COBRA_structure_fields.csv +0037 fid = fopen(fullfile(ravenPath,'src','conversion','COBRA_structure_fields.csv')); % Taken from https://github.com/opencobra/cobratoolbox/blob/develop/src/base/io/definitions/COBRA_structure_fields.csv 0038 fields = textscan(fid,repmat('%s',1,15),'Delimiter','\t','HeaderLines',1); 0039 fields = fields{1}; 0040 fclose(fid); diff --git a/docs/html/index.html b/docs/html/index.html index 0253ebde..9a173c32 100644 --- a/docs/html/index.html +++ b/docs/html/index.html @@ -2,29 +2,69 @@ "http://www.w3.org/TR/REC-html40/loose.dtd"> - Index for Directory . - - + Matlab Index + + - + - -
    < Master indexIndex for . >
    +

    Matlab Index

    +

    Matlab Directories

    + +

    Matlab Files found in these Directories

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    FSEOF exportForGit getTransportRxns removeIdentifierPrefix
    INITStepDesc exportModel getUniProtScores removeLowScoreGenes
    addExchangeRxns exportToExcelFormat grRuleToDNF removeMets
    addGenesRaven extractMiriam grRuleToString removeRavenFromPath
    addIdentifierPrefix fetchRavenDataAsset groupRxnScores removeReactions
    addMets fillGaps guessComposition renameModelGenes
    addRavenToUserPath findDuplicateRxns haveFlux replaceMets
    addRxns findGeneDeletions importModel reporterMetabolites
    addRxnsGenesMets findLeakMetabolite isDnfGrRule rescaleModelForINIT
    addTransport findPotentialErrors keggDataVersion reverseRxns
    analyzeSampling findRAVENroot legacyMethodNotice runDynamicFBA
    applyCondition fitParameters loadDeltaGCSV runINIT
    assignCompartments fitTasks makeFakeBlastStructure runPhenotypePhasePlane
    assignSBOterms ftINIT mergeCompartments runProductionEnvelope
    buildEquation ftINITFillGaps mergeLinear runRAVENtests
    buildGlobalGPR ftINITInternalAlg mergeModels runRobustnessAnalysis
    buildGlobalKEGGModel gapFillFastCore modelSummary runSimpleOptKnock
    canExchange gapFillFastLP optimizeProb sampleACHR
    changeGrRules gapFillMILP parallelWorkersRAVEN sampleCHRR
    changeRxns gapFillSwiftCore parseFormulas sampleChebyshevCenter
    checkFileExistence gapFillTopological parseGrRule sampleMaxVolEllipse
    checkFunctionUniqueness gapReport parseHPA sampleWarmupPoints
    checkModelStruct generateNewIds parseHPArna saveDeltaGCSV
    checkProduction getAllRxnsFromGenes parseRAVENargs scaleBiomassFraction
    checkSolution getAllSubGraphs parseRxnEqu scaleBiomassPseudoreaction
    checkTasks getAllowedBounds parseScores scoreModel
    cleanSheet getBiomassFractions parseTaskList setExchangeBounds
    closeModel getBlast parseYAML setGAM
    compareFluxes getDiamond permuteModel setParam
    compareMultipleModels getElementalBalance predictLocalization setRavenProgress
    constructEquations getEssentialRxns prepINITModel setRavenSolver
    constructS getExchangeRxns printFluxes simplifyModel
    contractModel getFluxZ printModelStats solveLP
    convertCharArray getGeneData printOrange sortIdentifiers
    convertToIrrev getGenesFromGrRules processProteinFastaFile sortModel
    copyToComps getINITModel progressReport splitProbForConditioning
    curateModelFromTables getINITSteps randomSampling standardizeGrRules
    defaultCompartmentMap getIndexes ravenCobraWrapper standardizeModelFieldOrder
    deleteUnusedGenes getKEGGModelForOrganism ravenList traceFluxPath
    deprecationWarning getMinNrFluxes ravenModelFields updateDocumentation
    diffModels getMinimalMedium readFasta walkFluxes
    downloadGenomeData getModelFromHomology readKEGGTable writeExcel
    downloadRavenBinaries getModelFromKEGG readYAMLmodel writeFasta
    editMiriam getPhylDist removeBadRxns writeYAMLmodel
    expandModel getRxnsInComp removeGenes
    -

    Index for .

    - -

    Matlab files in this directory:

    - -
     checkInstallationcheckInstallation DEPRECATED. Use checkRaven instead.
     checkRavencheckRaven
    - - -

    Subsequent directories:

    -
      -
    • .git
    • .github
    • INIT
    • analysis
    • annotation
    • biomass
    • comparison
    • conditions
    • conversion
    • curation
    • deprecated
    • doc
    • docs
    • gapfilling
    • installation
    • io
    • localization
    • manipulation
    • queries
    • readme
    • reconstruction
    • software
    • solver
    • tasks
    • testing
    • tutorial
    • utils

    Generated by m2html © 2005
    diff --git a/docs/html/installation/downloadRavenBinaries.html b/docs/html/installation/downloadRavenBinaries.html index 9e3ae378..fe226a92 100644 --- a/docs/html/installation/downloadRavenBinaries.html +++ b/docs/html/installation/downloadRavenBinaries.html @@ -108,13 +108,13 @@

    SOURCE CODE ^case 'blast+' 0045 tag = 'blast-2.17.0'; 0046 asset = ['blast-2.17.0-' plat '.zip']; -0047 destDir = fullfile(ravenDir,'software','blast+'); +0047 destDir = fullfile(ravenDir,'src','external','blast+'); 0048 sentinel = fullfile(destDir,'blastp'); % bare name on every OS (.exe on Windows) 0049 execs = {'blastp','makeblastdb'}; 0050 case 'diamond' 0051 tag = 'diamond-2.1.17'; 0052 asset = ['diamond-2.1.17-' plat '.zip']; -0053 destDir = fullfile(ravenDir,'software','diamond'); +0053 destDir = fullfile(ravenDir,'src','external','diamond'); 0054 sentinel = fullfile(destDir,'diamond'); 0055 execs = {'diamond'}; 0056 case 'hmmer' @@ -125,7 +125,7 @@

    SOURCE CODE ^'hmmer-3.4.0'; 0062 asset = ['hmmer-3.4.0-' plat '.zip']; 0063 end -0064 destDir = fullfile(ravenDir,'software','hmmer'); +0064 destDir = fullfile(ravenDir,'src','external','hmmer'); 0065 sentinel = fullfile(destDir,'hmmsearch'); 0066 execs = {'hmmsearch'}; 0067 otherwise @@ -142,7 +142,7 @@

    SOURCE CODE ^continue; % already provisioned 0079 end 0080 -0081 zipPath = fetchRavenDataAsset(fullfile(ravenDir,'software'),tag,asset); +0081 zipPath = fetchRavenDataAsset(fullfile(ravenDir,'src','external'),tag,asset); 0082 unzip(zipPath,destDir); 0083 delete(zipPath); 0084 diff --git a/docs/html/installation/findRAVENroot.html b/docs/html/installation/findRAVENroot.html index db39bea4..f4cde4f1 100644 --- a/docs/html/installation/findRAVENroot.html +++ b/docs/html/installation/findRAVENroot.html @@ -29,7 +29,7 @@

    SYNOPSIS ^DESCRIPTION ^

     findRAVENroot
        Finds the root of the RAVEN directory by searching for the path to
    -   RAVEN.png. Can also record the current directory, in case a function will
    +   version.txt. Can also record the current directory, in case a function will
        use the ravenPath to navigate to a precise folder, and it should return to
        the previous directory afterwards. See e.g. optimizeProb calling glpk.
     
    @@ -57,7 +57,7 @@ 

    SOURCE CODE ^
    0001 function [ravenPath, prevDir] = findRAVENroot()
     0002 % findRAVENroot
     0003 %   Finds the root of the RAVEN directory by searching for the path to
    -0004 %   RAVEN.png. Can also record the current directory, in case a function will
    +0004 %   version.txt. Can also record the current directory, in case a function will
     0005 %   use the ravenPath to navigate to a precise folder, and it should return to
     0006 %   the previous directory afterwards. See e.g. optimizeProb calling glpk.
     0007 %
    @@ -84,7 +84,7 @@ 

    SOURCE CODE ^''; 0029 if ispref('RAVEN','ravenPath') 0030 prefPath = getpref('RAVEN','ravenPath'); -0031 if isfile(fullfile(prefPath,'RAVEN.png')) +0031 if isfile(fullfile(prefPath,'version.txt')) 0032 ravenPath = prefPath; 0033 end 0034 end @@ -93,14 +93,14 @@

    SOURCE CODE ^'findRAVENroot')).file; 0038 rootFound = 0; 0039 while rootFound == 0 -0040 isRoot = isfile(fullfile(ravenPath,'RAVEN.png')); +0040 isRoot = isfile(fullfile(ravenPath,'version.txt')); 0041 if isRoot 0042 rootFound = 1; 0043 else 0044 ravenPathOld = ravenPath; 0045 ravenPath = fileparts(ravenPath); 0046 if strcmp(ravenPathOld,ravenPath) -0047 error('Cannot find the RAVEN root directory. Make sure you have not removed the RAVEN.png file from your RAVEN installation.') +0047 error('Cannot find the RAVEN root directory. Make sure you have not removed the version.txt file from your RAVEN installation.') 0048 end 0049 end 0050 end diff --git a/docs/html/installation/updateDocumentation.html b/docs/html/installation/updateDocumentation.html index fafe7404..abbca8aa 100644 --- a/docs/html/installation/updateDocumentation.html +++ b/docs/html/installation/updateDocumentation.html @@ -28,12 +28,12 @@

    SYNOPSIS ^DESCRIPTION ^

     updateDocumentation
    -    Regenerates the HTML documentation in RAVEN's doc directory from the
    -    function help texts. The doc directory is deleted and rebuilt, so
    +    Regenerates the HTML documentation in RAVEN's docs/html directory from the
    +    function help texts. The docs/html directory is deleted and rebuilt, so
         anything hand-edited there is lost.
     
         The Documentation workflow runs this on every push to a release branch
    -    and commits the result, so doc stays in step with the sources without
    +    and commits the result, so docs/html stays in step with the sources without
         anyone running it by hand. Run it locally only to preview.
     
         Usage: updateDocumentation()
    @@ -53,48 +53,48 @@

    CROSS-REFERENCE INFORMATION ^
 <h2><a name=SOURCE CODE ^

    0001 function updateDocumentation()
     0002 % updateDocumentation
    -0003 %    Regenerates the HTML documentation in RAVEN's doc directory from the
    -0004 %    function help texts. The doc directory is deleted and rebuilt, so
    +0003 %    Regenerates the HTML documentation in RAVEN's docs/html directory from the
    +0004 %    function help texts. The docs/html directory is deleted and rebuilt, so
     0005 %    anything hand-edited there is lost.
     0006 %
     0007 %    The Documentation workflow runs this on every push to a release branch
    -0008 %    and commits the result, so doc stays in step with the sources without
    +0008 %    and commits the result, so docs/html stays in step with the sources without
     0009 %    anyone running it by hand. Run it locally only to preview.
     0010 %
     0011 %    Usage: updateDocumentation()
     0012 
     0013 %Get the RAVEN path
     0014 ravenDir=findRAVENroot();
    -0015 %Make sure that RAVEN-provided m2html is used
    -0016 path(fullfile(ravenDir,'software','m2html'),path);
    -0017 %Get a non-redundant list of RAVEN subdirectories containing MATLAB
    -0018 %functions. Absolute paths are not compatible with M2HTML, so convert them
    -0019 %to the relative paths instead.
    -0020 ravenDirs=dir(fullfile(ravenDir,'**/*.m'));
    -0021 ravenDirs=unique({ravenDirs.folder})';
    -0022 
    -0023 %Get rid of MATLAB functions from external software
    -0024 ravenDirs(startsWith(ravenDirs,strcat(ravenDir,filesep,'software')))=[];
    -0025 ravenDirs(startsWith(ravenDirs,strcat(ravenDir,filesep,'legacy',filesep,'software')))=[];
    +0015 srcDir=fullfile(ravenDir,'src');
    +0016 %Make sure that RAVEN-provided m2html is used
    +0017 path(fullfile(ravenDir,'src','external','m2html'),path);
    +0018 %Get a non-redundant list of RAVEN subdirectories containing MATLAB
    +0019 %functions. Absolute paths are not compatible with M2HTML, so convert them
    +0020 %to the relative paths instead.
    +0021 ravenDirs=dir(fullfile(srcDir,'**/*.m'));
    +0022 ravenDirs=unique({ravenDirs.folder})';
    +0023 
    +0024 %Get rid of MATLAB functions from external software
    +0025 ravenDirs(startsWith(ravenDirs,strcat(srcDir,filesep,'external')))=[];
     0026 
     0027 %Skip deprecated wrappers: they are on the path so existing scripts keep
     0028 %working, but documenting them would advertise functions that are on their
     0029 %way out. Each one names its replacement when called.
    -0030 ravenDirs(startsWith(ravenDirs,strcat(ravenDir,filesep,'deprecated')))=[];
    +0030 ravenDirs(startsWith(ravenDirs,strcat(srcDir,filesep,'deprecated')))=[];
     0031 
     0032 %Remove keggModel.mat if it exists
    -0033 if exist(fullfile(ravenDir,'reconstruction','kegg','keggModel.mat'), 'file') == 2
    -0034     delete(fullfile(ravenDir,'reconstruction','kegg','keggModel.mat'));
    +0033 if exist(fullfile(ravenDir,'src','reconstruction','kegg','keggModel.mat'), 'file') == 2
    +0034     delete(fullfile(ravenDir,'src','reconstruction','kegg','keggModel.mat'));
     0035 end
     0036 
    -0037 %Remove the existing "doc" directory from RAVEN
    -0038 docDir=fullfile(ravenDir,'doc');
    +0037 %Remove the existing "docs/html" directory from RAVEN
    +0038 docDir=fullfile(ravenDir,'docs','html');
     0039 if isfolder(docDir)
     0040     rmdir(docDir,'s');
     0041 end
     0042 
     0043 %Make relative path
    -0044 relStart = numel(ravenDir)+2;
    +0044 relStart = numel(srcDir)+2;
     0045 for i=1:numel(ravenDirs)
     0046     ravenDirs{i,1} = ravenDirs{i,1}(relStart:end);
     0047 end
    @@ -111,11 +111,11 @@ 

    SOURCE CODE ^end 0059 end 0060 -0061 %Save the current working directory and go to RAVEN root directory +0061 %Save the current working directory and go to RAVEN source directory 0062 originalDir=pwd; -0063 cd(ravenDir); +0063 cd(srcDir); 0064 %Generate HTML documentation files for RAVEN MATLAB functions -0065 m2html('mFiles',ravenDirs,'htmldir','doc'); +0065 m2html('mFiles',ravenDirs,'htmldir',docDir); 0066 %Go back to the original working directory 0067 cd(originalDir); 0068 diff --git a/docs/html/reconstruction/homology/getBlast.html b/docs/html/reconstruction/homology/getBlast.html index fe82412e..6b329d13 100644 --- a/docs/html/reconstruction/homology/getBlast.html +++ b/docs/html/reconstruction/homology/getBlast.html @@ -202,7 +202,7 @@

    SOURCE CODE ^end 0110 0111 %Fetch the BLAST+ binaries on demand if they are not already present -0112 if ~exist(fullfile(ravenPath,'software','blast+',['blastp' binEnd]),'file') +0112 if ~exist(fullfile(ravenPath,'src','external','blast+',['blastp' binEnd]),'file') 0113 downloadRavenBinaries({'blast+'}); 0114 end 0115 @@ -214,7 +214,7 @@

    SOURCE CODE ^%Create a database for the new organism and blast each of the refFastaFiles 0123 %against it -0124 [status, message]=system(['"' fullfile(ravenPath,'software','blast+',['makeblastdb' binEnd]) '" -in "' fastaFile{1} '" -out "' fullfile(tmpDB, 'tmpDB') '" -dbtype prot']); +0124 [status, message]=system(['"' fullfile(ravenPath,'src','external','blast+',['makeblastdb' binEnd]) '" -in "' fastaFile{1} '" -out "' fullfile(tmpDB, 'tmpDB') '" -dbtype prot']); 0125 if developMode 0126 blastReport.dbHashes.phr{numel(blastReport.dbHashes.phr)+1}=filemd5(fullfile(tmpDB, 'tmpDB.phr')); 0127 blastReport.dbHashes.pot{numel(blastReport.dbHashes.pot)+1}=filemd5(fullfile(tmpDB, 'tmpDB.pot')); @@ -229,7 +229,7 @@

    SOURCE CODE ^if ~hideVerbose 0137 fprintf(['BLASTing "' modelIDs{i} '" against "' organismID{1} '"..\n']); 0138 end -0139 [status, message]=system(['"' fullfile(ravenPath,'software','blast+',['blastp' binEnd]) '" -query "' refFastaFiles{i} '" -out "' outFile '_' num2str(i) '" -db "' fullfile(tmpDB, 'tmpDB') '" -evalue 10e-5 -outfmt "10 qseqid sseqid evalue pident length bitscore ppos" -num_threads "' cores '"']); +0139 [status, message]=system(['"' fullfile(ravenPath,'src','external','blast+',['blastp' binEnd]) '" -query "' refFastaFiles{i} '" -out "' outFile '_' num2str(i) '" -db "' fullfile(tmpDB, 'tmpDB') '" -evalue 10e-5 -outfmt "10 qseqid sseqid evalue pident length bitscore ppos" -num_threads "' cores '"']); 0140 if developMode 0141 blastReport.blastTxtOutput{numel(blastReport.blastTxtOutput)+1}=importdata([outFile '_' num2str(i)]); 0142 end @@ -245,11 +245,11 @@

    SOURCE CODE ^if ~hideVerbose 0153 fprintf(['BLASTing "' organismID{1} '" against "' modelIDs{i} '"..\n']); 0154 end -0155 [status, message]=system(['"' fullfile(ravenPath,'software','blast+',['makeblastdb' binEnd]) '" -in "' refFastaFiles{i} '" -out "' fullfile(tmpDB, 'tmpDB') '" -dbtype prot']); +0155 [status, message]=system(['"' fullfile(ravenPath,'src','external','blast+',['makeblastdb' binEnd]) '" -in "' refFastaFiles{i} '" -out "' fullfile(tmpDB, 'tmpDB') '" -dbtype prot']); 0156 if status~=0 0157 error('makeblastdb did not run successfully, error:\n%s',strip(message)) 0158 end -0159 [status, message]=system(['"' fullfile(ravenPath,'software','blast+',['blastp' binEnd]) '" -query "' fastaFile{1} '" -out "' outFile '_r' num2str(i) '" -db "' fullfile(tmpDB, 'tmpDB') '" -evalue 10e-5 -outfmt "10 qseqid sseqid evalue pident length bitscore ppos" -num_threads "' cores '"']); +0159 [status, message]=system(['"' fullfile(ravenPath,'src','external','blast+',['blastp' binEnd]) '" -query "' fastaFile{1} '" -out "' outFile '_r' num2str(i) '" -db "' fullfile(tmpDB, 'tmpDB') '" -evalue 10e-5 -outfmt "10 qseqid sseqid evalue pident length bitscore ppos" -num_threads "' cores '"']); 0160 if developMode 0161 blastReport.dbHashes.phr{numel(blastReport.dbHashes.phr)+1}=filemd5(fullfile(tmpDB, 'tmpDB.phr')); 0162 blastReport.dbHashes.pot{numel(blastReport.dbHashes.pot)+1}=filemd5(fullfile(tmpDB, 'tmpDB.pot')); diff --git a/docs/html/reconstruction/homology/getDiamond.html b/docs/html/reconstruction/homology/getDiamond.html index 7f8fc140..24f24400 100644 --- a/docs/html/reconstruction/homology/getDiamond.html +++ b/docs/html/reconstruction/homology/getDiamond.html @@ -191,7 +191,7 @@

    SOURCE CODE ^end 0099 0100 %Fetch the DIAMOND binary on demand if it is not already present -0101 if ~exist(fullfile(ravenPath,'software','diamond',['diamond' binEnd]),'file') +0101 if ~exist(fullfile(ravenPath,'src','external','diamond',['diamond' binEnd]),'file') 0102 downloadRavenBinaries({'diamond'}); 0103 end 0104 @@ -203,7 +203,7 @@

    SOURCE CODE ^%Create a database for the new organism and blast each of the refFastaFiles 0112 %against it -0113 [status, message]=system(['"' fullfile(ravenPath,'software','diamond',['diamond' binEnd]) '" makedb --in "' fastaFile{1} '" --db "' fullfile(tmpDB) '"']); +0113 [status, message]=system(['"' fullfile(ravenPath,'src','external','diamond',['diamond' binEnd]) '" makedb --in "' fastaFile{1} '" --db "' fullfile(tmpDB) '"']); 0114 if developMode 0115 diamondReport.dbHashes{numel(diamondReport.dbHashes)+1} = char(regexp(message,'[a-f0-9]{32}','match')); 0116 end @@ -215,7 +215,7 @@

    SOURCE CODE ^if ~hideVerbose 0123 fprintf(['Running DIAMOND blastp with "' modelIDs{i} '" against "' organismID{1} '"..\n']); 0124 end -0125 [status, message]=system(['"' fullfile(ravenPath,'software','diamond',['diamond' binEnd]) '" blastp --query "' refFastaFiles{i} '" --out "' outFile '_' num2str(i) '" --db "' fullfile(tmpDB) '" --more-sensitive --outfmt 6 qseqid sseqid evalue pident length bitscore ppos --threads ' cores ]); +0125 [status, message]=system(['"' fullfile(ravenPath,'src','external','diamond',['diamond' binEnd]) '" blastp --query "' refFastaFiles{i} '" --out "' outFile '_' num2str(i) '" --db "' fullfile(tmpDB) '" --more-sensitive --outfmt 6 qseqid sseqid evalue pident length bitscore ppos --threads ' cores ]); 0126 if developMode 0127 diamondReport.diamondTxtOutput{numel(diamondReport.diamondTxtOutput)+1}=importdata([outFile '_' num2str(i)]); 0128 end @@ -231,11 +231,11 @@

    SOURCE CODE ^if ~hideVerbose 0139 fprintf(['Running DIAMOND blastp with "' organismID{1} '" against "' modelIDs{i} '"..\n']); 0140 end -0141 [status, message1]=system(['"' fullfile(ravenPath,'software','diamond',['diamond' binEnd]) '" makedb --in "' refFastaFiles{i} '" --db "' fullfile(tmpDB) '"']); +0141 [status, message1]=system(['"' fullfile(ravenPath,'src','external','diamond',['diamond' binEnd]) '" makedb --in "' refFastaFiles{i} '" --db "' fullfile(tmpDB) '"']); 0142 if status~=0 0143 error('DIAMOND makedb did not run successfully, error:\n%s',strip(message1)) 0144 end -0145 [status, message]=system(['"' fullfile(ravenPath,'software','diamond',['diamond' binEnd]) '" blastp --query "' fastaFile{1} '" --out "' outFile '_r' num2str(i) '" --db "' fullfile(tmpDB) '" --more-sensitive --outfmt 6 qseqid sseqid evalue pident length bitscore ppos --threads ' cores]); +0145 [status, message]=system(['"' fullfile(ravenPath,'src','external','diamond',['diamond' binEnd]) '" blastp --query "' fastaFile{1} '" --out "' outFile '_r' num2str(i) '" --db "' fullfile(tmpDB) '" --more-sensitive --outfmt 6 qseqid sseqid evalue pident length bitscore ppos --threads ' cores]); 0146 if developMode 0147 diamondReport.dbHashes{numel(diamondReport.dbHashes)+1} = char(regexp(message1,'[a-f0-9]{32}','match')); 0148 diamondReport.diamondTxtOutput{numel(diamondReport.diamondTxtOutput)+1}=importdata([outFile '_r' num2str(i)]); diff --git a/docs/html/reconstruction/kegg/buildGlobalKEGGModel.html b/docs/html/reconstruction/kegg/buildGlobalKEGGModel.html index 450013ff..c571402f 100644 --- a/docs/html/reconstruction/kegg/buildGlobalKEGGModel.html +++ b/docs/html/reconstruction/kegg/buildGlobalKEGGModel.html @@ -96,7 +96,7 @@

    SOURCE CODE ^% reaction ids carrying the corresponding rxn_flags quality flag. 0027 0028 kver=keggDataVersion(); -0029 keggDir=fullfile(ravenPath,'reconstruction','kegg'); +0029 keggDir=fullfile(ravenPath,'src','reconstruction','kegg'); 0030 archive=fetchRavenDataAsset(keggDir,kver,[kver '_core.tar.gz']); 0031 0032 coreDir=fullfile(keggDir,[kver '_core']); diff --git a/docs/html/reconstruction/kegg/getKEGGModelForOrganism.html b/docs/html/reconstruction/kegg/getKEGGModelForOrganism.html index cb705488..9abcfc97 100644 --- a/docs/html/reconstruction/kegg/getKEGGModelForOrganism.html +++ b/docs/html/reconstruction/kegg/getKEGGModelForOrganism.html @@ -451,7 +451,7 @@

    SOURCE CODE ^%First remove all reactions without genes 0296 if keepSpontaneous==true 0297 fprintf('Removing non-spontaneous reactions without GPR rules... '); -0298 load(fullfile(ravenPath,'reconstruction','kegg','keggModel.mat'),'isSpontaneous'); +0298 load(fullfile(ravenPath,'src','reconstruction','kegg','keggModel.mat'),'isSpontaneous'); 0299 I=~any(model.rxnGeneMat,2)&~ismember(model.rxns,isSpontaneous); 0300 spontRxnsWithGenes=model.rxns(any(model.rxnGeneMat,2)&~ismember(model.rxns,isSpontaneous)); 0301 else @@ -524,12 +524,12 @@

    SOURCE CODE ^''; 0369 end 0370 %Fetch the HMMER binary on demand if it is not already present -0371 if ~exist(fullfile(ravenPath,'software','hmmer',['hmmsearch' binEnd]),'file') +0371 if ~exist(fullfile(ravenPath,'src','external','hmmer',['hmmsearch' binEnd]),'file') 0372 downloadRavenBinaries({'hmmer'}); 0373 end 0374 tblFile=[tempname '.tblout']; 0375 fprintf('Querying the user-specified FASTA file against the KEGG Orthology specific HMMs... '); -0376 [status, output]=system(['"' fullfile(ravenPath,'software','hmmer',['hmmsearch' binEnd]) '" --cpu "' num2str(cores) '" --tblout "' tblFile '" "' libraryFile '" "' fastaFile '"']); +0376 [status, output]=system(['"' fullfile(ravenPath,'src','external','hmmer',['hmmsearch' binEnd]) '" --cpu "' num2str(cores) '" --tblout "' tblFile '" "' libraryFile '" "' fastaFile '"']); 0377 if status~=0 0378 EM=['Error when querying the concatenated HMM library:\n' output]; 0379 error('RAVEN:badInput', '%s', EM); diff --git a/docs/html/reconstruction/kegg/getModelFromKEGG.html b/docs/html/reconstruction/kegg/getModelFromKEGG.html index c4b8d064..0bf74839 100644 --- a/docs/html/reconstruction/kegg/getModelFromKEGG.html +++ b/docs/html/reconstruction/kegg/getModelFromKEGG.html @@ -132,7 +132,7 @@

    SOURCE CODE ^'reconstruction','kegg','keggModel.mat'); +0052 modelFile=fullfile(ravenPath,'src','reconstruction','kegg','keggModel.mat'); 0053 if isfile(modelFile) 0054 fprintf(['Importing the global KEGG model from ' strrep(modelFile,'\','/') '... ']); 0055 load(modelFile,'model','KOModel','isSpontaneous','isUndefinedStoich','isIncomplete','isGeneral'); diff --git a/docs/html/reconstruction/kegg/getPhylDist.html b/docs/html/reconstruction/kegg/getPhylDist.html index ffb9de19..654bbdb3 100644 --- a/docs/html/reconstruction/kegg/getPhylDist.html +++ b/docs/html/reconstruction/kegg/getPhylDist.html @@ -93,7 +93,7 @@

    SOURCE CODE ^'reconstruction','kegg','keggPhylDist.mat'); +0030 distFile=fullfile(ravenPath,'src','reconstruction','kegg','keggPhylDist.mat'); 0031 if ~exist(distFile,'file') 0032 error('getPhylDist:noData', ... 0033 ['keggPhylDist.mat not found at ' strrep(distFile,'\','/') '.\n' ... diff --git a/docs/html/reconstruction/kegg/keggDataVersion.html b/docs/html/reconstruction/kegg/keggDataVersion.html index f2a044e1..2c505c3e 100644 --- a/docs/html/reconstruction/kegg/keggDataVersion.html +++ b/docs/html/reconstruction/kegg/keggDataVersion.html @@ -68,7 +68,7 @@

    SOURCE CODE ^% the raven-data release tag, e.g. 'kegg118'. 0014 0015 ravenPath=findRAVENroot(); -0016 versionFile=fullfile(ravenPath,'reconstruction','kegg','KEGG_VERSION.md'); +0016 versionFile=fullfile(ravenPath,'src','reconstruction','kegg','KEGG_VERSION.md'); 0017 fid=fopen(versionFile,'r'); 0018 if fid==-1 0019 error('keggDataVersion:fileNotFound','Cannot read %s.',strrep(versionFile,'\','/')) diff --git a/docs/html/solver/optimizeProb.html b/docs/html/solver/optimizeProb.html index d7a4187f..fa2f41ed 100644 --- a/docs/html/solver/optimizeProb.html +++ b/docs/html/solver/optimizeProb.html @@ -300,7 +300,7 @@

    SOURCE CODE ^% Ensure that RAVEN glpk binary is used, return to original 0225 % directory afterwards 0226 [ravenDir,currDir]=findRAVENroot(); -0227 cd(fullfile(ravenDir,'software','GLPKmex')) +0227 cd(fullfile(ravenDir,'src','external','GLPKmex')) 0228 [xopt, fmin, errnum, extra] = glpk(prob.c, prob.A, prob.b, prob.lb, prob.ub, prob.csense, prob.vartype, prob.osense, solverparams); 0229 cd(currDir) 0230 diff --git a/docs/html/solver/setRavenSolver.html b/docs/html/solver/setRavenSolver.html index 97a7ca0e..84700cd0 100644 --- a/docs/html/solver/setRavenSolver.html +++ b/docs/html/solver/setRavenSolver.html @@ -105,15 +105,15 @@

    SOURCE CODE ^end 0044 else 0045 ravenDir = findRAVENroot(); -0046 if ~exist(fullfile(ravenDir,'software','scip','scip.mexw64'),'file') +0046 if ~exist(fullfile(ravenDir,'src','external','scip','scip.mexw64'),'file') 0047 try 0048 disp('Downloading and installing RAVEN-provided SCIP MEX binary...') -0049 websave(fullfile(ravenDir,'software','scip_mex_win.zip'),'https://github.com/SysBioChalmers/RAVEN/releases/download/v2.8.6/scip_mex_win.zip'); % Should be updated to release with SCIP functionality +0049 websave(fullfile(ravenDir,'src','external','scip_mex_win.zip'),'https://github.com/SysBioChalmers/RAVEN/releases/download/v2.8.6/scip_mex_win.zip'); % Should be updated to release with SCIP functionality 0050 catch 0051 error('Unable to download SCIP MEX binary from the RAVEN GitHub page.') 0052 end -0053 unzip(fullfile(ravenDir,'software','scip_mex_win.zip'),fullfile(ravenDir,'software','scip')); -0054 delete(fullfile(ravenDir,'software','scip_mex_win.zip')); +0053 unzip(fullfile(ravenDir,'src','external','scip_mex_win.zip'),fullfile(ravenDir,'src','external','scip')); +0054 delete(fullfile(ravenDir,'src','external','scip_mex_win.zip')); 0055 end 0056 try 0057 scip; % The pre-compiled MEX file might fail diff --git a/docs/html/testing/function_tests/RavenTestCase.html b/docs/html/testing/function_tests/RavenTestCase.html deleted file mode 100644 index 28f2b591..00000000 --- a/docs/html/testing/function_tests/RavenTestCase.html +++ /dev/null @@ -1,171 +0,0 @@ - - - - Description of RavenTestCase - - - - - - - - - -
    Home > testing > function_tests > RavenTestCase.m
    - - - -

    RavenTestCase -

    - -

    PURPOSE ^

    -
    - -

    SYNOPSIS ^

    -
    This is a script file.
    - -

    DESCRIPTION ^

    -
    - - -

    CROSS-REFERENCE INFORMATION ^

    -This function calls: - -This function is called by: - - - -

    SUBFUNCTIONS ^

    - - -

    SOURCE CODE ^

    -
    0001 classdef (Abstract) RavenTestCase < matlab.unittest.TestCase
    -0002 % RavenTestCase  Shared base class for the RAVEN function test suite.
    -0003 %
    -0004 %   Provides, once per test class:
    -0005 %     - ravenRoot : the RAVEN installation root.
    -0006 %     - model     : a small E. coli textbook model in RAVEN format, reloaded
    -0007 %                   fresh for every test method so mutations cannot leak
    -0008 %                   between tests.
    -0009 %     - a deterministic LP solver (GLPK, which ships with RAVEN).
    -0010 %
    -0011 %   It also offers assume* helpers that mark a test as filtered (not failed)
    -0012 %   when an optional dependency is missing, so the suite stays green in any
    -0013 %   environment. Concrete test classes subclass this and add a methods(Test)
    -0014 %   block.
    -0015 
    -0016     properties
    -0017         ravenRoot char
    -0018         model struct          % reset before every test method
    -0019     end
    -0020 
    -0021     properties (Access = protected)
    -0022         modelTemplate struct  % pristine copy, loaded once per class
    -0023     end
    -0024 
    -0025     methods (TestClassSetup)
    -0026         function setupRaven(testCase)
    -0027             testCase.ravenRoot = findRAVENroot();
    -0028             loaded = load(fullfile(testCase.ravenRoot,'testing','function_tests', ...
    -0029                 'test_data','ecoli_textbook.mat'),'model');
    -0030             testCase.modelTemplate = loaded.model;
    -0031             try
    -0032                 setRavenSolver('glpk');
    -0033             catch
    -0034                 % leave whatever solver is configured; solver tests assume below
    -0035             end
    -0036 
    -0037             % Silence progress reporting for the duration of the test class,
    -0038             % restoring whatever the user had configured afterwards.
    -0039             origProgress = getpref('RAVEN','progressBar','auto');
    -0040             setpref('RAVEN','progressBar','none');
    -0041             testCase.addTeardown(@() setpref('RAVEN','progressBar',origProgress));
    -0042         end
    -0043 
    -0044         function suppressFigures(testCase)
    -0045             % Keep plotting functions from popping windows or leaking figures.
    -0046             orig = get(groot, 'defaultFigureVisible');
    -0047             set(groot, 'defaultFigureVisible', 'off');
    -0048             testCase.addTeardown(@() set(groot, 'defaultFigureVisible', orig));
    -0049             testCase.addTeardown(@() close('all'));
    -0050         end
    -0051     end
    -0052 
    -0053     methods (TestMethodSetup)
    -0054         function freshModel(testCase)
    -0055             % Hand each test an untouched copy of the model.
    -0056             testCase.model = testCase.modelTemplate;
    -0057         end
    -0058     end
    -0059 
    -0060     methods (Access = protected)
    -0061         function assumeSolver(testCase, solverName)
    -0062             % Skip unless the named solver is on the MATLAB path.
    -0063             testCase.assumeNotEmpty(which(solverName), ...
    -0064                 sprintf('Solver "%s" not available; test skipped.', solverName));
    -0065         end
    -0066 
    -0067         function assumeDependency(testCase, isAvailable, label)
    -0068             % Skip when an external binary / network / GUI dependency is absent.
    -0069             testCase.assumeTrue(logical(isAvailable), ...
    -0070                 sprintf('Dependency "%s" not available; test skipped.', label));
    -0071         end
    -0072 
    -0073         function assumeMILPSolver(testCase)
    -0074             % Switch to a MILP-capable solver for this test, or skip if none.
    -0075             % GLPK (the default) cannot solve MILPs in RAVEN.
    -0076             if ~isempty(which('gurobi'))
    -0077                 evalc('setRavenSolver(''gurobi'')');
    -0078             else
    -0079                 ok = false;
    -0080                 try, evalc('setRavenSolver(''scip'')'); ok = true; catch, end %#ok<NOCOM>
    -0081                 testCase.assumeTrue(ok, 'No MILP solver (gurobi/scip) available; test skipped.');
    -0082             end
    -0083             testCase.addTeardown(@() evalc('setRavenSolver(''glpk'')'));
    -0084         end
    -0085 
    -0086         function m = taskTestModel(~)
    -0087             % Small synthetic model used by task/gap-filling tests.
    -0088             m = struct();
    -0089             m.id = 'testModel'; m.rxns = {}; m.S = []; m.rev = [];
    -0090             m.mets = {'as';'ac';'bc';'cc';'dc';'ec';'es';'fc'};
    -0091             m.metNames = {'a';'a';'b';'c';'d';'e';'e';'f'};
    -0092             m.comps = {'s';'c'}; m.compNames = m.comps;
    -0093             m.metComps = [1;2;2;2;2;2;1;2];
    -0094             m.genes = {'G1';'G2';'G3';'G4';'G5';'G6';'G7';'G8';'G9';'G10'};
    -0095             m.grRules = {}; m.rxnGeneMat = [];
    -0096             r = struct();
    -0097             r.rxns = {'R1';'R2';'R3';'R4';'R5';'R6';'R7';'R8';'R9';'R10'};
    -0098             r.equations = {'=> a[s]';'a[s] <=> a[c]';'a[c] <=> b[c] + c[c]'; ...
    -0099                 'a[c] <=> 2 d[c]';'b[c] + c[c] => e[c]';'2 d[c] => e[c]'; ...
    -0100                 'e[c] => e[s]';'e[s] =>';'a[c] <=> f[c]';'f[c] <=> e[c]'};
    -0101             r.grRules = {'';'';'G3';'G4';'G5';'G6';'G7';'';'G9';'G10'};
    -0102             evalc('m = addRxns(m, r, 3);');
    -0103             m.c = [0;0;0;0;0;0;0;1;0;0];
    -0104             m.ub = repmat(1000,10,1);
    -0105             m.lb = [0;-1000;-1000;-1000;0;0;0;0;-1000;-1000];
    -0106             m.rxnNames = m.rxns;
    -0107             m.b = repmat(0,8,1);
    -0108             m = closeModel(m);
    -0109         end
    -0110 
    -0111         function t = taskTestStruct(~)
    -0112             % Task that the model above can satisfy: make e[s] from a[s].
    -0113             t = struct();
    -0114             t.id = 'Gen e[s] from a[s]'; t.description = t.id;
    -0115             t.shouldFail = false; t.printFluxes = false; t.comments = '';
    -0116             t.inputs = {'a[s]'}; t.LBin = 0; t.UBin = inf;
    -0117             t.outputs = {'e[s]'}; t.LBout = 1; t.UBout = 1;
    -0118             t.equations = {}; t.LBequ = []; t.UBequ = [];
    -0119             t.changed = {}; t.LBrxn = {}; t.UBrxn = {};
    -0120         end
    -0121     end
    -0122 end
    -
    Generated by m2html © 2005
    - - \ No newline at end of file diff --git a/docs/html/testing/function_tests/index.html b/docs/html/testing/function_tests/index.html deleted file mode 100644 index fa1d7670..00000000 --- a/docs/html/testing/function_tests/index.html +++ /dev/null @@ -1,31 +0,0 @@ - - - - Index for Directory testing/function_tests - - - - - - - - - - -
    < Master indexIndex for testing/function_tests >
    - -

    Index for testing/function_tests

    - -

    Matlab files in this directory:

    - -
     RavenTestCase
     tAnalysis
     tAnnotation
     tAssignCompartments
     tBinaries
     tBiomass
     tComparison
     tConditions
     tConversion
     tCuration
     tGapfilling
     tGenomeData
     tINIT
     tIO
     tInstallation
     tLocalization
     tManipulation
     tNamedArgs
     tParseRAVENargs
     tQueries
     tReconstruction
     tSampling
     tSolver
     tSyntax
     tTasks
     tUtils
    - - -

    Subsequent directories:

    - - -
    Generated by m2html © 2005
    - - \ No newline at end of file diff --git a/docs/html/testing/function_tests/private/getExprForRxnScore.html b/docs/html/testing/function_tests/private/getExprForRxnScore.html deleted file mode 100644 index 5c8b6d55..00000000 --- a/docs/html/testing/function_tests/private/getExprForRxnScore.html +++ /dev/null @@ -1,114 +0,0 @@ - - - - Description of getExprForRxnScore - - - - - - - - - -
    Home > testing > function_tests > private > getExprForRxnScore.m
    - - - -

    getExprForRxnScore -

    - -

    PURPOSE ^

    -
    getExprForRxnScore Convert a reaction score to the required gene expression.
    - -

    SYNOPSIS ^

    -
    function expr = getExprForRxnScore(scores, varargin)
    - -

    DESCRIPTION ^

    -
     getExprForRxnScore  Convert a reaction score to the required gene expression.
    -
    - Converts a reaction score to the gene expression (CPM or TPM) required to
    - get that reaction score, if the GPR is only a single gene. Useful function
    - primarily in test cases, where you want to be able to define the reaction
    - scores of rxns, but need to send in gene expression.
    -
    - Parameters
    - ----------
    - scores : double
    -     vector of scores to convert.
    -
    - Name-Value Arguments
    - --------------------
    - threshold : double
    -     gene expression threshold, matching the one used to originally
    -     calculate the reaction score; scales the resulting expression value
    -     (default 1).
    -
    - Returns
    - -------
    - expr : double
    -     the resulting gene expression vector.
    -
    - Examples
    - --------
    -     expr = getExprForRxnScore(scores, threshold);
    - - -

    CROSS-REFERENCE INFORMATION ^

    -This function calls: -
      -
    -This function is called by: -
      -
    - - - - -

    SOURCE CODE ^

    -
    0001 function expr = getExprForRxnScore(scores, varargin)
    -0002 % getExprForRxnScore  Convert a reaction score to the required gene expression.
    -0003 %
    -0004 % Converts a reaction score to the gene expression (CPM or TPM) required to
    -0005 % get that reaction score, if the GPR is only a single gene. Useful function
    -0006 % primarily in test cases, where you want to be able to define the reaction
    -0007 % scores of rxns, but need to send in gene expression.
    -0008 %
    -0009 % Parameters
    -0010 % ----------
    -0011 % scores : double
    -0012 %     vector of scores to convert.
    -0013 %
    -0014 % Name-Value Arguments
    -0015 % --------------------
    -0016 % threshold : double
    -0017 %     gene expression threshold, matching the one used to originally
    -0018 %     calculate the reaction score; scales the resulting expression value
    -0019 %     (default 1).
    -0020 %
    -0021 % Returns
    -0022 % -------
    -0023 % expr : double
    -0024 %     the resulting gene expression vector.
    -0025 %
    -0026 % Examples
    -0027 % --------
    -0028 %     expr = getExprForRxnScore(scores, threshold);
    -0029 
    -0030 p=parseRAVENargs(varargin, {'threshold',1});
    -0031 threshold=p.threshold;
    -0032 
    -0033 %This is how the score is calculated: 5*log(expression./threshold)
    -0034 %expression = threshold*10.^(scores/5)
    -0035 %This is a bit confusing - it seems that it is threshold*e.^(scores/5)
    -0036 %This is probably what is being used in scoreModel, this code
    -0037 %negates that perfectly, see the T0009 test case.
    -0038 
    -0039 expr = threshold*exp(scores/5);
    -0040 
    -0041 end
    -
    Generated by m2html © 2005
    - - \ No newline at end of file diff --git a/docs/html/testing/function_tests/private/index.html b/docs/html/testing/function_tests/private/index.html deleted file mode 100644 index 5549866d..00000000 --- a/docs/html/testing/function_tests/private/index.html +++ /dev/null @@ -1,29 +0,0 @@ - - - - Index for Directory testing/function_tests/private - - - - - - - - - - -
    < Master indexIndex for testing/function_tests/private >
    - -

    Index for testing/function_tests/private

    - -

    Matlab files in this directory:

    - -
     getExprForRxnScoregetExprForRxnScore Convert a reaction score to the required gene expression.
    - - - - -
    Generated by m2html © 2005
    - - \ No newline at end of file diff --git a/docs/html/testing/function_tests/tAnalysis.html b/docs/html/testing/function_tests/tAnalysis.html deleted file mode 100644 index e84acb49..00000000 --- a/docs/html/testing/function_tests/tAnalysis.html +++ /dev/null @@ -1,401 +0,0 @@ - - - - Description of tAnalysis - - - - - - - - - -
    Home > testing > function_tests > tAnalysis.m
    - - - -

    tAnalysis -

    - -

    PURPOSE ^

    -
    - -

    SYNOPSIS ^

    -
    This is a script file.
    - -

    DESCRIPTION ^

    -
    - - -

    CROSS-REFERENCE INFORMATION ^

    -This function calls: - -This function is called by: - - - -

    SUBFUNCTIONS ^

    - - -

    SOURCE CODE ^

    -
    0001 classdef tAnalysis < RavenTestCase
    -0002 % tAnalysis  Tests for the flux-analysis and simulation functions in analysis/.
    -0003 
    -0004     methods (Test)
    -0005 
    -0006         function getAllowedBoundsSizes(testCase)
    -0007             [mn, mx] = getAllowedBounds(testCase.model, 1:10);
    -0008             testCase.verifyNumElements(mn, 10);
    -0009             testCase.verifyNumElements(mx, 10);
    -0010             testCase.verifyTrue(all(mx >= mn - 1e-9));
    -0011         end
    -0012 
    -0013         function getEssentialRxnsReturnsList(testCase)
    -0014             evalc('[ess, idx] = getEssentialRxns(testCase.model);');
    -0015             testCase.verifyClass(ess, 'cell');
    -0016             testCase.verifyTrue(all(ismember(ess, testCase.model.rxns)));
    -0017         end
    -0018 
    -0019         function haveFluxReturnsLogical(testCase)
    -0020             I = haveFlux(testCase.model);
    -0021             testCase.verifyNumElements(I, numel(testCase.model.rxns));
    -0022         end
    -0023 
    -0024         function haveFluxSeedIsReproducible(testCase)
    -0025             % The order reactions are tested in is randomised; a given
    -0026             % seed must make that order (and so results) reproducible.
    -0027             I1 = haveFlux(testCase.model, 'seed', 42);
    -0028             I2 = haveFlux(testCase.model, 'seed', 42);
    -0029             testCase.verifyEqual(I1, I2);
    -0030         end
    -0031 
    -0032         function getMinNrFluxesReturnsFlux(testCase)
    -0033             testCase.assumeMILPSolver();
    -0034             evalc('[x, I, exitFlag] = getMinNrFluxes(testCase.model, testCase.model.rxns);');
    -0035             testCase.verifyNotEmpty(x);
    -0036             %A problem this small is solved well within the time limit
    -0037             testCase.verifyEqual(exitFlag, 1);
    -0038         end
    -0039 
    -0040         function getMinNrFluxesFormulationsAgree(testCase)
    -0041             % The two formulations build different MILPs for the same
    -0042             % problem, so they may pick different reactions, but both
    -0043             % minimise the same objective and must therefore pick the same
    -0044             % number of them.
    -0045             testCase.assumeMILPSolver();
    -0046             evalc('[xIrrev, Iirrev, flagIrrev] = getMinNrFluxes(testCase.model, testCase.model.rxns, [], [], ''irrev'');');
    -0047             evalc('[xRev, Irev, flagRev] = getMinNrFluxes(testCase.model, testCase.model.rxns, [], [], ''reversible'');');
    -0048             testCase.verifyEqual(flagIrrev, 1);
    -0049             testCase.verifyEqual(flagRev, 1);
    -0050             testCase.verifyNumElements(xRev, numel(testCase.model.rxns));
    -0051             testCase.verifyNumElements(Irev, numel(testCase.model.rxns));
    -0052             testCase.verifyEqual(sum(Irev), sum(Iirrev));
    -0053         end
    -0054 
    -0055         function getMinNrFluxesResolveTiesIsDeterministic(testCase)
    -0056             % The reversible-formulation MILP is degenerate on a real model
    -0057             % (see getMinNrFluxesFormulationsAgree above): resolveTies pins
    -0058             % it to a canonical answer instead of leaving the choice to the
    -0059             % solver/seed, so three independent runs must all agree
    -0060             % (raven-gecko-parity#104).
    -0061             testCase.assumeMILPSolver();
    -0062             evalc(['[~, I1, flag1] = getMinNrFluxes(testCase.model, testCase.model.rxns, ' ...
    -0063                 '[], [], ''reversible'', false, ''resolveTies'', true);']);
    -0064             evalc(['[~, I2, flag2] = getMinNrFluxes(testCase.model, testCase.model.rxns, ' ...
    -0065                 '[], [], ''reversible'', false, ''resolveTies'', true);']);
    -0066             evalc(['[~, I3, flag3] = getMinNrFluxes(testCase.model, testCase.model.rxns, ' ...
    -0067                 '[], [], ''reversible'', false, ''resolveTies'', true);']);
    -0068             testCase.verifyEqual(flag1, 1);
    -0069             testCase.verifyEqual(flag2, 1);
    -0070             testCase.verifyEqual(flag3, 1);
    -0071             testCase.verifyEqual(I1, I2);
    -0072             testCase.verifyEqual(I2, I3);
    -0073         end
    -0074 
    -0075         function getMinNrFluxesResolveTiesOnlySupportsReversible(testCase)
    -0076             testCase.verifyError( ...
    -0077                 @() getMinNrFluxes(testCase.model, testCase.model.rxns, [], [], 'irrev', ...
    -0078                     false, 'resolveTies', true), ...
    -0079                 'RAVEN:badInput');
    -0080         end
    -0081 
    -0082         function getMinNrFluxesRejectsUnknownFormulation(testCase)
    -0083             testCase.verifyError( ...
    -0084                 @() getMinNrFluxes(testCase.model, testCase.model.rxns, [], [], 'both'), ...
    -0085                 'RAVEN:badInput');
    -0086         end
    -0087 
    -0088         function getAllSubGraphsReturnsResult(testCase)
    -0089             sg = getAllSubGraphs(testCase.model);
    -0090             testCase.verifyNotEmpty(sg);
    -0091         end
    -0092 
    -0093         function findGeneDeletionsRuns(testCase)
    -0094             evalc('[genes, fluxes] = findGeneDeletions(testCase.model, ''sgd'');');
    -0095             testCase.verifyNotEmpty(genes);
    -0096         end
    -0097 
    -0098         function findGeneDeletionsComputesGrRatio(testCase)
    -0099             evalc(['[genes, ~, ~, ~, grRatioMuts] = findGeneDeletions(testCase.model, ' ...
    -0100                 '''sgd'');']);
    -0101             testCase.verifyNotEmpty(genes);
    -0102             testCase.verifyEqual(numel(grRatioMuts), numel(genes));
    -0103             testCase.verifyGreaterThan(max(grRatioMuts), 0);
    -0104         end
    -0105 
    -0106         function traceFluxPathReturnsCells(testCase)
    -0107             testCase.assumeSolver('solveLP');
    -0108             sol = solveLP(testCase.model);
    -0109             % Use two reactions that must be metabolically connected
    -0110             biomassRxn = testCase.model.rxns{find(testCase.model.c == 1, 1)};
    -0111             [~, exchIdx] = getExchangeRxns(testCase.model);
    -0112             % Pick an exchange reaction with non-zero flux
    -0113             exchFlux = sol.x(exchIdx);
    -0114             active   = exchIdx(abs(exchFlux) > 1e-8);
    -0115             if isempty(active), testCase.assumeTrue(false); end
    -0116             targetRxn = testCase.model.rxns{active(1)};
    -0117             evalc('[p, m, f] = traceFluxPath(testCase.model, sol.x, biomassRxn, targetRxn, ''verbose'', false);');
    -0118             % Either a path is found (cell + double) or not — both are valid
    -0119             testCase.verifyClass(f, 'double');
    -0120             testCase.verifyGreaterThanOrEqual(f, 0);
    -0121             testCase.verifyLessThanOrEqual(f, 1);
    -0122         end
    -0123 
    -0124         function traceFluxPathSameReactionIsIdentity(testCase)
    -0125             testCase.assumeSolver('solveLP');
    -0126             sol  = solveLP(testCase.model);
    -0127             rxn1 = testCase.model.rxns{find(testCase.model.c == 1, 1)};
    -0128             [p, m, f] = traceFluxPath(testCase.model, sol.x, rxn1, rxn1, 'verbose', false);
    -0129             testCase.verifyEqual(f, 1.0);
    -0130             testCase.verifyNumElements(p, 1);
    -0131             testCase.verifyEmpty(m);
    -0132         end
    -0133 
    -0134         function walkFluxesRefusesNonInteractive(testCase)
    -0135             % The test suite itself runs under -batch, so this exercises the
    -0136             % real guard rather than a mocked one.
    -0137             testCase.assumeTrue(batchStartupOptionUsed, ...
    -0138                 'Test runner is not using -batch; the non-interactive guard cannot be exercised here.');
    -0139             testCase.assumeSolver('solveLP');
    -0140             sol = solveLP(testCase.model);
    -0141             biomassRxn = testCase.model.rxns{find(testCase.model.c == 1, 1)};
    -0142             testCase.verifyError(@() walkFluxes(testCase.model, sol.x, biomassRxn), ...
    -0143                 'walkFluxes:nonInteractive');
    -0144         end
    -0145 
    -0146         function compareFluxesReturnsResult(testCase)
    -0147             testCase.assumeSolver('solveLP');
    -0148             solA = solveLP(testCase.model);
    -0149             o2exch = find(strcmp(testCase.model.rxnNames, 'O2 exchange'), 1);
    -0150             modelAna = setParam(testCase.model, 'eq', testCase.model.rxns(o2exch), 0);
    -0151             solB = solveLP(modelAna);
    -0152             result = compareFluxes(testCase.model, solA.x, solB.x, 'verbose', false);
    -0153             testCase.verifyTrue(isfield(result, 'turnedOn'));
    -0154             testCase.verifyTrue(isfield(result, 'turnedOff'));
    -0155             testCase.verifyTrue(isfield(result, 'flipped'));
    -0156             testCase.verifyTrue(isfield(result, 'changed'));
    -0157             testCase.verifyClass(result.changed.rxn, 'cell');
    -0158             testCase.verifyNotEmpty(result.changed.rxn);
    -0159             testCase.verifyEqual(numel(result.changed.flux1), numel(result.changed.rxn));
    -0160             testCase.verifyEqual(numel(result.changed.absDelta), numel(result.changed.rxn));
    -0161             % sorted descending
    -0162             testCase.verifyTrue(all(diff(result.changed.absDelta) <= 0));
    -0163         end
    -0164 
    -0165         function compareFluxesIdenticalIsEmpty(testCase)
    -0166             testCase.assumeSolver('solveLP');
    -0167             sol = solveLP(testCase.model);
    -0168             result = compareFluxes(testCase.model, sol.x, sol.x, 'verbose', false);
    -0169             testCase.verifyEmpty(result.changed.rxn);
    -0170             testCase.verifyEmpty(result.turnedOn);
    -0171             testCase.verifyEmpty(result.turnedOff);
    -0172         end
    -0173 
    -0174         function compareFluxesMetaboliteListRestrictsResult(testCase)
    -0175             testCase.assumeSolver('solveLP');
    -0176             solA = solveLP(testCase.model);
    -0177             o2exch = find(strcmp(testCase.model.rxnNames, 'O2 exchange'), 1);
    -0178             modelAna = setParam(testCase.model, 'eq', testCase.model.rxns(o2exch), 0);
    -0179             solB = solveLP(modelAna);
    -0180             unfiltered = compareFluxes(testCase.model, solA.x, solB.x, 'verbose', false);
    -0181             testCase.assumeNotEmpty(unfiltered.changed.rxn);
    -0182 
    -0183             % Name a metabolite of the largest-changing reaction, so the
    -0184             % filtered result is guaranteed to be non-empty and the subset
    -0185             % checks below are not satisfied vacuously.
    -0186             topRxn = strcmp(testCase.model.rxns, unfiltered.changed.rxn{1});
    -0187             met = testCase.model.metNames{find(testCase.model.S(:,topRxn) ~= 0, 1)};
    -0188             filtered = compareFluxes(testCase.model, solA.x, solB.x, ...
    -0189                 'metaboliteList', {met}, 'verbose', false);
    -0190             testCase.verifyNotEmpty(filtered.changed.rxn);
    -0191 
    -0192             % Every kept reaction must involve the named metabolite, and the
    -0193             % filtered result can only be a subset of the unfiltered one.
    -0194             metIdx = strcmpi(met, testCase.model.metNames);
    -0195             withMet = testCase.model.rxns(any(testCase.model.S(metIdx,:) ~= 0, 1));
    -0196             testCase.verifyTrue(all(ismember(filtered.changed.rxn, withMet)));
    -0197             testCase.verifyTrue(all(ismember(filtered.changed.rxn, unfiltered.changed.rxn)));
    -0198         end
    -0199 
    -0200         function compareFluxesUnknownMetaboliteWarns(testCase)
    -0201             testCase.assumeSolver('solveLP');
    -0202             sol = solveLP(testCase.model);
    -0203             testCase.verifyWarning(@() compareFluxes(testCase.model, sol.x, sol.x, ...
    -0204                 'metaboliteList', {'no such metabolite'}, 'verbose', false), ...
    -0205                 'RAVEN:warning');
    -0206         end
    -0207 
    -0208         function getFluxZZeroVarianceSignMatchesGeneralCase(testCase)
    -0209             % The zero-variance branch's sign must agree with the general
    -0210             % branch: positive when flux increased from A to B, negative
    -0211             % when it decreased.
    -0212             solA = [1 1 1; 5 5 5];    % rxn1 constant at 1, rxn2 constant at 5
    -0213             solB = [5 5 5; 1 1 1];    % rxn1 increased to 5, rxn2 decreased to 1
    -0214             Z = getFluxZ(solA, solB);
    -0215             testCase.verifyEqual(Z(1), 100);
    -0216             testCase.verifyEqual(Z(2), -100);
    -0217         end
    -0218 
    -0219         function getFluxZComputesScores(testCase)
    -0220             n = numel(testCase.model.rxns);
    -0221             Z = getFluxZ(rand(n, 20), rand(n, 20));
    -0222             testCase.verifyNumElements(Z, n);
    -0223         end
    -0224 
    -0225         function analyzeSamplingRuns(testCase)
    -0226             n = numel(testCase.model.rxns);
    -0227             Tex = randn(n, 1);           % per-reaction expression t-scores
    -0228             solutionsA = rand(n, 20);
    -0229             solutionsB = rand(n, 20);
    -0230             evalc('scores = analyzeSampling(Tex, 18, solutionsA, solutionsB, false);');
    -0231             testCase.verifySize(scores, [n 3]);
    -0232         end
    -0233 
    -0234         function randomSamplingRuns(testCase)
    -0235             % Bare call exercises the default method (ACHR).
    -0236             evalc('sols = randomSampling(testCase.model, 5);');
    -0237             testCase.verifyEqual(size(sols, 1), numel(testCase.model.rxns));
    -0238         end
    -0239 
    -0240         function randomSamplingDeterministicWithSeed(testCase)
    -0241             % goodRxns reuse is specific to the random-objective method.
    -0242             evalc('[~, gR] = randomSampling(testCase.model, 0, ''method'', ''randomObjective'', ''runParallel'', false);');
    -0243             evalc('s1 = randomSampling(testCase.model, 5, ''method'', ''randomObjective'', ''seed'', 42, ''runParallel'', false, ''goodRxns'', gR);');
    -0244             evalc('s2 = randomSampling(testCase.model, 5, ''method'', ''randomObjective'', ''seed'', 42, ''runParallel'', false, ''goodRxns'', gR);');
    -0245             testCase.verifyEqual(s1, s2);
    -0246         end
    -0247 
    -0248         function reporterMetabolitesRuns(testCase)
    -0249             pvals = rand(numel(testCase.model.genes), 1);
    -0250             rm = reporterMetabolites(testCase.model, testCase.model.genes, pvals);
    -0251             testCase.verifyClass(rm, 'struct');
    -0252         end
    -0253 
    -0254         function reporterMetabolitesKeepsPercentInMetNames(testCase)
    -0255             % A metNames entry containing "%" must survive intact in the
    -0256             % outputFile report, not be truncated by fprintf misreading it
    -0257             % as a format directive.
    -0258             m = testCase.model;
    -0259             m.metNames{1} = 'metabolite 30% pure';
    -0260             pvals = rand(numel(m.genes), 1);
    -0261             outFile = [tempname '.txt'];
    -0262             c = onCleanup(@() delete(outFile));
    -0263             evalc('reporterMetabolites(m, m.genes, pvals, ''outputFile'', outFile);');
    -0264             content = fileread(outFile);
    -0265             testCase.verifySubstring(content, 'metabolite 30% pure');
    -0266         end
    -0267 
    -0268         function reporterMetabolitesIsDeterministic(testCase)
    -0269             % Closed-form background correction (RM1) must produce identical
    -0270             % Z-scores on repeated calls with identical inputs.
    -0271             pvals = rand(numel(testCase.model.genes), 1);
    -0272             rm1 = reporterMetabolites(testCase.model, testCase.model.genes, pvals);
    -0273             rm2 = reporterMetabolites(testCase.model, testCase.model.genes, pvals);
    -0274             testCase.verifyEqual(rm1.metZScores, rm2.metZScores);
    -0275         end
    -0276 
    -0277         function FSEOFRuns(testCase)
    -0278             biomassRxn = testCase.model.rxns{find(testCase.model.c == 1, 1)};
    -0279             [~, exchIdx] = getExchangeRxns(testCase.model);
    -0280             targetRxn = testCase.model.rxns{exchIdx(1)};
    -0281             outFile = [tempname '.txt'];
    -0282             evalc('targets = FSEOF(testCase.model, biomassRxn, targetRxn, 5, 0.9, outFile);');
    -0283             testCase.verifyClass(targets, 'struct');
    -0284         end
    -0285 
    -0286         function runRobustnessAnalysisRuns(testCase)
    -0287             [~, exchIdx] = getExchangeRxns(testCase.model);
    -0288             controlRxn = testCase.model.rxns{exchIdx(1)};
    -0289             evalc('[cFlux, oFlux] = runRobustnessAnalysis(testCase.model, controlRxn, 5);');
    -0290             testCase.verifyNumElements(cFlux, 5);
    -0291         end
    -0292 
    -0293         function runProductionEnvelopeRuns(testCase)
    -0294             biomassRxn = testCase.model.rxns{find(testCase.model.c == 1, 1)};
    -0295             [~, exchIdx] = getExchangeRxns(testCase.model);
    -0296             targetRxn = testCase.model.rxns{exchIdx(1)};
    -0297             evalc('[bio, tgt] = runProductionEnvelope(testCase.model, targetRxn, biomassRxn, 5);');
    -0298             testCase.verifyNotEmpty(bio);
    -0299         end
    -0300 
    -0301         function runPhenotypePhasePlaneRuns(testCase)
    -0302             [~, exchIdx] = getExchangeRxns(testCase.model);
    -0303             r1 = testCase.model.rxns{exchIdx(1)};
    -0304             r2 = testCase.model.rxns{exchIdx(2)};
    -0305             evalc('g = runPhenotypePhasePlane(testCase.model, r1, r2, 3, 3);');
    -0306             testCase.verifyNotEmpty(g);
    -0307         end
    -0308 
    -0309         function runDynamicFBARuns(testCase)
    -0310             % Use glucose (consumed) as substrate and plot target; the function
    -0311             % always plots, so the plotted reaction must carry non-zero flux.
    -0312             [~, exchIdx] = getExchangeRxns(testCase.model);
    -0313             glcMet = find(strcmp(testCase.model.metNames, 'D-Glucose'), 1);
    -0314             glcExch = exchIdx(any(testCase.model.S(glcMet, exchIdx) ~= 0, 1));
    -0315             subRxn = testCase.model.rxns(glcExch);
    -0316             evalc(['c = runDynamicFBA(testCase.model, subRxn, 10, 0.1, 0.1, 2, ' ...
    -0317                 'subRxn, {});']);
    -0318             testCase.verifyNotEmpty(c);
    -0319         end
    -0320 
    -0321         function getMinimalMediumReturnsMedium(testCase)
    -0322             testCase.assumeMILPSolver();
    -0323             evalc('[med, idx] = getMinimalMedium(testCase.model, ''verbose'', false);');
    -0324             testCase.verifyClass(med, 'cell');
    -0325             testCase.verifyClass(idx, 'double');
    -0326             % Every returned reaction must have lb < 0 (it is an uptake exchange)
    -0327             [~, exchIdx] = getExchangeRxns(testCase.model);
    -0328             testCase.verifyTrue(all(ismember(idx, exchIdx)));
    -0329             testCase.verifyTrue(all(testCase.model.lb(idx) < 0));
    -0330         end
    -0331 
    -0332         function getMinimalMediumExplicitGrowth(testCase)
    -0333             testCase.assumeMILPSolver();
    -0334             sol = solveLP(testCase.model);
    -0335             minG = 0.05 * sol.f;
    -0336             evalc('[med, idx] = getMinimalMedium(testCase.model, ''minGrowth'', minG, ''verbose'', false);');
    -0337             testCase.verifyClass(med, 'cell');
    -0338             testCase.verifyNotEmpty(med);
    -0339         end
    -0340 
    -0341         function runSimpleOptKnockRuns(testCase)
    -0342             testCase.assumeMILPSolver();
    -0343             biomassRxn = testCase.model.rxns{find(testCase.model.c == 1, 1)};
    -0344             [~, exchIdx] = getExchangeRxns(testCase.model);
    -0345             targetRxn = testCase.model.rxns{exchIdx(1)};
    -0346             evalc(['out = runSimpleOptKnock(testCase.model, targetRxn, biomassRxn, ' ...
    -0347                 'testCase.model.rxns(1:5), ''rxns'', 1);']);
    -0348             testCase.verifyClass(out, 'struct');
    -0349         end
    -0350 
    -0351     end
    -0352 end
    -
    Generated by m2html © 2005
    - - \ No newline at end of file diff --git a/docs/html/testing/function_tests/tAnnotation.html b/docs/html/testing/function_tests/tAnnotation.html deleted file mode 100644 index befc22c5..00000000 --- a/docs/html/testing/function_tests/tAnnotation.html +++ /dev/null @@ -1,110 +0,0 @@ - - - - Description of tAnnotation - - - - - - - - - -
    Home > testing > function_tests > tAnnotation.m
    - - - -

    tAnnotation -

    - -

    PURPOSE ^

    -
    - -

    SYNOPSIS ^

    -
    This is a script file.
    - -

    DESCRIPTION ^

    -
    - - -

    CROSS-REFERENCE INFORMATION ^

    -This function calls: - -This function is called by: - - - -

    SUBFUNCTIONS ^

    - - -

    SOURCE CODE ^

    -
    0001 classdef tAnnotation < RavenTestCase
    -0002 % tAnnotation  Tests for the annotation/metadata functions in annotation/.
    -0003 
    -0004     methods (Test)
    -0005 
    -0006         function editMiriamAddsAnnotation(testCase)
    -0007             m2 = editMiriam(testCase.model, 'met', 1, 'bigg.metabolite', 'testval', 'add');
    -0008             testCase.verifyClass(m2, 'struct');
    -0009             testCase.verifyEqual(numel(m2.mets), numel(testCase.model.mets));
    -0010         end
    -0011 
    -0012         function editMiriamRemovesAnnotation(testCase)
    -0013             m2 = editMiriam(testCase.model, 'met', 1, 'bigg.metabolite', 'testval', 'add');
    -0014             m3 = editMiriam(m2, 'met', 1, 'bigg.metabolite', '', 'remove');
    -0015             testCase.verifyClass(m3, 'struct');
    -0016             [~, names] = extractMiriam(m3.metMiriams(1));
    -0017             testCase.verifyFalse(any(strcmp('bigg.metabolite', names)));
    -0018         end
    -0019 
    -0020         function extractMiriamReturnsNames(testCase)
    -0021             [miriams, names] = extractMiriam(testCase.model.metMiriams); %#ok<ASGLU>
    -0022             testCase.verifyNotEmpty(names);
    -0023         end
    -0024 
    -0025         function assignSBOtermsRuns(testCase)
    -0026             evalc('m2 = assignSBOterms(testCase.model);');
    -0027             testCase.verifyClass(m2, 'struct');
    -0028         end
    -0029 
    -0030         function deltaGCsvRoundTrip(testCase)
    -0031             m = testCase.model;
    -0032             m.metDeltaG = (1:numel(m.mets))';
    -0033             m.rxnDeltaG = (1:numel(m.rxns))';
    -0034             metCsv = [tempname '.csv'];
    -0035             rxnCsv = [tempname '.csv'];
    -0036             testCase.addTeardown(@() delete(metCsv));
    -0037             testCase.addTeardown(@() delete(rxnCsv));
    -0038             evalc('saveDeltaGCSV(m, metCsv, rxnCsv);');
    -0039             base = testCase.model;
    -0040             evalc('m2 = loadDeltaGCSV(base, metCsv, rxnCsv);');
    -0041             testCase.verifyEqual(m2.metDeltaG, m.metDeltaG, 'AbsTol', 1e-6);
    -0042             testCase.verifyEqual(m2.rxnDeltaG, m.rxnDeltaG, 'AbsTol', 1e-6);
    -0043         end
    -0044 
    -0045         function loadDeltaGCsvStoresValuesLiterally(testCase)
    -0046             % No filtering of any kind: every matched value, including
    -0047             % yeast-GEM's own "no measurement" placeholder (10000000.0), is
    -0048             % stored exactly as the CSV states it.
    -0049             m = testCase.model;
    -0050             metCsv = [tempname '.csv'];
    -0051             testCase.addTeardown(@() delete(metCsv));
    -0052             fid = fopen(metCsv, 'w');
    -0053             fprintf(fid, 'Var1,Var2\n%s,-42.5\n%s,10000000.0\n', m.mets{1}, m.mets{2});
    -0054             fclose(fid);
    -0055             evalc('md = loadDeltaGCSV(m, ''metCsv'', metCsv);');
    -0056             testCase.verifyEqual(md.metDeltaG(1), -42.5, 'AbsTol', 1e-9);
    -0057             testCase.verifyEqual(md.metDeltaG(2), 1e7, 'AbsTol', 1e-3);
    -0058         end
    -0059 
    -0060     end
    -0061 end
    -
    Generated by m2html © 2005
    - - \ No newline at end of file diff --git a/docs/html/testing/function_tests/tAssignCompartments.html b/docs/html/testing/function_tests/tAssignCompartments.html deleted file mode 100644 index ac954454..00000000 --- a/docs/html/testing/function_tests/tAssignCompartments.html +++ /dev/null @@ -1,113 +0,0 @@ - - - - Description of tAssignCompartments - - - - - - - - - -
    Home > testing > function_tests > tAssignCompartments.m
    - - - -

    tAssignCompartments -

    - -

    PURPOSE ^

    -
    - -

    SYNOPSIS ^

    -
    This is a script file.
    - -

    DESCRIPTION ^

    -
    - - -

    CROSS-REFERENCE INFORMATION ^

    -This function calls: - -This function is called by: - - - -

    SUBFUNCTIONS ^

    - - -

    SOURCE CODE ^

    -
    0001 classdef tAssignCompartments < RavenTestCase
    -0002 % tAssignCompartments  Tests for the functionality-constrained compartment-assignment MILP.
    -0003 %
    -0004 %   Uses a tiny single-compartment toy: EX_A (uptake) -> r1: A->B -> bio: B-> (objective), with
    -0005 %   gene g1 (on r1) scored higher for a hypothetical compartment 'm' than for the cytosol 'c'.
    -0006 %   The decisive checks are that the functionality constraint can override the localization score
    -0007 %   and that the returned model stays able to grow. Requires a MILP solver.
    -0008 
    -0009     methods (Test)
    -0010 
    -0011         function functionalityOverridesScore(testCase)
    -0012             testCase.assumeMILPSolver();
    -0013             model = tAssignCompartments.toy();
    -0014             GSS = tAssignCompartments.gss();
    -0015             % B is non-transportable, so placing r1 in 'm' would strand B where biomass (in 'c')
    -0016             % cannot reach it -> r1 must stay in 'c', against g1's higher 'm' score.
    -0017             evalc(['[oModel, place, ~, eFlag] = assignCompartments(model, GSS, {''r1''}, ' ...
    -0018                    '''defaultCompartment'', ''c'', ''transportable'', {}, ''verbose'', false);']);
    -0019             testCase.verifyEqual(eFlag, 1);
    -0020             testCase.verifyEqual(place.compartment{1}, 'c');
    -0021             sol = solveLP(oModel);
    -0022             testCase.verifyGreaterThan(sol.f, 1e-6);          % applied model still grows
    -0023         end
    -0024 
    -0025         function scoreRespectedWhenFunctional(testCase)
    -0026             testCase.assumeMILPSolver();
    -0027             model = tAssignCompartments.toy();
    -0028             GSS = tAssignCompartments.gss();
    -0029             % Transports allowed and cheap -> r1 can follow its score to 'm' and stay functional.
    -0030             evalc(['[oModel, place, trans, eFlag] = assignCompartments(model, GSS, {''r1''}, ' ...
    -0031                    '''defaultCompartment'', ''c'', ''transportCost'', 0.1, ''verbose'', false);']);
    -0032             testCase.verifyEqual(eFlag, 1);
    -0033             testCase.verifyEqual(place.compartment{1}, 'm');
    -0034             testCase.verifyGreaterThan(numel(trans.mets), 0); % transports were added
    -0035             sol = solveLP(oModel);
    -0036             testCase.verifyGreaterThan(sol.f, 1e-6);
    -0037         end
    -0038 
    -0039         function infeasibleGrowthFloorReported(testCase)
    -0040             testCase.assumeMILPSolver();
    -0041             model = tAssignCompartments.toy();
    -0042             GSS = tAssignCompartments.gss();
    -0043             evalc(['[~, ~, ~, eFlag] = assignCompartments(model, GSS, {''r1''}, ' ...
    -0044                    '''defaultCompartment'', ''c'', ''minGrowth'', 1e6, ''verbose'', false);']);
    -0045             testCase.verifyEqual(eFlag, -1);                  % growth floor unreachable
    -0046         end
    -0047 
    -0048     end
    -0049 
    -0050     methods (Static)
    -0051         function model = toy()
    -0052             model.id='toy'; model.comps={'c'}; model.compNames={'cytoplasm'};
    -0053             model.mets={'A_c';'B_c'}; model.metNames={'A';'B'}; model.metComps=[1;1];
    -0054             model.S=sparse([-1 -1 0; 0 1 -1]);     % [A;B] x [EX_A r1 bio]
    -0055             model.rxns={'EX_A';'r1';'bio'}; model.rxnNames=model.rxns;
    -0056             model.lb=[-10;0;0]; model.ub=[1000;1000;1000]; model.rev=[1;0;0];
    -0057             model.c=[0;0;1]; model.b=zeros(2,1);
    -0058             model.genes={'g1'}; model.grRules={'';'g1';''}; model.rxnGeneMat=sparse([0;1;0]);
    -0059         end
    -0060         function GSS = gss()
    -0061             GSS.genes={'g1'}; GSS.compartments={'c';'m'}; GSS.scores=[0.4 0.9];
    -0062         end
    -0063     end
    -0064 end
    -
    Generated by m2html © 2005
    - - \ No newline at end of file diff --git a/docs/html/testing/function_tests/tBinaries.html b/docs/html/testing/function_tests/tBinaries.html deleted file mode 100644 index b119a8eb..00000000 --- a/docs/html/testing/function_tests/tBinaries.html +++ /dev/null @@ -1,94 +0,0 @@ - - - - Description of tBinaries - - - - - - - - - -
    Home > testing > function_tests > tBinaries.m
    - - - -

    tBinaries -

    - -

    PURPOSE ^

    -
    - -

    SYNOPSIS ^

    -
    This is a script file.
    - -

    DESCRIPTION ^

    -
    - - -

    CROSS-REFERENCE INFORMATION ^

    -This function calls: - -This function is called by: - - - -

    SUBFUNCTIONS ^

    - - -

    SOURCE CODE ^

    -
    0001 classdef tBinaries < matlab.unittest.TestCase
    -0002 % tBinaries  On-demand download of RAVEN's external binaries from raven-data.
    -0003 %
    -0004 %   Validates downloadRavenBinaries on the current platform: each tool is
    -0005 %   fetched into software/<tool>/, the executable is present, and (for the CLI
    -0006 %   tools) it actually runs. On macOS, running the downloaded binary also
    -0007 %   confirms the Gatekeeper quarantine attribute was cleared.
    -0008 %
    -0009 %   Exercised on Linux and macOS by the "binary-tests" CI job. Requires network
    -0010 %   access to https://github.com/SysBioChalmers/raven-data .
    -0011 
    -0012     methods (Test)
    -0013         function blast(testCase)
    -0014             testCase.fetchAndRun('blast+', 'blastp', '-version');
    -0015             testCase.verifyTrue(isfile(testCase.binPath('blast+','makeblastdb')), ...
    -0016                 'makeblastdb not found after download');
    -0017         end
    -0018 
    -0019         function diamond(testCase)
    -0020             testCase.fetchAndRun('diamond', 'diamond', 'version');
    -0021         end
    -0022 
    -0023         function hmmer(testCase)
    -0024             testCase.fetchAndRun('hmmer', 'hmmsearch', '-h');
    -0025         end
    -0026     end
    -0027 
    -0028     methods (Access = private)
    -0029         function p = binPath(~, tool, name)
    -0030             if ispc; ext = '.exe'; elseif ismac; ext = '.mac'; else; ext = ''; end
    -0031             p = fullfile(findRAVENroot(),'software',tool,[name ext]);
    -0032         end
    -0033 
    -0034         function fetchAndRun(testCase, tool, name, versionArg)
    -0035             % Download the tool, then confirm the executable exists and runs.
    -0036             downloadRavenBinaries({tool});
    -0037             exe = testCase.binPath(tool, name);
    -0038             testCase.verifyTrue(isfile(exe), ...
    -0039                 sprintf('%s not found after download', name));
    -0040             [status, ~] = system(['"' exe '" ' versionArg]);
    -0041             testCase.verifyEqual(status, 0, ...
    -0042                 sprintf('%s did not execute (exit status %d)', name, status));
    -0043         end
    -0044     end
    -0045 end
    -
    Generated by m2html © 2005
    - - \ No newline at end of file diff --git a/docs/html/testing/function_tests/tBiomass.html b/docs/html/testing/function_tests/tBiomass.html deleted file mode 100644 index d9cd0d9f..00000000 --- a/docs/html/testing/function_tests/tBiomass.html +++ /dev/null @@ -1,180 +0,0 @@ - - - - Description of tBiomass - - - - - - - - - -
    Home > testing > function_tests > tBiomass.m
    - - - -

    tBiomass -

    - -

    PURPOSE ^

    -
    - -

    SYNOPSIS ^

    -
    This is a script file.
    - -

    DESCRIPTION ^

    -
    - - -

    CROSS-REFERENCE INFORMATION ^

    -This function calls: - -This function is called by: - - - -

    SUBFUNCTIONS ^

    - - -

    SOURCE CODE ^

    -
    0001 classdef tBiomass < RavenTestCase
    -0002 % tBiomass  Tests for the biomass-composition functions in biomass/.
    -0003 %
    -0004 %   The textbook E. coli model has a single lumped biomass reaction rather
    -0005 %   than per-component pseudoreactions, so component-scaling functions are
    -0006 %   tested on their missing-pseudoreaction error path.
    -0007 
    -0008     methods (Test)
    -0009 
    -0010         function getBiomassFractionsHandlesMissingComponents(testCase)
    -0011             cfg = testCase.biomassConfig();
    -0012             fractions = getBiomassFractions(testCase.model, cfg);
    -0013             testCase.verifyTrue(isfield(fractions, 'total'));
    -0014         end
    -0015 
    -0016         function setGAMReturnsModel(testCase)
    -0017             biomassRxn = testCase.model.rxns{find(testCase.model.c == 1, 1)};
    -0018             evalc(['m2 = setGAM(testCase.model, 50, biomassRxn, ' ...
    -0019                 '{''ATP'',''ADP'',''H2O'',''H+'',''Phosphate''});']);
    -0020             testCase.verifyClass(m2, 'struct');
    -0021         end
    -0022 
    -0023         function scaleBiomassFractionErrorsOnMissingComponent(testCase)
    -0024             cfg = testCase.biomassConfig();
    -0025             testCase.verifyError(@() scaleBiomassFraction(testCase.model, cfg, ...
    -0026                 'protein', 0.5), ?MException);
    -0027         end
    -0028 
    -0029         function scaleBiomassPseudoreactionErrorsOnMissingComponent(testCase)
    -0030             cfg = testCase.biomassConfig();
    -0031             testCase.verifyError(@() scaleBiomassPseudoreaction(testCase.model, cfg, ...
    -0032                 'protein', 0.9), ?MException);
    -0033         end
    -0034 
    -0035         function scaleBiomassPseudoreactionRebalancesProton(testCase)
    -0036             % Rescaling the substrates rebalances H+ so the pseudoreaction
    -0037             % stays charge neutral.
    -0038             [m, cfg] = tBiomass.protonToyModel();
    -0039             out = scaleBiomassPseudoreaction(m, cfg, 'protein', 0.5);
    -0040             % x_c: -0.5 * charge -2 = +1, so H+ must come in at -1
    -0041             testCase.verifyEqual(full(out.S(strcmp(out.mets,'x_c'), 1)), -0.5, 'AbsTol', 1e-9);
    -0042             testCase.verifyEqual(full(out.S(strcmp(out.mets,'h_c'), 1)), -1, 'AbsTol', 1e-9);
    -0043         end
    -0044 
    -0045         function scaleBiomassPseudoreactionRefusesUnknownCharge(testCase)
    -0046             % An unset charge on a participating metabolite leaves the charge
    -0047             % balance unknown. Treating it as neutral would silently write the
    -0048             % wrong proton coefficient, so the rescale must refuse instead.
    -0049             [m, cfg] = tBiomass.protonToyModel();
    -0050             m.metCharges(strcmp(m.mets,'x_c')) = NaN;
    -0051             testCase.verifyError(@() scaleBiomassPseudoreaction(m, cfg, 'protein', 0.5), ...
    -0052                 'scaleBiomassPseudoreaction:unknownCharge');
    -0053         end
    -0054 
    -0055         function fitParametersSetsUpperBoundPerXRxn(testCase)
    -0056             % model.ub for a second xRxn must come from its own column of
    -0057             % xValues, not the first column broadcast to every xRxn (which
    -0058             % also inverts that reaction's bounds whenever its value is
    -0059             % smaller than the first column's).
    -0060             m = struct();
    -0061             m.id='t'; m.rxns={'R1';'R2';'R3';'R4'}; m.rxnNames=m.rxns;
    -0062             m.mets={'m1';'m2'}; m.metNames=m.mets; m.metComps=[1;1];
    -0063             m.comps={'c'}; m.compNames={'c'};
    -0064             m.S=sparse([1 0 -1 0; 0 1 0 -1]); % R1:=>m1  R2:=>m2  R3:m1=>  R4:m2=>
    -0065             m.lb=[0;0;0;0]; m.ub=[1000;1000;1000;1000]; m.rev=[0;0;0;0];
    -0066             m.c=[0;0;0;0]; m.b=zeros(2,1);
    -0067             m.genes={}; m.grRules={'';'';'';''}; m.rxnGeneMat=sparse(4,0);
    -0068             pos.position={[]}; pos.isNegative={[]};
    -0069             evalc(['[~,fitnessScore] = fitParameters(m, {''R1'';''R2''}, [5 7], ' ...
    -0070                 '{''R3'';''R4''}, [5 7], pos, ''fitToRatio'', false);']);
    -0071             % R3/R4 are only reachable via mass balance from R1/R2's fixed
    -0072             % (lb=ub) values, so a correct fit has exactly zero residual.
    -0073             testCase.verifyEqual(fitnessScore, 0, 'AbsTol', 1e-9);
    -0074         end
    -0075 
    -0076         function fitParametersRuns(testCase)
    -0077             % Minimal single-parameter fit of an exchange flux. fitParameters
    -0078             % uses fminsearch (base MATLAB), not quadprog, so no toolbox guard.
    -0079             biomassRxnIdx = find(testCase.model.c == 1, 1);
    -0080             biomassRxn = testCase.model.rxns{biomassRxnIdx};
    -0081             % parameterPositions.position/isNegative are cell arrays, one cell
    -0082             % per fitted parameter, of linear S-matrix indices (see
    -0083             % fitParameters' docstring) -- fit the coefficient of the first
    -0084             % reactant in the biomass reaction.
    -0085             metIdx = find(testCase.model.S(:,biomassRxnIdx) < 0, 1);
    -0086             pos.position = {sub2ind(size(testCase.model.S), metIdx, biomassRxnIdx)};
    -0087             pos.isNegative = {true};
    -0088             evalc(['p = fitParameters(testCase.model, {biomassRxn}, 0.5, ' ...
    -0089                 '{biomassRxn}, 0.5, pos);']);
    -0090             testCase.verifyNotEmpty(p);
    -0091         end
    -0092 
    -0093     end
    -0094 
    -0095     methods (Static, Access = private)
    -0096         function [m, cfg] = protonToyModel()
    -0097             % One pseudoreaction: x_c -> protein, with h_c available to carry
    -0098             % the charge balance.
    -0099             m = struct();
    -0100             m.id        = 'toy';
    -0101             m.rxns      = {'R1'};
    -0102             m.rxnNames  = {'protein pseudoreaction'};
    -0103             m.mets      = {'x_c';'h_c';'prot_c'};
    -0104             m.metNames  = {'x';'h';'protein'};
    -0105             m.metComps  = [1;1;1];
    -0106             m.metCharges= [-2;1;0];
    -0107             m.comps     = {'c'};
    -0108             m.compNames = {'cytosol'};
    -0109             m.S         = sparse([-1;0;1]);
    -0110             m.lb = 0; m.ub = 1000; m.rev = 0; m.c = 1; m.b = zeros(3,1);
    -0111             m.grRules = {''}; m.genes = {}; m.rxnGeneMat = sparse(1,0);
    -0112 
    -0113             cfg = struct();
    -0114             cfg.biomass_rxn = 'R1';
    -0115             cfg.proton_met  = 'h_c';
    -0116             cfg.components  = {struct('name','protein', ...
    -0117                 'pseudoreaction_name','protein pseudoreaction', ...
    -0118                 'mass_strategy','mw')};
    -0119         end
    -0120     end
    -0121 
    -0122     methods (Access = private)
    -0123         function cfg = biomassConfig(testCase)
    -0124             biomassRxn = testCase.model.rxns{find(testCase.model.c == 1, 1)};
    -0125             cfg.biomass_rxn = biomassRxn;
    -0126             cfg.proton_met = 'h_c';
    -0127             cfg.components = {struct('name','protein', ...
    -0128                 'pseudoreaction_name','no_such_pseudoreaction','mass_strategy','mw')};
    -0129         end
    -0130     end
    -0131 end
    -
    Generated by m2html © 2005
    - - \ No newline at end of file diff --git a/docs/html/testing/function_tests/tComparison.html b/docs/html/testing/function_tests/tComparison.html deleted file mode 100644 index c4777044..00000000 --- a/docs/html/testing/function_tests/tComparison.html +++ /dev/null @@ -1,156 +0,0 @@ - - - - Description of tComparison - - - - - - - - - -
    Home > testing > function_tests > tComparison.m
    - - - -

    tComparison -

    - -

    PURPOSE ^

    -
    - -

    SYNOPSIS ^

    -
    This is a script file.
    - -

    DESCRIPTION ^

    -
    - - -

    CROSS-REFERENCE INFORMATION ^

    -This function calls: - -This function is called by: - - - -

    SUBFUNCTIONS ^

    - - -

    SOURCE CODE ^

    -
    0001 classdef tComparison < RavenTestCase
    -0002 % tComparison  Tests for the multi-model comparison functions in comparison/.
    -0003 
    -0004     methods (Test)
    -0005 
    -0006         function compareMultipleModelsRuns(testCase)
    -0007             modelA = testCase.model; modelA.id = 'modelA';
    -0008             modelB = removeReactions(testCase.model, testCase.model.rxns(1:5), ...
    -0009                 true, true, true); modelB.id = 'modelB';
    -0010             evalc('cs = compareMultipleModels({modelA, modelB});');
    -0011             testCase.verifyClass(cs, 'struct');
    -0012         end
    -0013 
    -0014         function compareMultipleModelsReportsIdentityOverlap(testCase)
    -0015             % Covers the overlap comparison merged in from the now-removed
    -0016             % compareRxnsGenesMetsComps: rxns/mets/genes/eccodes/metNames/equ/uEqu.
    -0017             modelA = testCase.model; modelA.id = 'modelA';
    -0018             removedRxn = testCase.model.rxns{1};
    -0019             modelB = removeReactions(testCase.model, testCase.model.rxns(1), ...
    -0020                 true, true, true); modelB.id = 'modelB';
    -0021             evalc('cs = compareMultipleModels({modelA, modelB});');
    -0022             for field = {'rxns','mets','genes','eccodes','metNames','equ','uEqu'}
    -0023                 testCase.verifyTrue(isfield(cs, field{1}));
    -0024                 testCase.verifyTrue(isfield(cs.(field{1}), 'comparison'));
    -0025                 testCase.verifyTrue(isfield(cs.(field{1}), 'nElements'));
    -0026             end
    -0027             % modelA has one more reaction than modelB, so the rxns overlap must
    -0028             % show a combination where only modelA is included.
    -0029             onlyA = cs.rxns.comparison(:,1) & ~cs.rxns.comparison(:,2);
    -0030             testCase.verifyTrue(any(cs.rxns.nElements(onlyA) > 0));
    -0031             testCase.verifyTrue(any(strcmp(modelA.rxns, removedRxn)));
    -0032         end
    -0033 
    -0034         function diffModelsEqualToItself(testCase)
    -0035             r = diffModels(testCase.model, testCase.model);
    -0036             testCase.verifyTrue(r.equal);
    -0037             testCase.verifyEmpty(r.differences);
    -0038         end
    -0039 
    -0040         function diffModelsDetectsDroppedReaction(testCase)
    -0041             modelB = removeReactions(testCase.model, testCase.model.rxns(1), ...
    -0042                 true, true, true);
    -0043             r = diffModels(testCase.model, modelB);
    -0044             testCase.verifyFalse(r.equal);
    -0045             testCase.verifyTrue(ismember(testCase.model.rxns{1}, r.rxnsOnlyInA));
    -0046             testCase.verifyTrue(any(contains(r.differences, 'reactions only in A')));
    -0047         end
    -0048 
    -0049         function diffModelsDetectsBoundDifference(testCase)
    -0050             modelB = testCase.model;
    -0051             modelB.lb(1) = modelB.lb(1) - 42;
    -0052             r = diffModels(testCase.model, modelB);
    -0053             testCase.verifyFalse(r.equal);
    -0054             testCase.verifyTrue(any(contains(r.differences, 'bounds')));
    -0055         end
    -0056 
    -0057         function diffModelsStoichWithinToleranceIsEqual(testCase)
    -0058             modelB = testCase.model;
    -0059             nz = find(modelB.S(:,1), 1);
    -0060             modelB.S(nz,1) = modelB.S(nz,1) + 1e-12;
    -0061             r = diffModels(testCase.model, modelB, 'stoichTol', 1e-9);
    -0062             testCase.verifyTrue(r.equal);
    -0063         end
    -0064 
    -0065         function diffModelsStoichOutsideToleranceIsNotEqual(testCase)
    -0066             modelB = testCase.model;
    -0067             nz = find(modelB.S(:,1), 1);
    -0068             modelB.S(nz,1) = modelB.S(nz,1) + 5;
    -0069             r = diffModels(testCase.model, modelB);
    -0070             testCase.verifyFalse(r.equal);
    -0071             testCase.verifyTrue(any(contains(r.differences, 'coef[')));
    -0072         end
    -0073 
    -0074         function diffModelsGrRuleIsOrderInsensitive(testCase)
    -0075             % The improvement over a string compare: "a and b" == "b and a".
    -0076             withGpr = find(cellfun(@(r) contains(r,' and '), testCase.model.grRules), 1);
    -0077             testCase.assumeNotEmpty(withGpr, 'model has an AND grRule');
    -0078             modelB = testCase.model;
    -0079             genes = strtrim(strsplit(modelB.grRules{withGpr}, ' and '));
    -0080             modelB.grRules{withGpr} = strjoin(fliplr(genes), ' and ');
    -0081             r = diffModels(testCase.model, modelB);
    -0082             % this reaction must not be reported as a grRule difference
    -0083             testCase.verifyFalse(any(contains(r.differences, ...
    -0084                 [testCase.model.rxns{withGpr} ': grRule'])));
    -0085         end
    -0086 
    -0087         function diffModelsGrRuleLogicalChangeIsDetected(testCase)
    -0088             withGpr = find(cellfun(@(r) contains(r,' and '), testCase.model.grRules), 1);
    -0089             testCase.assumeNotEmpty(withGpr, 'model has an AND grRule');
    -0090             modelB = testCase.model;
    -0091             modelB.grRules{withGpr} = strrep(modelB.grRules{withGpr}, ' and ', ' or ');
    -0092             r = diffModels(testCase.model, modelB);
    -0093             testCase.verifyFalse(r.equal);
    -0094             testCase.verifyTrue(any(contains(r.differences, ...
    -0095                 [testCase.model.rxns{withGpr} ': grRule'])));
    -0096         end
    -0097 
    -0098         function diffModelsTruncatesPerCategory(testCase)
    -0099             modelB = testCase.model;
    -0100             modelB.lb = modelB.lb - 1; % a bound diff on every reaction
    -0101             r = diffModels(testCase.model, modelB, 'maxPerCategory', 5);
    -0102             testCase.verifyFalse(r.equal);
    -0103             testCase.verifyTrue(any(contains(r.differences, 'truncated at 5')));
    -0104         end
    -0105 
    -0106     end
    -0107 end
    -
    Generated by m2html © 2005
    - - \ No newline at end of file diff --git a/docs/html/testing/function_tests/tConditions.html b/docs/html/testing/function_tests/tConditions.html deleted file mode 100644 index 94c738d2..00000000 --- a/docs/html/testing/function_tests/tConditions.html +++ /dev/null @@ -1,141 +0,0 @@ - - - - Description of tConditions - - - - - - - - - -
    Home > testing > function_tests > tConditions.m
    - - - -

    tConditions -

    - -

    PURPOSE ^

    -
    - -

    SYNOPSIS ^

    -
    This is a script file.
    - -

    DESCRIPTION ^

    -
    - - -

    CROSS-REFERENCE INFORMATION ^

    -This function calls: - -This function is called by: - - - -

    SUBFUNCTIONS ^

    - - -

    SOURCE CODE ^

    -
    0001 classdef tConditions < RavenTestCase
    -0002 % tConditions  Tests for the condition-application functions in conditions/.
    -0003 
    -0004     methods (Test)
    -0005 
    -0006         function applyConditionSetsBounds(testCase)
    -0007             rxn = testCase.model.rxns{1};
    -0008             condition.bounds = {struct('rxn', rxn, 'lb', 0, 'ub', 0)};
    -0009             evalc('m2 = applyCondition(testCase.model, condition);');
    -0010             idx = strcmp(m2.rxns, rxn);
    -0011             testCase.verifyEqual(m2.ub(idx), 0);
    -0012             testCase.verifyEqual(m2.lb(idx), 0);
    -0013         end
    -0014 
    -0015         function applyConditionResetExchangesZeroesUptake(testCase)
    -0016             % The prelude reopens exchanges to (0, 1000), i.e. no uptake.
    -0017             m = testCase.model;
    -0018             [~, exchangeRxns] = getExchangeRxns(m);
    -0019             m.lb(exchangeRxns) = -37;
    -0020             m.ub(exchangeRxns) = 42;
    -0021             condition.prelude.reset_exchanges = 'both';
    -0022             evalc('m2 = applyCondition(m, condition);');
    -0023             testCase.verifyEqual(unique(m2.lb(exchangeRxns)), 0);
    -0024             testCase.verifyEqual(unique(m2.ub(exchangeRxns)), 1000);
    -0025         end
    -0026 
    -0027         function applyConditionRemoveMetZeroesCoefficient(testCase)
    -0028             % remove_mets drops a metabolite from the cofactor pseudoreaction.
    -0029             m = tConditions.cofactorModel();
    -0030             condition.cofactor_pseudoreaction = struct('rxn_id', 'R_cofactor', ...
    -0031                 'remove_mets', {{struct('met', 'nadh_c')}});
    -0032             evalc('m2 = applyCondition(m, condition);');
    -0033             testCase.verifyEqual(full(m2.S(strcmp(m2.mets,'nadh_c'), 1)), 0);
    -0034         end
    -0035 
    -0036         function applyConditionRecomputesChargeBalance(testCase)
    -0037             % charge_balance_met takes whatever coefficient makes the
    -0038             % pseudoreaction charge neutral, recomputed after the removals.
    -0039             m = tConditions.cofactorModel();
    -0040             condition.cofactor_pseudoreaction = struct('rxn_id', 'R_cofactor', ...
    -0041                 'remove_mets', {{struct('met', 'nadh_c')}}, ...
    -0042                 'charge_balance_met', 'h_c');
    -0043             evalc('m2 = applyCondition(m, condition);');
    -0044             % Left after removing NADH: atp_c (-1, charge -4) and amp_c
    -0045             % (+1, charge -2), a residual of +2, so H+ must enter at -2.
    -0046             testCase.verifyEqual(full(m2.S(strcmp(m2.mets,'h_c'), 1)), -2);
    -0047             % which is to say the reaction ends up charge neutral
    -0048             idx = find(m2.S(:,1));
    -0049             testCase.verifyEqual(sum(full(m2.S(idx,1)) .* m2.metCharges(idx)), 0);
    -0050         end
    -0051 
    -0052         function applyConditionBiomassDeltaAddsToCoefficient(testCase)
    -0053             % A stoichiometry delta adds to the existing coefficient rather
    -0054             % than replacing it.
    -0055             m = tConditions.cofactorModel();
    -0056             m.rxns{2} = 'R_biomass'; m.rxnNames{2} = 'R_biomass';
    -0057             before = full(m.S(strcmp(m.mets,'atp_c'), 2));
    -0058             condition.biomass_stoichiometry_delta = struct('rxn_id', 'R_biomass', ...
    -0059                 'add', {{struct('met', 'atp_c', 'coef', -0.5)}});
    -0060             evalc('m2 = applyCondition(m, condition);');
    -0061             testCase.verifyEqual(full(m2.S(strcmp(m2.mets,'atp_c'), 2)), before - 0.5);
    -0062         end
    -0063 
    -0064     end
    -0065 
    -0066     methods (Static, Access = private)
    -0067 
    -0068         function m = cofactorModel()
    -0069             % R_cofactor consumes ATP and NADH; R_other consumes ATP.
    -0070             m = struct();
    -0071             m.id        = 'cofac';
    -0072             m.rxns      = {'R_cofactor';'R_other'};
    -0073             m.rxnNames  = {'R_cofactor';'R_other'};
    -0074             m.mets      = {'atp_c';'nadh_c';'amp_c';'h_c'};
    -0075             m.metNames  = {'ATP';'NADH';'AMP';'H+'};
    -0076             m.metComps  = [1;1;1;1];
    -0077             m.metCharges= [-4;-2;-2;1];
    -0078             m.comps     = {'c'};
    -0079             m.compNames = {'cytosol'};
    -0080             %              R_cofactor R_other
    -0081             m.S = sparse([  -1        -1;    % atp_c
    -0082                             -1         0;    % nadh_c
    -0083                              1         0;    % amp_c
    -0084                              0         0]);  % h_c
    -0085             m.lb = [0;0]; m.ub = [1000;1000]; m.rev = [0;0]; m.c = [0;0];
    -0086             m.b = zeros(4,1);
    -0087             m.genes = {}; m.grRules = {'';''}; m.rxnGeneMat = sparse(2,0);
    -0088             m.metFormulas = {'C10';'C21';'C10';'H'};
    -0089         end
    -0090 
    -0091     end
    -0092 end
    -
    Generated by m2html © 2005
    - - \ No newline at end of file diff --git a/docs/html/testing/function_tests/tConversion.html b/docs/html/testing/function_tests/tConversion.html deleted file mode 100644 index 154e7667..00000000 --- a/docs/html/testing/function_tests/tConversion.html +++ /dev/null @@ -1,137 +0,0 @@ - - - - Description of tConversion - - - - - - - - - -
    Home > testing > function_tests > tConversion.m
    - - - -

    tConversion -

    - -

    PURPOSE ^

    -
    - -

    SYNOPSIS ^

    -
    This is a script file.
    - -

    DESCRIPTION ^

    -
    - - -

    CROSS-REFERENCE INFORMATION ^

    -This function calls: - -This function is called by: - - - -

    SUBFUNCTIONS ^

    - - -

    SOURCE CODE ^

    -
    0001 classdef tConversion < RavenTestCase
    -0002 % tConversion  Tests for the model format-conversion functions in conversion/.
    -0003 
    -0004     methods (Test)
    -0005 
    -0006         function identifierPrefixRoundTrip(testCase)
    -0007             % Adding then removing SBML identifier prefixes is the identity.
    -0008             [mp, ~] = addIdentifierPrefix(testCase.model);
    -0009             [mr, ~] = removeIdentifierPrefix(mp);
    -0010             testCase.verifyEqual(mr.rxns, testCase.model.rxns);
    -0011             testCase.verifyEqual(mr.mets, testCase.model.mets);
    -0012             testCase.verifyEqual(mr.genes, testCase.model.genes);
    -0013         end
    -0014 
    -0015         function identifierPrefixKeepsGenesWithPunctuationWhole(testCase)
    -0016             % A gene id with "-", "." or ":" gets one prefix in grRules, the
    -0017             % same one it gets in model.genes, so the two still match.
    -0018             m = testCase.model;
    -0019             m.genes = [m.genes; {'5-HT1A'; 'b2:1'}];
    -0020             m.grRules{1} = '5-HT1A and (b2:1 or 5-HT1A)';
    -0021             [mp, ~] = addIdentifierPrefix(m, 'fields', {'genes'});
    -0022             testCase.verifyEqual(mp.grRules{1}, 'G_5-HT1A and (G_b2:1 or G_5-HT1A)');
    -0023             testCase.verifyTrue(all(ismember(getGenesFromGrRules(mp.grRules(1)), mp.genes)));
    -0024         end
    -0025 
    -0026         function removeIdentifierPrefixSkipsMissingFields(testCase)
    -0027             % A model missing an optional field (genes, metNames, rxnNames,
    -0028             % id) must not make the default field list throw; it should
    -0029             % just be skipped.
    -0030             m = rmfield(testCase.model, {'genes','metNames','rxnNames','id'});
    -0031             m.rxns = strcat('R_', m.rxns);
    -0032             m2 = removeIdentifierPrefix(m);
    -0033             testCase.verifyFalse(any(startsWith(m2.rxns, 'R_')));
    -0034         end
    -0035 
    -0036         function ravenCobraWrapperMarksCobra(testCase)
    -0037             cobra = ravenCobraWrapper(testCase.model);
    -0038             testCase.verifyTrue(isfield(cobra, 'rules'));   % COBRA-only field
    -0039         end
    -0040 
    -0041         function ravenCobraWrapperRoundTrip(testCase)
    -0042             cobra = ravenCobraWrapper(testCase.model);
    -0043             back = ravenCobraWrapper(cobra);
    -0044             testCase.verifyEqual(back.rxns, testCase.model.rxns);
    -0045             testCase.verifyNumElements(back.mets, numel(testCase.model.mets));
    -0046             testCase.verifyEqual(size(back.S), size(testCase.model.S));
    -0047         end
    -0048 
    -0049         function ravenCobraWrapperGeneFieldsUseGeneMiriams(testCase)
    -0050             % Gene annotation fields must come from geneMiriams, not from
    -0051             % whatever extractMiriam last returned for metMiriams.
    -0052             m = testCase.model;
    -0053             m.metMiriams = cell(numel(m.mets),1);
    -0054             m.metMiriams{1}.name  = {'hmdb'};
    -0055             m.metMiriams{1}.value = {'HMDB00001'};
    -0056             m.geneMiriams = cell(numel(m.genes),1);
    -0057             m.geneMiriams{1}.name  = {'ncbigene'};
    -0058             m.geneMiriams{1}.value = {'12345'};
    -0059             evalc('newModel = ravenCobraWrapper(m);');
    -0060             testCase.verifyEqual(numel(newModel.geneEntrezID), numel(newModel.genes));
    -0061             testCase.verifyEqual(newModel.geneEntrezID{1}, '12345');
    -0062         end
    -0063 
    -0064         function ravenCobraWrapperEscapesRegexGeneNames(testCase)
    -0065             % A gene id containing a regex metacharacter ('.') must be
    -0066             % matched literally in grRules, not as a wildcard that could
    -0067             % also match an unrelated gene id (e.g. 'G.1' vs 'GX1').
    -0068             m = testCase.model;
    -0069             m.genes{1} = 'G.1';
    -0070             m.genes{2} = 'GX1';
    -0071             m.grRules{3} = 'GX1';
    -0072             evalc('cobra = ravenCobraWrapper(m);');
    -0073             testCase.verifyEqual(cobra.rules{3}, 'x(2)');
    -0074         end
    -0075 
    -0076         function standardizeFieldOrderPreservesFields(testCase)
    -0077             m2 = standardizeModelFieldOrder(testCase.model);
    -0078             testCase.verifyEqual(sort(fieldnames(m2)), sort(fieldnames(testCase.model)));
    -0079         end
    -0080 
    -0081         function standardizeFieldOrderIsIdempotent(testCase)
    -0082             m2 = standardizeModelFieldOrder(testCase.model);
    -0083             m3 = standardizeModelFieldOrder(m2);
    -0084             testCase.verifyEqual(fieldnames(m3), fieldnames(m2));
    -0085         end
    -0086 
    -0087     end
    -0088 end
    -
    Generated by m2html © 2005
    - - \ No newline at end of file diff --git a/docs/html/testing/function_tests/tCuration.html b/docs/html/testing/function_tests/tCuration.html deleted file mode 100644 index a1e64a22..00000000 --- a/docs/html/testing/function_tests/tCuration.html +++ /dev/null @@ -1,145 +0,0 @@ - - - - Description of tCuration - - - - - - - - - -
    Home > testing > function_tests > tCuration.m
    - - - -

    tCuration -

    - -

    PURPOSE ^

    -
    - -

    SYNOPSIS ^

    -
    This is a script file.
    - -

    DESCRIPTION ^

    -
    - - -

    CROSS-REFERENCE INFORMATION ^

    -This function calls: - -This function is called by: - - - -

    SUBFUNCTIONS ^

    - - -

    SOURCE CODE ^

    -
    0001 classdef tCuration < RavenTestCase
    -0002 % tCuration  Tests for the table-driven curation functions in curation/.
    -0003 %
    -0004 %   curateModelFromTables reads its mets/genes/rxns input from tab-delimited
    -0005 %   files; the tests below write those files to a temporary directory.
    -0006 
    -0007     methods (Test)
    -0008 
    -0009         function curateModelFromTablesKeepsSupersetReaction(testCase)
    -0010             % A model reaction that uses the candidate's metabolites *and*
    -0011             % others is a different reaction, not a duplicate: adding
    -0012             % A + B -> C to a model holding A + B -> C + H must leave the
    -0013             % existing reaction alone and add the candidate separately.
    -0014             model = tCuration.toyModel();
    -0015             [coeffsF, infoF] = tCuration.writeRxnTables(testCase, ...
    -0016                 {'A','B','C'}, [-1 -1 1]);
    -0017             evalc(['out = curateModelFromTables(model,''none'',''rxnsCoeffs'',' ...
    -0018                 'coeffsF,''rxnsInfo'',infoF);']);
    -0019 
    -0020             testCase.verifyNumElements(out.rxns, 2);
    -0021             % The existing reaction keeps its name and its fourth metabolite
    -0022             testCase.verifyEqual(out.rxnNames{1}, 'ATP hydrolysis with proton');
    -0023             testCase.verifyNumElements(find(out.S(:,1)), 4);
    -0024             % The candidate is added as a new reaction
    -0025             testCase.verifyEqual(out.rxnNames{2}, 'newRxn');
    -0026             testCase.verifyNumElements(find(out.S(:,2)), 3);
    -0027         end
    -0028 
    -0029         function curateModelFromTablesOverwritesExactDuplicate(testCase)
    -0030             % An exact stoichiometric match is still treated as the same
    -0031             % reaction and has its annotation overwritten.
    -0032             model = tCuration.toyModel();
    -0033             [coeffsF, infoF] = tCuration.writeRxnTables(testCase, ...
    -0034                 {'A','B','C','H'}, [-1 -1 1 1]);
    -0035             evalc(['out = curateModelFromTables(model,''none'',''rxnsCoeffs'',' ...
    -0036                 'coeffsF,''rxnsInfo'',infoF);']);
    -0037 
    -0038             testCase.verifyNumElements(out.rxns, 1);
    -0039             testCase.verifyEqual(out.rxnNames{1}, 'newRxn');
    -0040         end
    -0041 
    -0042     end
    -0043 
    -0044     methods (Static, Access = private)
    -0045 
    -0046         function model = toyModel()
    -0047             % Single reaction: A + B -> C + H, all in one compartment.
    -0048             model = struct();
    -0049             model.id        = 'toy';
    -0050             model.rxns      = {'R1'};
    -0051             model.rxnNames  = {'ATP hydrolysis with proton'};
    -0052             model.mets      = {'m1';'m2';'m3';'m4'};
    -0053             model.metNames  = {'A';'B';'C';'H'};
    -0054             model.comps     = {'c'};
    -0055             model.compNames = {'cytosol'};
    -0056             model.metComps  = [1;1;1;1];
    -0057             model.S         = sparse([-1;-1;1;1]);
    -0058             model.lb        = 0;
    -0059             model.ub        = 1000;
    -0060             model.rev       = 0;
    -0061             model.c         = 0;
    -0062             model.b         = zeros(4,1);
    -0063             model.genes     = {};
    -0064             model.grRules   = {''};
    -0065             model.rxnGeneMat= sparse(1,0);
    -0066             model.metFormulas = {'';'';'';''};
    -0067             model.subSystems  = {{''}};
    -0068             model.eccodes     = {''};
    -0069             model.rxnNotes    = {''};
    -0070             model.rxnReferences = {''};
    -0071             model.rxnConfidenceScores = 0;
    -0072         end
    -0073 
    -0074         function [coeffsF, infoF] = writeRxnTables(testCase, metNames, coeffs)
    -0075             % Write a one-reaction rxnsCoeffs/rxnsInfo pair for 'newRxn'.
    -0076             d = tempname; mkdir(d);
    -0077             testCase.addTeardown(@() rmdir(d,'s'));
    -0078             coeffsF = fullfile(d,'coeffs.tsv');
    -0079             infoF   = fullfile(d,'info.tsv');
    -0080 
    -0081             fid = fopen(coeffsF,'w');
    -0082             fprintf(fid,'rxnIdx\trxnNames\tmetNames\tcomps\tcoefficient\n');
    -0083             for i=1:numel(metNames)
    -0084                 fprintf(fid,'1\tnewRxn\t%s\tc\t%d\n', metNames{i}, coeffs(i));
    -0085             end
    -0086             fclose(fid);
    -0087 
    -0088             fid = fopen(infoF,'w');
    -0089             fprintf(fid,['rxnIdx\trxnNames\tgrRules\tlb\tub\trev\tsubSystems\t' ...
    -0090                 'eccodes\trxnNotes\trxnReferences\trxnConfidenceScores\n']);
    -0091             fprintf(fid,'1\tnewRxn\t\t0\t1000\t0\t\t\t\t\t\n');
    -0092             fclose(fid);
    -0093         end
    -0094 
    -0095     end
    -0096 end
    -
    Generated by m2html © 2005
    - - \ No newline at end of file diff --git a/docs/html/testing/function_tests/tGapfilling.html b/docs/html/testing/function_tests/tGapfilling.html deleted file mode 100644 index 3021a5fe..00000000 --- a/docs/html/testing/function_tests/tGapfilling.html +++ /dev/null @@ -1,488 +0,0 @@ - - - - Description of tGapfilling - - - - - - - - - -
    Home > testing > function_tests > tGapfilling.m
    - - - -

    tGapfilling -

    - -

    PURPOSE ^

    -
    - -

    SYNOPSIS ^

    -
    This is a script file.
    - -

    DESCRIPTION ^

    -
    - - -

    CROSS-REFERENCE INFORMATION ^

    -This function calls: - -This function is called by: - - - -

    SUBFUNCTIONS ^

    - - -

    SOURCE CODE ^

    -
    0001 classdef tGapfilling < RavenTestCase
    -0002 % tGapfilling  Tests for the gap-analysis and gap-filling functions in gapfilling/.
    -0003 
    -0004     methods (Test)
    -0005 
    -0006         function canExchangeConsumeReturnsLogical(testCase)
    -0007             out = canExchange(testCase.model, 'consume', testCase.model.mets(1:3));
    -0008             testCase.verifyClass(out, 'logical');
    -0009             testCase.verifyNumElements(out, 3);
    -0010         end
    -0011 
    -0012         function canExchangeProduceReturnsLogical(testCase)
    -0013             out = canExchange(testCase.model, 'produce', testCase.model.mets(1:3));
    -0014             testCase.verifyClass(out, 'logical');
    -0015             testCase.verifyNumElements(out, 3);
    -0016         end
    -0017 
    -0018         function canExchangeInvalidDirectionErrors(testCase)
    -0019             testCase.verifyError( ...
    -0020                 @() canExchange(testCase.model, 'neither'), ...
    -0021                 'RAVEN:badInput');
    -0022         end
    -0023 
    -0024         function checkProductionReturnsIndices(testCase)
    -0025             evalc('notProduced = checkProduction(testCase.model);');
    -0026             testCase.verifyClass(notProduced, 'double');
    -0027         end
    -0028 
    -0029         function findLeakMetaboliteProduceReturnsSolution(testCase)
    -0030             evalc('[sol, metabolite] = findLeakMetabolite(testCase.model, ''produce'');');
    -0031             testCase.verifyNotEmpty(sol);
    -0032         end
    -0033 
    -0034         function findLeakMetaboliteConsumeReturnsSolution(testCase)
    -0035             evalc('[sol, metabolite] = findLeakMetabolite(testCase.model, ''consume'');');
    -0036             testCase.verifyNotEmpty(sol);
    -0037         end
    -0038 
    -0039         function findLeakMetaboliteInvalidDirectionErrors(testCase)
    -0040             testCase.verifyError( ...
    -0041                 @() findLeakMetabolite(testCase.model, 'neither'), ...
    -0042                 'RAVEN:badInput');
    -0043         end
    -0044 
    -0045         function deprecatedMakeSomethingStillWorks(testCase)
    -0046             % The deprecated/ wrapper must keep returning what it always did.
    -0047             % Its warning is once-per-session, so it is not asserted here
    -0048             % (ordering across tests would decide whether it fires); the
    -0049             % warning itself is covered by tUtils/deprecationWarning*.
    -0050             evalc('[sol, metabolite] = makeSomething(testCase.model);');
    -0051             testCase.verifyNotEmpty(sol);
    -0052         end
    -0053 
    -0054         function deprecatedConsumeSomethingStillWorks(testCase)
    -0055             evalc('[sol, metabolite] = consumeSomething(testCase.model);');
    -0056             testCase.verifyNotEmpty(sol);
    -0057         end
    -0058 
    -0059         function deprecatedConsumeSomethingKeepsItsOwnArgumentOrder(testCase)
    -0060             % consumeSomething's positional order has no allowExcretion,
    -0061             % unlike findLeakMetabolite's. Passing its 5th argument
    -0062             % (ignoreIntBounds) must reach ignoreIntBounds, not params --
    -0063             % forwarding varargin verbatim used to shift it silently.
    -0064             evalc(['[sol, metabolite] = consumeSomething(testCase.model, ' ...
    -0065                 '[], false, false, [], true);']);
    -0066             testCase.verifyNotEmpty(sol);
    -0067         end
    -0068 
    -0069         function fillGapsProducesModel(testCase)
    -0070             testCase.assumeMILPSolver();
    -0071             modelDB = testCase.model; modelDB.id = 'DB';   % template
    -0072             gapModel = removeReactions(modelDB, (1:10));
    -0073             gapModel.id = 'gapModel';                      % must differ from template
    -0074             evalc('[~,~,~,newModel] = fillGaps(gapModel, modelDB);');
    -0075             testCase.verifyClass(newModel, 'struct');
    -0076         end
    -0077 
    -0078         function gapReportRuns(testCase)
    -0079             evalc('noFluxRxns = gapReport(testCase.model);');
    -0080             testCase.verifyClass(noFluxRxns, 'cell');
    -0081         end
    -0082 
    -0083         function gapReportKeepsPercentInModelName(testCase)
    -0084             % A model.name containing "%" must survive intact in the
    -0085             % report header, not be truncated by fprintf misreading it as
    -0086             % a format directive.
    -0087             m = testCase.model;
    -0088             m.name = 'ecoli 50% subset';
    -0089             out = evalc('gapReport(m);');
    -0090             testCase.verifySubstring(out, 'ecoli 50% subset');
    -0091         end
    -0092 
    -0093         function fitTasksShouldFailWarningHasRealNewlineAndPercent(testCase)
    -0094             % The SHOULD FAIL warning embeds the task id/description; a
    -0095             % literal "\n" must become a real newline (not print as the
    -0096             % two characters backslash-n), and a "%" in the task id must
    -0097             % survive intact rather than being read as a format directive.
    -0098             refModel = testCase.taskTestModel(); refModel.id = 'DB';
    -0099             task = testCase.taskTestStruct();
    -0100             task.shouldFail = true;
    -0101             task.id = 'Task 50% done'; task.description = task.id;
    -0102             lastwarn('');
    -0103             evalc('fitTasks(refModel, refModel, [], true, [], task);');
    -0104             msg = lastwarn();
    -0105             testCase.verifySubstring(msg, 'Task 50% done');
    -0106             testCase.verifyFalse(contains(msg, '\n'));
    -0107         end
    -0108 
    -0109         function fitTasksProducesModel(testCase)
    -0110             testCase.assumeMILPSolver();
    -0111             refModel = testCase.taskTestModel(); refModel.id = 'DB';
    -0112             gapModel = removeReactions(refModel, {'R2'}); gapModel.id = 'testModel';
    -0113             task = testCase.taskTestStruct();
    -0114             evalc('[outModel, addedRxns] = fitTasks(gapModel, refModel, [], true, [], task);');
    -0115             testCase.verifyClass(outModel, 'struct');
    -0116             testCase.verifyTrue(ismember('R2', outModel.rxns));
    -0117         end
    -0118 
    -0119         function fitTasksPrintOutputKeepsPercentInTaskId(testCase)
    -0120             % The per-task "Added N reaction(s)" notice embeds the task
    -0121             % id/description; a "%" there must survive intact, not be
    -0122             % read as an fprintf format directive.
    -0123             testCase.assumeMILPSolver();
    -0124             refModel = testCase.taskTestModel(); refModel.id = 'DB';
    -0125             gapModel = removeReactions(refModel, {'R2'}); gapModel.id = 'testModel';
    -0126             task = testCase.taskTestStruct();
    -0127             task.id = 'Task 50% done'; task.description = task.id;
    -0128             out = evalc('fitTasks(gapModel, refModel, [], true, [], task);');
    -0129             testCase.verifySubstring(out, 'Task 50% done');
    -0130         end
    -0131 
    -0132         function fitTasksAcceptsAllMetsShorthands(testCase)
    -0133             % ALLMETS and ALLMETSIN[comp] open uptake for a whole model or a
    -0134             % whole compartment rather than for a listed metabolite, and
    -0135             % write the input bounds along a different route than a named
    -0136             % metabolite does. The task needs e[s] out of a[s], and the model
    -0137             % is missing R2, the only route from a[s] into the cell.
    -0138             testCase.assumeMILPSolver();
    -0139             refModel = testCase.taskTestModel(); refModel.id = 'DB';
    -0140             gapModel = removeReactions(refModel, {'R2'}); gapModel.id = 'testModel';
    -0141 
    -0142             % Opening the extracellular compartment is the same thing as
    -0143             % naming a[s], so R2 is still the reaction that has to be added
    -0144             task = testCase.taskTestStruct();
    -0145             task.inputs = {'ALLMETSIN[s]'};
    -0146             evalc('outModel = fitTasks(gapModel, refModel, [], false, [], task);');
    -0147             testCase.verifyTrue(ismember('R2', outModel.rxns));
    -0148 
    -0149             % ALLMETS also opens uptake of the cytosolic metabolites, e[c]
    -0150             % among them, so the task is satisfiable without crossing the
    -0151             % membrane at all and nothing needs to be added
    -0152             task.inputs = {'ALLMETS'};
    -0153             evalc('outModel = fitTasks(gapModel, refModel, [], false, [], task);');
    -0154             testCase.verifyFalse(ismember('R2', outModel.rxns));
    -0155         end
    -0156 
    -0157         function fitTasksRejectsUnknownAllMetsInCompartment(testCase)
    -0158             % The compartment named in ALLMETSIN has to exist, otherwise the
    -0159             % task silently constrains nothing.
    -0160             refModel = testCase.taskTestModel(); refModel.id = 'DB';
    -0161             gapModel = removeReactions(refModel, {'R2'}); gapModel.id = 'testModel';
    -0162             task = testCase.taskTestStruct();
    -0163             task.inputs = {'ALLMETSIN[z]'};
    -0164             testCase.verifyError( ...
    -0165                 @() fitTasks(gapModel, refModel, [], false, [], task), ...
    -0166                 'RAVEN:badInput');
    -0167         end
    -0168 
    -0169         function gapFillFastCoreReturnsLogical(testCase)
    -0170             % gapFillFastCore should return a logical vector the same length as model.rxns.
    -0171             model = testCase.model;
    -0172             coreIdx = 1;   % any single reaction as the core
    -0173             active = gapFillFastCore(model, coreIdx, 1e-4);
    -0174             testCase.verifyClass(active, 'logical');
    -0175             testCase.verifyNumElements(active, numel(model.rxns));
    -0176             testCase.verifyTrue(active(coreIdx));  % core reaction must be active
    -0177         end
    -0178 
    -0179         function gapFillFastCoreRejectsTriviallySelfCancelingReversibleCore(testCase)
    -0180             % A reversible core reaction touching only its own,
    -0181             % otherwise-unused metabolites cannot carry any real
    -0182             % steady-state flux (mass balance on those metabolites forces
    -0183             % v=0). Forcing both its forward and reverse irreversible
    -0184             % copies to >= epsilon must not let them satisfy the core
    -0185             % requirement by canceling each other out.
    -0186             model = testCase.model;
    -0187             r.rxns = {'isolatedRev'};
    -0188             r.equations = {'newA[c] <=> newB[c]'};
    -0189             evalc('model = addRxns(model, r, 3, ''c'', true);');
    -0190             coreIdx = getIndexes(model,'isolatedRev','rxns');
    -0191             active = gapFillFastCore(model, coreIdx, 1e-4);
    -0192             testCase.verifyFalse(active(coreIdx));
    -0193         end
    -0194 
    -0195         function gapFillSwiftCoreReturnsLogical(testCase)
    -0196             % gapFillSwiftCore returns same shape as gapFillFastCore.
    -0197             model = testCase.model;
    -0198             coreIdx = 1;
    -0199             active = gapFillSwiftCore(model, coreIdx, 1e-4);
    -0200             testCase.verifyClass(active, 'logical');
    -0201             testCase.verifyNumElements(active, numel(model.rxns));
    -0202             testCase.verifyTrue(active(coreIdx));
    -0203         end
    -0204 
    -0205         function gapFillSwiftCoreRejectsTriviallySelfCancelingReversibleCore(testCase)
    -0206             % Same issue as gapFillFastCore's identically-named test: a
    -0207             % reversible core reaction touching only its own,
    -0208             % otherwise-unused metabolites cannot carry any real
    -0209             % steady-state flux, so forcing both its forward and reverse
    -0210             % irreversible copies to >= epsilon must not let them satisfy
    -0211             % the core requirement by canceling each other out.
    -0212             model = testCase.model;
    -0213             r.rxns = {'isolatedRev'};
    -0214             r.equations = {'newA[c] <=> newB[c]'};
    -0215             evalc('model = addRxns(model, r, 3, ''c'', true);');
    -0216             coreIdx = getIndexes(model,'isolatedRev','rxns');
    -0217             active = gapFillSwiftCore(model, coreIdx, 1e-4);
    -0218             testCase.verifyFalse(active(coreIdx));
    -0219         end
    -0220 
    -0221         function fillGapsIdentifiesOwnRxnsWhenRxnFromPreset(testCase)
    -0222             % model.rxnFrom, as e.g. getModelFromHomology output already
    -0223             % carries it, must not stop fillGaps from recognising the
    -0224             % model's own reactions when checking which of them regain
    -0225             % flux via the template.
    -0226             testCase.assumeMILPSolver();
    -0227             modelDB = testCase.model; modelDB.id = 'DB';
    -0228             gapModel = removeReactions(modelDB, (1:10));
    -0229             gapModel.id = 'gapModel';
    -0230             gapModel.rxnFrom = repmat({'someTemplate'}, numel(gapModel.rxns), 1);
    -0231             evalc('[newConnected,~,~,newModel] = fillGaps(gapModel, modelDB);');
    -0232             testCase.verifyNotEmpty(newConnected);
    -0233             testCase.verifyTrue(all(ismember(gapModel.rxns, newModel.rxns)));
    -0234         end
    -0235 
    -0236         function gapFillFastLPReturnsAddedRxns(testCase)
    -0237             % gapFillFastLP should identify database reactions that rescue blocked draft reactions.
    -0238             modelDB = testCase.model; modelDB.id = 'DB';
    -0239             gapModel = removeReactions(modelDB, modelDB.rxns(1:5));
    -0240             gapModel.id = 'gapModel';
    -0241             evalc('[addedRxns, newModel, cannotConnect] = gapFillFastLP(gapModel, modelDB, ''verbose'', false);');
    -0242             testCase.verifyClass(addedRxns, 'cell');
    -0243             testCase.verifyClass(newModel, 'struct');
    -0244             testCase.verifyClass(cannotConnect, 'cell');
    -0245             % Added reactions must all be present in the returned model and be
    -0246             % universal-derived (not original draft reactions). mergeModels
    -0247             % appends the template model id to deduplicate reaction IDs shared by
    -0248             % draft and DB, so added IDs may carry a '_DB' suffix rather than
    -0249             % matching modelDB.rxns verbatim.
    -0250             if ~isempty(addedRxns)
    -0251                 testCase.verifyTrue(all(ismember(addedRxns, newModel.rxns)));
    -0252                 testCase.verifyFalse(any(ismember(addedRxns, gapModel.rxns)));
    -0253             end
    -0254         end
    -0255 
    -0256         function gapFillSwiftLPReturnsAddedRxns(testCase)
    -0257             % swiftLP variant should produce consistent results with fastLP.
    -0258             modelDB = testCase.model; modelDB.id = 'DB';
    -0259             gapModel = removeReactions(modelDB, modelDB.rxns(1:5));
    -0260             gapModel.id = 'gapModel';
    -0261             evalc('[addedRxns, newModel, ~] = gapFillFastLP(gapModel, modelDB, ''variant'', ''swift'', ''verbose'', false);');
    -0262             testCase.verifyClass(addedRxns, 'cell');
    -0263             testCase.verifyClass(newModel, 'struct');
    -0264         end
    -0265 
    -0266         function gapFillTopologicalIdentifiesGaps(testCase)
    -0267             % gapFillTopological should return a struct with the expected fields.
    -0268             modelDB = testCase.model; modelDB.id = 'DB';
    -0269             gapModel = removeReactions(modelDB, modelDB.rxns(1:5));
    -0270             gapModel.id = 'gapModel';
    -0271             evalc('result = gapFillTopological(gapModel, modelDB, ''verbose'', false);');
    -0272             testCase.verifyClass(result, 'struct');
    -0273             testCase.verifyTrue(isfield(result, 'reachableMets'));
    -0274             testCase.verifyTrue(isfield(result, 'blockedMets'));
    -0275             testCase.verifyTrue(isfield(result, 'candidateRxns'));
    -0276             testCase.verifyTrue(isfield(result, 'pruningFraction'));
    -0277             testCase.verifyClass(result.reachableMets, 'logical');
    -0278             testCase.verifyNumElements(result.reachableMets, numel(gapModel.mets));
    -0279         end
    -0280 
    -0281         function gapFillTopologicalReachesThroughInExchange(testCase)
    -0282             % A -> B -> C fed by an 'in' exchange on A. addExchangeRxns writes
    -0283             % 'in' as "=> A", which has lb = 0, so a seed test of lb < 0 finds
    -0284             % no uptake at all and every metabolite comes back blocked.
    -0285             m = testCase.chainModel();
    -0286             m = addExchangeRxns(m, 'in', {'A'});
    -0287             evalc(['result = gapFillTopological(m, m, ''targets'', ' ...
    -0288                 '{''A'',''B'',''C''}, ''verbose'', false);']);
    -0289             testCase.verifyTrue(all(result.reachableMets));
    -0290             testCase.verifyEmpty(result.blockedMets);
    -0291         end
    -0292 
    -0293         function gapFillTopologicalReachesThroughReversibleExchange(testCase)
    -0294             % "A <=>" supplies A too, but is 'reverse' rather than 'uptake'.
    -0295             m = testCase.chainModel();
    -0296             m = addExchangeRxns(m, 'both', {'A'});
    -0297             evalc(['result = gapFillTopological(m, m, ''targets'', ' ...
    -0298                 '{''A'',''B'',''C''}, ''verbose'', false);']);
    -0299             testCase.verifyTrue(all(result.reachableMets));
    -0300         end
    -0301 
    -0302         function gapFillTopologicalBlocksWithoutExchange(testCase)
    -0303             % Without any exchange nothing is producible: the positive control
    -0304             % for the two tests above.
    -0305             m = testCase.chainModel();
    -0306             evalc(['result = gapFillTopological(m, m, ''seeds'', {}, ''targets'', ' ...
    -0307                 '{''A'',''B'',''C''}, ''verbose'', false);']);
    -0308             testCase.verifyFalse(any(result.reachableMets));
    -0309             testCase.verifyNumElements(result.blockedMets, 3);
    -0310         end
    -0311 
    -0312         function gapFillMILPRepairsGrowth(testCase)
    -0313             testCase.assumeMILPSolver();
    -0314             modelDB = testCase.model; modelDB.id = 'DB';
    -0315             gapModel = removeReactions(modelDB, modelDB.rxns(1:5));
    -0316             gapModel.id = 'gapModel';
    -0317             evalc('[addedRxns, reversedRxns, newModel, exitFlag] = gapFillMILP(gapModel, modelDB, ''verbose'', false);');
    -0318             testCase.verifyClass(addedRxns, 'cell');
    -0319             testCase.verifyClass(reversedRxns, 'cell');
    -0320             testCase.verifyClass(newModel, 'struct');
    -0321             testCase.verifyEqual(exitFlag, 1);
    -0322             % The repaired model should be able to produce objective flux.
    -0323             sol = solveLP(newModel);
    -0324             testCase.verifyNotEmpty(sol.f);
    -0325             testCase.verifyGreaterThan(sol.f, 0);
    -0326         end
    -0327 
    -0328         function gapFillMILPHandlesUnboundedUniversalReaction(testCase)
    -0329             % A universal reaction with ub=Inf (RAVEN's own default when no
    -0330             % upper-bound annotation is set) must not put an Inf
    -0331             % coefficient into the coupling constraint matrix -- SCIP
    -0332             % rejects that outright, and Inf*0 is NaN even where it
    -0333             % wouldn't be rejected.
    -0334             testCase.assumeMILPSolver();
    -0335             gapModel = struct();
    -0336             gapModel.id='gapModel'; gapModel.rxns={'R1';'ExB'}; gapModel.rxnNames=gapModel.rxns;
    -0337             gapModel.mets={'a';'b'}; gapModel.metNames=gapModel.mets; gapModel.metComps=[1;1];
    -0338             gapModel.comps={'c'}; gapModel.compNames={'c'};
    -0339             gapModel.S=sparse([-1 0; 1 -1]); % R1: a=>b   ExB: b=>
    -0340             gapModel.lb=[0;0]; gapModel.ub=[1000;1000]; gapModel.rev=[0;0];
    -0341             gapModel.c=[1;0]; gapModel.b=zeros(2,1);
    -0342             gapModel.genes={}; gapModel.grRules={'';''}; gapModel.rxnGeneMat=sparse(2,0);
    -0343 
    -0344             modelDB = struct();
    -0345             modelDB.id='DB'; modelDB.rxns={'ExA'}; modelDB.rxnNames=modelDB.rxns;
    -0346             modelDB.mets={'a'}; modelDB.metNames=modelDB.mets; modelDB.metComps=1;
    -0347             modelDB.comps={'c'}; modelDB.compNames={'c'};
    -0348             modelDB.S=sparse(1,1); modelDB.S(1,1)=1;
    -0349             modelDB.lb=0; modelDB.ub=Inf; modelDB.rev=0; modelDB.c=0; modelDB.b=0;
    -0350             modelDB.genes={}; modelDB.grRules={''}; modelDB.rxnGeneMat=sparse(1,0);
    -0351 
    -0352             evalc(['[addedRxns,~,newModel,exitFlag] = gapFillMILP(gapModel, modelDB, ' ...
    -0353                 '''verbose'', false);']);
    -0354             testCase.verifyEqual(exitFlag, 1);
    -0355             testCase.verifyEqual(addedRxns, {'ExA'});
    -0356             sol = solveLP(newModel);
    -0357             testCase.verifyGreaterThan(sol.f, 0);
    -0358         end
    -0359 
    -0360         function gapFillMILPKeepsRenamedUniversalReaction(testCase)
    -0361             % A universal reaction whose id collides with an unrelated
    -0362             % draft reaction gets renamed by mergeModels; that renamed
    -0363             % reaction must still end up in addedRxns and in newModel, not
    -0364             % be silently dropped by a lookup keyed on its original name.
    -0365             testCase.assumeMILPSolver();
    -0366             gapModel = struct();
    -0367             gapModel.id='gapModel'; gapModel.rxns={'R1';'Rname'}; gapModel.rxnNames=gapModel.rxns;
    -0368             gapModel.mets={'a';'b'}; gapModel.metNames=gapModel.mets; gapModel.metComps=[1;1];
    -0369             gapModel.comps={'c'}; gapModel.compNames={'c'};
    -0370             gapModel.S=sparse([-1 0; 1 -1]); % R1: a=>b   Rname (draft): b=>, irrelevant to growth
    -0371             gapModel.lb=[0;0]; gapModel.ub=[1000;1000]; gapModel.rev=[0;0];
    -0372             gapModel.c=[1;0]; gapModel.b=zeros(2,1);
    -0373             gapModel.genes={}; gapModel.grRules={'';''}; gapModel.rxnGeneMat=sparse(2,0);
    -0374 
    -0375             modelDB = struct();
    -0376             % Same id "Rname" as the draft's, but a completely different
    -0377             % reaction: this is the one actually needed to enable growth.
    -0378             modelDB.id='DB'; modelDB.rxns={'Rname'}; modelDB.rxnNames=modelDB.rxns;
    -0379             modelDB.mets={'a'}; modelDB.metNames=modelDB.mets; modelDB.metComps=1;
    -0380             modelDB.comps={'c'}; modelDB.compNames={'c'};
    -0381             modelDB.S=sparse(1,1); modelDB.S(1,1)=1; % =>a
    -0382             modelDB.lb=0; modelDB.ub=1000; modelDB.rev=0; modelDB.c=0; modelDB.b=0;
    -0383             modelDB.genes={}; modelDB.grRules={''}; modelDB.rxnGeneMat=sparse(1,0);
    -0384 
    -0385             evalc(['[addedRxns,~,newModel,exitFlag] = gapFillMILP(gapModel, modelDB, ' ...
    -0386                 '''verbose'', false);']);
    -0387             testCase.verifyEqual(exitFlag, 1);
    -0388             testCase.verifyNotEmpty(addedRxns);
    -0389             sol = solveLP(newModel);
    -0390             testCase.verifyGreaterThan(sol.f, 0);
    -0391         end
    -0392 
    -0393         function gapFillMILPReversesDirectionality(testCase)
    -0394             % gapFillMILP should reverse a reaction whose directionality is wrong.
    -0395             testCase.assumeMILPSolver();
    -0396             model = testCase.model;
    -0397             modelDB = model; modelDB.id = 'DB';
    -0398             gapModel = removeReactions(model, model.rxns(1:3));
    -0399             gapModel.id = 'gapModel';
    -0400             evalc('[~, reversedRxns, ~, ~] = gapFillMILP(gapModel, modelDB, ''verbose'', false);');
    -0401             testCase.verifyClass(reversedRxns, 'cell');
    -0402         end
    -0403 
    -0404         function fillGapsDispatchesFastLP(testCase)
    -0405             % fillGaps with 'algorithm','fastLP' should reach gapFillFastLP.
    -0406             modelDB = testCase.model; modelDB.id = 'DB';
    -0407             gapModel = removeReactions(modelDB, modelDB.rxns(1:5));
    -0408             gapModel.id = 'gapModel';
    -0409             evalc('[~,cannotConnect,addedRxns,newModel,exitFlag] = fillGaps(gapModel, modelDB, ''algorithm'', ''fastLP'', ''verbose'', false);');
    -0410             testCase.verifyClass(newModel, 'struct');
    -0411             testCase.verifyEqual(exitFlag, 1);
    -0412         end
    -0413 
    -0414         function fillGapsDispatchesGapfillMILP(testCase)
    -0415             testCase.assumeMILPSolver();
    -0416             modelDB = testCase.model; modelDB.id = 'DB';
    -0417             gapModel = removeReactions(modelDB, modelDB.rxns(1:5));
    -0418             gapModel.id = 'gapModel';
    -0419             evalc('[~,~,addedRxns,newModel,exitFlag] = fillGaps(gapModel, modelDB, ''algorithm'', ''gapfillMILP'', ''verbose'', false);');
    -0420             testCase.verifyClass(newModel, 'struct');
    -0421             testCase.verifyEqual(exitFlag, 1);
    -0422         end
    -0423 
    -0424     end
    -0425 
    -0426     methods (Access = private)
    -0427         function m = chainModel(~)
    -0428             % A -> B -> C, no exchanges; the caller adds the ones it needs.
    -0429             m = struct();
    -0430             m.rxns = {'R1';'R2'}; m.rxnNames = {'R1';'R2'};
    -0431             m.mets = {'A';'B';'C'}; m.metNames = {'A';'B';'C'}; m.metComps = [1;1;1];
    -0432             m.comps = {'c'}; m.compNames = {'c'};
    -0433             m.S = sparse([-1 0; 1 -1; 0 1]);
    -0434             m.lb = [0;0]; m.ub = [1000;1000]; m.rev = [0;0]; m.c = [0;1];
    -0435             m.b = [0;0;0];
    -0436             m.genes = {}; m.grRules = {'';''}; m.rxnGeneMat = sparse(2,0);
    -0437         end
    -0438     end
    -0439 end
    -
    Generated by m2html © 2005
    - - \ No newline at end of file diff --git a/docs/html/testing/function_tests/tGenomeData.html b/docs/html/testing/function_tests/tGenomeData.html deleted file mode 100644 index 8eb275ef..00000000 --- a/docs/html/testing/function_tests/tGenomeData.html +++ /dev/null @@ -1,145 +0,0 @@ - - - - Description of tGenomeData - - - - - - - - - -
    Home > testing > function_tests > tGenomeData.m
    - - - -

    tGenomeData -

    - -

    PURPOSE ^

    -
    - -

    SYNOPSIS ^

    -
    This is a script file.
    - -

    DESCRIPTION ^

    -
    - - -

    CROSS-REFERENCE INFORMATION ^

    -This function calls: - -This function is called by: - - - -

    SUBFUNCTIONS ^

    - - -

    SOURCE CODE ^

    -
    0001 classdef tGenomeData < RavenTestCase
    -0002 % tGenomeData  Tests for the genome-data utilities used in homology-based
    -0003 %              reconstruction (downloadGenomeData, getGeneData,
    -0004 %              processProteinFastaFile, renameModelGenes).
    -0005 %
    -0006 %   Parsing and renaming are tested offline with small GFF3/FASTA fixtures
    -0007 %   (one eukaryote, one prokaryote). The NCBI download is guarded and skips
    -0008 %   when there is no internet connection.
    -0009 
    -0010     methods (Access = protected)
    -0011         function d = fixtureDir(testCase)
    -0012             d = fullfile(testCase.ravenRoot,'testing','function_tests', ...
    -0013                 'test_data','genomeData');
    -0014         end
    -0015     end
    -0016 
    -0017     methods (Test)
    -0018 
    -0019         function getGeneDataReadsEukaryote(testCase)
    -0020             % gene -> mRNA -> CDS: the protein accession must come from the
    -0021             % CDS protein_id (NP_), not the mRNA transcript (NM_), and the
    -0022             % two CDS lines of one protein collapse to a single row.
    -0023             T = getGeneData(fullfile(testCase.fixtureDir(),'euk.gff'));
    -0024             testCase.verifyEqual(height(T), 2);
    -0025             i1 = find(strcmp(T.locus_tag,'LT1'),1);
    -0026             testCase.verifyEqual(T.old_locus_tag{i1}, 'OLD1');
    -0027             testCase.verifyEqual(T.GeneID{i1}, '111');
    -0028             testCase.verifyEqual(T.gene_name{i1}, 'AAA');
    -0029             testCase.verifyEqual(T.GenBank_protein{i1}, 'NP_111.1');
    -0030             i2 = find(strcmp(T.locus_tag,'LT2'),1);
    -0031             testCase.verifyEqual(T.GenBank_protein{i2}, 'NP_222.1');
    -0032         end
    -0033 
    -0034         function getGeneDataReadsProkaryote(testCase)
    -0035             % gene -> CDS directly.
    -0036             T = getGeneData(fullfile(testCase.fixtureDir(),'prok.gff'));
    -0037             testCase.verifyEqual(height(T), 1);
    -0038             testCase.verifyEqual(T.locus_tag{1}, 'b0001');
    -0039             testCase.verifyEqual(T.GeneID{1}, '333');
    -0040             testCase.verifyEqual(T.gene_name{1}, 'ccc');
    -0041             testCase.verifyEqual(T.GenBank_protein{1}, 'WP_333.1');
    -0042         end
    -0043 
    -0044         function getGeneDataWritesNoFileByDefault(testCase)
    -0045             prevFiles = dir('geneData.tsv');
    -0046             getGeneData(fullfile(testCase.fixtureDir(),'prok.gff'));
    -0047             testCase.verifyEqual(numel(dir('geneData.tsv')), numel(prevFiles));
    -0048         end
    -0049 
    -0050         function processProteinFastaFileRenamesHeaders(testCase)
    -0051             T   = getGeneData(fullfile(testCase.fixtureDir(),'euk.gff'));
    -0052             out = processProteinFastaFile(fullfile(testCase.fixtureDir(), ...
    -0053                 'euk_protein.faa'), T, 'locus_tag', tempdir);
    -0054             testCase.addTeardown(@() delete(out));
    -0055             fa = readFasta(out);
    -0056             headers = {fa.Header};
    -0057             % NP_111.1 -> LT1, NP_222.1 -> LT2, NP_999.9 has no match and is kept
    -0058             testCase.verifyTrue(ismember('LT1', headers));
    -0059             testCase.verifyTrue(ismember('LT2', headers));
    -0060             testCase.verifyTrue(any(startsWith(headers, 'NP_999.9')));
    -0061         end
    -0062 
    -0063         function renameModelGenesRenamesGenesAndGrRules(testCase)
    -0064             model.id         = 'test';
    -0065             model.rxns       = {'r1'; 'r2'};
    -0066             model.genes      = {'LT1'; 'LT2'};
    -0067             model.grRules    = {'LT1'; 'LT1 or LT2'};
    -0068             model.rxnGeneMat = sparse([1 0; 1 1]);
    -0069 
    -0070             T = getGeneData(fullfile(testCase.fixtureDir(),'euk.gff'));
    -0071             evalc('m2 = renameModelGenes(model, T, ''locus_tag'', ''gene_name'');');
    -0072 
    -0073             testCase.verifyEqual(sort(m2.genes), {'AAA'; 'BBB'});
    -0074             testCase.verifyFalse(any(contains(m2.grRules, 'LT1')));
    -0075             testCase.verifyTrue(any(contains(m2.grRules, 'AAA')));
    -0076             testCase.verifyTrue(any(contains(m2.grRules, 'BBB')));
    -0077             testCase.verifyEqual(size(m2.rxnGeneMat,2), numel(m2.genes));
    -0078         end
    -0079 
    -0080         function downloadGenomeDataDownloads(testCase)
    -0081             % Network test: download a small genome from NCBI Datasets.
    -0082             % Skipped when there is no internet connection.
    -0083             d = tempname; mkdir(d);
    -0084             testCase.addTeardown(@() rmdir(d,'s'));
    -0085             try
    -0086                 [gff, faa] = downloadGenomeData('GCF_000005845.2', d, false);
    -0087             catch ME
    -0088                 testCase.assumeFail(['Skipped, likely no internet connection: ' ME.message]);
    -0089                 return
    -0090             end
    -0091             testCase.verifyTrue(isfile(gff));
    -0092             testCase.verifyTrue(isfile(faa));
    -0093         end
    -0094 
    -0095     end
    -0096 end
    -
    Generated by m2html © 2005
    - - \ No newline at end of file diff --git a/docs/html/testing/function_tests/tINIT.html b/docs/html/testing/function_tests/tINIT.html deleted file mode 100644 index ad3b01a4..00000000 --- a/docs/html/testing/function_tests/tINIT.html +++ /dev/null @@ -1,997 +0,0 @@ - - - - Description of tINIT - - - - - - - - - - - - - -

    tINIT -

    - -

    PURPOSE ^

    -
    - -

    SYNOPSIS ^

    -
    This is a script file.
    - -

    DESCRIPTION ^

    -
    - - -

    CROSS-REFERENCE INFORMATION ^

    -This function calls: - -This function is called by: - - - -

    SUBFUNCTIONS ^

    - - -

    SOURCE CODE ^

    -
    0001 classdef tINIT < RavenTestCase
    -0002 % tINIT  Tests for the ftINIT context-specific modelling functions in INIT/.
    -0003 %
    -0004 %   The MILP-based pipeline is exercised end-to-end on small synthetic models
    -0005 %   (built by the local getTstModel* helpers, illustrated in tINITtestInfo/):
    -0006 %   ftINITPipelineRuns / ftINITWithTaskRuns / ftINITMetabolomicsRuns /
    -0007 %   ftINITFullVsThreeStepRuns cover prepINITModel + ftINIT and the internal
    -0008 %   ftINITInternalAlg / groupRxnScores helpers, fitTasksPreMerged* covers the
    -0009 %   gap-filling step (fitTasks with gapFillMode 'preMerged', ftINIT's mode),
    -0010 %   all guarded on a MILP solver (Gurobi/SCIP). The self-contained helpers
    -0011 %   (mergeLinear, groupRxnScores, reverseRxns, rescaleModelForINIT,
    -0012 %   scoreModel, getExprForRxnScore, parseHPA, parseHPArna) are tested
    -0013 %   directly against known results.
    -0014 
    -0015     methods (Test)
    -0016 
    -0017         function getINITStepsReturnsSteps(testCase)
    -0018             steps = getINITSteps([], '1+1');
    -0019             testCase.verifyNotEmpty(steps);
    -0020         end
    -0021 
    -0022         function getExprForRxnScoreRuns(testCase)
    -0023             expr = getExprForRxnScore(rand(10, 1), 1);
    -0024             testCase.verifyNotEmpty(expr);
    -0025         end
    -0026 
    -0027         function removeLowScoreGenesRuns(testCase)
    -0028             geneScores = randn(numel(testCase.model.genes), 1);
    -0029             evalc('m2 = removeLowScoreGenes(testCase.model, geneScores);');
    -0030             testCase.verifyClass(m2, 'struct');
    -0031         end
    -0032 
    -0033         function removeLowScoreGenesKeepsFieldsAligned(testCase)
    -0034             % getGenesFromGrRules returns a sorted gene list, but the
    -0035             % annotation fields are trimmed with a mask in the original gene
    -0036             % order. If newModel.genes took the sorted order, every annotation
    -0037             % field would shift relative to it. Use deliberately unsorted genes
    -0038             % so sorted != original, and drop one isozyme gene.
    -0039             m = struct();
    -0040             m.id = 'test';
    -0041             m.rxns = {'R1'}; m.rxnNames = {'R1'};
    -0042             m.mets = {'A';'B'}; m.metNames = {'A';'B'}; m.metComps = [1;1];
    -0043             m.comps = {'c'}; m.compNames = {'c'};
    -0044             m.S = sparse([-1;1]); m.lb = 0; m.ub = 1000; m.rev = 0; m.c = 0;
    -0045             m.b = [0;0];
    -0046             m.genes = {'G3';'G1';'G2'};                      % unsorted
    -0047             m.geneShortNames = {'short3';'short1';'short2'}; % aligned to genes
    -0048             m.grRules = {'G3 or G1 or G2'};
    -0049             m.rxnGeneMat = sparse([1 1 1]);
    -0050             scores = [1; -1; 1];   % G1 (negative) is dropped from the isozyme
    -0051             evalc('[mm, removed] = removeLowScoreGenes(m, scores);');
    -0052 
    -0053             testCase.verifyEqual(removed, {'G1'});
    -0054             % Each surviving gene keeps its own short name.
    -0055             for k = 1:numel(mm.genes)
    -0056                 want = strrep(mm.genes{k}, 'G', 'short');
    -0057                 testCase.verifyEqual(mm.geneShortNames{k}, want);
    -0058             end
    -0059             % rxnGeneMat columns must correspond to mm.genes, and R1 still uses
    -0060             % both surviving genes.
    -0061             testCase.verifyEqual(numel(mm.genes), size(mm.rxnGeneMat, 2));
    -0062             testCase.verifyEqual(full(mm.rxnGeneMat), ones(1, numel(mm.genes)));
    -0063         end
    -0064 
    -0065         function reverseRxnsRuns(testCase)
    -0066             % R1 = '=> a[s]'; R3 = 'a[c] <=> b[c] + c[c]'
    -0067             testModel = getTstModel();
    -0068             tmpModel  = reverseRxns(testModel, {'R1';'R3'});
    -0069             res       = constructEquations(tmpModel, {'R1';'R3'});
    -0070             expRes    = {'a[s] => ';'b[c] + c[c] <=> a[c]'};
    -0071             testCase.verifyTrue(all(strcmp(res, expRes)));
    -0072         end
    -0073 
    -0074         function rescaleModelForINITRuns(testCase)
    -0075             miniModel      = struct();
    -0076             miniModel.S    = [1,1000;-1,-40];
    -0077             miniModel.rxns = {'1';'2'};
    -0078             miniModel.mets = {'1';'2'};
    -0079             res = rescaleModelForINIT(miniModel, 10);
    -0080             testCase.verifyTrue(abs(res.S(1,2) - res.S(2,2)*-10) < 10^-6);
    -0081             testCase.verifyTrue(abs((abs(res.S(1,2)) + abs(res.S(2,2)))/2) - 1 < 10^-6);
    -0082         end
    -0083 
    -0084         function mergeLinearGroupsAndScores(testCase)
    -0085             % mergeLinear merges linear reaction chains; groupRxnScores then
    -0086             % aggregates the per-reaction scores onto the merged groups.
    -0087             testModel     = getTstModel();
    -0088             testRxnScores = getTstModelRxnScores();
    -0089             evalc('[reducedModel,origRxnIds,groupIds,reversedRxns] = mergeLinear(testModel, {});');
    -0090             % {R1,R2}, {R3,R5}, {R4,R6}, {R7,R8}, {R9,R10} are merged
    -0091             testCase.verifyTrue(all(groupIds == [1;1;2;3;2;3;4;4;5;5]));
    -0092             % R1, R3, R4, R7 irreversible, R9 reversible
    -0093             testCase.verifyTrue(all(reducedModel.rev == [0;0;0;0;1]));
    -0094             testCase.verifyTrue(all(reducedModel.lb == [0;0;0;0;-1000]));
    -0095 
    -0096             newRxnScores = groupRxnScores(reducedModel, testRxnScores, ...
    -0097                 origRxnIds, groupIds, ismember(origRxnIds, {'R1';'R2';'R8'}));
    -0098             testCase.verifyTrue(all(newRxnScores == [0;-0.5;7.5;-1;0.5]));
    -0099 
    -0100             % testModel4 has reactions that are not all linearly merged
    -0101             testModel4 = getTstModel4();
    -0102             evalc('[reducedModel,origRxnIds,groupIds,reversedRxns] = mergeLinear(testModel4, {});');
    -0103             % {R5,R6}, {R7,R8}, {R9,R10} are merged
    -0104             testCase.verifyTrue(all(groupIds == [0;0;0;0;1;1;2;2;3;3;0]));
    -0105             testCase.verifyTrue(all(reducedModel.rev == [1;0;1;0;1;1;0;0]));
    -0106             % some reactions flip direction when turned irreversible
    -0107             testCase.verifyTrue(strcmp(constructEquations(reducedModel, 'R9'), 'g[s] => '));
    -0108             testCase.verifyTrue(all(find(reversedRxns) == [6;9]));
    -0109         end
    -0110 
    -0111         function scoreModelRuns(testCase)
    -0112             arrayData.genes   = testCase.model.genes;
    -0113             arrayData.tissues = {'t1'};
    -0114             arrayData.levels  = abs(randn(numel(testCase.model.genes), 1)) + 1;
    -0115             arrayData.threshold = 1;
    -0116             evalc('rxnScores = scoreModel(testCase.model, [], arrayData, ''t1'', []);');
    -0117             testCase.verifyNumElements(rxnScores, numel(testCase.model.rxns));
    -0118         end
    -0119 
    -0120         function scoreModelHpaUnmeasuredCellTypeIsNotZero(testCase)
    -0121             % A gene not detected in one cell type of a tissue and not
    -0122             % measured in the other must keep its 'Not detected' score. An
    -0123             % unmeasured cell type is not a measurement of zero, and zero
    -0124             % ranks above 'Not detected' (-8), so scoring it as such would
    -0125             % also keep the gene from ever being pruned.
    -0126             m = testCase.model;
    -0127             hpaData.genes      = m.genes(1);
    -0128             hpaData.tissues    = {'liver';'liver'};
    -0129             hpaData.celltypes  = {'hepatocyte';'kupffer'};
    -0130             hpaData.levels     = {'Not detected'};
    -0131             hpaData.gene2Level = sparse(1,2);
    -0132             hpaData.gene2Level(1,1) = 1;   % not detected in hepatocyte
    -0133                                            % not measured in kupffer
    -0134 
    -0135             evalc(['[~,~,hpaScores] = scoreModel(m, hpaData, [], ' ...
    -0136                 '''liver'', ''multipleCellScoring'', ''max'');']);
    -0137             testCase.verifyEqual(hpaScores(1), -8, 'AbsTol', 1e-9);
    -0138 
    -0139             % The average is likewise taken over the measurements that exist
    -0140             evalc(['[~,~,hpaScores] = scoreModel(m, hpaData, [], ' ...
    -0141                 '''liver'', ''multipleCellScoring'', ''average'');']);
    -0142             testCase.verifyEqual(hpaScores(1), -8, 'AbsTol', 1e-9);
    -0143         end
    -0144 
    -0145         function scoreModelExactScores(testCase)
    -0146             % scoreModel must reproduce the known reaction scores for
    -0147             % the reference model prepared by prepINITModel.
    -0148             testCase.assumeMILPSolver();
    -0149             testModel = getTstModel();
    -0150             evalc('prepData = prepINITModel(testModel, {}, {}, false, {}, ''s'');');
    -0151             arrayData.genes     = testModel.genes;
    -0152             arrayData.tissues   = {'a'};
    -0153             arrayData.levels    = getExprForRxnScore(getTstModelRxnScores());
    -0154             arrayData.threshold = 1;
    -0155             rxnScores = scoreModel(prepData.refModel, [], arrayData, arrayData.tissues{1}, []);
    -0156             testCase.verifyTrue(all(abs(rxnScores - getTstModelRxnScores()) < 10^-10));
    -0157         end
    -0158 
    -0159         function parseHPAparsesSyntheticDump(testCase)
    -0160             % parseHPA reads a tab-separated HPA dump with the columns
    -0161             % Gene / Gene name / Tissue / Cell type / Level / Reliability.
    -0162             % Synthesise a tiny one so the parser is exercised without the
    -0163             % multi-gigabyte external download.
    -0164             g = testCase.model.genes(1:3);
    -0165             f = [tempname '.tsv'];
    -0166             testCase.addTeardown(@() delete(f));
    -0167             fid = fopen(f, 'w');
    -0168             fprintf(fid, 'Gene\tGene name\tTissue\tCell type\tLevel\tReliability\n');
    -0169             fprintf(fid, '%s\t%s\tliver\thepatocyte\tHigh\tApproved\n', g{1}, g{1});
    -0170             fprintf(fid, '%s\t%s\tliver\thepatocyte\tMedium\tApproved\n', g{2}, g{2});
    -0171             fprintf(fid, '%s\t%s\tbrain\tneuron\tLow\tSupported\n', g{3}, g{3});
    -0172             fclose(fid);
    -0173             evalc('hpa = parseHPA(f);');
    -0174             testCase.verifyClass(hpa, 'struct');
    -0175             testCase.verifyEqual(sort(hpa.genes), sort(g));
    -0176             testCase.verifyNumElements(hpa.tissues, 2);   % liver+brain cell types
    -0177         end
    -0178 
    -0179         function parseHPArnaParsesSyntheticDump(testCase)
    -0180             % parseHPArna (version 19) reads Gene / Gene name / Tissue /
    -0181             % TPM / pTPM / NX and builds a genes-by-tissues TPM matrix.
    -0182             g = testCase.model.genes(1:3);
    -0183             f = [tempname '.tsv'];
    -0184             testCase.addTeardown(@() delete(f));
    -0185             fid = fopen(f, 'w');
    -0186             fprintf(fid, 'Gene\tGene name\tTissue\tTPM\tpTPM\tNX\n');
    -0187             fprintf(fid, '%s\t%s\tliver\t12.5\t10.1\t8.0\n', g{1}, g{1});
    -0188             fprintf(fid, '%s\t%s\tbrain\t3.2\t2.9\t1.5\n', g{2}, g{2});
    -0189             fprintf(fid, '%s\t%s\tliver\t0.0\t0.0\t0.0\n', g{3}, g{3});
    -0190             fclose(fid);
    -0191             evalc('rna = parseHPArna(f);');
    -0192             testCase.verifyClass(rna, 'struct');
    -0193             testCase.verifyNumElements(rna.genes, 3);
    -0194             testCase.verifyEqual(size(rna.levels), [3 2]);   % 3 genes x 2 tissues
    -0195         end
    -0196 
    -0197         function getINITModelRuns(testCase)
    -0198             % The tINIT path is legacy but supported, and until now nothing
    -0199             % exercised it: a refactor of the machinery it shares with ftINIT
    -0200             % (checkTasks, simplifyModel, solveLP, removeReactions) could
    -0201             % break it without any test noticing.
    -0202             testCase.assumeMILPSolver();
    -0203             refModel = getTstModel();
    -0204             % getINITModel wants the closed form, which this fixture is not
    -0205             % built in; its own documentation gives this as the way to add it
    -0206             refModel.unconstrained = false(numel(refModel.mets),1);
    -0207 
    -0208             arrayData.genes     = refModel.genes;
    -0209             arrayData.tissues   = {'a'};
    -0210             arrayData.levels    = getExprForRxnScore(getTstModelRxnScores());
    -0211             arrayData.threshold = 1;
    -0212 
    -0213             evalc(['m = getINITModel(refModel, arrayData.tissues{1}, ' ...
    -0214                 '''arrayData'', arrayData, ''printReport'', false);']);
    -0215             testCase.verifyClass(m, 'struct');
    -0216             testCase.verifyNotEmpty(m.rxns);
    -0217             testCase.verifyTrue(all(ismember(m.rxns, refModel.rxns)));
    -0218             % R4 and R10 carry the highest scores, so neither should be cut
    -0219             testCase.verifyTrue(all(ismember({'R4';'R10'}, m.rxns)));
    -0220         end
    -0221 
    -0222         function ftINITPipelineRuns(testCase)
    -0223             % prepINITModel + ftINIT end-to-end on testModel without tasks.
    -0224             testCase.assumeMILPSolver();
    -0225             testModel  = getTstModel();
    -0226             testParams = struct();
    -0227             evalc('prepData = prepINITModel(testModel, {}, {}, false, {}, ''s'');');
    -0228             % R1 and R8 are exchange reactions without a GPR
    -0229             testCase.verifyTrue(all(strcmp( ...
    -0230                 prepData.refModel.rxns(prepData.toIgnoreExch), {'R1';'R8'})));
    -0231 
    -0232             arrayData.genes     = testModel.genes;
    -0233             arrayData.tissues   = {'a'};
    -0234             arrayData.levels    = getExprForRxnScore(getTstModelRxnScores());
    -0235             arrayData.threshold = 1;
    -0236 
    -0237             evalc(['resModel = ftINIT(prepData,arrayData.tissues{1},[],[],' ...
    -0238                 'arrayData,[],getINITSteps(),true,true,testParams,false);']);
    -0239             % R1 and R8 are added (no GPR, exchange); R2 (transport, no GPR) is
    -0240             % removed by the standard third step; R7 is not added (it has a GPR).
    -0241             testCase.verifyTrue(all(strcmp(resModel.rxns, ...
    -0242                 {'R1';'R4';'R6';'R8';'R9';'R10'})));
    -0243 
    -0244             % make R7 and R10 spontaneous
    -0245             evalc('prepData = prepINITModel(testModel, {}, {''R7'';''R10''}, false, {}, ''s'');');
    -0246             testCase.verifyTrue(all(strcmp( ...
    -0247                 prepData.refModel.rxns(prepData.toIgnoreExch | prepData.toIgnoreSpont), ...
    -0248                 {'R1';'R7';'R8';'R10'})));
    -0249             evalc(['resModel = ftINIT(prepData,arrayData.tissues{1},[],[],' ...
    -0250                 'arrayData,[],getINITSteps(),true,true,testParams,false);']);
    -0251             % the model now takes the "correct" path (incl. R2), skipping R9/R10
    -0252             testCase.verifyTrue(all(strcmp(resModel.rxns, ...
    -0253                 {'R1';'R2';'R4';'R6';'R7';'R8'})));
    -0254         end
    -0255 
    -0256         function ftINITResolveTiesAndProveAbsGapStillRunCorrectly(testCase)
    -0257             % resolveTies and proveAbsGap are threaded through ftINIT ->
    -0258             % ftINITInternalAlg (main extraction) and, for resolveTies, also
    -0259             % into fitTasks' gap-filling (raven-gecko-parity#104). This
    -0260             % fixture (same as ftINITPipelineRuns above) is too small to carry
    -0261             % a genuine tie or to expose the escalation's near-zero-objective
    -0262             % gap defect -- both are covered at genome scale in the PR
    -0263             % description -- so this only checks that turning either or both
    -0264             % on does not change the correct result or throw.
    -0265             % proveAbsGap is deliberately set to 0.05 here, not the
    -0266             % genome-scale-recommended 1.0: this fixture's reaction scores
    -0267             % (getTstModelRxnScores, as small as 0.5) make 1.0 genuinely loose
    -0268             % enough to accept a worse, different incumbent by design -- a
    -0269             % real property of a fixed absolute gap, not a bug -- so a value
    -0270             % below the model's own score granularity is what actually proves
    -0271             % the true optimum here.
    -0272             testCase.assumeMILPSolver();
    -0273             testModel  = getTstModel();
    -0274             testParams = struct();
    -0275             evalc('prepData = prepINITModel(testModel, {}, {}, false, {}, ''s'');');
    -0276 
    -0277             arrayData.genes     = testModel.genes;
    -0278             arrayData.tissues   = {'a'};
    -0279             arrayData.levels    = getExprForRxnScore(getTstModelRxnScores());
    -0280             arrayData.threshold = 1;
    -0281 
    -0282             expected = {'R1';'R4';'R6';'R8';'R9';'R10'};
    -0283 
    -0284             evalc(['resTies = ftINIT(prepData,arrayData.tissues{1},[],[],' ...
    -0285                 'arrayData,[],getINITSteps(),true,true,testParams,false,' ...
    -0286                 '''resolveTies'',true);']);
    -0287             testCase.verifyTrue(all(strcmp(resTies.rxns, expected)));
    -0288 
    -0289             evalc(['resGap = ftINIT(prepData,arrayData.tissues{1},[],[],' ...
    -0290                 'arrayData,[],getINITSteps(),true,true,testParams,false,' ...
    -0291                 '''proveAbsGap'',0.05);']);
    -0292             testCase.verifyTrue(all(strcmp(resGap.rxns, expected)));
    -0293 
    -0294             evalc(['resBoth = ftINIT(prepData,arrayData.tissues{1},[],[],' ...
    -0295                 'arrayData,[],getINITSteps(),true,true,testParams,false,' ...
    -0296                 '''resolveTies'',true,''proveAbsGap'',0.05);']);
    -0297             testCase.verifyTrue(all(strcmp(resBoth.rxns, expected)));
    -0298         end
    -0299 
    -0300         function ftINITWithTaskRuns(testCase)
    -0301             % A task requiring e[s] from a[s] forces R2 and R7 to be essential.
    -0302             testCase.assumeMILPSolver();
    -0303             testModel      = getTstModel();
    -0304             testModelTasks = getTstModelTasks();
    -0305             testParams     = struct();
    -0306             evalc('prepData = prepINITModel(testModel, testModelTasks, {}, false, {}, ''s'');');
    -0307             % essential rxns are reported on the linearly merged model (R1, R7)
    -0308             testCase.verifyTrue(all(strcmp(prepData.essentialRxns, {'R1';'R7'})));
    -0309 
    -0310             arrayData.genes     = testModel.genes;
    -0311             arrayData.tissues   = {'a'};
    -0312             arrayData.levels    = getExprForRxnScore(getTstModelRxnScores());
    -0313             arrayData.threshold = 1;
    -0314             evalc(['resModel = ftINIT(prepData,arrayData.tissues{1},[],[],' ...
    -0315                 'arrayData,[],getINITSteps(),true,true,testParams,false);']);
    -0316             % R2 and R7 essential => all rxns on except R3 and R5 (negative
    -0317             % score, not needed for the task)
    -0318             testCase.verifyTrue(all(strcmp(resModel.rxns, ...
    -0319                 {'R1';'R2';'R4';'R6';'R7';'R8';'R9';'R10'})));
    -0320         end
    -0321 
    -0322         function fitTasksPreMergedRuns(testCase)
    -0323             % Remove exchange rxns (required for gap filling) and create a gap
    -0324             % by removing R7; fitTasks with gapFillMode 'preMerged' must add
    -0325             % R7 back.
    -0326             testCase.assumeMILPSolver();
    -0327             testModel      = getTstModel();
    -0328             testModelTasks = getTstModelTasks();
    -0329             testRxnScores  = getTstModelRxnScores();
    -0330 
    -0331             mTempRef = closeModel(testModel);
    -0332             mTempRef = removeReactions(mTempRef, {'R1';'R8'});
    -0333             mTemp    = removeReactions(mTempRef, {'R7'});
    -0334             mTemp.id = 'tmp';
    -0335             tmpRxnScores = testRxnScores([2;3;4;5;6;7;9;10]);
    -0336             evalc(['[~,addedRxnMat] = fitTasks(mTemp,mTempRef,[],' ...
    -0337                 '''printOutput'',false,''rxnScores'',min(tmpRxnScores,-0.1),' ...
    -0338                 '''taskStructure'',testModelTasks,''gapFillMode'',''preMerged'',' ...
    -0339                 '''params'',struct(),''verbose'',false);']);
    -0340             % An equality rather than all(strcmp(...)): an empty selection
    -0341             % makes strcmp return an empty logical, which all() reports as
    -0342             % true, so a run that added nothing would pass silently.
    -0343             testCase.verifyEqual(mTempRef.rxns(any(addedRxnMat,2)), {'R7'});
    -0344         end
    -0345 
    -0346         function fitTasksPreMergedResolveTiesStillFillsCorrectly(testCase)
    -0347             % resolveTies is threaded through fitTasks (gapFillMode preMerged)
    -0348             % -> ftINITFillGaps -> getMinNrFluxes (raven-gecko-parity#104); this
    -0349             % exercises that whole chain end to end. Same fixture as
    -0350             % fitTasksPreMergedRuns above -- correctness (R7 still gets added
    -0351             % back) is what this checks, since that fixture has a unique fill
    -0352             % (no tie to actually break); getMinNrFluxesResolveTiesIsDeterministic
    -0353             % in tAnalysis.m covers the tie-breaking property itself.
    -0354             testCase.assumeMILPSolver();
    -0355             testModel      = getTstModel();
    -0356             testModelTasks = getTstModelTasks();
    -0357             testRxnScores  = getTstModelRxnScores();
    -0358 
    -0359             mTempRef = closeModel(testModel);
    -0360             mTempRef = removeReactions(mTempRef, {'R1';'R8'});
    -0361             mTemp    = removeReactions(mTempRef, {'R7'});
    -0362             mTemp.id = 'tmp';
    -0363             tmpRxnScores = testRxnScores([2;3;4;5;6;7;9;10]);
    -0364             evalc(['[~,addedRxnMat] = fitTasks(mTemp,mTempRef,[],' ...
    -0365                 '''printOutput'',false,''rxnScores'',min(tmpRxnScores,-0.1),' ...
    -0366                 '''taskStructure'',testModelTasks,''gapFillMode'',''preMerged'',' ...
    -0367                 '''params'',struct(),''verbose'',false,''resolveTies'',true);']);
    -0368             testCase.verifyEqual(mTempRef.rxns(any(addedRxnMat,2)), {'R7'});
    -0369         end
    -0370 
    -0371         function fitTasksPreMergedReportsUnfillableTask(testCase)
    -0372             % R7 is the only producer of e[s], so removing it from the
    -0373             % reference model as well leaves the task unfillable. R8 is kept
    -0374             % so that e[s] still takes part in a reaction, and R9 is withheld
    -0375             % from the model so the reference has something to offer: the
    -0376             % MILP is then the thing that has to report the task as
    -0377             % unfillable rather than as "Added 0 reaction(s)".
    -0378             testCase.assumeMILPSolver();
    -0379             testModel      = getTstModel();
    -0380             testModelTasks = getTstModelTasks();
    -0381             testRxnScores  = getTstModelRxnScores();
    -0382 
    -0383             mTempRef = closeModel(testModel);
    -0384             mTempRef = removeReactions(mTempRef, {'R1';'R7'});
    -0385             mTemp    = removeReactions(mTempRef, {'R9'});
    -0386             mTemp.id = 'tmp';
    -0387             tmpRxnScores = testRxnScores([2;3;4;5;6;8;9;10]);
    -0388             lastwarn('');
    -0389             evalc(['[~,addedRxnMat,failedTasks] = fitTasks(mTemp,mTempRef,[],' ...
    -0390                 '''printOutput'',false,''rxnScores'',min(tmpRxnScores,-0.1),' ...
    -0391                 '''taskStructure'',testModelTasks,''gapFillMode'',''preMerged'');']);
    -0392             testCase.verifyTrue(failedTasks(1));
    -0393             testCase.verifyFalse(any(addedRxnMat(:)));
    -0394             % Every way of failing sets failedTasks, so the reason has to be
    -0395             % checked too: without this the test would also pass if the
    -0396             % gap-filling attempt threw before it reached the solver.
    -0397             testCase.verifySubstring(lastwarn, 'no feasible solution exists');
    -0398         end
    -0399 
    -0400         function fitTasksPreMergedHandlesEmptyReferenceSet(testCase)
    -0401             % The reference model holds nothing the model does not already
    -0402             % have, so no reaction can be added. An empty reaction list
    -0403             % means "all reactions" to the MILP, which would otherwise
    -0404             % report the model itself as a solution to an unfillable task.
    -0405             testCase.assumeMILPSolver();
    -0406             testModel      = getTstModel();
    -0407             testModelTasks = getTstModelTasks();
    -0408             testRxnScores  = getTstModelRxnScores();
    -0409 
    -0410             mTempRef = closeModel(testModel);
    -0411             mTempRef = removeReactions(mTempRef, {'R1';'R7';'R8'});
    -0412             mTemp    = mTempRef;
    -0413             mTemp.id = 'tmp';
    -0414             tmpRxnScores = testRxnScores([2;3;4;5;6;9;10]);
    -0415             lastwarn('');
    -0416             evalc(['[~,addedRxnMat,failedTasks] = fitTasks(mTemp,mTempRef,[],' ...
    -0417                 '''printOutput'',false,''rxnScores'',min(tmpRxnScores,-0.1),' ...
    -0418                 '''taskStructure'',testModelTasks,''gapFillMode'',''preMerged'',' ...
    -0419                 '''params'',struct(),''verbose'',false);']);
    -0420             testCase.verifyTrue(failedTasks(1));
    -0421             testCase.verifyFalse(any(addedRxnMat(:)));
    -0422             testCase.verifySubstring(lastwarn, 'no feasible solution exists');
    -0423         end
    -0424 
    -0425         function fitTasksPreMergedWarningHasRealNewlineAndPercent(testCase)
    -0426             % The "could not be gap-filled" warning embeds the task
    -0427             % id/description; a literal "\n" must become a real newline
    -0428             % (not print as the two characters backslash-n), and a "%" in
    -0429             % the task id must survive intact.
    -0430             testCase.assumeMILPSolver();
    -0431             testModel      = getTstModel();
    -0432             testModelTasks = getTstModelTasks();
    -0433             testModelTasks.id = 'Task 50% test';
    -0434             testModelTasks.description = testModelTasks.id;
    -0435             testRxnScores  = getTstModelRxnScores();
    -0436 
    -0437             mTempRef = closeModel(testModel);
    -0438             mTempRef = removeReactions(mTempRef, {'R1';'R7';'R8'});
    -0439             mTemp    = mTempRef;
    -0440             mTemp.id = 'tmp';
    -0441             tmpRxnScores = testRxnScores([2;3;4;5;6;9;10]);
    -0442             lastwarn('');
    -0443             evalc(['fitTasks(mTemp,mTempRef,[],' ...
    -0444                 '''printOutput'',false,''rxnScores'',min(tmpRxnScores,-0.1),' ...
    -0445                 '''taskStructure'',testModelTasks,''gapFillMode'',''preMerged'',' ...
    -0446                 '''params'',struct(),''verbose'',false);']);
    -0447             msg = lastwarn();
    -0448             testCase.verifySubstring(msg, 'Task 50% test');
    -0449             testCase.verifyFalse(contains(msg, '\n'));
    -0450         end
    -0451 
    -0452         function ftINITFillGapsReportsTaskNeedingAnOrphanMet(testCase)
    -0453             % e[s] takes part only in R7 and R8. With both gone from the
    -0454             % reference model no reaction touches e[s] at all, so a task
    -0455             % requiring net production of it cannot be filled from that
    -0456             % reference. That requirement is carried in b, and simplifyModel
    -0457             % must leave the metabolite in place: without its row the MILP
    -0458             % solves a problem that no longer asks for e[s], and reports the
    -0459             % task as satisfiable.
    -0460             testCase.assumeMILPSolver();
    -0461             testModel     = getTstModel();
    -0462             testRxnScores = getTstModelRxnScores();
    -0463 
    -0464             mTempRef = closeModel(testModel);
    -0465             mTempRef = removeReactions(mTempRef, {'R1';'R7';'R8'});
    -0466             % R5 is dropped from the model only so that the reference offers
    -0467             % a candidate reaction to consider; adding it back does not make
    -0468             % e[s] reachable.
    -0469             mTemp    = removeReactions(mTempRef, {'R5'});
    -0470             mTemp.id = 'tmp';
    -0471             tmpRxnScores = min(testRxnScores([2;3;4;5;6;9;10]), -0.1);
    -0472 
    -0473             tModel = setTaskBounds(mTemp);
    -0474             tRef   = setTaskBounds(mTempRef);
    -0475             sol = solveLP(tModel);
    -0476             testCase.assertEmpty(sol.x, 'the task must start out infeasible');
    -0477 
    -0478             evalc(['[addedRxns,~,exitFlag] = ftINITFillGaps(tModel,mTemp,tRef,' ...
    -0479                 'false,true,tmpRxnScores,struct(),false);']);
    -0480             testCase.verifyEqual(exitFlag, -1);
    -0481             testCase.verifyEmpty(addedRxns);
    -0482         end
    -0483 
    -0484         function ftINITFillGapsReportsNoCandidateReactions(testCase)
    -0485             % The reference model holds no reaction the model does not
    -0486             % already have, so nothing can be added to satisfy the task. An
    -0487             % empty reaction list means "all reactions" to the MILP, and its
    -0488             % solution is then sized for every reaction rather than for the
    -0489             % empty set of candidates it is indexed against.
    -0490             testCase.assumeMILPSolver();
    -0491             testModel     = getTstModel();
    -0492             testRxnScores = getTstModelRxnScores();
    -0493 
    -0494             m = closeModel(testModel);
    -0495             m = removeReactions(m, {'R1'});   % R7/R8 stay, so e[s] is reachable
    -0496             mTemp = m;
    -0497             mTemp.id = 'tmp';
    -0498             tmpRxnScores = min(testRxnScores(2:10), -0.1);
    -0499 
    -0500             % Both sides hold the same reactions, so there is no candidate
    -0501             % to add. Only the reference may take up a[s], so only it can
    -0502             % satisfy the task.
    -0503             tRef   = setTaskBounds(m);
    -0504             tModel = setTaskBounds(mTemp);
    -0505             tModel.b(strcmp(tModel.mets, 'as'), 1) = 0;
    -0506             sol = solveLP(tModel);
    -0507             testCase.assertEmpty(sol.x, 'the task must start out infeasible');
    -0508 
    -0509             evalc(['[addedRxns,~,exitFlag] = ftINITFillGaps(tModel,mTemp,tRef,' ...
    -0510                 'false,true,tmpRxnScores,struct(),false);']);
    -0511             testCase.verifyEqual(exitFlag, -1);
    -0512             testCase.verifyEmpty(addedRxns);
    -0513         end
    -0514 
    -0515         function ftINITMetabolomicsRuns(testCase)
    -0516             % Detected metabolites steer ftINIT towards alternative pathways.
    -0517             testCase.assumeMILPSolver();
    -0518             testModel  = getTstModel();
    -0519             testParams = struct();
    -0520             evalc('prepData = prepINITModel(testModel, {}, {''R7'';''R10''}, false, {}, ''s'');');
    -0521 
    -0522             arrayData.genes     = testModel.genes;
    -0523             arrayData.tissues   = {'a'};
    -0524             arrayData.levels    = getExprForRxnScore(getTstModelRxnScores());
    -0525             arrayData.threshold = 1;
    -0526 
    -0527             % adding metabolite f favours R9/R10 again (presence of R2 is random)
    -0528             evalc(['resModel = ftINIT(prepData,arrayData.tissues{1},[],[],' ...
    -0529                 'arrayData,{''f''},getINITSteps(),true,true,testParams,false);']);
    -0530             if length(resModel.rxns) == 7
    -0531                 testCase.verifyTrue(all(strcmp(resModel.rxns, ...
    -0532                     {'R1';'R4';'R6';'R7';'R8';'R9';'R10'})));
    -0533             else
    -0534                 testCase.verifyTrue(all(strcmp(resModel.rxns, ...
    -0535                     {'R1';'R2';'R4';'R6';'R7';'R8';'R9';'R10'})));
    -0536             end
    -0537 
    -0538             % metabolites a, e, f give the same result
    -0539             evalc(['resModel = ftINIT(prepData,arrayData.tissues{1},[],[],' ...
    -0540                 'arrayData,{''f'';''a'';''e''},getINITSteps(),true,true,testParams,false);']);
    -0541             if length(resModel.rxns) == 7
    -0542                 testCase.verifyTrue(all(strcmp(resModel.rxns, ...
    -0543                     {'R1';'R4';'R6';'R7';'R8';'R9';'R10'})));
    -0544             else
    -0545                 testCase.verifyTrue(all(strcmp(resModel.rxns, ...
    -0546                     {'R1';'R2';'R4';'R6';'R7';'R8';'R9';'R10'})));
    -0547             end
    -0548 
    -0549             % metabolite b turns on R2 and R3/R5 and turns off R9
    -0550             evalc(['resModel = ftINIT(prepData,arrayData.tissues{1},[],[],' ...
    -0551                 'arrayData,{''b''},getINITSteps(),true,true,testParams,false);']);
    -0552             testCase.verifyTrue(all(strcmp(resModel.rxns, ...
    -0553                 {'R1';'R2';'R3';'R4';'R5';'R6';'R7';'R8'})));
    -0554 
    -0555             % testModel5 contains reactions that are not linearly merged
    -0556             testModel5 = getTstModel5();
    -0557             arrayData.genes  = testModel5.genes;
    -0558             arrayData.levels = getExprForRxnScore(getTstModel5RxnScores());
    -0559 
    -0560             evalc('prepData5 = prepINITModel(testModel5, {}, {''R7'';''R10''}, false, {}, ''s'');');
    -0561             evalc(['resModel = ftINIT(prepData5,arrayData.tissues{1},[],[],' ...
    -0562                 'arrayData,{},getINITSteps(),true,true,testParams,false);']);
    -0563             % a->g->e via R11/R13 (score -2) ties the R2 path (score -2), so
    -0564             % which of the two is returned is a tie-break that differs between
    -0565             % solvers. Neither route uses R9/R10. isequal rather than strcmp,
    -0566             % which errors on two lists of different length.
    -0567             testCase.verifyTrue( ...
    -0568                 isequal(resModel.rxns, {'R1';'R4';'R6';'R7';'R8';'R11';'R13'}) || ...
    -0569                 isequal(resModel.rxns, {'R1';'R2';'R4';'R6';'R7';'R8'}));
    -0570 
    -0571             % adding metabolite g drops R7
    -0572             evalc('prepData5 = prepINITModel(testModel5, {}, {''R10''}, false, {}, ''s'');');
    -0573             arrayData.levels(7) = getExprForRxnScore(-1.1); % avoid randomness
    -0574             evalc(['resModel = ftINIT(prepData5,arrayData.tissues{1},[],[],' ...
    -0575                 'arrayData,{''g''},getINITSteps(),true,true,testParams,false);']);
    -0576             testCase.verifyTrue(all(strcmp(resModel.rxns, ...
    -0577                 {'R1';'R4';'R6';'R8';'R11';'R13'})));
    -0578         end
    -0579 
    -0580         function ftINITFullVsThreeStepRuns(testCase)
    -0581             % On a larger model the three-step and "full" variants must give
    -0582             % consistent results.
    -0583             testCase.assumeMILPSolver();
    -0584             testModelL          = getTstModelL();
    -0585             testModelLGeneScores = getTstModelLGeneScores();
    -0586             testParams          = struct();
    -0587 
    -0588             arrayDataL.genes     = testModelL.genes;
    -0589             arrayDataL.tissues   = {'t1'};
    -0590             arrayDataL.levels    = getExprForRxnScore(testModelLGeneScores, 1);
    -0591             arrayDataL.threshold = 1;
    -0592 
    -0593             evalc('prepDataL = prepINITModel(testModelL, [], {}, false, {}, ''s'');');
    -0594             evalc(['mres = ftINIT(prepDataL,arrayDataL.tissues{1},[],[],' ...
    -0595                 'arrayDataL,[],getINITSteps(),true,true,testParams,false);']);
    -0596             evalc(['mres2 = ftINIT(prepDataL,arrayDataL.tissues{1},[],[],' ...
    -0597                 'arrayDataL,[],getINITSteps([], ''full''),true,true,testParams,false);']);
    -0598 
    -0599             expResult = {'S1';'S2';'S3';'S4';'S5';'S6';'S7';'S8';'S9';'E1';'E2'; ...
    -0600                 'E3';'E4';'E5';'E6';'E8';'E9';'R1';'R2';'R3';'R4';'R5';'R6';'R7'; ...
    -0601                 'R8';'R9';'R12';'R13';'R14';'R15'};
    -0602             testCase.verifyTrue(all(contains(mres.rxns,  expResult)));
    -0603             testCase.verifyTrue(all(contains(mres2.rxns, expResult)));
    -0604         end
    -0605 
    -0606         function ftINITSeriesVariantsRun(testCase)
    -0607             % Exercises the 2-step series from the paper, including the
    -0608             % allowExcretion constraint it relies on.
    -0609             testCase.assumeMILPSolver();
    -0610             testModel  = getTstModel();
    -0611             testParams = struct();
    -0612             evalc('prepData = prepINITModel(testModel, {}, {}, false, {}, ''s'');');
    -0613             arrayData.genes     = testModel.genes;
    -0614             arrayData.tissues   = {'a'};
    -0615             arrayData.levels    = getExprForRxnScore(getTstModelRxnScores());
    -0616             arrayData.threshold = 1;
    -0617 
    -0618             steps = getINITSteps([], '2+1');
    -0619             evalc(['resModel = ftINIT(prepData,arrayData.tissues{1},[],[],' ...
    -0620                 'arrayData,[],steps,true,true,testParams,false);']);
    -0621             % Same answer as the '1+1' series in ftINITPipelineRuns.
    -0622             testCase.verifyEqual(resModel.rxns, {'R1';'R4';'R6';'R8';'R9';'R10'});
    -0623 
    -0624             steps = getINITSteps([], '2+0');
    -0625             evalc(['resModel = ftINIT(prepData,arrayData.tissues{1},[],[],' ...
    -0626                 'arrayData,[],steps,true,true,testParams,false);']);
    -0627             % '2+0' skips step 3, so the GPR-less transport R2 survives.
    -0628             testCase.verifyEqual(resModel.rxns, {'R1';'R2';'R4';'R6';'R8';'R9';'R10'});
    -0629         end
    -0630 
    -0631         function scoreModelDataPrecedenceReactionPrefersHpaWholeRule(testCase)
    -0632             % dataPrecedence 'reaction': any gene of a reaction having HPA
    -0633             % data means array data is ignored for the whole reaction, not
    -0634             % just that gene. R7 is "G1 or G4", G1 has (low) HPA data and G4
    -0635             % only array data; per-gene scoring would let G4's higher array
    -0636             % score win, per-reaction scoring must not.
    -0637             m = scoringTestModel();
    -0638             a = scoringArrayData();
    -0639             h = scoringHpaDataLowG1();
    -0640             perGene = scoreModel(m, h, a, 't1', ...
    -0641                 'isozymeScoring', 'max', 'complexScoring', 'max');
    -0642             perRxn  = scoreModel(m, h, a, 't1', ...
    -0643                 'isozymeScoring', 'max', 'complexScoring', 'max', ...
    -0644                 'dataPrecedence', 'reaction');
    -0645             r7 = strcmp(m.rxns, 'R7');
    -0646 
    -0647             testCase.verifyEqual(perRxn(r7), -8, 'AbsTol', 1e-12);
    -0648             % Per gene, G4's array score is seen too, and it is higher
    -0649             testCase.verifyEqual(perGene(r7), 5*log(3), 'AbsTol', 1e-12);
    -0650             testCase.verifyNotEqual(perRxn(r7), perGene(r7));
    -0651         end
    -0652 
    -0653         function scoreModelAverageReducesDownTheRule(testCase)
    -0654             % multipleGeneScoring 'average' averages down the grRule, so a
    -0655             % complex counts once against its isozymes rather than once per
    -0656             % subunit. R5 is "(G1 and G2) or G3".
    -0657             m = scoringTestModel();
    -0658             a = scoringArrayData();
    -0659             scores = scoreModel(m, [], a, 't1', ...
    -0660                 'isozymeScoring', 'average', 'complexScoring', 'average');
    -0661             r5 = strcmp(m.rxns, 'R5');
    -0662 
    -0663             g = 5*log([1.5; 2; 2.5]);          % scores of G1, G2, G3
    -0664             testCase.verifyEqual(scores(r5), mean([mean(g(1:2)); g(3)]), ...
    -0665                 'AbsTol', 1e-12);
    -0666             testCase.verifyNotEqual(scores(r5), mean(g));   % not a flat mean
    -0667         end
    -0668 
    -0669         function scoreModelReadsGrRulesNotRxnGeneMat(testCase)
    -0670             % A model carrying its gene associations only in grRules (no
    -0671             % rxnGeneMat) must still score every reaction correctly.
    -0672             m = scoringTestModel();
    -0673             a = scoringArrayData();
    -0674             noMat = m;
    -0675             noMat.rxnGeneMat = [];
    -0676             withMat = scoreModel(m, [], a, 't1');
    -0677             withoutMat = scoreModel(noMat, [], a, 't1');
    -0678             testCase.verifyEqual(withoutMat, withMat, 'AbsTol', 1e-12);
    -0679         end
    -0680 
    -0681         function scoreModelRejectsUnknownDataPrecedence(testCase)
    -0682             m = scoringTestModel();
    -0683             a = scoringArrayData();
    -0684             testCase.verifyError( ...
    -0685                 @() scoreModel(m, [], a, 't1', 'dataPrecedence', 'model'), ...
    -0686                 'RAVEN:badInput');
    -0687         end
    -0688 
    -0689     end
    -0690 end
    -0691 
    -0692 %==========================================================================
    -0693 % Synthetic test models, illustrated in tINITtestInfo/06.svg and 07.svg.
    -0694 %==========================================================================
    -0695 
    -0696 function testModel = getTstModel()
    -0697 testModel = struct();
    -0698 testModel.id = 'testModel';
    -0699 testModel.rxns = {};
    -0700 testModel.S = [];
    -0701 testModel.rev = [];
    -0702 testModel.mets = {'as';'ac';'bc';'cc';'dc';'ec';'es';'fc'};
    -0703 testModel.metNames = {'a';'a';'b';'c';'d';'e';'e';'f'};
    -0704 testModel.comps = {'s';'c'};
    -0705 testModel.compNames = testModel.comps;
    -0706 testModel.metComps = [1;2;2;2;2;2;1;2];
    -0707 testModel.genes = {'G1';'G2';'G3';'G4';'G5';'G6';'G7';'G8';'G9';'G10'};
    -0708 testModel.grRules = {};
    -0709 testModel.rxnGeneMat = [];
    -0710 
    -0711 rxnsToAdd = struct();
    -0712 rxnsToAdd.rxns = {'R1';'R2';'R3';'R4';'R5';'R6';'R7';'R8';'R9';'R10'};
    -0713 rxnsToAdd.equations = {'=> a[s]';...
    -0714     'a[s] <=> a[c]';...
    -0715     'a[c] <=> b[c] + c[c]';...
    -0716     'a[c] <=> 2 d[c]';...
    -0717     'b[c] + c[c] => e[c]';...
    -0718     '2 d[c] => e[c]';...
    -0719     'e[c] => e[s]';...
    -0720     'e[s] =>';...
    -0721     'a[c] <=> f[c]';...
    -0722     'f[c] <=> e[c]'};
    -0723 rxnsToAdd.grRules = {'';'';'G3';'G4';'G5';'G6';'G7';'';'G9';'G10'};
    -0724 evalc('testModel = addRxns(testModel,rxnsToAdd, 3);');
    -0725 testModel.c = [0;0;0;0;0;0;0;1;0;0];
    -0726 testModel.ub = repmat(1000,10,1);
    -0727 testModel.lb = [0;-1000;-1000;-1000;0;0;0;0;-1000;-1000];
    -0728 testModel.rxnNames = testModel.rxns;
    -0729 testModel.b = repmat(0,8,1);
    -0730 end
    -0731 
    -0732 function testModelRxnScores = getTstModelRxnScores()
    -0733 testModelRxnScores = [-2;-2;-1;7;0.5;0.5;-1;-2;-3;3.5];
    -0734 end
    -0735 
    -0736 function model = setTaskBounds(model)
    -0737 % The b bounds that fitTasks (gapFillMode 'preMerged') derives from getTstModelTasks:
    -0738 % a[s] may be taken up freely, e[s] has to leave at exactly one unit.
    -0739 model.b = zeros(numel(model.mets), 2);
    -0740 model.b(strcmp(model.mets, 'as'), :) = [-inf 0];
    -0741 model.b(strcmp(model.mets, 'es'), :) = [1 1];
    -0742 end
    -0743 
    -0744 function testModelTasks = getTstModelTasks()
    -0745 testModelTasks = struct();
    -0746 testModelTasks.id = 'Gen e[s] from a[s]';
    -0747 testModelTasks.description = 'Gen e[s] from a[s]';
    -0748 testModelTasks.shouldFail = false;
    -0749 testModelTasks.printFluxes = false;
    -0750 testModelTasks.comments = '';
    -0751 testModelTasks.inputs = {'a[s]'};
    -0752 testModelTasks.LBin = 0;
    -0753 testModelTasks.UBin = inf;
    -0754 testModelTasks.outputs = {'e[s]'};
    -0755 testModelTasks.LBout = 1;
    -0756 testModelTasks.UBout = 1;
    -0757 testModelTasks.equations = {};
    -0758 testModelTasks.LBequ = [];
    -0759 testModelTasks.UBequ = [];
    -0760 testModelTasks.changed = {};
    -0761 testModelTasks.LBrxn = {};
    -0762 testModelTasks.UBrxn = {};
    -0763 end
    -0764 
    -0765 function testModel2 = getTstModel2()
    -0766 testModel2 = struct();
    -0767 testModel2.id = 'testModel2';
    -0768 testModel2.rxns = {};
    -0769 testModel2.S = [];
    -0770 testModel2.rev = [];
    -0771 testModel2.mets = {'a';'b'};
    -0772 testModel2.metNames = {'a';'b'};
    -0773 testModel2.comps = {'s'};
    -0774 testModel2.compNames = testModel2.comps;
    -0775 testModel2.metComps = [1;1];
    -0776 testModel2.genes = {'G1';'G2';'G3';'G4'};
    -0777 testModel2.grRules = {};
    -0778 testModel2.rxnGeneMat = [];
    -0779 
    -0780 rxnsToAdd = struct();
    -0781 rxnsToAdd.rxns = {'R1';'R2';'R3';'R4'};
    -0782 rxnsToAdd.equations = {'a[s] <=>';...
    -0783     'a[s] => b[s]';...
    -0784     'a[s] <=> b[s]';...
    -0785     'b[s] =>'};
    -0786 rxnsToAdd.grRules = testModel2.genes;
    -0787 evalc('testModel2 = addRxns(testModel2,rxnsToAdd,3,'''',true,true);');
    -0788 testModel2.c = [0;0;0;1];
    -0789 testModel2.ub = repmat(1000,4,1);
    -0790 testModel2.lb = [-1000;0;-1000;0];
    -0791 testModel2.rxnNames = testModel2.rxns;
    -0792 testModel2.b = zeros(2,1);
    -0793 end
    -0794 
    -0795 function testModel4 = getTstModel4()
    -0796 testModel4 = getTstModel2();
    -0797 
    -0798 rxnsToAdd = struct();
    -0799 rxnsToAdd.rxns = {'R5';'R6';'R7';'R8';'R9';'R10';'R11'};
    -0800 rxnsToAdd.equations = {'5 a[s] <=> 5 d[s]';...
    -0801     'e[s] <=> d[s]';
    -0802     'f[s] + g[s] <=> e[s]';
    -0803     'b[s] <=> f[s]';
    -0804     'h[s] <=> g[s]';
    -0805     'h[s] =>';
    -0806     'e[s] => g[s]'};
    -0807 rxnsToAdd.grRules = {'G5';'G6';'G7';'G8';'G9';'G10';'G11'};
    -0808 [~,testModel4] = evalc("addRxns(testModel4,rxnsToAdd, 3, [], true, true);");
    -0809 end
    -0810 
    -0811 function testModel5 = getTstModel5()
    -0812 testModel = getTstModel();
    -0813 
    -0814 rxnsToAdd = struct();
    -0815 rxnsToAdd.rxns = {'R11';'R12';'R13';'R14'};
    -0816 rxnsToAdd.equations = {'a[c] <=> g[c]';...
    -0817     'a[c] <=> g[c]';...
    -0818     'g[c] <=> e[c]';...
    -0819     'g[c] <=> e[c]'};
    -0820 rxnsToAdd.grRules = {'G11';'G12';'G13';'G14'};
    -0821 [~,testModel5] = evalc("addRxns(testModel,rxnsToAdd, 3, [], true, true);");
    -0822 end
    -0823 
    -0824 function testModel5RxnScores = getTstModel5RxnScores()
    -0825 testModel5RxnScores = [getTstModelRxnScores();-1;-1.5;-1;-1.5];
    -0826 end
    -0827 
    -0828 function testModelL = getTstModelL()
    -0829 testModelL = struct();
    -0830 testModelL.id = 'testModel';
    -0831 testModelL.rxns = {};
    -0832 testModelL.S = [];
    -0833 testModelL.rev = [];
    -0834 testModelL.metNames = {'e1';'e2';'e3';'e4';'e5';'e6';'e7';'e8';'e9';'e1';'e2';'e3';'e4';'e5';'e6';'e7';'e8';'e9';'x1';'x2';'x3';'x4';'x5';'x6';'x7';'x8';'x9';'x10';'x11'};
    -0835 testModelL.comps = {'s';'c'};
    -0836 testModelL.compNames = testModelL.comps;
    -0837 testModelL.metComps = [1;1;1;1;1;1;1;1;1;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2;2];
    -0838 testModelL.mets = strcat(testModelL.metNames, testModelL.comps(testModelL.metComps));
    -0839 
    -0840 testModelL.grRules = {};
    -0841 testModelL.rxnGeneMat = [];
    -0842 
    -0843 testModelL.genes = {'Ge1';'Ge2';'Ge4';'Ge5';'Ge7';'Ge9'; 'Gr1';'Gr2';'Gr3';'Gr5';'Gr6';'Gr7';'Gr8';'Gr9';'Gr10';'Gr11';'Gr12';'Gr14';'Gr15'};
    -0844 
    -0845 testModelL.ub = [];
    -0846 testModelL.lb = [];
    -0847 
    -0848 rxnsToAdd = struct();
    -0849 rxnsToAdd.rxns = {  'S1';'S2';'S3';'S4';'S5';'S6';'S7';'S8';'S9';'E1';'E2';'E2b';'E3';'E4';'E5';'E6';'E7';'E8';'E9';'R1';'R2';'R3';'R4';'R5';'R6';'R7';'R8';'R9';'R10';'R11';'R12';'R13';'R14';'R15'};
    -0850 rxnsToAdd.grRules = {'';  '';  '';  '';  '';  '';  '';  '';  ''; 'Ge1';'Ge2';'';'';'Ge4';'Ge5';'';'Ge7';'';'Ge9'; 'Gr1';'Gr2';'Gr3';'';'Gr5';'Gr6';'Gr7';'Gr8';'Gr9';'Gr10';'Gr11';'Gr12';'';'Gr14';'Gr15'};
    -0851 rxnsToAdd.equations = {'e1[s] <=>';...
    -0852     'e2[s] <=>';...
    -0853     'e3[s] <=>';...
    -0854     'e4[s] <=>';...
    -0855     'e5[s] <=>';...
    -0856     'e6[s] <=>';...
    -0857     'e7[s] <=>';...
    -0858     'e8[s] <=>';...
    -0859     'e9[s] <=>';...
    -0860     'e1[s] <=> e1[c]';...
    -0861     'e2[s] <=> e2[c]';...
    -0862     'e2[s] <=> e2[c]';... %b variant
    -0863     'e3[s] <=> e3[c]';...
    -0864     'e4[s] <=> e4[c]';...
    -0865     'e5[s] <=> e5[c]';...
    -0866     'e6[s] <=> e6[c]';...
    -0867     'e7[s] <=> e7[c]';...
    -0868     'e8[s] <=> e8[c]';...
    -0869     'e9[s] <=> e9[c]';...
    -0870     'e1[c] + e2[c] <=> x1[c]';... %R1
    -0871     'e1[c] + e3[c] => x2[c] + x3[c]';... %R2
    -0872     'e4[c] + x3[c] => x4[c] + x5[c]';... %R3
    -0873     'e5[c] + e6[c] + x4[c] => 2 x2[c] + x6[c]';... %R4
    -0874     'x1[c] + x2[c] <=> x7[c] + 2 x8[c]';... %R5
    -0875     'x2[c] + x8[c] => x3[c] + x9[c]';... %R6
    -0876     'x4[c] <=> x9[c]';... %R7
    -0877     'x5[c] <=> x9[c]';... %R8
    -0878     'x6[c] <=> x10[c]';... %R9
    -0879     'x6[c] <=> x11[c]';... %R10
    -0880     'x10[c] + 2 x11[c] => e7[c]';... %R11
    -0881     'x9[c] + x10[c] <=> e8[c]';... %R12
    -0882     'x7[c] + x8[c] + x9[c] => e9[c]';... %R13
    -0883     'x6[c] => x9[c]';... %R14
    -0884     'x3[c] => x9[c]'... %R15
    -0885     };
    -0886 evalc('testModelL = addRxns(testModelL,rxnsToAdd, 3);');
    -0887 testModelL.c = [0;0;0;0;0;0;0;1;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0];
    -0888 testModelL.rxnNames = testModelL.rxns;
    -0889 testModelL.b = repmat(0,length(testModelL.mets),1);
    -0890 end
    -0891 
    -0892 function testModelLGeneScores = getTstModelLGeneScores()
    -0893 %testModelL.genes = {'Ge1';'Ge2';'Ge4';'Ge5';'Ge7';'Ge9'; 'Gr1';'Gr2';'Gr3';'Gr5';'Gr6';'Gr7';'Gr8';'Gr9';'Gr10';'Gr11';'Gr12';'Gr14';'Gr15'};
    -0894 testModelLGeneScores = [3; -1;   8;    6;    -5;    5;     4;    5;    2;    3;    6;    1;    3;    1;    -3;    1;     3;      1;    2];
    -0895 end
    -0896 
    -0897 %==========================================================================
    -0898 % Fixtures for the scoreModel* tests: a small model whose grRules cover
    -0899 % every rule shape the scoring treats differently (none, single, OR, AND,
    -0900 % nested, mixed-data-source OR), and matching array/HPA data.
    -0901 %==========================================================================
    -0902 
    -0903 function m = scoringTestModel()
    -0904 m = struct();
    -0905 m.id = 'scoringTest';
    -0906 m.rxns = {}; m.S = []; m.rev = [];
    -0907 m.mets     = {'ac'; 'bc'; 'cc'; 'dc'; 'ec'};
    -0908 m.metNames = {'a'; 'b'; 'c'; 'd'; 'e'};
    -0909 m.comps = {'c'}; m.compNames = m.comps;
    -0910 m.metComps = [1; 1; 1; 1; 1];
    -0911 m.genes = {'G1'; 'G2'; 'G3'; 'G4'};
    -0912 m.grRules = {}; m.rxnGeneMat = [];
    -0913 r = struct();
    -0914 r.rxns = {'R1'; 'R2'; 'R3'; 'R4'; 'R5'; 'R6'; 'R7'};
    -0915 r.equations = {'=> a[c]'; 'a[c] => b[c]'; 'b[c] => c[c]'; ...
    -0916     'c[c] => d[c]'; 'a[c] => d[c]'; 'd[c] => e[c]'; 'e[c] =>'};
    -0917 r.grRules = {''; 'G1'; 'G1 or G2'; 'G1 and G2'; ...
    -0918     '(G1 and G2) or G3'; 'G4'; 'G1 or G4'};
    -0919 evalc('m = addRxns(m, r, 3);');
    -0920 m.c = zeros(7, 1);
    -0921 m.lb = zeros(7, 1);
    -0922 m.ub = repmat(1000, 7, 1);
    -0923 m.rxnNames = m.rxns;
    -0924 m.b = zeros(5, 1);
    -0925 evalc('[m.grRules, m.rxnGeneMat] = standardizeGrRules(m, true);');
    -0926 end
    -0927 
    -0928 function a = scoringArrayData()
    -0929 % Distinct, uncapped scores: with a threshold of 1 the score of each gene
    -0930 % is 5*log(level), i.e. 2.03, 3.47, 4.58 and 5.49.
    -0931 a = struct();
    -0932 a.genes     = {'G1'; 'G2'; 'G3'; 'G4'};
    -0933 a.tissues   = {'t1'; 't2'};
    -0934 a.celltypes = {'ct1'; 'ct2'};
    -0935 a.levels    = [1.5 1; 2 1; 2.5 1; 3 1];
    -0936 a.threshold = ones(4, 1);
    -0937 end
    -0938 
    -0939 function h = scoringHpaDataLowG1()
    -0940 % Only G1 has HPA data, and its level is the lowest one, so any array
    -0941 % score for the other genes outranks it.
    -0942 h = struct();
    -0943 h.genes      = {'G1'};
    -0944 h.tissues    = {'t1'};
    -0945 h.celltypes  = {'ct1'};
    -0946 h.levels     = {'High', 'Medium', 'Low', 'None'};
    -0947 h.gene2Level = 4;
    -0948 end
    -
    Generated by m2html © 2005
    - - \ No newline at end of file diff --git a/docs/html/testing/function_tests/tIO.html b/docs/html/testing/function_tests/tIO.html deleted file mode 100644 index 527bfec2..00000000 --- a/docs/html/testing/function_tests/tIO.html +++ /dev/null @@ -1,378 +0,0 @@ - - - - Description of tIO - - - - - - - - - - - - - -

    tIO -

    - -

    PURPOSE ^

    -
    - -

    SYNOPSIS ^

    -
    This is a script file.
    - -

    DESCRIPTION ^

    -
    - - -

    CROSS-REFERENCE INFORMATION ^

    -This function calls: - -This function is called by: - - - -

    SUBFUNCTIONS ^

    - - -

    SOURCE CODE ^

    -
    0001 classdef tIO < RavenTestCase
    -0002 % tIO  Tests for the import/export and file-utility functions in io/.
    -0003 %
    -0004 %   Format support is exercised mainly via export->import round-trips on the
    -0005 %   test model and via importing the tutorial 'empty' files.
    -0006 
    -0007     methods (Test)
    -0008 
    -0009         function exportForGitWritesDependencies(testCase)
    -0010             % Exercises the toolbox-version lookup in exportForGit via the
    -0011             % dependencies.txt it writes
    -0012             f = fullfile(testCase.ravenRoot,'tutorial','empty.xml');
    -0013             evalc('model = importModel(f);');
    -0014             outDir = tempname; mkdir(outDir);
    -0015             c = onCleanup(@() rmdir(outDir,'s'));
    -0016             evalc("exportForGit(model,'path',outDir,'formats',{'xml'},'subDirs',false)");
    -0017             dep = fileread(fullfile(outDir,'dependencies.txt'));
    -0018             testCase.verifySubstring(dep, 'RAVEN_toolbox');
    -0019         end
    -0020 
    -0021         function exportForGitMainBranchFlagRejectsNonMain(testCase)
    -0022             % mainBranchFlag should error when RAVEN is *not* on main/master,
    -0023             % which this test, run from a feature branch, always satisfies.
    -0024             outDir = tempname; mkdir(outDir);
    -0025             c = onCleanup(@() rmdir(outDir,'s'));
    -0026             testCase.verifyError(@() evalc(['exportForGit(testCase.model,''path'',outDir,' ...
    -0027                 '''formats'',{''xml''},''subDirs'',false,''mainBranchFlag'',true);']), ...
    -0028                 ?MException);
    -0029         end
    -0030 
    -0031         function importModelHandlesSpeciesAndReactionEdgeCases(testCase)
    -0032             % A model where: some species have no SBO term while others do
    -0033             % (and disagree with each other); a multi-reaction objective;
    -0034             % a reaction with more than one SUBSYSTEM note; and species
    -0035             % whose charge is genuinely (not just by default) zero.
    -0036             f = fullfile(testCase.ravenRoot,'testing','function_tests','test_data', ...
    -0037                 'importModelEdgeCases.xml');
    -0038             evalc('m = importModel(f);');
    -0039 
    -0040             % SBO terms must land on the metabolite that actually has them,
    -0041             % not be shifted onto the wrong one because one species (m2)
    -0042             % has none.
    -0043             testCase.verifyTrue(isfield(m,'metMiriams'));
    -0044             testCase.verifyEmpty(m.metMiriams{strcmp(m.mets,'m2')});
    -0045             testCase.verifyNotEmpty(m.metMiriams{strcmp(m.mets,'m3')});
    -0046 
    -0047             % Both reactions in the combined objective must be captured.
    -0048             testCase.verifyEqual(m.c(strcmp(m.rxns,'r1')), 1);
    -0049             testCase.verifyEqual(m.c(strcmp(m.rxns,'r2')), 2);
    -0050 
    -0051             % Two SUBSYSTEM notes on one reaction must stay two entries.
    -0052             testCase.verifyEqual(numel(m.subSystems{strcmp(m.rxns,'r1')}), 2);
    -0053 
    -0054             % All-genuinely-neutral charges must not be dropped.
    -0055             testCase.verifyTrue(isfield(m,'metCharges'));
    -0056             testCase.verifyEqual(m.metCharges, zeros(4,1));
    -0057         end
    -0058 
    -0059         function importModelStripsRegexSpecialCompartmentName(testCase)
    -0060             % A compartment name containing regex metacharacters ("Golgi
    -0061             % (cis)") must still be recognized and stripped from the
    -0062             % metabolite name suffix, not corrupt the matching pattern.
    -0063             f = fullfile(testCase.ravenRoot,'testing','function_tests','test_data', ...
    -0064                 'importModelSpecialCompName.xml');
    -0065             evalc('m = importModel(f);');
    -0066             testCase.verifyEqual(m.metNames{1}, 'metabolite one');
    -0067         end
    -0068 
    -0069         function importModelReadsSBML(testCase)
    -0070             f = fullfile(testCase.ravenRoot,'tutorial','empty.xml');
    -0071             evalc('m = importModel(f);');
    -0072             testCase.verifyTrue(isfield(m, 'rxns'));
    -0073         end
    -0074 
    -0075         function readYAMLmodelReadsYml(testCase)
    -0076             f = fullfile(testCase.ravenRoot,'tutorial','empty.yml');
    -0077             evalc('m = readYAMLmodel(f);');
    -0078             testCase.verifyTrue(isfield(m, 'rxns'));
    -0079         end
    -0080 
    -0081         function parseYAMLReturnsNestedTree(testCase)
    -0082             f = [tempname '.yml'];
    -0083             testCase.addTeardown(@() delete(f));
    -0084             fid = fopen(f,'w');
    -0085             fprintf(fid, [ ...
    -0086                 '# a full-line comment\n' ...
    -0087                 'prelude:\n' ...
    -0088                 '  reset_exchanges: out  # trailing comment\n' ...
    -0089                 'cofactor_pseudoreaction:\n' ...
    -0090                 '  rxn_id: r_4598\n' ...
    -0091                 '  remove_mets:\n' ...
    -0092                 '    - { met: s_3714 }\n' ...
    -0093                 'bounds:\n' ...
    -0094                 '  - { rxn: r_1654, lb: -1000 }\n' ...
    -0095                 '  - { rxn: r_1663, lb: 0, ub: 0 }\n' ...
    -0096                 'expected_uptake_count: 15\n']);
    -0097             fclose(fid);
    -0098 
    -0099             out = parseYAML(f);
    -0100             testCase.verifyEqual(out.prelude.reset_exchanges, 'out')
    -0101             testCase.verifyEqual(out.cofactor_pseudoreaction.rxn_id, 'r_4598')
    -0102             testCase.verifyEqual(out.cofactor_pseudoreaction.remove_mets{1}.met, 's_3714')
    -0103             testCase.verifyEqual(numel(out.bounds), 2)
    -0104             testCase.verifyEqual(out.bounds{1}.rxn, 'r_1654')
    -0105             testCase.verifyEqual(out.bounds{1}.lb, -1000)
    -0106             testCase.verifyFalse(isfield(out.bounds{1}, 'ub'))
    -0107             testCase.verifyEqual(out.bounds{2}.ub, 0)
    -0108             testCase.verifyEqual(out.expected_uptake_count, 15)
    -0109         end
    -0110 
    -0111         function exportImportSBMLRoundTrip(testCase)
    -0112             f = [tempname '.xml'];
    -0113             testCase.addTeardown(@() delete(f));
    -0114             evalc('exportModel(testCase.model, f);');
    -0115             evalc('m2 = importModel(f);');
    -0116             testCase.verifyEqual(numel(m2.rxns), numel(testCase.model.rxns));
    -0117         end
    -0118 
    -0119         function exportImportSBMLRoundTripPreservesDeltaG(testCase)
    -0120             % rxnDeltaG/metDeltaG round-trip through a <p>deltaG: VALUE</p>
    -0121             % notes entry -- the same key and format raven-toolbox reads
    -0122             % and writes, so either toolbox's SBML file is a complete
    -0123             % round trip for the other (RAVEN#724). An untouched reaction
    -0124             % or metabolite stays NaN, and the field is omitted entirely
    -0125             % if nothing in the model has a value. Values are the kind of
    -0126             % magnitude a real thermodynamic estimate has, not contrived
    -0127             % high-precision ones: num2str's default precision (no digits
    -0128             % argument) is exact for these, same as Confidence Level.
    -0129             m = testCase.model;
    -0130             m.rxnDeltaG = NaN(numel(m.rxns), 1);
    -0131             m.rxnDeltaG(1) = -8.7;
    -0132             m.metDeltaG = NaN(numel(m.mets), 1);
    -0133             m.metDeltaG(1) = 7.5;
    -0134 
    -0135             f = [tempname '.xml'];
    -0136             testCase.addTeardown(@() delete(f));
    -0137             evalc('exportModel(m, f);');
    -0138             evalc('m2 = importModel(f);');
    -0139 
    -0140             testCase.verifyEqual(m2.rxnDeltaG(1), -8.7, 'AbsTol', 1e-9);
    -0141             testCase.verifyTrue(isnan(m2.rxnDeltaG(2)));
    -0142             testCase.verifyEqual(m2.metDeltaG(1), 7.5, 'AbsTol', 1e-9);
    -0143             testCase.verifyTrue(isnan(m2.metDeltaG(2)));
    -0144         end
    -0145 
    -0146         function exportModelOmitsDeltaGFieldWhenAllNaN(testCase)
    -0147             f = [tempname '.xml'];
    -0148             testCase.addTeardown(@() delete(f));
    -0149             evalc('exportModel(testCase.model, f);');
    -0150             evalc('m2 = importModel(f);');
    -0151             testCase.verifyFalse(isfield(m2, 'rxnDeltaG'));
    -0152             testCase.verifyFalse(isfield(m2, 'metDeltaG'));
    -0153         end
    -0154 
    -0155         function exportToExcelFormatWritesFile(testCase)
    -0156             f = [tempname '.xlsx'];
    -0157             testCase.addTeardown(@() delete(f));
    -0158             evalc('exportToExcelFormat(testCase.model, f);');
    -0159             testCase.verifyTrue(exist(f,'file')==2);
    -0160         end
    -0161 
    -0162         function exportToExcelFormatWritesEcSheets(testCase)
    -0163             % Enzyme-constrained (GECKO) models get two extra export-only
    -0164             % sheets, ENZYMES and ENZRXNS, holding the model.ec contents.
    -0165             model = testCase.model;
    -0166             model.ec.geckoLight = false;
    -0167             model.ec.rxns     = model.rxns(1:2);
    -0168             model.ec.kcat     = [13.7; 0];
    -0169             model.ec.source   = {'brenda'; ''};
    -0170             model.ec.notes    = {'note1'; ''};
    -0171             model.ec.eccodes  = {'1.1.1.1'; '2.7.1.1;2.7.1.2'};
    -0172             model.ec.genes    = model.genes(1:2);
    -0173             model.ec.enzymes  = {'P0A1'; 'P0A2'};
    -0174             model.ec.mw       = [51000; NaN];
    -0175             model.ec.sequence = {'MABC'; 'MDEF'};
    -0176             model.ec.concs    = [NaN; 0.5];
    -0177             model.ec.rxnEnzMat = [1 2; 0 1];   % R1: P0A1 x1, P0A2 x2; R2: P0A2 x1
    -0178 
    -0179             f = [tempname '.xlsx'];
    -0180             testCase.addTeardown(@() delete(f));
    -0181             evalc('exportToExcelFormat(model, f);');
    -0182 
    -0183             sheets = sheetnames(f);
    -0184             testCase.verifyTrue(any(strcmp(sheets,'ENZYMES')));
    -0185             testCase.verifyTrue(any(strcmp(sheets,'ENZRXNS')));
    -0186 
    -0187             % NaN mw/conc are written as blanks; the kcat 0 sentinel is kept.
    -0188             enz = readcell(f,'Sheet','ENZYMES');
    -0189             testCase.verifyEqual(string(enz(1,2:6)),["ID","GENE","MW","SEQUENCE","CONC"]);
    -0190             testCase.verifyEqual(string(enz{2,2}),"P0A1");
    -0191             testCase.verifyEqual(enz{2,4},51000,'AbsTol',1e-9);
    -0192 
    -0193             ecrxn = readcell(f,'Sheet','ENZRXNS');
    -0194             testCase.verifyEqual(string(ecrxn(1,2:7)),["ID","KCAT","SOURCE","NOTE","EC-NUMBER","ENZYMES"]);
    -0195             testCase.verifyEqual(ecrxn{2,3},13.7,'AbsTol',1e-9);
    -0196             testCase.verifyEqual(string(ecrxn{3,6}),"2.7.1.1;2.7.1.2");
    -0197             % ENZYMES column: 'enzyme:count' subunit stoichiometry from rxnEnzMat
    -0198             testCase.verifyEqual(string(ecrxn{2,7}),"P0A1:1;P0A2:2");
    -0199             testCase.verifyEqual(string(ecrxn{3,7}),"P0A2:1");
    -0200         end
    -0201 
    -0202         function writeReadYAMLRoundTrip(testCase)
    -0203             f = [tempname '.yml'];
    -0204             testCase.addTeardown(@() delete(f));
    -0205             evalc('writeYAMLmodel(testCase.model, f);');
    -0206             evalc('m2 = readYAMLmodel(f);');
    -0207             testCase.verifyEqual(numel(m2.rxns), numel(testCase.model.rxns));
    -0208         end
    -0209 
    -0210         function writeReadYAMLNestsEcRxnEnzymes(testCase)
    -0211             % Each ec-rxns entry lists its enzymes as a mapping under the
    -0212             % "enzymes" key, so the enzyme lines are indented deeper than
    -0213             % that key; at the same depth a YAML parser reads them as
    -0214             % siblings of "enzymes" and the mapping as empty.
    -0215             model = testCase.model;
    -0216             model.ec.geckoLight = false;
    -0217             model.ec.rxns     = model.rxns(1:2);
    -0218             model.ec.kcat     = [13.7; 2];
    -0219             model.ec.source   = {'brenda'; 'dlkcat'};
    -0220             model.ec.notes    = {''; ''};
    -0221             model.ec.eccodes  = {'1.1.1.1'; '2.7.1.1;2.7.1.2'};
    -0222             model.ec.genes    = model.genes(1:2);
    -0223             model.ec.enzymes  = {'P0A1'; 'P0A2'};
    -0224             model.ec.mw       = [51000; 42000];
    -0225             model.ec.sequence = {'MABC'; 'MDEF'};
    -0226             model.ec.concs    = [NaN; NaN];
    -0227             model.ec.rxnEnzMat = [1 2; 0 1];
    -0228 
    -0229             f = [tempname '.yml'];
    -0230             testCase.addTeardown(@() delete(f));
    -0231             evalc('writeYAMLmodel(model, f);');
    -0232 
    -0233             lines = splitlines(fileread(f));
    -0234             enzKey = find(strcmp(lines, '    - enzymes: !!omap'));
    -0235             testCase.verifyNumElements(enzKey, 2);
    -0236             testCase.verifyEqual(lines(enzKey(1)+(1:2)), ...
    -0237                 {'      - P0A1: 1.0'; '      - P0A2: 2.0'});
    -0238             testCase.verifyEqual(lines(enzKey(2)+1), {'      - P0A2: 1.0'});
    -0239 
    -0240             evalc('m2 = readYAMLmodel(f);');
    -0241             testCase.verifyEqual(m2.ec.enzymes, model.ec.enzymes);
    -0242             testCase.verifyEqual(m2.ec.rxnEnzMat, model.ec.rxnEnzMat);
    -0243         end
    -0244 
    -0245         function writeReadYAMLPreservesCompartmentAnnotations(testCase)
    -0246             % Compartment annotations must round-trip, and must not be read
    -0247             % back as additional compartments.
    -0248             m = testCase.model;
    -0249             m.compMiriams = cell(numel(m.comps),1);
    -0250             m.compMiriams{1}.name  = {'go'};
    -0251             m.compMiriams{1}.value = {'GO:0005737'};
    -0252             f = [tempname '.yml'];
    -0253             testCase.addTeardown(@() delete(f));
    -0254             evalc('writeYAMLmodel(m, f);');
    -0255             evalc('m2 = readYAMLmodel(f);');
    -0256             testCase.verifyEqual(m2.comps, m.comps);
    -0257             testCase.verifyEqual(m2.compNames, m.compNames);
    -0258             testCase.verifyEqual(m2.compMiriams{1}.name, {'go'});
    -0259             testCase.verifyEqual(m2.compMiriams{1}.value, {'GO:0005737'});
    -0260         end
    -0261 
    -0262         function writeReadYAMLPreservesUnknownAnnotationFields(testCase)
    -0263             % A caller-specific metaData field with no RAVEN-defined slot
    -0264             % (e.g. a `geckopy_version` field) must survive a write/read
    -0265             % round trip instead of being silently dropped, same as the
    -0266             % fixed annoFields set (taxonomy here).
    -0267             m = testCase.model;
    -0268             m.annotation.taxonomy = 'taxonomy/4932';
    -0269             m.annotation.geckopyVersion = '0.2.1';
    -0270             f = [tempname '.yml'];
    -0271             testCase.addTeardown(@() delete(f));
    -0272             evalc('writeYAMLmodel(m, f);');
    -0273             evalc('m2 = readYAMLmodel(f);');
    -0274             testCase.verifyEqual(m2.annotation.taxonomy, 'taxonomy/4932');
    -0275             testCase.verifyEqual(m2.annotation.geckopyVersion, '0.2.1');
    -0276         end
    -0277 
    -0278         function exportForGitWritesRepo(testCase)
    -0279             d = [tempname filesep];
    -0280             mkdir(d);
    -0281             testCase.addTeardown(@() rmdir(d, 's'));
    -0282             evalc('exportForGit(testCase.model, ''ec'', d, {''yml''});');
    -0283             testCase.verifyNotEmpty(dir(fullfile(d,'**','*.yml')));
    -0284         end
    -0285 
    -0286         function checkFileExistenceFindsFile(testCase)
    -0287             f = fullfile(testCase.ravenRoot,'testing','function_tests', ...
    -0288                 'test_data','ecoli_textbook.mat');
    -0289             out = checkFileExistence(f, 1, false, true);
    -0290             testCase.verifyNotEmpty(out);
    -0291         end
    -0292 
    -0293         function checkFileExistenceAcceptsForwardSlashes(testCase)
    -0294             % An absolute Windows path may be written with forward slashes:
    -0295             % MATLAB accepts them everywhere, and anything handing RAVEN a path
    -0296             % built by another tool is likely to use them. Treating such a path
    -0297             % as relative appends the working directory and turns
    -0298             % "C:/data/model.mat" into "C:\somewhere\C:\data\model.mat".
    -0299             f = fullfile(testCase.ravenRoot,'testing','function_tests', ...
    -0300                 'test_data','ecoli_textbook.mat');
    -0301             out = checkFileExistence(strrep(f,'\','/'), 1, false, true);
    -0302             testCase.verifyNotEmpty(out);
    -0303         end
    -0304 
    -0305         function cleanSheetTrimsComments(testCase)
    -0306             raw = {'#comment', '#comment'; 'a', 'b'; '', ''};
    -0307             out = cleanSheet(raw);
    -0308             testCase.verifyClass(out, 'cell');
    -0309         end
    -0310 
    -0311         function writeExcelRoundTrips(testCase)
    -0312             % writeExcel generates the .xlsx (Office Open XML) directly, with
    -0313             % no external library; read it back with base MATLAB to confirm
    -0314             % the sheets, values and types survive.
    -0315             f = [tempname '.xlsx'];
    -0316             testCase.addTeardown(@() delete(f));
    -0317             s(1).name='RXNS'; s(1).header={'ID','VALUE'};
    -0318             s(1).data={'r1',1.5; 'r2',-3};
    -0319             s(2).name='METS'; s(2).header={'ID'}; s(2).data={'m1'};
    -0320             writeExcel(f, s);
    -0321             testCase.verifyEqual(sort(string(sheetnames(f))),["METS";"RXNS"]);
    -0322             raw = readcell(f,'Sheet','RXNS');
    -0323             testCase.verifyEqual(string(raw(1,:)),["ID","VALUE"]);
    -0324             testCase.verifyEqual(string(raw{2,1}),"r1");
    -0325             testCase.verifyEqual(raw{2,2},1.5,'AbsTol',1e-9);
    -0326         end
    -0327 
    -0328     end
    -0329 end
    -
    Generated by m2html © 2005
    - - \ No newline at end of file diff --git a/docs/html/testing/function_tests/tInstallation.html b/docs/html/testing/function_tests/tInstallation.html deleted file mode 100644 index 6ae79b2a..00000000 --- a/docs/html/testing/function_tests/tInstallation.html +++ /dev/null @@ -1,114 +0,0 @@ - - - - Description of tInstallation - - - - - - - - - -
    Home > testing > function_tests > tInstallation.m
    - - - -

    tInstallation -

    - -

    PURPOSE ^

    -
    - -

    SYNOPSIS ^

    -
    This is a script file.
    - -

    DESCRIPTION ^

    -
    - - -

    CROSS-REFERENCE INFORMATION ^

    -This function calls: - -This function is called by: - - - -

    SUBFUNCTIONS ^

    - - -

    SOURCE CODE ^

    -
    0001 classdef tInstallation < RavenTestCase
    -0002 % tInstallation  Tests for the setup/installation helpers in installation/.
    -0003 %
    -0004 %   addRavenToUserPath, removeRavenFromPath and updateDocumentation have
    -0005 %   destructive / irreversible side effects (rewrite the user's startup.m,
    -0006 %   remove RAVEN from the live path, regenerate the whole doc tree). They are
    -0007 %   therefore only checked for existence here, not executed.
    -0008 
    -0009     methods (Test)
    -0010 
    -0011         function findRAVENrootReturnsInstallFolder(testCase)
    -0012             p = findRAVENroot();
    -0013             testCase.verifyTrue(isfolder(p));
    -0014             testCase.verifyTrue(exist(fullfile(p,'installation','findRAVENroot.m'),'file')==2);
    -0015         end
    -0016 
    -0017         function findRAVENrootSurvivesClearAndRepeatCalls(testCase)
    -0018             % The resolved path is cached for the session (findRAVENroot is
    -0019             % called on every solver invocation, so re-reading the
    -0020             % preference from disk every time is wasteful). Repeated calls
    -0021             % must keep returning the same root, and it must still resolve
    -0022             % correctly after being cleared, which is what checkRaven
    -0023             % does when the RAVEN.ravenPath preference changes.
    -0024             p1 = findRAVENroot();
    -0025             p2 = findRAVENroot();
    -0026             testCase.verifyEqual(p2, p1);
    -0027             clear('findRAVENroot');
    -0028             p3 = findRAVENroot();
    -0029             testCase.verifyEqual(p3, p1);
    -0030         end
    -0031 
    -0032         function checkRavenReturnsVersion(testCase)
    -0033             [~, currVer] = evalc('checkRaven(false, false)');
    -0034             testCase.verifyNotEmpty(currVer);
    -0035         end
    -0036 
    -0037         function checkInstallationWarnsAndForwards(testCase)
    -0038             % checkRaven itself issues other warnings during its checks (e.g.
    -0039             % SBML id-prefixing), which would overwrite lastwarn before it
    -0040             % could be inspected here. verifyWarning tracks every warning
    -0041             % issued during the call, not just the most recent one.
    -0042             testCase.verifyWarning(@() evalc('checkInstallation(false, false)'), 'RAVEN:deprecated');
    -0043             [~, currVer] = evalc('checkInstallation(false, false)');
    -0044             testCase.verifyNotEmpty(currVer);
    -0045         end
    -0046 
    -0047         function checkFunctionUniquenessRuns(testCase)
    -0048             [~, status] = evalc('checkFunctionUniqueness()');
    -0049             testCase.verifyTrue(islogical(status) || isnumeric(status));
    -0050         end
    -0051 
    -0052         function addRavenToUserPathExists(testCase)
    -0053             testCase.verifyEqual(exist('addRavenToUserPath','file'), 2);
    -0054         end
    -0055 
    -0056         function removeRavenFromPathExists(testCase)
    -0057             testCase.verifyEqual(exist('removeRavenFromPath','file'), 2);
    -0058         end
    -0059 
    -0060         function updateDocumentationExists(testCase)
    -0061             testCase.verifyEqual(exist('updateDocumentation','file'), 2);
    -0062         end
    -0063 
    -0064     end
    -0065 end
    -
    Generated by m2html © 2005
    - - \ No newline at end of file diff --git a/docs/html/testing/function_tests/tLocalization.html b/docs/html/testing/function_tests/tLocalization.html deleted file mode 100644 index 19ffd845..00000000 --- a/docs/html/testing/function_tests/tLocalization.html +++ /dev/null @@ -1,188 +0,0 @@ - - - - Description of tLocalization - - - - - - - - - -
    Home > testing > function_tests > tLocalization.m
    - - - -

    tLocalization -

    - -

    PURPOSE ^

    -
    - -

    SYNOPSIS ^

    -
    This is a script file.
    - -

    DESCRIPTION ^

    -
    - - -

    CROSS-REFERENCE INFORMATION ^

    -This function calls: - -This function is called by: - - - -

    SUBFUNCTIONS ^

    - - -

    SOURCE CODE ^

    -
    0001 classdef tLocalization < RavenTestCase
    -0002 % tLocalization  Tests for the subcellular-localization score parsers in localization/.
    -0003 %
    -0004 %   Exercises parseScores for each supported source (DeepLoc 2, MULocDeep, the COMPARTMENTS
    -0005 %   database and UniProt) plus defaultCompartmentMap, using small fixture files written to a
    -0006 %   temporary location. No external predictor or network access is needed.
    -0007 
    -0008     methods (Test)
    -0009 
    -0010         function deeploc(testCase)
    -0011             f = i_tmp(testCase, '.csv');
    -0012             writelines([
    -0013                 "Protein_ID,Localizations,Signals,Cytoplasm,Nucleus,Mitochondrion"
    -0014                 "G1,Cytoplasm,,0.8,0.1,0.05"
    -0015                 "G2,Mitochondrion,SP,0.05,0.15,0.9"], f);
    -0016             GSS = parseScores(f, 'predictor', 'deeploc');
    -0017             testCase.verifyEqual(sort(string(GSS.compartments(:)')), ...
    -0018                 ["Cytoplasm" "Mitochondrion" "Nucleus"]);
    -0019             testCase.verifyEqual(i_score(GSS,'G1','Cytoplasm'), 1, 'AbsTol', 1e-9);
    -0020             testCase.verifyEqual(i_score(GSS,'G2','Mitochondrion'), 1, 'AbsTol', 1e-9);
    -0021         end
    -0022 
    -0023         function deeplocWithCompartmentMap(testCase)
    -0024             f = i_tmp(testCase, '.csv');
    -0025             writelines([
    -0026                 "Protein_ID,Localizations,Signals,Cytoplasm,Nucleus,Mitochondrion,Plastid"
    -0027                 "G1,Cytoplasm,,0.8,0.1,0.05,0.4"], f);
    -0028             GSS = parseScores(f, 'predictor', 'deeploc', 'compartmentMap', defaultCompartmentMap());
    -0029             % labels mapped to model ids; Plastid (no fungal equivalent) dropped
    -0030             testCase.verifyEqual(sort(string(GSS.compartments(:)')), ["c" "m" "n"]);
    -0031             testCase.verifyEqual(i_score(GSS,'G1','c'), 1, 'AbsTol', 1e-9);
    -0032         end
    -0033 
    -0034         function mulocdeep(testCase)
    -0035             f = i_tmp(testCase, '.tsv');
    -0036             t = char(9);
    -0037             writelines([
    -0038                 string(['protein' t 'Cytoplasm' t 'Mitochondrion' t 'Peroxisome'])
    -0039                 string(['G1' t '0.2' t '0.7' t '0.1'])
    -0040                 string(['G2' t '0.9' t '0.05' t '0.05'])], f);
    -0041             GSS = parseScores(f, 'predictor', 'mulocdeep', 'compartmentMap', defaultCompartmentMap());
    -0042             testCase.verifyEqual(sort(string(GSS.compartments(:)')), ["c" "m" "p"]);
    -0043             testCase.verifyEqual(i_score(GSS,'G1','m'), 1, 'AbsTol', 1e-9);
    -0044             testCase.verifyEqual(i_score(GSS,'G2','c'), 1, 'AbsTol', 1e-9);
    -0045         end
    -0046 
    -0047         function compartments(testCase)
    -0048             f = i_tmp(testCase, '.tsv');
    -0049             t = char(9);
    -0050             writelines([
    -0051                 string(['YGL001C' t 'ERG26' t 'GO:0005739' t 'Mitochondrion' t '4.5'])
    -0052                 string(['YGL001C' t 'ERG26' t 'GO:0005829' t 'Cytosol'       t '2.0'])
    -0053                 string(['YGL002W' t 'ABC1'  t 'GO:0005777' t 'Peroxisome'    t '3.0'])
    -0054                 string(['YGL002W' t 'ABC1'  t 'GO:0099999' t 'Plastid'       t '5.0'])], f);
    -0055             GSS = parseScores(f, 'predictor', 'compartments', ...
    -0056                               'compartmentMap', defaultCompartmentMap());
    -0057             testCase.verifyEqual(sort(string(GSS.compartments(:)')), ["c" "m" "p"]);
    -0058             testCase.verifyEqual(i_score(GSS,'YGL001C','m'), 1, 'AbsTol', 1e-9);          % 4.5 → 1
    -0059             testCase.verifyEqual(i_score(GSS,'YGL001C','c'), 2.0/4.5, 'AbsTol', 1e-9);
    -0060             % minConfidence drops the weak cytosol annotation
    -0061             GSS2 = parseScores(f, 'predictor', 'compartments', ...
    -0062                                'compartmentMap', defaultCompartmentMap(), 'minConfidence', 3.0);
    -0063             testCase.verifyFalse(any(strcmp(GSS2.compartments,'c')));
    -0064         end
    -0065 
    -0066         function uniprot(testCase)
    -0067             f = i_tmp(testCase, '.tsv');
    -0068             t = char(9);
    -0069             writelines([
    -0070                 string(['Entry' t 'Gene Names (primary)' t 'Gene Names (ordered locus)' t ...
    -0071                         'Subcellular location [CC]'])
    -0072                 string(['P00890' t 'CIT1' t 'YNR001C' t ...
    -0073                         'SUBCELLULAR LOCATION: Mitochondrion matrix {ECO:1}.'])
    -0074                 string(['P12345' t 'GENEX' t 'YAL001C' t ...
    -0075                         'SUBCELLULAR LOCATION: Cytoplasm. Nucleus {ECO:2}. ' ...
    -0076                         'Note=Shuttles to the mitochondrion under stress.'])], f);
    -0077             GSS = parseScores(f, 'predictor', 'uniprot', 'idColumn', 'Gene Names (ordered locus)');
    -0078             testCase.verifyEqual(i_score(GSS,'YNR001C','m'), 1, 'AbsTol', 1e-9);
    -0079             testCase.verifyEqual(i_score(GSS,'YAL001C','c'), 1, 'AbsTol', 1e-9);
    -0080             testCase.verifyEqual(i_score(GSS,'YAL001C','n'), 1, 'AbsTol', 1e-9);
    -0081             % the "mitochondrion" mention inside Note=… is ignored
    -0082             testCase.verifyEqual(i_score(GSS,'YAL001C','m'), 0, 'AbsTol', 1e-9);
    -0083         end
    -0084 
    -0085         function uniprotWholeWordMatch(testCase)
    -0086             % 'Cytoplasmic vesicle' must NOT be mis-read as cytosol ('c') via substring match
    -0087             f = i_tmp(testCase, '.tsv');
    -0088             t = char(9);
    -0089             writelines([
    -0090                 string(['Entry' t 'Subcellular location [CC]'])
    -0091                 string(['YAA' t 'SUBCELLULAR LOCATION: Cytoplasmic vesicle.'])
    -0092                 string(['YBB' t 'SUBCELLULAR LOCATION: Cytoplasm.'])], f);
    -0093             GSS = parseScores(f, 'predictor', 'uniprot');
    -0094             testCase.verifyEqual(i_score(GSS,'YBB','c'), 1, 'AbsTol', 1e-9);
    -0095             testCase.verifyFalse(any(strcmp(GSS.genes,'YAA')));   % no real location -> dropped
    -0096         end
    -0097 
    -0098         function uniprotMultiIsoformNote(testCase)
    -0099             % a Note= between isoform blocks must not swallow the later isoform's location
    -0100             f = i_tmp(testCase, '.tsv');
    -0101             t = char(9);
    -0102             writelines([
    -0103                 string(['Entry' t 'Subcellular location [CC]'])
    -0104                 string(['YCC' t 'SUBCELLULAR LOCATION: [Isoform 1]: Mitochondrion {ECO:1}. ' ...
    -0105                         'Note=Regulated under stress. [Isoform 2]: Cytoplasm {ECO:2}.'])], f);
    -0106             GSS = parseScores(f, 'predictor', 'uniprot');
    -0107             testCase.verifyEqual(i_score(GSS,'YCC','m'), 1, 'AbsTol', 1e-9);
    -0108             testCase.verifyEqual(i_score(GSS,'YCC','c'), 1, 'AbsTol', 1e-9);   % isoform 2 kept
    -0109         end
    -0110 
    -0111         function wolfRemoved(testCase)
    -0112             f = i_tmp(testCase, '.txt');
    -0113             writelines("anything", f);
    -0114             testCase.verifyError(@() parseScores(f, 'predictor', 'wolf'), 'RAVEN:badInput');
    -0115         end
    -0116 
    -0117     end
    -0118 end
    -0119 
    -0120 % ------------------------------------------------------------------------- helpers
    -0121 
    -0122 function f = i_tmp(testCase, ext)
    -0123 f = [tempname ext];
    -0124 testCase.addTeardown(@() i_delete(f));
    -0125 end
    -0126 
    -0127 function i_delete(f)
    -0128 if isfile(f); delete(f); end
    -0129 end
    -0130 
    -0131 function s = i_score(GSS, gene, comp)
    -0132 gi = strcmp(GSS.genes, gene);
    -0133 ci = strcmp(GSS.compartments, comp);
    -0134 if ~any(gi) || ~any(ci)
    -0135     s = 0;
    -0136 else
    -0137     s = GSS.scores(gi, ci);
    -0138 end
    -0139 end
    -
    Generated by m2html © 2005
    - - \ No newline at end of file diff --git a/docs/html/testing/function_tests/tManipulation.html b/docs/html/testing/function_tests/tManipulation.html deleted file mode 100644 index fdfc2df7..00000000 --- a/docs/html/testing/function_tests/tManipulation.html +++ /dev/null @@ -1,1005 +0,0 @@ - - - - Description of tManipulation - - - - - - - - - -
    Home > testing > function_tests > tManipulation.m
    - - - -

    tManipulation -

    - -

    PURPOSE ^

    -
    - -

    SYNOPSIS ^

    -
    This is a script file.
    - -

    DESCRIPTION ^

    -
    - - -

    CROSS-REFERENCE INFORMATION ^

    -This function calls: - -This function is called by: - - - -

    SUBFUNCTIONS ^

    - - -

    SOURCE CODE ^

    -
    0001 classdef tManipulation < RavenTestCase
    -0002 % tManipulation  Tests for the structural model-editing functions in manipulation/.
    -0003 %
    -0004 %   Assertions favour structural invariants (counts, membership, round-trips,
    -0005 %   idempotence) over full golden-model comparisons, so they stay robust while
    -0006 %   still exercising each function's main path. evalc is used to suppress the
    -0007 %   informational printing some functions do.
    -0008 
    -0009     methods (Test)
    -0010 
    -0011         function addExchangeRxnsAddsOne(testCase)
    -0012             [m2, added] = addExchangeRxns(testCase.model, 'both', '6pgl_c');
    -0013             testCase.verifyEqual(numel(m2.rxns), numel(testCase.model.rxns) + 1);
    -0014             testCase.verifyNumElements(added, 1);
    -0015         end
    -0016 
    -0017         function addGenesRavenAddsGenes(testCase)
    -0018             g.genes = {'testgene1','testgene2'};
    -0019             g.geneShortNames = {'s1','s2'};
    -0020             m2 = addGenesRaven(testCase.model, g);
    -0021             testCase.verifyEqual(numel(m2.genes), numel(testCase.model.genes) + 2);
    -0022             testCase.verifyTrue(all(ismember(g.genes, m2.genes)));
    -0023         end
    -0024 
    -0025         function addGenesRavenPartialOverlapAddsRemainder(testCase)
    -0026             % When some genesToAdd.genes already exist in the model, the
    -0027             % others must still be added, and every parallel field must stay
    -0028             % aligned with the (trimmed) gene list.
    -0029             g.genes = {testCase.model.genes{1}; 'newgene1'};
    -0030             g.geneShortNames = {'existing','s1'};
    -0031             evalc('m2 = addGenesRaven(testCase.model, g);');
    -0032             testCase.verifyEqual(numel(m2.genes), numel(testCase.model.genes) + 1);
    -0033             testCase.verifyTrue(ismember('newgene1', m2.genes));
    -0034             testCase.verifyEqual(numel(m2.geneShortNames), numel(m2.genes));
    -0035         end
    -0036 
    -0037         function addMetsAddsMets(testCase)
    -0038             mta.metNames = {'newMetA','newMetB'};
    -0039             mta.compartments = {'c','e'};
    -0040             evalc('m2 = addMets(testCase.model, mta);');
    -0041             testCase.verifyEqual(numel(m2.mets), numel(testCase.model.mets) + 2);
    -0042         end
    -0043 
    -0044         function addRxnsAllowNewGenesKeepsGeneIdsIntact(testCase)
    -0045             % A gene id that merely contains "and"/"or" as a substring
    -0046             % (e.g. "band1") must not be shredded by the grRule parser used
    -0047             % to discover new genes.
    -0048             r.rxns = 'newRxn1';
    -0049             r.equations = '2-Oxoglutarate => TEST';
    -0050             r.grRules = 'band1 and orfeo2';
    -0051             evalc('m2 = addRxns(testCase.model, r, 2, ''c'', true, true);');
    -0052             testCase.verifyTrue(all(ismember({'band1','orfeo2'}, m2.genes)));
    -0053         end
    -0054 
    -0055         function addRxnsPrintsNewGeneIdWithPercent(testCase)
    -0056             % A new gene id containing "%" must not be truncated when
    -0057             % printed to the "New genes added" notice.
    -0058             r.rxns = 'newRxn1';
    -0059             r.equations = '2-Oxoglutarate => TEST';
    -0060             r.grRules = 'NEWGENE_50%_test';
    -0061             out = evalc('addRxns(testCase.model, r, 2, ''c'', true, true);');
    -0062             testCase.verifySubstring(out, 'NEWGENE_50%_test');
    -0063         end
    -0064 
    -0065         function addRxnsAddsRxn(testCase)
    -0066             r.rxns = 'newRxn1';
    -0067             r.equations = '2-Oxoglutarate => TEST';
    -0068             evalc('m2 = addRxns(testCase.model, r, 2, ''c'', true);');
    -0069             testCase.verifyEqual(numel(m2.rxns), numel(testCase.model.rxns) + 1);
    -0070             testCase.verifyTrue(ismember('newRxn1', m2.rxns));
    -0071         end
    -0072 
    -0073         function addRxnsKeepsSpontaneousAligned(testCase)
    -0074             % A model that already tracks spontaneous must keep it aligned
    -0075             % with rxns after adding reactions that don't specify it, and
    -0076             % accept an explicit spontaneous value for the new ones too.
    -0077             m = testCase.model;
    -0078             m.spontaneous = false(numel(m.rxns), 1);
    -0079             r.rxns = 'newRxn1';
    -0080             r.equations = '2-Oxoglutarate => TEST';
    -0081             evalc('m2 = addRxns(m, r, 2, ''c'', true);');
    -0082             testCase.verifyEqual(numel(m2.spontaneous), numel(m2.rxns));
    -0083             testCase.verifyFalse(m2.spontaneous(end));
    -0084 
    -0085             r2.rxns = 'newRxn2';
    -0086             r2.equations = '2-Oxoglutarate => TEST2';
    -0087             r2.spontaneous = true;
    -0088             evalc('m3 = addRxns(m, r2, 2, ''c'', true);');
    -0089             testCase.verifyEqual(numel(m3.spontaneous), numel(m3.rxns));
    -0090             testCase.verifyTrue(m3.spontaneous(end));
    -0091         end
    -0092 
    -0093         function addRxnsKeepsEquationsAligned(testCase)
    -0094             % A model that already tracks equations must keep it aligned
    -0095             % with rxns after adding a reaction, reusing the equation as
    -0096             % given rather than leaving the field short by one.
    -0097             m = testCase.model;
    -0098             m.equations = constructEquations(m);
    -0099             r.rxns = 'newRxn1';
    -0100             r.equations = '2-Oxoglutarate => TEST';
    -0101             evalc('m2 = addRxns(m, r, 2, ''c'', true);');
    -0102             testCase.verifyEqual(numel(m2.equations), numel(m2.rxns));
    -0103             testCase.verifySubstring(m2.equations{end}, 'TEST');
    -0104         end
    -0105 
    -0106         function addRxnsStringEqnTypeIdAlias(testCase)
    -0107             % 'id' is the string alias for eqnType=1 (match by model.mets).
    -0108             r.rxns = {'idAliasRxn'};
    -0109             r.equations = {[testCase.model.mets{1} ' => ' testCase.model.mets{2}]};
    -0110             m1 = addRxns(testCase.model, r, 1);
    -0111             m2 = addRxns(testCase.model, r, 'id');
    -0112             testCase.verifyEqual(m1.S, m2.S);
    -0113         end
    -0114 
    -0115         function addRxnsStringEqnTypeNameAlias(testCase)
    -0116             % 'name' is the string alias for eqnType=2 (match by model.metNames).
    -0117             r.rxns = {'nameAliasRxn'};
    -0118             r.equations = {'2-Oxoglutarate => NAMETEST'};
    -0119             evalc('m1 = addRxns(testCase.model, r, 2, ''c'', true);');
    -0120             evalc('m2 = addRxns(testCase.model, r, ''name'', ''c'', true);');
    -0121             testCase.verifyEqual(m1.S, m2.S);
    -0122         end
    -0123 
    -0124         function addRxnsStringEqnTypeNameCompAlias(testCase)
    -0125             % 'name[comp]' is the string alias for eqnType=3.
    -0126             r.rxns = {'namecompAliasRxn'};
    -0127             r.equations = {'2-Oxoglutarate[c] => COMPTEST[c]'};
    -0128             evalc('m1 = addRxns(testCase.model, r, 3, [], true);');
    -0129             evalc('m2 = addRxns(testCase.model, r, ''name[comp]'', [], true);');
    -0130             testCase.verifyEqual(m1.S, m2.S);
    -0131         end
    -0132 
    -0133         function addRxnsInvalidStringEqnTypeErrors(testCase)
    -0134             % An unrecognised string for eqnType must throw.
    -0135             r.rxns = {'errRxn'};
    -0136             r.equations = {[testCase.model.mets{1} ' => ' testCase.model.mets{2}]};
    -0137             testCase.verifyError(@() addRxns(testCase.model, r, 'invalid'), ?MException);
    -0138         end
    -0139 
    -0140         function addRxnsGenesMetsCopiesFromSource(testCase)
    -0141             sbmlFile = fullfile(testCase.ravenRoot,'tutorial','empty.xml');
    -0142             testCase.assumeDependency(exist(sbmlFile,'file')==2, 'tutorial/empty.xml');
    -0143             evalc('src = importModel(sbmlFile, [], true);');
    -0144             evalc('m2 = addRxnsGenesMets(testCase.model, src, ''r1'', true);');
    -0145             testCase.verifyTrue(ismember('r1', m2.rxns));
    -0146         end
    -0147 
    -0148         function addRxnsGenesMetsKeepsPercentInAlreadyPresentNotice(testCase)
    -0149             % A rxn id containing "%" that is already present in the
    -0150             % target model must survive intact in the notice, not be
    -0151             % truncated by fprintf misreading it as a format directive.
    -0152             model = testCase.model;
    -0153             model.rxns{1} = 'RXN_50%_present';
    -0154             sourceModel = model;
    -0155             r.rxns = 'newRxn1';
    -0156             r.equations = [model.mets{1} ' => ' model.mets{2}];
    -0157             evalc('sourceModel = addRxns(sourceModel, r, 1, ''c'', true);');
    -0158             out = evalc(['addRxnsGenesMets(model, sourceModel, ' ...
    -0159                 '{''RXN_50%_present'',''newRxn1''});']);
    -0160             testCase.verifySubstring(out, 'RXN_50%_present');
    -0161         end
    -0162 
    -0163         function addTransportAddsRxn(testCase)
    -0164             evalc(['m2 = addTransport(testCase.model, ''c'', ''e'', ' ...
    -0165                 '{''6-phospho-D-glucono-1,5-lactone''}, false, false, ''tr_'');']);
    -0166             testCase.verifyGreaterThan(numel(m2.rxns), numel(testCase.model.rxns));
    -0167         end
    -0168 
    -0169         function addTransportOnModelWithoutAnnotationField(testCase)
    -0170             % The default LB/UB fallback must not assume model.annotation
    -0171             % exists at all, only that it may lack defaultLB/defaultUB.
    -0172             m = testCase.model;
    -0173             if isfield(m,'annotation')
    -0174                 m = rmfield(m,'annotation');
    -0175             end
    -0176             evalc(['m2 = addTransport(m, ''c'', ''e'', ' ...
    -0177                 '{''6-phospho-D-glucono-1,5-lactone''}, false, false, ''tr_'');']);
    -0178             testCase.verifyGreaterThan(numel(m2.rxns), numel(m.rxns));
    -0179         end
    -0180 
    -0181         function addTransportAcceptsRowOrientedMetNames(testCase)
    -0182             % Regression test for SysBioChalmers/RAVEN#722: a metNames cell
    -0183             % array keeps whatever orientation the caller passed it in, and
    -0184             % a plain {'a','b'} literal is row-oriented. A single-metabolite
    -0185             % cell (as in addTransportAddsRxn above) cannot catch this ---
    -0186             % a 1x1 cell is the same shape whichever way you call it "row"
    -0187             % or "column" --- so this needs two or more metabolites, both
    -0188             % already present in the target compartment (the default
    -0189             % onlyToExisting=true path, which is where this crashed).
    -0190             metNames = {'CO2', 'Formate'}; %#ok<NASGU> % row-oriented on purpose
    -0191             evalc('m2 = addTransport(testCase.model, ''c'', {''e''}, ''metNames'', metNames);');
    -0192             testCase.verifyEqual(numel(m2.rxns), numel(testCase.model.rxns) + 2);
    -0193             testCase.verifyEqual(size(m2.rxnNames, 2), 1);
    -0194         end
    -0195 
    -0196         function addRxnsCleanEquationDoesNotWarn(testCase)
    -0197             % The "metabolite on both sides" warning must only fire for the
    -0198             % equations it names, not on every call.
    -0199             rxnsToAdd.rxns      = {'newRxn'};
    -0200             rxnsToAdd.equations = {[testCase.model.mets{1} ' => ' testCase.model.mets{2}]};
    -0201             lastwarn('');
    -0202             evalc('addRxns(testCase.model, rxnsToAdd, 1);');
    -0203             testCase.verifyEmpty(strfind(lastwarn, 'both as substrate and product')); %#ok<STRIFY>
    -0204         end
    -0205 
    -0206         function changeGrRulesUpdatesRule(testCase)
    -0207             m2 = changeGrRules(testCase.model, 'ACKr', 'b2296 and b1849', true);
    -0208             idx = strcmp(m2.rxns, 'ACKr');
    -0209             testCase.verifyEqual(m2.grRules{idx}, 'b2296 and b1849');
    -0210         end
    -0211 
    -0212         function changeRxnsUpdatesEquation(testCase)
    -0213             evalc('m2 = changeRxns(testCase.model, ''ACKr'', ''2-Oxoglutarate <=> TEST'', 2, ''c'', true);');
    -0214             testCase.verifyEqual(numel(m2.rxns), numel(testCase.model.rxns));
    -0215         end
    -0216 
    -0217         function closeModelReturnsStruct(testCase)
    -0218             m2 = closeModel(testCase.model);
    -0219             testCase.verifyClass(m2, 'struct');
    -0220             testCase.verifyGreaterThanOrEqual(numel(m2.rxns), numel(testCase.model.rxns));
    -0221         end
    -0222 
    -0223         function closeModelHandlesTwoColumnBAndMetNotes(testCase)
    -0224             % b(numel(b)+1)=0 is a linear index, which for an N-by-2 b
    -0225             % (net-production bounds) does not append a row; it errors.
    -0226             % metNotes must also be padded like the other optional
    -0227             % per-metabolite fields, or it goes out of sync with mets.
    -0228             m = tManipulation.twoMetModel();  % R1: a => b
    -0229             r.rxns = {'R2'}; r.equations = {'b =>'};  % sink, so closeModel adds a boundary met
    -0230             evalc('m = addRxns(m, r, 1, [], false);');
    -0231             m.b = [zeros(2,1) ones(2,1)];     % two-column b
    -0232             m.metNotes = {'note a';'note b'};
    -0233             m2 = closeModel(m);
    -0234             testCase.verifyEqual(size(m2.b,2), 2);
    -0235             testCase.verifyEqual(size(m2.b,1), numel(m2.mets));
    -0236             testCase.verifyEqual(numel(m2.metNotes), numel(m2.mets));
    -0237         end
    -0238 
    -0239         function closeModelDetectsScaledAndMultiMetSinks(testCase)
    -0240             % closeModel's boundary-reaction rule is "metabolites on only
    -0241             % one side" (matching getExchangeRxns), not "coefficients
    -0242             % summing to 1 in absolute value": a scaled single-metabolite
    -0243             % sink and a multi-metabolite one must both be detected and
    -0244             % closed, while a genuine two-sided reaction is left alone.
    -0245             m = tManipulation.twoMetModel();  % R1: a => b, a genuine reaction
    -0246             r.rxns = {'R2';'R3'};
    -0247             r.equations = {'2 a =>'; '0.5 a + 0.5 b =>'};
    -0248             evalc('m = addRxns(m, r, 1, [], false);');
    -0249             nMetsBefore = numel(m.mets);
    -0250             m2 = closeModel(m);
    -0251 
    -0252             % Exactly R2 and R3 are exchange-like, so exactly two boundary
    -0253             % metabolites are added, one per closed reaction.
    -0254             testCase.verifyEqual(numel(m2.mets) - nMetsBefore, 2);
    -0255             testCase.verifyEqual(numel(m2.metNames), numel(m2.mets));
    -0256             testCase.verifyEqual(numel(m2.metComps), numel(m2.mets));
    -0257 
    -0258             idx = getIndexes(m2, {'R1';'R2';'R3'}, 'rxns');
    -0259             boundaryComp = numel(m2.comps);
    -0260             touchesBoundary = full(any(m2.S(m2.metComps==boundaryComp, idx) ~= 0, 1));
    -0261             testCase.verifyEqual(touchesBoundary, [false true true]);
    -0262         end
    -0263 
    -0264         function contractModelNoMoreRxns(testCase)
    -0265             evalc('m2 = contractModel(testCase.model);');
    -0266             testCase.verifyLessThanOrEqual(numel(m2.rxns), numel(testCase.model.rxns));
    -0267         end
    -0268 
    -0269         function convertToIrrevAllIrreversible(testCase)
    -0270             m2 = convertToIrrev(testCase.model);
    -0271             testCase.verifyTrue(all(m2.rev == 0));
    -0272             testCase.verifyGreaterThanOrEqual(numel(m2.rxns), numel(testCase.model.rxns));
    -0273         end
    -0274 
    -0275         function convertToIrrevSplitsBoundsAndStoichiometry(testCase)
    -0276             % A reversible reaction with bounds (-500,1000) keeps (0,1000) and
    -0277             % its stoichiometry, while the _REV copy gets (0,500) and the
    -0278             % negated stoichiometry.
    -0279             m = tManipulation.twoMetModel();
    -0280             m.rev = 1; m.lb = -500; m.ub = 1000;
    -0281             m2 = convertToIrrev(m);
    -0282 
    -0283             fwd = strcmp(m2.rxns, 'R1');
    -0284             rev = strcmp(m2.rxns, 'R1_REV');
    -0285             testCase.verifyTrue(any(rev));
    -0286             testCase.verifyEqual(full(m2.lb(fwd)), 0);
    -0287             testCase.verifyEqual(full(m2.ub(fwd)), 1000);
    -0288             testCase.verifyEqual(full(m2.lb(rev)), 0);
    -0289             testCase.verifyEqual(full(m2.ub(rev)), 500);
    -0290             testCase.verifyEqual(full(m2.S(:,fwd)), [-1;1]);
    -0291             testCase.verifyEqual(full(m2.S(:,rev)), [1;-1]);
    -0292         end
    -0293 
    -0294         function convertToIrrevReverseCopyInheritsGrRule(testCase)
    -0295             % The _REV copy is catalysed by the same genes as the forward one.
    -0296             m = tManipulation.twoMetModel();
    -0297             m.rev = 1; m.lb = -500; m.ub = 1000;
    -0298             m.genes = {'g1'}; m.grRules = {'g1'}; m.rxnGeneMat = sparse(1,1,1);
    -0299             m2 = convertToIrrev(m);
    -0300             testCase.verifyEqual(m2.grRules{strcmp(m2.rxns,'R1_REV')}, 'g1');
    -0301         end
    -0302 
    -0303         function convertToIrrevReverseCopyInheritsSpontaneousAndPwys(testCase)
    -0304             % The _REV copy must carry the same spontaneous/pwys annotation
    -0305             % as the forward reaction, keeping both fields aligned with rxns.
    -0306             m = tManipulation.twoMetModel();
    -0307             m.rev = 1; m.lb = -500; m.ub = 1000;
    -0308             m.spontaneous = true;
    -0309             m.pwys = {'pathway1'};
    -0310             m2 = convertToIrrev(m);
    -0311             testCase.verifyEqual(numel(m2.spontaneous), numel(m2.rxns));
    -0312             testCase.verifyEqual(numel(m2.pwys), numel(m2.rxns));
    -0313             testCase.verifyTrue(m2.spontaneous(strcmp(m2.rxns,'R1_REV')));
    -0314             testCase.verifyEqual(m2.pwys{strcmp(m2.rxns,'R1_REV')}, 'pathway1');
    -0315         end
    -0316 
    -0317         function convertToIrrevRev2irrevPointsAtReverseCopy(testCase)
    -0318             % rev2irrev{origIdx}'s second element must be the actual
    -0319             % position of that reaction's reverse copy in irrevModel
    -0320             % (numOrigRxns+i), not just its rank among reversible
    -0321             % reactions.
    -0322             m = tManipulation.twoMetModel();       % R1: a -> b
    -0323             r.rxns = {'R2'}; r.equations = {'b <=> a'};
    -0324             evalc('m = addRxns(m, r, 1, [], false);');
    -0325             m.rev(2) = 1; m.lb(2) = -1000; m.ub(2) = 1000;
    -0326             [irrevModel, ~, rev2irrev] = convertToIrrev(m);
    -0327             pair = rev2irrev{2};
    -0328             testCase.verifyEqual(pair(1), 2);
    -0329             testCase.verifyEqual(irrevModel.rxns{pair(2)}, 'R2_REV');
    -0330         end
    -0331 
    -0332         function findDuplicateRxnsIgnoreDirection(testCase)
    -0333             % a -> b and b -> a are the same reaction run backwards, so they
    -0334             % group by default and stay separate when direction matters.
    -0335             m = tManipulation.twoMetModel();
    -0336             m.rxns     = {'R1';'R2'};
    -0337             m.rxnNames = {'R1';'R2'};
    -0338             m.S   = sparse([-1 1; 1 -1]);
    -0339             m.lb  = [0;0]; m.ub = [1000;1000]; m.rev = [0;0]; m.c = [0;0];
    -0340             m.grRules = {'';''}; m.rxnGeneMat = sparse(2,0);
    -0341 
    -0342             pairs = findDuplicateRxns(m);
    -0343             testCase.verifyEqual(pairs, [1 2]);
    -0344 
    -0345             pairs = findDuplicateRxns(m, 'ignoreDirection', false);
    -0346             testCase.verifyEmpty(pairs);
    -0347         end
    -0348 
    -0349         function changeGrRulesAppendsToExistingRule(testCase)
    -0350             % replace=false must OR the new rule onto the existing one rather
    -0351             % than overwrite it, and add the new gene to the model.
    -0352             m2 = changeGrRules(testCase.model, 'ACKr', 'b9999', false);
    -0353             rule = m2.grRules{strcmp(m2.rxns, 'ACKr')};
    -0354             testCase.verifySubstring(rule, 'b9999');
    -0355             testCase.verifySubstring(rule, ' or ');
    -0356             % the gene the reaction already had must still be in the rule
    -0357             oldRule = testCase.model.grRules{strcmp(testCase.model.rxns, 'ACKr')};
    -0358             oldGene = regexp(oldRule, 'b\d+', 'match', 'once');
    -0359             testCase.verifySubstring(rule, oldGene);
    -0360             testCase.verifyTrue(ismember('b9999', m2.genes));
    -0361         end
    -0362 
    -0363         function copyToCompsDeleteOriginalIsAMove(testCase)
    -0364             % deleteOriginal turns the copy into a move: the reaction count is
    -0365             % unchanged and the original compartment's copy is gone.
    -0366             evalc('copied = copyToComps(testCase.model, {''p''}, ''rxns'', ''ACKr'');');
    -0367             evalc(['moved = copyToComps(testCase.model, {''p''}, ''rxns'', ''ACKr'', ' ...
    -0368                 '''deleteOriginal'', true);']);
    -0369             testCase.verifyEqual(numel(copied.rxns), numel(testCase.model.rxns) + 1);
    -0370             testCase.verifyEqual(numel(moved.rxns), numel(testCase.model.rxns));
    -0371         end
    -0372 
    -0373         function mergeModelsMetParamDecidesUnification(testCase)
    -0374             % The same metabolite under two ids unifies when matching on names
    -0375             % and stays distinct when matching on ids.
    -0376             a = tManipulation.namedMetModel('glc_c', 'A');
    -0377             b = tManipulation.namedMetModel('glucose_c', 'B');
    -0378             evalc('byName = mergeModels({a; b});');
    -0379             evalc('byId   = mergeModels({a; b}, ''metParam'', ''mets'');');
    -0380             testCase.verifyEqual(nnz(strcmp(byName.metNames, 'Glucose')), 1);
    -0381             testCase.verifyEqual(nnz(strcmp(byId.metNames, 'Glucose')), 2);
    -0382         end
    -0383 
    -0384         function mergeModelsKeepsSpontaneousAligned(testCase)
    -0385             % A model carrying spontaneous merged with one that doesn't
    -0386             % must keep the field aligned with rxns, defaulting the
    -0387             % other model's reactions to false rather than leaving it
    -0388             % short.
    -0389             a = tManipulation.namedMetModel('glc_c', 'A');
    -0390             a.spontaneous = true;
    -0391             b = tManipulation.namedMetModel('glucose_c', 'B');
    -0392             evalc('merged = mergeModels({a; b}, ''metParam'', ''mets'');');
    -0393             testCase.verifyEqual(numel(merged.spontaneous), numel(merged.rxns));
    -0394             testCase.verifyTrue(merged.spontaneous(strcmp(merged.rxns,'R_A')));
    -0395             testCase.verifyFalse(merged.spontaneous(strcmp(merged.rxns,'R_B')));
    -0396         end
    -0397 
    -0398         function copyToCompsDefaultCompOutsideAddsCompartment(testCase)
    -0399             % Adding a new compartment without specifying compOutside must
    -0400             % not error when the model already tracks compOutside.
    -0401             m = testCase.model;
    -0402             m.compOutside = repmat({''}, numel(m.comps), 1);
    -0403             evalc('m2 = copyToComps(m, {''p''}, ''ACKr'');');
    -0404             testCase.verifyEqual(numel(m2.compOutside), numel(m2.comps));
    -0405         end
    -0406 
    -0407         function copyToCompsAddsCompartment(testCase)
    -0408             evalc('m2 = copyToComps(testCase.model, {''p''}, ''ACKr'');');
    -0409             testCase.verifyTrue(ismember('p', m2.comps));
    -0410             testCase.verifyGreaterThan(numel(m2.rxns), numel(testCase.model.rxns));
    -0411         end
    -0412 
    -0413         function deleteUnusedGenesNoMoreGenes(testCase)
    -0414             evalc('m2 = deleteUnusedGenes(testCase.model);');
    -0415             testCase.verifyLessThanOrEqual(numel(m2.genes), numel(testCase.model.genes));
    -0416         end
    -0417 
    -0418         function expandModelNoFewerRxns(testCase)
    -0419             evalc('m2 = expandModel(testCase.model);');
    -0420             testCase.verifyGreaterThanOrEqual(numel(m2.rxns), numel(testCase.model.rxns));
    -0421         end
    -0422 
    -0423         function findDuplicateRxnsReturnsResult(testCase)
    -0424             pairs = findDuplicateRxns(testCase.model);
    -0425             testCase.verifyTrue(isnumeric(pairs) || islogical(pairs) || iscell(pairs));
    -0426         end
    -0427 
    -0428         function generateNewIdsUniqueAndPrefixed(testCase)
    -0429             ids = generateNewIds(testCase.model, 'rxns', 'r_', 5);
    -0430             testCase.verifyNumElements(ids, 5);
    -0431             testCase.verifyEqual(numel(unique(ids)), 5);
    -0432             testCase.verifyTrue(all(startsWith(ids, 'r_')));
    -0433         end
    -0434 
    -0435         function generateNewIdsEscapesRegexPrefix(testCase)
    -0436             % A prefix containing a regex metacharacter ('.') must be
    -0437             % matched literally: 'pX999' does not use the 'p.' prefix and
    -0438             % must not be picked up as if '.' were a wildcard.
    -0439             m.rxns = {'p.001'; 'pX999'};
    -0440             ids = generateNewIds(m, 'rxns', 'p.', 'quantity', 1);
    -0441             testCase.verifyEqual(ids{1}, 'p.002');
    -0442         end
    -0443 
    -0444         function mergeCompartmentsSingleComp(testCase)
    -0445             evalc('m2 = mergeCompartments(testCase.model);');
    -0446             testCase.verifyNumElements(m2.comps, 1);
    -0447         end
    -0448 
    -0449         function mergeCompartmentsWarnsWhenUnconstrainedMissing(testCase)
    -0450             % The warning's own text says it fires because there is no
    -0451             % unconstrained field to tell single-metabolite reactions apart
    -0452             % from real exchange reactions; the condition guarding it
    -0453             % checked the opposite, firing only when the field WAS present.
    -0454             m = testCase.model;
    -0455             testCase.verifyWarning(@() mergeCompartments(m,'deleteRxnsWithOneMet',true), ...
    -0456                 'RAVEN:warning');
    -0457         end
    -0458 
    -0459         function mergeCompartmentsDropsStaleCompMiriams(testCase)
    -0460             % Merging collapses every compartment into one, so any
    -0461             % per-compartment MIRIAM annotation must not be left behind at the
    -0462             % original length (which would desync compMiriams from comps).
    -0463             m = testCase.model;
    -0464             m.compMiriams = cell(numel(m.comps),1);
    -0465             m.compMiriams{1}.name  = {'go'};
    -0466             m.compMiriams{1}.value = {'GO:0005737'};
    -0467             evalc('m2 = mergeCompartments(m);');
    -0468             if isfield(m2,'compMiriams')
    -0469                 testCase.verifyNumElements(m2.compMiriams, numel(m2.comps));
    -0470             end
    -0471         end
    -0472 
    -0473         function mergeModelsReturnsStruct(testCase)
    -0474             modelB = removeReactions(testCase.model, testCase.model.rxns(1:5), true, true, true);
    -0475             evalc('merged = mergeModels({testCase.model; modelB});');
    -0476             testCase.verifyClass(merged, 'struct');
    -0477             testCase.verifyNotEmpty(merged.rxns);
    -0478         end
    -0479 
    -0480         function mergeModelsSelfCollisionGetsUniqueIds(testCase)
    -0481             % A source model with two internally duplicate reaction ids
    -0482             % ('R1' twice) that both collide with the growing merged model
    -0483             % must each be renamed to a distinct id, not the same one.
    -0484             a = tManipulation.twoMetModel();
    -0485             b = tManipulation.duplicateRxnModel('M2');
    -0486             evalc('merged = mergeModels({a; b});');
    -0487             testCase.verifyEqual(numel(unique(merged.rxns)), numel(merged.rxns));
    -0488             newIds = merged.rxns(2:3);
    -0489             testCase.verifyTrue(all(startsWith(newIds, 'R1_M2')));
    -0490         end
    -0491 
    -0492         function permuteModelReversesOrder(testCase)
    -0493             n = numel(testCase.model.rxns);
    -0494             m2 = permuteModel(testCase.model, n:-1:1, 'rxns');
    -0495             testCase.verifyEqual(m2.rxns, flipud(testCase.model.rxns));
    -0496         end
    -0497 
    -0498         function permuteModelRemapsRxnComps(testCase)
    -0499             % After swapping compartments the rxnComps and geneComps indices
    -0500             % in the OUTPUT must follow the permutation; the input is unchanged.
    -0501             m = struct();
    -0502             m.rxns = {'r1'; 'r2'};
    -0503             m.mets = {'a_c'; 'b_e'};
    -0504             m.S    = sparse([1 -1; -1 1]);
    -0505             m.lb   = [0; 0]; m.ub = [1000; 1000]; m.rev = [0; 0];
    -0506             m.c    = [1; 0]; m.b  = [0; 0];
    -0507             m.comps     = {'c'; 'e'};
    -0508             m.compNames = {'Cytoplasm'; 'Extracellular'};
    -0509             m.metComps  = [1; 2];
    -0510             m.genes     = {'g1'; 'g2'};
    -0511             m.rxnGeneMat = sparse(eye(2));
    -0512             m.grRules   = {'g1'; 'g2'};
    -0513             m.rxnComps  = [1; 2];   % r1→comps{1}, r2→comps{2}
    -0514             m.geneComps = [1; 2];
    -0515             m2 = permuteModel(m, [2, 1], 'comps');
    -0516             % Swap: old comps{1}→new position 2, old comps{2}→new position 1
    -0517             testCase.verifyEqual(m2.rxnComps,  [2; 1]);
    -0518             testCase.verifyEqual(m2.geneComps, [2; 1]);
    -0519             % Input must be unmodified
    -0520             testCase.verifyEqual(m.rxnComps,  [1; 2]);
    -0521             testCase.verifyEqual(m.geneComps, [1; 2]);
    -0522         end
    -0523 
    -0524         function removeBadRxnsReturnsStruct(testCase)
    -0525             evalc('m2 = removeBadRxns(testCase.model);');
    -0526             testCase.verifyClass(m2, 'struct');
    -0527         end
    -0528 
    -0529         function removeBadRxnsSeedIsReproducible(testCase)
    -0530             % Which reaction is removed among several equally-valid
    -0531             % candidates is randomised; a given seed must make the choice
    -0532             % (and thus the result) reproducible across runs.
    -0533             evalc(['[~, r1] = removeBadRxns(testCase.model, ''rxnRules'', 3, ''seed'', 42);' ...
    -0534                 '[~, r2] = removeBadRxns(testCase.model, ''rxnRules'', 3, ''seed'', 42);']);
    -0535             testCase.verifyEqual(r1, r2);
    -0536         end
    -0537 
    -0538         function removeGenesRemovesGene(testCase)
    -0539             m2 = removeGenes(testCase.model, 'b1817', true, true, false);
    -0540             testCase.verifyFalse(ismember('b1817', m2.genes));
    -0541             testCase.verifyLessThan(numel(m2.genes), numel(testCase.model.genes));
    -0542         end
    -0543 
    -0544         function removeMetsRemovesMet(testCase)
    -0545             m2 = removeMets(testCase.model, 'Acetate', true, true, true, true);
    -0546             testCase.verifyLessThan(numel(m2.mets), numel(testCase.model.mets));
    -0547         end
    -0548 
    -0549         function removeReactionsRemovesRxn(testCase)
    -0550             m2 = removeReactions(testCase.model, testCase.model.rxns(1), true, true, true);
    -0551             testCase.verifyEqual(numel(m2.rxns), numel(testCase.model.rxns) - 1);
    -0552             testCase.verifyFalse(ismember(testCase.model.rxns{1}, m2.rxns));
    -0553         end
    -0554 
    -0555         function replaceMetsMergesMet(testCase)
    -0556             evalc('m2 = replaceMets(testCase.model, ''Acetaldehyde'', ''Acetate'');');
    -0557             testCase.verifyLessThan(numel(m2.mets), numel(testCase.model.mets));
    -0558         end
    -0559 
    -0560         function replaceMetsVerboseKeepsPercentInRxnId(testCase)
    -0561             % A reaction id containing "%" that is reported by 'verbose'
    -0562             % must survive intact, not be truncated by fprintf misreading
    -0563             % it as a format directive.
    -0564             m = struct();
    -0565             m.id='t'; m.rxns={'R_50%_test';'R2'}; m.rxnNames=m.rxns;
    -0566             m.mets={'x';'y';'z'}; m.metNames=m.mets; m.metComps=[1;1;1];
    -0567             m.comps={'c'}; m.compNames={'c'};
    -0568             m.S=sparse([-1 0; 0 -1; 1 1]); % R_50%_test: x=>z   R2: y=>z
    -0569             m.lb=[0;0]; m.ub=[1000;1000]; m.rev=[0;0]; m.c=[0;0]; m.b=zeros(3,1);
    -0570             m.genes={}; m.grRules={'';''}; m.rxnGeneMat=sparse(2,0);
    -0571             out = evalc('replaceMets(m, ''x'', ''y'', ''verbose'', true);');
    -0572             testCase.verifySubstring(out, 'R_50%_test');
    -0573         end
    -0574 
    -0575         function replaceMetsByIdAddsRatherThanOverwrites(testCase)
    -0576             % A reaction where the replacement metabolite is already itself
    -0577             % a participant must keep that contribution: x+y=> must become
    -0578             % 2y=>, not just y=>, once x is replaced by y.
    -0579             m = struct();
    -0580             m.id='t'; m.rxns={'R1';'R2'}; m.rxnNames=m.rxns;
    -0581             m.mets={'x';'y';'z'}; m.metNames=m.mets; m.metComps=[1;1;1];
    -0582             m.comps={'c'}; m.compNames={'c'};
    -0583             m.S=sparse([0 -1; 0 -1; -1 0]); % R1: z=>   R2: x+y=>
    -0584             m.lb=[0;0]; m.ub=[1000;1000]; m.rev=[0;0]; m.c=[0;0]; m.b=zeros(3,1);
    -0585             m.genes={}; m.grRules={'';''}; m.rxnGeneMat=sparse(2,0);
    -0586             evalc('m2 = replaceMets(m, ''x'', ''y'', ''identifiers'', true);');
    -0587             yRow = strcmp(m2.mets,'y');
    -0588             r2 = strcmp(m2.rxns,'R2');
    -0589             testCase.verifyEqual(full(m2.S(yRow,r2)), -2);
    -0590         end
    -0591 
    -0592         function replaceMetsByNameKeepsBShapeAndRuns(testCase)
    -0593             % A model with a two-column b (net-production bounds) must
    -0594             % keep that shape after the metabolites-with-duplicate-name
    -0595             % merge, and the final contractModel call must not error from
    -0596             % a stale post-deletion metabolite index or a disabled
    -0597             % distReverse.
    -0598             m = struct();
    -0599             m.id='t'; m.rxns={'R1';'R2'}; m.rxnNames=m.rxns;
    -0600             m.mets={'ox1';'ox2';'w'}; m.metNames={'oxygen';'o2';'w'};
    -0601             m.metComps=[1;1;1]; m.comps={'c'}; m.compNames={'c'};
    -0602             m.S=sparse([-1 0; 0 -1; 1 1]); % R1: oxygen=>w   R2: o2=>w
    -0603             m.lb=[0;0]; m.ub=[1000;1000]; m.rev=[0;0]; m.c=[0;0];
    -0604             m.b=[zeros(3,1) ones(3,1)]; % two-column b
    -0605             m.genes={}; m.grRules={'';''}; m.rxnGeneMat=sparse(2,0);
    -0606             evalc('m2 = replaceMets(m, ''oxygen'', ''o2'');');
    -0607             testCase.verifyEqual(size(m2.b,2), 2);
    -0608             testCase.verifyEqual(size(m2.b,1), numel(m2.mets));
    -0609             testCase.verifyEqual(numel(m2.mets), 2); % oxygen and o2 merged
    -0610         end
    -0611 
    -0612         function setExchangeBoundsFindsAllMultiExchangeMets(testCase)
    -0613             % Every metabolite exchanged by more than one reaction must be
    -0614             % reported, not just whichever one happens to line up between
    -0615             % two differently-sized index ranges compared directly against
    -0616             % each other.
    -0617             m = struct();
    -0618             m.id='t';
    -0619             m.rxns={'r1';'r2';'r3';'r4';'r5';'r6';'r7'}; m.rxnNames=m.rxns;
    -0620             m.mets={'met1';'met2';'met3';'met4';'met5'};
    -0621             m.metNames={'MetOne';'MetTwo';'MetThree';'MetFour';'MetFive'};
    -0622             m.metComps=[1;1;1;1;1]; m.comps={'c'}; m.compNames={'c'};
    -0623             % r1:=>met5  r2:=>met1  r3:=>met2  r4:met5=>  r5:=>met3  r6:=>met4  r7:met3=>
    -0624             m.S = sparse(5,7);
    -0625             m.S(5,1)=1; m.S(1,2)=1; m.S(2,3)=1; m.S(5,4)=-1; m.S(3,5)=1; m.S(4,6)=1; m.S(3,7)=-1;
    -0626             m.lb=-1000*ones(7,1); m.ub=1000*ones(7,1); m.rev=ones(7,1); m.c=zeros(7,1); m.b=zeros(5,1);
    -0627             m.genes={}; m.grRules=repmat({''},7,1); m.rxnGeneMat=sparse(7,0);
    -0628             txt = evalc('setExchangeBounds(m);');
    -0629             testCase.verifySubstring(txt, 'MetThree');
    -0630             testCase.verifySubstring(txt, 'MetFive');
    -0631         end
    -0632 
    -0633         function setExchangeBoundsRuns(testCase)
    -0634             evalc('m2 = setExchangeBounds(testCase.model, {''ac_e'';''akg_e''}, -500, 500);');
    -0635             testCase.verifyClass(m2, 'struct');
    -0636         end
    -0637 
    -0638         function setParamUncOnModelWithoutAnnotationField(testCase)
    -0639             % 'unc' falls back to default LB/UB (-1000/1000) when there is
    -0640             % no annotation field at all, not just when annotation exists
    -0641             % but lacks defaultLB/defaultUB.
    -0642             m = testCase.model;
    -0643             if isfield(m,'annotation')
    -0644                 m = rmfield(m,'annotation');
    -0645             end
    -0646             m2 = setParam(m, 'unc', m.rxns(1), 0);
    -0647             idx = strcmp(m2.rxns, m.rxns{1});
    -0648             testCase.verifyEqual(m2.lb(idx), -1000);
    -0649             testCase.verifyEqual(m2.ub(idx), 1000);
    -0650         end
    -0651 
    -0652         function setParamObjective(testCase)
    -0653             m2 = setParam(testCase.model, 'obj', testCase.model.rxns(1), 1);
    -0654             idx = strcmp(m2.rxns, testCase.model.rxns{1});
    -0655             testCase.verifyEqual(m2.c(idx), 1);
    -0656         end
    -0657 
    -0658         function setParamUpperBound(testCase)
    -0659             m2 = setParam(testCase.model, 'ub', testCase.model.rxns(1), 42);
    -0660             idx = strcmp(m2.rxns, testCase.model.rxns{1});
    -0661             testCase.verifyEqual(m2.ub(idx), 42);
    -0662         end
    -0663 
    -0664         function simplifyModelReturnsStruct(testCase)
    -0665             evalc('m2 = simplifyModel(testCase.model);');
    -0666             testCase.verifyClass(m2, 'struct');
    -0667             testCase.verifyLessThanOrEqual(numel(m2.rxns), numel(testCase.model.rxns));
    -0668         end
    -0669 
    -0670         function simplifyModelKeepsMetsConstrainedByB(testCase)
    -0671             % R2 is constrained to zero flux, so deleteZeroInterval removes
    -0672             % it and leaves C in no reaction at all. A non-zero b is a
    -0673             % boundary condition on C, and removing the metabolite takes
    -0674             % that row of b with it, so whatever solves the reduced model
    -0675             % is no longer asked to produce C.
    -0676             m = struct();
    -0677             m.id = 'test';
    -0678             m.rxns = {'R1';'R2'}; m.rxnNames = m.rxns;
    -0679             m.mets = {'A';'B';'C'}; m.metNames = m.mets; m.metComps = [1;1;1];
    -0680             m.comps = {'c'}; m.compNames = m.comps;
    -0681             m.S = sparse([-1 0; 1 -1; 0 1]);   % R1: A => B, R2: B => C
    -0682             m.lb = [0;0]; m.ub = [1000;0]; m.rev = [0;0]; m.c = [0;0];
    -0683             m.genes = {}; m.grRules = {'';''}; m.rxnGeneMat = sparse(2,0);
    -0684             m.b = zeros(3,2);
    -0685             m.b(3,:) = [1 1];                  % C must be produced, 1 unit
    -0686 
    -0687             evalc('[reduced,~,deletedMets] = simplifyModel(m,false,false,true);');
    -0688             testCase.verifyTrue(ismember('C', reduced.mets));
    -0689             testCase.verifyFalse(ismember('C', deletedMets));
    -0690             testCase.verifyEqual(reduced.b(strcmp(reduced.mets,'C'),:), [1 1]);
    -0691 
    -0692             % Without that constraint C is dead weight and is removed.
    -0693             m.b(3,:) = [0 0];
    -0694             evalc('reduced = simplifyModel(m,false,false,true);');
    -0695             testCase.verifyFalse(ismember('C', reduced.mets));
    -0696         end
    -0697 
    -0698         function sortIdentifiersSortsRxns(testCase)
    -0699             m2 = sortIdentifiers(testCase.model);
    -0700             testCase.verifyEqual(sort(m2.rxns), m2.rxns);
    -0701         end
    -0702 
    -0703         function sortModelPreservesCounts(testCase)
    -0704             m2 = sortModel(testCase.model);
    -0705             testCase.verifyEqual(numel(m2.rxns), numel(testCase.model.rxns));
    -0706             testCase.verifyEqual(numel(m2.mets), numel(testCase.model.mets));
    -0707         end
    -0708 
    -0709         function sortReactionOrderSeedIsReproducible(testCase)
    -0710             % The local search proposes swaps randomly; a given seed must
    -0711             % make its result reproducible across runs.
    -0712             m = testCase.model;
    -0713             m.subSystems = repmat({{'ALL'}}, numel(m.rxns), 1);
    -0714             m1 = sortModel(m, 'sortReversible', false, 'sortReactionOrder', true, 'seed', 5);
    -0715             m2 = sortModel(m, 'sortReversible', false, 'sortReactionOrder', true, 'seed', 5);
    -0716             testCase.verifyEqual(m1.rxns, m2.rxns);
    -0717         end
    -0718 
    -0719         function sortReactionOrderUsesSubsystemsOwnColumns(testCase)
    -0720             % sortReactionOrder must score and reorder a subsystem's own
    -0721             % reactions, not whichever columns happen to occupy the first
    -0722             % nRxns positions of the whole model: with a chain A->B->C->D
    -0723             % split across R2 (A=>B), R3 (B=>C) and R1 (C=>D) -- placed
    -0724             % after two unrelated filler reactions so the subsystem is NOT
    -0725             % at the start of model.rxns -- the only production-before-
    -0726             % consumption order is R2, then R3, then R1.
    -0727             m = struct();
    -0728             m.id='t'; m.rxns={'FILLER1';'FILLER2';'R1';'R2';'R3'}; m.rxnNames=m.rxns;
    -0729             m.mets={'fa';'fb';'fc';'fd';'A';'B';'C';'D'}; m.metNames=m.mets;
    -0730             m.metComps=ones(8,1); m.comps={'c'}; m.compNames={'c'};
    -0731             S=zeros(8,5);
    -0732             S(1,1)=-1; S(2,1)=1;  % FILLER1: fa=>fb
    -0733             S(3,2)=-1; S(4,2)=1;  % FILLER2: fc=>fd
    -0734             S(7,3)=-1; S(8,3)=1;  % R1: C=>D
    -0735             S(5,4)=-1; S(6,4)=1;  % R2: A=>B
    -0736             S(6,5)=-1; S(7,5)=1;  % R3: B=>C
    -0737             m.S=sparse(S);
    -0738             m.lb=zeros(5,1); m.ub=ones(5,1)*1000; m.rev=zeros(5,1); m.c=zeros(5,1);
    -0739             m.b=zeros(8,1);
    -0740             m.genes={}; m.grRules=repmat({''},5,1); m.rxnGeneMat=sparse(5,0);
    -0741             m.subSystems={{};{};{'CHAIN'};{'CHAIN'};{'CHAIN'}};
    -0742 
    -0743             rng(1);
    -0744             m2=sortModel(m,'sortReversible',false,'sortReactionOrder',true);
    -0745             posR1=find(strcmp(m2.rxns,'R1'));
    -0746             posR2=find(strcmp(m2.rxns,'R2'));
    -0747             posR3=find(strcmp(m2.rxns,'R3'));
    -0748             testCase.verifyLessThan(posR2, posR3);
    -0749             testCase.verifyLessThan(posR3, posR1);
    -0750         end
    -0751 
    -0752         function standardizeGrRulesReturnsRules(testCase)
    -0753             evalc('grRules = standardizeGrRules(testCase.model);');
    -0754             testCase.verifyNumElements(grRules, numel(testCase.model.rxns));
    -0755         end
    -0756 
    -0757         function findPotentialErrorsFlagsOnlyNonDnf(testCase)
    -0758             m = struct();
    -0759             m.rxns = {'R1';'R2';'R3';'R4'};
    -0760             m.grRules = {'((G1 and G2) or G3)'      % DNF, just bracketed
    -0761                          '(G1 or G2) or (G3 and G4)' % DNF
    -0762                          'G1 or G2'                  % DNF
    -0763                          '(G1 or G2) and (G3 or G4)'};% genuinely non-DNF
    -0764             issues = findPotentialErrors(m);
    -0765             testCase.verifyEqual(vertcat(issues.index), 4);
    -0766         end
    -0767 
    -0768         function findPotentialErrorsReportsUnparseable(testCase)
    -0769             m = struct();
    -0770             m.rxns = {'R1'};
    -0771             m.grRules = {'(G1 and G2'};
    -0772             issues = findPotentialErrors(m);
    -0773             testCase.verifyNumElements(issues, 1);
    -0774             testCase.verifySubstring(issues(1).reason, 'Cannot be parsed');
    -0775         end
    -0776 
    -0777         function standardizeGrRulesRepairsBracketedDnf(testCase)
    -0778             % A rule that is DNF but redundantly bracketed must be repaired,
    -0779             % not skipped: standardizeGrRules leaves flagged rules alone, so a
    -0780             % false positive from findPotentialErrors silently prevents repair.
    -0781             m = struct();
    -0782             m.rxns = {'R1'};
    -0783             m.grRules = {'((G1 and G2) or G3)'};
    -0784             m.genes = {'G1';'G2';'G3'};
    -0785             m.rxnGeneMat = sparse([1 1 1]);
    -0786             [grRules,~,indexes2check] = standardizeGrRules(m, true);
    -0787             testCase.verifyEmpty(indexes2check);
    -0788             testCase.verifyEqual(grRules{1}, '(G1 and G2) or G3');
    -0789         end
    -0790 
    -0791         function standardizeGrRulesKeepsPercentInWarning(testCase)
    -0792             % A rxn id containing a literal "%" must survive intact in the
    -0793             % "potentially problematic relationships" warning, not be
    -0794             % truncated by sprintf/warning misreading it as a directive.
    -0795             m.rxns = {'RXN_50%_TEST'};
    -0796             m.grRules = {'(G1 or G2) and G3'};
    -0797             m.genes = {'G1';'G2';'G3'};
    -0798             m.rxnGeneMat = sparse([1 1 1]);
    -0799             lastwarn('');
    -0800             evalc('standardizeGrRules(m);');
    -0801             msg = lastwarn();
    -0802             testCase.verifySubstring(msg, 'RXN_50%_TEST');
    -0803         end
    -0804 
    -0805         function removeGenesMatchesWholeGeneIds(testCase)
    -0806             % Gene "10" is a prefix of "100". Removing it must not take "100"
    -0807             % with it, which an unanchored substring search would.
    -0808             m = testCase.gprTestModel('10 or 100', {'10';'100'}, [1 1]);
    -0809             r = removeGenes(m, {'10'});
    -0810             testCase.verifyEqual(r.grRules{1}, '100');
    -0811             testCase.verifyEqual(r.ub(1), 1000);
    -0812         end
    -0813 
    -0814         function removeGenesDropsWholeComplex(testCase)
    -0815             % A complex missing a subunit cannot form; the other isozyme lives.
    -0816             m = testCase.gprTestModel('(G1 and G2) or G3', {'G1';'G2';'G3'}, [1 1 1]);
    -0817             r = removeGenes(m, {'G1'});
    -0818             testCase.verifyEqual(r.grRules{1}, 'G3');
    -0819         end
    -0820 
    -0821         function removeGenesBlocksWhenNoEnzymeLeft(testCase)
    -0822             m = testCase.gprTestModel('G1 and G2', {'G1';'G2'}, [1 1]);
    -0823             r = removeGenes(m, {'G1'});
    -0824             testCase.verifyEmpty(r.grRules{1});
    -0825             testCase.verifyEqual(r.lb(1), 0);
    -0826             testCase.verifyEqual(r.ub(1), 0);
    -0827         end
    -0828 
    -0829         function expandModelKeepsMandatorySubunit(testCase)
    -0830             % "g1 and (g2 or g3)" is two isozymes, both needing g1. Stripping
    -0831             % brackets and splitting on ' or ' loses g1 from the second.
    -0832             m = testCase.gprTestModel('g1 and (g2 or g3)', {'g1';'g2';'g3'}, [1 1 1]);
    -0833             e = expandModel(m);
    -0834             testCase.verifyEqual(sort(e.grRules), {'g1 and g2';'g1 and g3'});
    -0835         end
    -0836 
    -0837         function expandModelCopiesSpontaneousAndPwys(testCase)
    -0838             % Each isozyme copy created by splitting an OR rule must inherit
    -0839             % the source reaction's spontaneous/pwys annotation, keeping both
    -0840             % fields aligned with rxns.
    -0841             m = testCase.gprTestModel('g1 or g2', {'g1';'g2'}, [1 1]);
    -0842             m.spontaneous = true;
    -0843             m.pwys = {'pathway1'};
    -0844             e = expandModel(m);
    -0845             testCase.verifyEqual(numel(e.spontaneous), numel(e.rxns));
    -0846             testCase.verifyEqual(numel(e.pwys), numel(e.rxns));
    -0847             testCase.verifyTrue(all(e.spontaneous));
    -0848             testCase.verifyTrue(all(strcmp(e.pwys, 'pathway1')));
    -0849         end
    -0850 
    -0851         function expandModelCopiesRxnScores(testCase)
    -0852             % Each isozyme copy created by splitting an OR rule must
    -0853             % inherit the source reaction's rxnScores, keeping it aligned
    -0854             % with rxns.
    -0855             m = testCase.gprTestModel('g1 or g2', {'g1';'g2'}, [1 1]);
    -0856             m.rxnScores = 2.5;
    -0857             e = expandModel(m);
    -0858             testCase.verifyEqual(numel(e.rxnScores), numel(e.rxns));
    -0859             testCase.verifyTrue(all(e.rxnScores == 2.5));
    -0860         end
    -0861 
    -0862         function expandModelDistributesBothSides(testCase)
    -0863             % Two or:s, but four isozymes.
    -0864             m = testCase.gprTestModel('(g1 or g2) and (g3 or g4)', ...
    -0865                 {'g1';'g2';'g3';'g4'}, [1 1 1 1]);
    -0866             [e, rxnToCheck] = expandModel(m);
    -0867             testCase.verifyEqual(sort(e.grRules), ...
    -0868                 {'g1 and g3';'g1 and g4';'g2 and g3';'g2 and g4'});
    -0869             testCase.verifyEqual(rxnToCheck, {'R1'});
    -0870         end
    -0871 
    -0872         function expandModelLeavesDnfAlone(testCase)
    -0873             % An OR of complexes expands without needing distributivity, so it
    -0874             % must not be reported as needing a check.
    -0875             m = testCase.gprTestModel('(g1 and g2) or (g3 and g4)', ...
    -0876                 {'g1';'g2';'g3';'g4'}, [1 1 1 1]);
    -0877             [e, rxnToCheck] = expandModel(m);
    -0878             testCase.verifyEqual(sort(e.grRules), {'g1 and g2';'g3 and g4'});
    -0879             testCase.verifyEmpty(rxnToCheck);
    -0880         end
    -0881 
    -0882     end
    -0883 
    -0884     methods (Access = private)
    -0885         function m = gprTestModel(~, grRule, genes, rxnGeneRow)
    -0886             % Smallest model that removeGenes/expandModel will operate on.
    -0887             m = struct();
    -0888             m.rxns = {'R1'}; m.rxnNames = {'R1'};
    -0889             m.mets = {'A';'B'}; m.metNames = {'A';'B'}; m.metComps = [1;1];
    -0890             m.comps = {'c'}; m.compNames = {'c'};
    -0891             m.S = sparse([-1;1]); m.lb = 0; m.ub = 1000; m.rev = 0; m.c = 0;
    -0892             m.b = [0;0];
    -0893             m.genes = genes;
    -0894             m.grRules = {grRule};
    -0895             m.rxnGeneMat = sparse(rxnGeneRow);
    -0896         end
    -0897     end
    -0898 
    -0899     methods (Static, Access = private)
    -0900 
    -0901         function m = twoMetModel()
    -0902             % Single reaction a -> b in one compartment.
    -0903             m = struct();
    -0904             m.id        = 'toy';
    -0905             m.rxns      = {'R1'};
    -0906             m.rxnNames  = {'R1'};
    -0907             m.mets      = {'a';'b'};
    -0908             m.metNames  = {'a';'b'};
    -0909             m.metComps  = [1;1];
    -0910             m.comps     = {'c'};
    -0911             m.compNames = {'cytosol'};
    -0912             m.S         = sparse([-1;1]);
    -0913             m.lb = 0; m.ub = 1000; m.rev = 0; m.c = 0; m.b = zeros(2,1);
    -0914             m.genes = {}; m.grRules = {''}; m.rxnGeneMat = sparse(1,0);
    -0915             m.metFormulas = {'C';'C'};
    -0916         end
    -0917 
    -0918         function m = namedMetModel(glucoseId, modelId)
    -0919             % Glucose[c] under a caller-chosen id, consumed by one reaction.
    -0920             m = struct();
    -0921             m.id        = modelId;
    -0922             m.rxns      = {['R_' modelId]};
    -0923             m.rxnNames  = m.rxns;
    -0924             m.mets      = {glucoseId; ['product_' modelId]};
    -0925             m.metNames  = {'Glucose'; ['Product' modelId]};
    -0926             m.metComps  = [1;1];
    -0927             m.comps     = {'c'};
    -0928             m.compNames = {'cytosol'};
    -0929             m.S         = sparse([-1;1]);
    -0930             m.lb = 0; m.ub = 1000; m.rev = 0; m.c = 0; m.b = zeros(2,1);
    -0931             m.genes = {}; m.grRules = {''}; m.rxnGeneMat = sparse(1,0);
    -0932             m.metFormulas = {'C6H12O6';'C'};
    -0933         end
    -0934 
    -0935         function m = duplicateRxnModel(modelId)
    -0936             % Two reactions, both named 'R1' internally. mergeModels'
    -0937             % own docstring says duplicate reaction ids "might appear" in
    -0938             % a model, so this is a real (if unusual) input shape to
    -0939             % guard the merge against, not a contrived one.
    -0940             m = struct();
    -0941             m.id        = modelId;
    -0942             m.rxns      = {'R1'; 'R1'};
    -0943             m.rxnNames  = {'R1a'; 'R1b'};
    -0944             m.mets      = {'x'; 'y'; 'z'};
    -0945             m.metNames  = m.mets;
    -0946             m.metComps  = [1;1;1];
    -0947             m.comps     = {'c'};
    -0948             m.compNames = {'cytosol'};
    -0949             m.S         = sparse([-1 0; 1 -1; 0 1]);
    -0950             m.lb = [0;0]; m.ub = [1000;1000]; m.rev = [0;0]; m.c = [0;0]; m.b = zeros(3,1);
    -0951             m.genes = {}; m.grRules = {'';''}; m.rxnGeneMat = sparse(2,0);
    -0952             m.metFormulas = {'C';'C';'C'};
    -0953         end
    -0954 
    -0955     end
    -0956 end
    -
    Generated by m2html © 2005
    - - \ No newline at end of file diff --git a/docs/html/testing/function_tests/tNamedArgs.html b/docs/html/testing/function_tests/tNamedArgs.html deleted file mode 100644 index 2c523225..00000000 --- a/docs/html/testing/function_tests/tNamedArgs.html +++ /dev/null @@ -1,98 +0,0 @@ - - - - Description of tNamedArgs - - - - - - - - - -
    Home > testing > function_tests > tNamedArgs.m
    - - - -

    tNamedArgs -

    - -

    PURPOSE ^

    -
    - -

    SYNOPSIS ^

    -
    This is a script file.
    - -

    DESCRIPTION ^

    -
    - - -

    CROSS-REFERENCE INFORMATION ^

    -This function calls: - -This function is called by: - - - -

    SUBFUNCTIONS ^

    - - -

    SOURCE CODE ^

    -
    0001 classdef tNamedArgs < RavenTestCase
    -0002 % tNamedArgs  Verifies that functions converted to parseRAVENargs accept both
    -0003 %             the positional and the name-value calling conventions, and that
    -0004 %             the two forms give identical results.
    -0005 
    -0006     methods (Test)
    -0007 
    -0008         function getIndexesPositionalAndNamedMatch(testCase)
    -0009             pos = getIndexes(testCase.model, testCase.model.rxns(1:3), 'rxns', true);
    -0010             nv  = getIndexes(testCase.model, testCase.model.rxns(1:3), 'rxns', ...
    -0011                 'returnLogical', true);
    -0012             testCase.verifyEqual(nv, pos);
    -0013         end
    -0014 
    -0015         function addTransportPositionalAndNamedMatch(testCase)
    -0016             evalc(['m1 = addTransport(testCase.model, ''c'', ''e'', ' ...
    -0017                 '{''6-phospho-D-glucono-1,5-lactone''}, false, false, ''tr_'');']);
    -0018             evalc(['m2 = addTransport(testCase.model, ''c'', ''e'', ' ...
    -0019                 '''metNames'', {''6-phospho-D-glucono-1,5-lactone''}, ' ...
    -0020                 '''isRev'', false, ''onlyToExisting'', false, ''prefix'', ''tr_'');']);
    -0021             testCase.verifyEqual(numel(m2.rxns), numel(m1.rxns));
    -0022         end
    -0023 
    -0024         function addTransportNamedSubsetOfOptions(testCase)
    -0025             % A subset of options by name; the rest keep their defaults.
    -0026             evalc(['m = addTransport(testCase.model, ''c'', ''e'', ' ...
    -0027                 '''metNames'', {''6-phospho-D-glucono-1,5-lactone''}, ' ...
    -0028                 '''onlyToExisting'', false);']);
    -0029             testCase.verifyGreaterThan(numel(m.rxns), numel(testCase.model.rxns));
    -0030         end
    -0031 
    -0032         function exportToExcelFormatPositionalAndNamed(testCase)
    -0033             f1 = [tempname '.xlsx'];
    -0034             f2 = [tempname '.xlsx'];
    -0035             testCase.addTeardown(@() delete(f1));
    -0036             testCase.addTeardown(@() delete(f2));
    -0037             evalc('exportToExcelFormat(testCase.model, f1, true);');
    -0038             evalc('exportToExcelFormat(testCase.model, ''fileName'', f2, ''sortIds'', true);');
    -0039             testCase.verifyTrue(exist(f1,'file')==2);
    -0040             testCase.verifyTrue(exist(f2,'file')==2);
    -0041         end
    -0042 
    -0043         function tooManyPositionalErrors(testCase)
    -0044             testCase.verifyError(@() getIndexes(testCase.model, ...
    -0045                 testCase.model.rxns(1), 'rxns', true, 'extra'), ?MException);
    -0046         end
    -0047 
    -0048     end
    -0049 end
    -
    Generated by m2html © 2005
    - - \ No newline at end of file diff --git a/docs/html/testing/function_tests/tParseRAVENargs.html b/docs/html/testing/function_tests/tParseRAVENargs.html deleted file mode 100644 index 40710ef5..00000000 --- a/docs/html/testing/function_tests/tParseRAVENargs.html +++ /dev/null @@ -1,113 +0,0 @@ - - - - Description of tParseRAVENargs - - - - - - - - - -
    Home > testing > function_tests > tParseRAVENargs.m
    - - - -

    tParseRAVENargs -

    - -

    PURPOSE ^

    -
    - -

    SYNOPSIS ^

    -
    This is a script file.
    - -

    DESCRIPTION ^

    -
    - - -

    CROSS-REFERENCE INFORMATION ^

    -This function calls: - -This function is called by: - - - -

    SUBFUNCTIONS ^

    - - -

    SOURCE CODE ^

    -
    0001 classdef tParseRAVENargs < matlab.unittest.TestCase
    -0002 % tParseRAVENargs  Tests for the parseRAVENargs optional-argument resolver.
    -0003 
    -0004     methods (Test)
    -0005 
    -0006         function defaultsWhenEmpty(testCase)
    -0007             p = parseRAVENargs({}, {'a', 1; 'b', 'x'});
    -0008             testCase.verifyEqual(p.a, 1);
    -0009             testCase.verifyEqual(p.b, 'x');
    -0010         end
    -0011 
    -0012         function positionalAssignment(testCase)
    -0013             p = parseRAVENargs({10, 'y'}, {'a', 1; 'b', 'x'});
    -0014             testCase.verifyEqual(p.a, 10);
    -0015             testCase.verifyEqual(p.b, 'y');
    -0016         end
    -0017 
    -0018         function partialPositionalKeepsLaterDefaults(testCase)
    -0019             p = parseRAVENargs({10}, {'a', 1; 'b', 'x'});
    -0020             testCase.verifyEqual(p.a, 10);
    -0021             testCase.verifyEqual(p.b, 'x');
    -0022         end
    -0023 
    -0024         function nameValueAssignment(testCase)
    -0025             p = parseRAVENargs({'b', 'y'}, {'a', 1; 'b', 'x'});
    -0026             testCase.verifyEqual(p.a, 1);     % untouched -> default
    -0027             testCase.verifyEqual(p.b, 'y');
    -0028         end
    -0029 
    -0030         function nameValueIsCaseInsensitive(testCase)
    -0031             p = parseRAVENargs({'B', 'y'}, {'a', 1; 'b', 'x'});
    -0032             testCase.verifyEqual(p.b, 'y');
    -0033         end
    -0034 
    -0035         function nameValueBothParams(testCase)
    -0036             p = parseRAVENargs({'b', 'y', 'a', 5}, {'a', 1; 'b', 'x'});
    -0037             testCase.verifyEqual(p.a, 5);
    -0038             testCase.verifyEqual(p.b, 'y');
    -0039         end
    -0040 
    -0041         function oddCountStaysPositional(testCase)
    -0042             % A single string value that happens to match a name is positional.
    -0043             p = parseRAVENargs({'a'}, {'a', 1; 'b', 'x'});
    -0044             testCase.verifyEqual(p.a, 'a');
    -0045         end
    -0046 
    -0047         function tooManyPositionalErrors(testCase)
    -0048             testCase.verifyError(@() parseRAVENargs({1, 2, 3}, {'a', 1; 'b', 2}), ...
    -0049                 'parseRAVENargs:tooManyArgs');
    -0050         end
    -0051 
    -0052         function validatorRejectsInvalid(testCase)
    -0053             spec = {'n', 1, @mustBeScalarOrEmpty};
    -0054             testCase.verifyError(@() parseRAVENargs({'n', [1 2 3]}, spec), ?MException);
    -0055         end
    -0056 
    -0057         function validatorAcceptsValid(testCase)
    -0058             spec = {'n', 1, @mustBeScalarOrEmpty};
    -0059             p = parseRAVENargs({'n', 7}, spec);
    -0060             testCase.verifyEqual(p.n, 7);
    -0061         end
    -0062 
    -0063     end
    -0064 end
    -
    Generated by m2html © 2005
    - - \ No newline at end of file diff --git a/docs/html/testing/function_tests/tQueries.html b/docs/html/testing/function_tests/tQueries.html deleted file mode 100644 index 7cf93dd9..00000000 --- a/docs/html/testing/function_tests/tQueries.html +++ /dev/null @@ -1,491 +0,0 @@ - - - - Description of tQueries - - - - - - - - - -
    Home > testing > function_tests > tQueries.m
    - - - -

    tQueries -

    - -

    PURPOSE ^

    -
    - -

    SYNOPSIS ^

    -
    This is a script file.
    - -

    DESCRIPTION ^

    -
    - - -

    CROSS-REFERENCE INFORMATION ^

    -This function calls: - -This function is called by: - - - -

    SUBFUNCTIONS ^

    - - -

    SOURCE CODE ^

    -
    0001 classdef tQueries < RavenTestCase
    -0002 % tQueries  Tests for the read-only query/accessor functions in queries/.
    -0003 
    -0004     methods (Test)
    -0005 
    -0006         function buildEquationReversible(testCase)
    -0007             eqn = buildEquation({'a';'b'}, [-1;1], true);
    -0008             testCase.verifyClass(eqn, 'char');
    -0009             testCase.verifySubstring(eqn, 'a');
    -0010             testCase.verifySubstring(eqn, 'b');
    -0011             testCase.verifySubstring(eqn, '<=>');
    -0012         end
    -0013 
    -0014         function buildEquationIrreversible(testCase)
    -0015             eqn = buildEquation({'a';'b'}, [-1;1], false);
    -0016             testCase.verifySubstring(eqn, '=>');
    -0017             testCase.verifyEmpty(strfind(eqn, '<=>')); %#ok<STRIFY>
    -0018         end
    -0019 
    -0020         function checkModelStructValidModel(testCase)
    -0021             % A valid model must not throw when errors are requested.
    -0022             % Advisory warnings (unused elements, bounds notes, etc.) are
    -0023             % expected MATLAB warnings in the new system; the check is
    -0024             % only that no error is raised.
    -0025             checkModelStruct(testCase.model, true);
    -0026         end
    -0027 
    -0028         function checkModelStructNoFalsePositiveOnWordStartingName(testCase)
    -0029             % A metabolite name beginning with a non-numeric word must not be
    -0030             % flagged as "begins with a number": str2double of that word is
    -0031             % NaN, and any(NaN) is true in MATLAB.
    -0032             m = testCase.model;
    -0033             m.metNames{1} = 'alpha keto acid';
    -0034             issues = checkModelStruct(m);
    -0035             hit = arrayfun(@(x) contains(x.message,'begin with a number'), issues);
    -0036             testCase.verifyFalse(any(hit));
    -0037         end
    -0038 
    -0039         function checkModelStructFlagsDuplicateNonSboMiriam(testCase)
    -0040             % Two metabolites with different names sharing the same
    -0041             % non-SBO MIRIAM (e.g. the same KEGG id) must be flagged:
    -0042             % regexp's [] "no match" result must not be mistaken for a
    -0043             % match via bitwise negation (~[] is also [], so the check
    -0044             % never fired at all before the fix).
    -0045             m = testCase.model;
    -0046             idx = find(~strcmp(m.metNames, m.metNames{1}), 1);
    -0047             m.metMiriams = cell(numel(m.mets),1);
    -0048             m.metMiriams{1}.name = {'kegg.compound'}; m.metMiriams{1}.value = {'C00031'};
    -0049             m.metMiriams{idx}.name = {'kegg.compound'}; m.metMiriams{idx}.value = {'C00031'};
    -0050             issues = checkModelStruct(m);
    -0051             hit = arrayfun(@(x) contains(x.message,'more than one unique metabolite name'), issues);
    -0052             testCase.verifyTrue(any(hit));
    -0053         end
    -0054 
    -0055         function checkModelStructThrowErrorsFalseSkipsAfterMissingField(testCase)
    -0056             % throwErrors=false must warn about a missing required field
    -0057             % without then crashing trying to read that very field in a
    -0058             % later check.
    -0059             m = rmfield(testCase.model, 'id');
    -0060             evalc('checkModelStruct(m, ''throwErrors'', false);');
    -0061         end
    -0062 
    -0063         function constructEquationsAllRxns(testCase)
    -0064             eqns = constructEquations(testCase.model);
    -0065             testCase.verifyClass(eqns, 'cell');
    -0066             testCase.verifyNumElements(eqns, numel(testCase.model.rxns));
    -0067             testCase.verifyTrue(all(~cellfun(@isempty, eqns)));
    -0068         end
    -0069 
    -0070         function constructEquationsSubset(testCase)
    -0071             eqns = constructEquations(testCase.model, testCase.model.rxns(1:3));
    -0072             testCase.verifyNumElements(eqns, 3);
    -0073         end
    -0074 
    -0075         function constructSSimple(testCase)
    -0076             [S, mets] = constructS({'a + b => c'});
    -0077             testCase.verifyEqual(sort(mets(:)), {'a';'b';'c'});
    -0078             testCase.verifySize(S, [3 1]);
    -0079             % reactants negative, product positive (S is sparse)
    -0080             testCase.verifyEqual(full(S), [-1;-1;1]);
    -0081         end
    -0082 
    -0083         function constructSLeadingNumberIsPartOfName(testCase)
    -0084             % A metabolite whose name starts with a number must not have that
    -0085             % number read as a stoichiometric coefficient when the metabolite
    -0086             % list says the whole entry is a metabolite.
    -0087             mets = {'2 oxoglutarate';'succinate'};
    -0088             [S, outMets] = constructS({'2 oxoglutarate => succinate'}, 'mets', mets);
    -0089             testCase.verifyEqual(outMets, mets);
    -0090             testCase.verifyEqual(full(S), [-1;1]);
    -0091         end
    -0092 
    -0093         function constructSLeadingNumberStillReadsCoefficient(testCase)
    -0094             % With no such metabolite, the leading number is a coefficient.
    -0095             mets = {'oxoglutarate';'succinate'};
    -0096             [S, outMets] = constructS({'2 oxoglutarate => succinate'}, 'mets', mets);
    -0097             testCase.verifyEqual(outMets, mets);
    -0098             testCase.verifyEqual(full(S), [-2;1]);
    -0099         end
    -0100 
    -0101         function constructSSumsRepeatedMetabolite(testCase)
    -0102             % A metabolite written more than once on the same side is one
    -0103             % coefficient, not the last occurrence.
    -0104             [S, mets] = constructS({'2 a + a => b'}, 'mets', {'a';'b'});
    -0105             testCase.verifyEqual(mets, {'a';'b'});
    -0106             testCase.verifyEqual(full(S), [-3;1]);
    -0107         end
    -0108 
    -0109         function constructSCancelsMetaboliteOnBothSides(testCase)
    -0110             % A metabolite on both sides cancels, which is exactly what
    -0111             % badRxns reports: the reaction is empty in the S matrix.
    -0112             [S, ~, badRxns] = constructS({'atp + adp <=> adp + atp'}, ...
    -0113                 'mets', {'atp';'adp'});
    -0114             testCase.verifyEqual(full(S), [0;0]);
    -0115             testCase.verifyTrue(badRxns(1));
    -0116         end
    -0117 
    -0118         function constructSMissingMetKeepsPercentInErrorNoRxns(testCase)
    -0119             % A missing metabolite name containing "%" must survive intact
    -0120             % in the error message, not be truncated by sprintf misreading
    -0121             % it as a format directive.
    -0122             testCase.verifyError(@() constructS({'a + met_with_%_sign => b'}, ...
    -0123                 'mets', {'a';'b'}), 'RAVEN:badInput');
    -0124             try
    -0125                 constructS({'a + met_with_%_sign => b'}, 'mets', {'a';'b'});
    -0126             catch e
    -0127                 testCase.verifySubstring(e.message, 'met_with_%_sign');
    -0128             end
    -0129         end
    -0130 
    -0131         function constructSMissingMetKeepsPercentInErrorWithRxns(testCase)
    -0132             % Same, but via the reaction-annotated branch (rxns supplied),
    -0133             % which also splices in the reaction id.
    -0134             try
    -0135                 constructS({'a + met_with_%_sign => b'}, 'mets', {'a';'b'}, ...
    -0136                     'rxns', {'rxn_%_id'});
    -0137                 testCase.verifyFail('Expected an error to be thrown.');
    -0138             catch e
    -0139                 testCase.verifySubstring(e.message, 'met_with_%_sign');
    -0140                 testCase.verifySubstring(e.message, 'rxn_%_id');
    -0141             end
    -0142         end
    -0143 
    -0144         function getAllRxnsFromGenesType(testCase)
    -0145             % Use a reaction that has a gene association.
    -0146             withGpr = testCase.model.rxns(find(~cellfun(@isempty, ...
    -0147                 testCase.model.grRules), 1));
    -0148             allRxns = getAllRxnsFromGenes(testCase.model, withGpr);
    -0149             testCase.verifyClass(allRxns, 'cell');
    -0150             testCase.verifyTrue(all(ismember(withGpr, allRxns)));
    -0151         end
    -0152 
    -0153         function getElementalBalanceFields(testCase)
    -0154             bs = getElementalBalance(testCase.model);
    -0155             testCase.verifyTrue(isfield(bs, 'balanceStatus'));
    -0156             testCase.verifyTrue(isfield(bs, 'leftComp'));
    -0157             testCase.verifyTrue(isfield(bs, 'rightComp'));
    -0158             testCase.verifySize(bs.balanceStatus, [numel(testCase.model.rxns) 1]);
    -0159         end
    -0160 
    -0161         function getElementalBalanceEmptyRxnIsUnbalanced(testCase)
    -0162             % A reaction with no metabolites (all-zero S column) must not
    -0163             % be falsely reported as balanced.
    -0164             m = testCase.model;
    -0165             m.rxns{end+1}    = 'emptyRxn';
    -0166             m.S(:, end+1)    = 0;
    -0167             m.lb(end+1)      = 0;
    -0168             m.ub(end+1)      = 1000;
    -0169             m.rev(end+1)     = 0;
    -0170             m.c(end+1)       = 0;
    -0171             m.grRules{end+1} = '';
    -0172             if isfield(m, 'rxnNames'),   m.rxnNames{end+1}  = 'emptyRxn'; end
    -0173             if isfield(m, 'rxnGeneMat'), m.rxnGeneMat(end+1,:) = 0;       end
    -0174             bs = getElementalBalance(m);
    -0175             testCase.verifyLessThanOrEqual(bs.balanceStatus(end), -1);
    -0176         end
    -0177 
    -0178         function getElementalBalanceReportsChargeBalance(testCase)
    -0179             % A charge-imbalanced reaction is reported as such, while the
    -0180             % elemental verdict is left alone.
    -0181             m = testCase.model;
    -0182             m.mets      = {'a';'b'};
    -0183             m.metNames  = {'a';'b'};
    -0184             m.metComps  = [1;1];
    -0185             m.metFormulas = {'H';'H'};
    -0186             m.metCharges  = [0;1];
    -0187             m.comps     = {'c'};
    -0188             m.compNames = {'cytosol'};
    -0189             m.rxns      = {'R1'};
    -0190             m.rxnNames  = {'R1'};
    -0191             m.S         = sparse([-1;1]);
    -0192             m.lb = 0; m.ub = 1000; m.rev = 0; m.c = 0; m.b = zeros(2,1);
    -0193             m.grRules = {''}; m.genes = {}; m.rxnGeneMat = sparse(1,0);
    -0194 
    -0195             bs = getElementalBalance(m);
    -0196             testCase.verifyEqual(bs.chargeStatus(1), 0);
    -0197             testCase.verifyEqual(bs.chargeResidual(1), 1, 'AbsTol', 1e-9);
    -0198             % a -> b is elementally balanced (H on both sides)
    -0199             testCase.verifyEqual(bs.balanceStatus(1), 1);
    -0200 
    -0201             % Balancing the charge flips chargeStatus, not balanceStatus
    -0202             m.metCharges = [1;1];
    -0203             bs = getElementalBalance(m);
    -0204             testCase.verifyEqual(bs.chargeStatus(1), 1);
    -0205             testCase.verifyEqual(bs.chargeResidual(1), 0, 'AbsTol', 1e-9);
    -0206         end
    -0207 
    -0208         function getElementalBalanceChargeUnknownIsNotZero(testCase)
    -0209             % An unset charge on a participating metabolite makes the charge
    -0210             % balance unknown, and must not be reported as balanced. An unset
    -0211             % charge on a metabolite that does not participate must not leak
    -0212             % into the reaction's residual.
    -0213             m = testCase.model;
    -0214             m.mets      = {'a';'b';'spectator'};
    -0215             m.metNames  = {'a';'b';'spectator'};
    -0216             m.metComps  = [1;1;1];
    -0217             m.metFormulas = {'H';'H';'H'};
    -0218             m.metCharges  = [1;1;NaN];
    -0219             m.comps     = {'c'};
    -0220             m.compNames = {'cytosol'};
    -0221             m.rxns      = {'R1'};
    -0222             m.rxnNames  = {'R1'};
    -0223             m.S         = sparse([-1;1;0]);
    -0224             m.lb = 0; m.ub = 1000; m.rev = 0; m.c = 0; m.b = zeros(3,1);
    -0225             m.grRules = {''}; m.genes = {}; m.rxnGeneMat = sparse(1,0);
    -0226 
    -0227             % The NaN belongs to a metabolite outside the reaction
    -0228             bs = getElementalBalance(m);
    -0229             testCase.verifyEqual(bs.chargeStatus(1), 1);
    -0230             testCase.verifyEqual(bs.chargeResidual(1), 0, 'AbsTol', 1e-9);
    -0231 
    -0232             % Now the NaN is on a participant: unknown, not balanced
    -0233             m.metCharges = [1;NaN;0];
    -0234             bs = getElementalBalance(m);
    -0235             testCase.verifyEqual(bs.chargeStatus(1), -1);
    -0236             testCase.verifyTrue(isnan(bs.chargeResidual(1)));
    -0237         end
    -0238 
    -0239         function getExchangeRxnsConsistent(testCase)
    -0240             [exch, idx] = getExchangeRxns(testCase.model);
    -0241             testCase.verifyClass(exch, 'cell');
    -0242             testCase.verifyNotEmpty(exch);
    -0243             testCase.verifyEqual(testCase.model.rxns(idx), exch);
    -0244         end
    -0245 
    -0246         function getExchangeRxnsWithBoundaryMets(testCase)
    -0247             % A model that still carries boundary metabolites (an
    -0248             % "unconstrained" field, as every model from importModel does)
    -0249             % takes a different branch than one where they have been
    -0250             % removed. Both have to return reaction indexes.
    -0251             m = testCase.taskTestModel();  % closeModel'd, so it has boundary mets
    -0252             testCase.assertTrue(isfield(m, 'unconstrained'));
    -0253             testCase.assertTrue(any(m.unconstrained ~= 0));
    -0254 
    -0255             [exch, idx] = getExchangeRxns(m);
    -0256             testCase.verifyEqual(sort(exch), {'R1'; 'R8'});
    -0257             testCase.verifyEqual(m.rxns(idx), exch);
    -0258 
    -0259             % R1 supplies a[s] ("=> a[s]"), R8 removes e[s] ("e[s] =>")
    -0260             testCase.verifyEqual(getExchangeRxns(m, 'in'), {'R1'});
    -0261             testCase.verifyEqual(getExchangeRxns(m, 'out'), {'R8'});
    -0262         end
    -0263 
    -0264         function getExchangeRxnsAgreeAcrossBoundaryRemoval(testCase)
    -0265             % Removing the boundary metabolites must not change which
    -0266             % reactions are exchange reactions.
    -0267             m = testCase.taskTestModel();
    -0268             withBoundary = getExchangeRxns(m);
    -0269             withoutBoundary = getExchangeRxns(simplifyModel(m));
    -0270             testCase.verifyEqual(sort(withoutBoundary), sort(withBoundary));
    -0271         end
    -0272 
    -0273         function getExchangeRxnsThirdOutputIsMetIndex(testCase)
    -0274             [~, idx, mets] = getExchangeRxns(testCase.model);
    -0275             testCase.verifyNumElements(mets, numel(idx));
    -0276             % Each returned index must point to a metabolite with a
    -0277             % non-zero entry in the exchange reaction's S column.
    -0278             for i = 1:numel(idx)
    -0279                 testCase.verifyNotEqual(mets(i), 0);
    -0280                 testCase.verifyNotEqual(testCase.model.S(mets(i), idx(i)), 0);
    -0281             end
    -0282         end
    -0283 
    -0284         function getGenesFromGrRulesMatchesModel(testCase)
    -0285             [genes, rxnGeneMat] = getGenesFromGrRules(testCase.model.grRules);
    -0286             testCase.verifyTrue(iscellstr(genes)); %#ok<ISCLSTR>
    -0287             testCase.verifySize(rxnGeneMat, [numel(testCase.model.rxns) numel(genes)]);
    -0288             testCase.verifyEqual(sort(genes(:)), sort(testCase.model.genes(:)));
    -0289         end
    -0290 
    -0291         function getIndexesByName(testCase)
    -0292             idx = getIndexes(testCase.model, testCase.model.rxns(1:3), 'rxns');
    -0293             testCase.verifyEqual(idx, [1;2;3]);
    -0294         end
    -0295 
    -0296         function getIndexesLogical(testCase)
    -0297             lg = getIndexes(testCase.model, testCase.model.rxns(1), 'rxns', true);
    -0298             testCase.verifyClass(lg, 'logical');
    -0299             testCase.verifyEqual(find(lg), 1);
    -0300         end
    -0301 
    -0302         function getIndexesLogicalMaskAllTrueReturnsIndices(testCase)
    -0303             % A logical all-true mask of length n must return numeric [1..n],
    -0304             % not be passed through as a logical array: this requires an
    -0305             % islogical check, since all() cannot distinguish the two cases.
    -0306             nR = numel(testCase.model.rxns);
    -0307             idx = getIndexes(testCase.model, true(nR, 1), 'rxns');
    -0308             testCase.verifyClass(idx, 'double');
    -0309             testCase.verifyEqual(idx, (1:nR)');
    -0310         end
    -0311 
    -0312         function getRxnsInCompType(testCase)
    -0313             I = getRxnsInComp(testCase.model, 'c');
    -0314             testCase.verifyNotEmpty(I);
    -0315         end
    -0316 
    -0317         function getTransportRxnsType(testCase)
    -0318             tr = getTransportRxns(testCase.model);
    -0319             testCase.verifyClass(tr, 'logical');
    -0320             testCase.verifyNumElements(tr, numel(testCase.model.rxns));
    -0321         end
    -0322 
    -0323         function parseFormulasMW(testCase)
    -0324             [elements, useMat, exitFlag, MW] = parseFormulas(testCase.model.metFormulas); %#ok<ASGLU>
    -0325             testCase.verifyNumElements(MW, numel(testCase.model.mets));
    -0326             testCase.verifyTrue(isfield(elements, 'abbrevs'));
    -0327         end
    -0328 
    -0329         function parseFormulasRejectsPartialParse(testCase)
    -0330             % A formula that stops at an unrecognised element is not parsed,
    -0331             % even though the part before it was readable. Reporting it as
    -0332             % parsed would hand back a silently truncated composition.
    -0333             [elements, useMat, exitFlag] = parseFormulas({'C6H12O6';'C6Zz3'});
    -0334             testCase.verifyEqual(exitFlag, [1;-1]);
    -0335             testCase.verifyEqual(sum(useMat(2,:)), 0);
    -0336             % The good formula is unaffected
    -0337             cIdx = strcmp(elements.abbrevs, 'C');
    -0338             testCase.verifyEqual(useMat(1, cIdx), 6);
    -0339         end
    -0340 
    -0341         function parseFormulasSingleFormulaKeepsElements(testCase)
    -0342             % With one formula useMat is a row vector; the unused-element
    -0343             % pruning must still test each element separately.
    -0344             [elements, useMat] = parseFormulas({'C6H12O6'});
    -0345             testCase.verifySize(useMat, [1 numel(elements.abbrevs)]);
    -0346             testCase.verifyEqual(sort(elements.abbrevs(:)), {'C';'H';'O'});
    -0347         end
    -0348 
    -0349         function parseFormulasUnknownMassBlanksOnlyItsOwnFormula(testCase)
    -0350             % R and X have no mass. A formula that uses one gets MW = NaN;
    -0351             % formulas that do not must keep their weight.
    -0352             [~, ~, ~, MW] = parseFormulas({'C6H12O6';'C2R';'H2O';'CX'});
    -0353             testCase.verifyFalse(isnan(MW(1)));
    -0354             testCase.verifyTrue(isnan(MW(2)));
    -0355             testCase.verifyFalse(isnan(MW(3)));
    -0356             testCase.verifyTrue(isnan(MW(4)));
    -0357         end
    -0358 
    -0359         function parseRxnEquMetNames(testCase)
    -0360             mets = parseRxnEqu({'a + b => c'});
    -0361             testCase.verifyTrue(all(ismember({'a','b','c'}, mets)));
    -0362         end
    -0363 
    -0364         function modelSummaryNoFluxRuns(testCase)
    -0365             out = evalc('modelSummary(testCase.model)');
    -0366             testCase.verifyClass(out, 'char');
    -0367             testCase.verifySubstring(out, 'Reactions');
    -0368         end
    -0369 
    -0370         function modelSummaryWithFluxRuns(testCase)
    -0371             testCase.assumeSolver('solveLP');
    -0372             sol = solveLP(testCase.model);
    -0373             out = evalc('modelSummary(testCase.model, ''fluxes'', sol.x)');
    -0374             testCase.verifyClass(out, 'char');
    -0375             testCase.verifySubstring(out, 'Objective value');
    -0376         end
    -0377 
    -0378         function printFluxesRecognizesBoundaryMetExchanges(testCase)
    -0379             % A model closed via closeModel represents an exchange as
    -0380             % realMet <=> realMet[b] -- one reactant and one product --
    -0381             % which "no reactants or no products" alone does not recognise,
    -0382             % printing nothing for any of them.
    -0383             m = testCase.taskTestModel();
    -0384             [~,exchIdx] = getExchangeRxns(m,'all');
    -0385             testCase.assumeNotEmpty(exchIdx, 'Fixture has no exchange reactions after closeModel.');
    -0386             flux = ones(numel(m.rxns),1);
    -0387             out = evalc('printFluxes(m, flux, ''onlyExchange'', true);');
    -0388             testCase.verifySubstring(out, m.rxns{exchIdx(1)});
    -0389         end
    -0390 
    -0391         function getTransportRxnsExcludesBoundaryMetExchanges(testCase)
    -0392             % closeModel copies the real metabolite's own name onto its
    -0393             % boundary counterpart, so an exchange reaction has the same
    -0394             % "same name, different compartment" shape as a genuine
    -0395             % transport reaction.
    -0396             m = testCase.taskTestModel();
    -0397             [~,exchIdx] = getExchangeRxns(m,'all');
    -0398             testCase.assumeNotEmpty(exchIdx, 'Fixture has no exchange reactions after closeModel.');
    -0399             tr = getTransportRxns(m);
    -0400             testCase.verifyFalse(any(tr(exchIdx)));
    -0401         end
    -0402 
    -0403         function printFluxesRuns(testCase)
    -0404             testCase.assumeSolver('solveLP');
    -0405             sol = solveLP(testCase.model);
    -0406             out = evalc('printFluxes(testCase.model, sol.x, true)');
    -0407             testCase.verifyClass(out, 'char');
    -0408         end
    -0409 
    -0410         function printFluxesKeepsPercentInRxnName(testCase)
    -0411             % A rxnName containing a literal "%" must not be reinterpreted
    -0412             % as a format directive, which would truncate everything after
    -0413             % it instead of printing the flux line in full.
    -0414             m = testCase.model;
    -0415             m.rxnNames{1} = 'reaction with 50% yield';
    -0416             flux = zeros(numel(m.rxns),1);
    -0417             flux(1) = 1;
    -0418             out = evalc('printFluxes(m, flux, false)');
    -0419             testCase.verifySubstring(out, 'reaction with 50% yield');
    -0420         end
    -0421 
    -0422         function printModelStatsRuns(testCase)
    -0423             out = evalc('printModelStats(testCase.model)');
    -0424             testCase.verifyClass(out, 'char');
    -0425         end
    -0426 
    -0427         function printModelStatsKeepsPercentInModelName(testCase)
    -0428             % model.name/model.id/compNames/mets/rxns are spliced into
    -0429             % fprintf templates; a literal "%" in any of them must not be
    -0430             % reinterpreted as a format directive.
    -0431             m = testCase.model;
    -0432             m.name = 'test model with 50% coverage';
    -0433             m.S(1,:) = 0; % make mets{1} unused so it hits the errorText path
    -0434             m.metNames{1} = 'unused met 30% pure';
    -0435             out = evalc(['printModelStats(m, ''printModelIssues'', true, ' ...
    -0436                 '''printDetails'', true);']);
    -0437             testCase.verifySubstring(out, 'test model with 50% coverage');
    -0438             testCase.verifySubstring(out, 'unused met 30% pure');
    -0439         end
    -0440 
    -0441     end
    -0442 end
    -
    Generated by m2html © 2005
    - - \ No newline at end of file diff --git a/docs/html/testing/function_tests/tReconstruction.html b/docs/html/testing/function_tests/tReconstruction.html deleted file mode 100644 index 4b8681f6..00000000 --- a/docs/html/testing/function_tests/tReconstruction.html +++ /dev/null @@ -1,220 +0,0 @@ - - - - Description of tReconstruction - - - - - - - - - -
    Home > testing > function_tests > tReconstruction.m
    - - - -

    tReconstruction -

    - -

    PURPOSE ^

    -
    - -

    SYNOPSIS ^

    -
    This is a script file.
    - -

    DESCRIPTION ^

    -
    - - -

    CROSS-REFERENCE INFORMATION ^

    -This function calls: - -This function is called by: - - - -

    SUBFUNCTIONS ^

    - - -

    SOURCE CODE ^

    -
    0001 classdef tReconstruction < RavenTestCase
    -0002 % tReconstruction  Tests for the de-novo reconstruction functions in reconstruction/.
    -0003 %
    -0004 %   Homology and KEGG reconstruction depend on external binaries (BLAST+,
    -0005 %   DIAMOND) and on a local KEGG dump / aligners. Those are guarded and skip
    -0006 %   when unavailable; the self-contained functions are tested directly.
    -0007 
    -0008     methods (Test)
    -0009 
    -0010         function guessCompositionRuns(testCase)
    -0011             evalc('[m2, guessed] = guessComposition(testCase.model);');
    -0012             testCase.verifyClass(m2, 'struct');
    -0013         end
    -0014 
    -0015         function guessCompositionAccountsForStoichiometricCoefficient(testCase)
    -0016             % A -> 2 B, with A's formula known (CH4). B's own coefficient is
    -0017             % 2, so its atom counts must be half of A's (C0.5H4/2 = C0.5H2),
    -0018             % not identical to A's.
    -0019             model = tReconstruction.toyModel();
    -0020             model.metFormulas = {'CH4'; ''};
    -0021             evalc('[m2, guessedFor, couldNotGuess] = guessComposition(model);');
    -0022             testCase.verifyEqual(guessedFor, {'B'});
    -0023             testCase.verifyEmpty(couldNotGuess);
    -0024             testCase.verifyEqual(m2.metFormulas{2}, 'C0.5H2');
    -0025         end
    -0026 
    -0027         function makeFakeBlastStructureReturnsStruct(testCase)
    -0028             % makeFakeBlastStructure requires at least 10 ortholog pairs.
    -0029             ol = [testCase.model.genes(1:10), strcat('t_', testCase.model.genes(1:10))];
    -0030             bs = makeFakeBlastStructure(ol, 'srcModel', 'tgtOrg');
    -0031             testCase.verifyClass(bs, 'struct');
    -0032         end
    -0033 
    -0034         function getModelFromHomologyBuildsDraft(testCase)
    -0035             src = testCase.model; src.id = 'srcModel';
    -0036             n = min(20, numel(src.genes));
    -0037             ol = [src.genes(1:n), strcat('t_', src.genes(1:n))];
    -0038             bs = makeFakeBlastStructure(ol, 'srcModel', 'tgtOrg');
    -0039             evalc('draft = getModelFromHomology({src}, bs, ''tgtOrg'');');
    -0040             testCase.verifyClass(draft, 'struct');
    -0041         end
    -0042 
    -0043         function getModelFromHomologyComplexPolicyAndOptions(testCase)
    -0044             % A small template: a complex, an isozyme pair, a gene-free
    -0045             % reaction, and two template genes that map to one new gene.
    -0046             t = struct();
    -0047             t.id = 'tmpl'; t.name = 'tmpl';
    -0048             t.mets = {'a_c';'b_c';'d_c'}; t.metNames = {'A';'B';'D'};
    -0049             t.comps = {'c'}; t.compNames = {'cytosol'}; t.metComps = [1;1;1];
    -0050             t.rxns = {'Rcplx';'Riso';'Rfree';'Rdup'};
    -0051             t.rxnNames = t.rxns;
    -0052             t.S = sparse([-1 0 0 -1; 0 -1 0 1; 1 1 -1 0]);
    -0053             t.lb = [0;0;0;0]; t.ub = [1000;1000;1000;1000]; t.c = [0;0;0;0]; t.b = [0;0;0];
    -0054             t.rev = [0;0;0;0];
    -0055             t.grRules = {'tg1 and tg2'; 'tg3 or tg4'; ''; 'tg3 or tg4'};
    -0056             t.genes = {'tg1';'tg2';'tg3';'tg4'};
    -0057             t.rxnGeneMat = sparse([1 1 0 0; 0 0 1 1; 0 0 0 0; 0 0 1 1]);
    -0058             t.rxnNotes = {'n1';'n2';'n3';'n4'};
    -0059             t.rxnConfidenceScores = [4;4;4;4];
    -0060             t.version = '9.9.9';
    -0061             % makeFakeBlastStructure wants at least 10 pairs; the extra ones
    -0062             % are genes the template does not have
    -0063             pairs = [{'tg1','ng1'; 'tg3','ng3'; 'tg4','ng3'}; ...
    -0064                      [strcat('x', string(1:7))', strcat('nx', string(1:7))']];
    -0065             pairs = cellstr(pairs);
    -0066             bs = makeFakeBlastStructure(pairs, 'tmpl', 'new');
    -0067             evalc('flag = getModelFromHomology({t}, bs, ''new'', ''minLen'', 0, ''minIde'', 0);');
    -0068             evalc('keep = getModelFromHomology({t}, bs, ''new'', ''minLen'', 0, ''minIde'', 0, ''complexPolicy'', ''keep'', ''keepGeneFree'', true, ''preserveNotes'', true);');
    -0069             evalc('drop = getModelFromHomology({t}, bs, ''new'', ''minLen'', 0, ''minIde'', 0, ''complexPolicy'', ''drop'');');
    -0070             % flag: unmapped subunit kept as OLD_; gene-free reaction dropped
    -0071             testCase.verifyTrue(contains(flag.grRules{strcmp(flag.rxns,'Rcplx')}, 'OLD_tmpl_tg2'));
    -0072             testCase.verifyFalse(ismember('Rfree', flag.rxns));
    -0073             % two template genes mapping to one new gene appear once
    -0074             testCase.verifyEqual(flag.grRules{strcmp(flag.rxns,'Riso')}, 'ng3');
    -0075             % keep: subunit dropped, gene-free kept, template notes kept
    -0076             testCase.verifyEqual(keep.grRules{strcmp(keep.rxns,'Rcplx')}, 'ng1');
    -0077             testCase.verifyTrue(ismember('Rfree', keep.rxns));
    -0078             testCase.verifyEqual(keep.rxnNotes{strcmp(keep.rxns,'Rcplx')}, 'n1');
    -0079             testCase.verifyEqual(keep.rxnConfidenceScores(strcmp(keep.rxns,'Rcplx')), 4);
    -0080             % drop: the incomplete complex is removed
    -0081             testCase.verifyFalse(ismember('Rcplx', drop.rxns));
    -0082             testCase.verifyTrue(ismember('Riso', drop.rxns));
    -0083             % the template version is not inherited
    -0084             testCase.verifyFalse(isfield(flag, 'version') && strcmp(flag.version, '9.9.9'));
    -0085             testCase.verifyEqual(flag.rxnNotes{1}, 'Included by getModelFromHomology');
    -0086         end
    -0087 
    -0088         function getModelFromHomologyRejectsUnknownComplexPolicy(testCase)
    -0089             src = testCase.model; src.id = 'srcModel';
    -0090             bs = makeFakeBlastStructure([src.genes(1:10), strcat('t_', src.genes(1:10))], 'srcModel', 'tgtOrg');
    -0091             testCase.verifyError(@() getModelFromHomology({src}, bs, 'tgtOrg', 'complexPolicy', 'maybe'), ?MException);
    -0092         end
    -0093 
    -0094         function getBlastRunsWhenAvailable(testCase)
    -0095             fa1 = fullfile(testCase.ravenRoot,'testing','function_tests','test_data','human_galactosidases.fa');
    -0096             fa2 = fullfile(testCase.ravenRoot,'testing','function_tests','test_data','yeast_galactosidases.fa');
    -0097             testCase.assumeDependency(exist(fa1,'file')==2, 'test FASTA files');
    -0098             ok = true;
    -0099             try, evalc('bs = getBlast(''human'', fa1, {''yeast''}, {fa2});'); catch, ok = false; end
    -0100             testCase.assumeTrue(ok, 'BLAST+ binary not functional in this environment');
    -0101             testCase.verifyClass(bs, 'struct');
    -0102         end
    -0103 
    -0104         function getDiamondRunsWhenAvailable(testCase)
    -0105             fa1 = fullfile(testCase.ravenRoot,'testing','function_tests','test_data','human_galactosidases.fa');
    -0106             fa2 = fullfile(testCase.ravenRoot,'testing','function_tests','test_data','yeast_galactosidases.fa');
    -0107             testCase.assumeDependency(exist(fa1,'file')==2, 'test FASTA files');
    -0108             ok = true;
    -0109             try, evalc('bs = getDiamond(''human'', fa1, {''yeast''}, {fa2});'); catch, ok = false; end
    -0110             testCase.assumeTrue(ok, 'DIAMOND binary not functional in this environment');
    -0111             testCase.verifyClass(bs, 'struct');
    -0112         end
    -0113 
    -0114         function getKEGGModelForOrganismNeedsData(testCase)
    -0115             testCase.assumeFail('Requires keggModel.mat and an HMM library from raven-data.');
    -0116         end
    -0117 
    -0118         function getModelFromKEGGNeedsData(testCase)
    -0119             matFile = fullfile(testCase.ravenRoot, 'reconstruction', 'kegg', 'keggModel.mat');
    -0120             testCase.assumeFalse(exist(matFile, 'file') == 2, ...
    -0121                 'keggModel.mat is present; skipping the build-from-artefacts path.');
    -0122             testCase.assumeFail('Downloads and assembles the full KEGG artefact set from raven-data; not run automatically.');
    -0123         end
    -0124 
    -0125         function buildGlobalGPRJoinsGenesThroughSharedKO(testCase)
    -0126             % Offline unit test of the join at the core of buildGlobalKEGGModel:
    -0127             % two reactions sharing a KO, a KO used by two organisms' genes, and
    -0128             % a reaction with no KO at all (should end up with no genes).
    -0129             rxns = {'R1'; 'R2'; 'R3'};
    -0130             koReaction = table({'K1'; 'K1'; 'K2'}, {'R1'; 'R2'; 'R2'}, ...
    -0131                 'VariableNames', {'ko', 'reaction'});
    -0132             organismGeneKO = table({'a'; 'a'; 'b'}, {'g1'; 'g2'; 'g1'}, {'K1'; 'K2'; 'K1'}, ...
    -0133                 'VariableNames', {'organism', 'gene', 'ko'});
    -0134             [genes, rxnGeneMat] = buildGlobalGPR(rxns, koReaction, organismGeneKO);
    -0135             testCase.verifyEqual(genes, {'a:g1'; 'a:g2'; 'b:g1'});
    -0136             expected = [1 0 1; 1 1 1; 0 0 0];
    -0137             testCase.verifyEqual(full(rxnGeneMat), expected);
    -0138         end
    -0139 
    -0140         function getPhylDistNeedsData(testCase)
    -0141             distFile = fullfile(testCase.ravenRoot, 'reconstruction', 'kegg', 'keggPhylDist.mat');
    -0142             testCase.assumeFalse(exist(distFile, 'file') == 2, ...
    -0143                 'keggPhylDist.mat is present; skipping error-path test.');
    -0144             testCase.verifyError(@() getPhylDist(), 'getPhylDist:noData');
    -0145         end
    -0146 
    -0147     end
    -0148 
    -0149     methods (Static)
    -0150         function model = toyModel()
    -0151             % Single reaction: A -> 2 B, one compartment.
    -0152             model = struct();
    -0153             model.id          = 'toy';
    -0154             model.name        = 'toy';
    -0155             model.mets        = {'m1'; 'm2'};
    -0156             model.metNames    = {'A'; 'B'};
    -0157             model.comps       = {'c'};
    -0158             model.compNames   = {'cytosol'};
    -0159             model.metComps    = [1; 1];
    -0160             model.metFormulas = {''; ''};
    -0161             model.rxns        = {'R1'};
    -0162             model.rxnNames    = {'R1'};
    -0163             model.S           = sparse([-1; 2]);
    -0164             model.lb          = 0;
    -0165             model.ub          = 1000;
    -0166             model.rev         = 0;
    -0167             model.c           = 0;
    -0168             model.b           = zeros(2, 1);
    -0169         end
    -0170     end
    -0171 end
    -
    Generated by m2html © 2005
    - - \ No newline at end of file diff --git a/docs/html/testing/function_tests/tSampling.html b/docs/html/testing/function_tests/tSampling.html deleted file mode 100644 index 2feb3b10..00000000 --- a/docs/html/testing/function_tests/tSampling.html +++ /dev/null @@ -1,235 +0,0 @@ - - - - Description of tSampling - - - - - - - - - -
    Home > testing > function_tests > tSampling.m
    - - - -

    tSampling -

    - -

    PURPOSE ^

    -
    - -

    SYNOPSIS ^

    -
    This is a script file.
    - -

    DESCRIPTION ^

    -
    - - -

    CROSS-REFERENCE INFORMATION ^

    -This function calls: - -This function is called by: - - - -

    SUBFUNCTIONS ^

    - - -

    SOURCE CODE ^

    -
    0001 classdef tSampling < RavenTestCase
    -0002 % tSampling  Tests for the MCMC flux samplers (CHRR, ACHR, MVE) dispatched
    -0003 % through randomSampling.
    -0004 %
    -0005 %   The maximum-volume ellipsoid solver is the numerical crux of CHRR, so it is
    -0006 %   validated directly against analytic cases (box -> unit ball, scaled/sheared
    -0007 %   box, triangle -> Steiner inellipse) before the end-to-end samplers are
    -0008 %   exercised on the textbook model. All methods are LP-only, so the default
    -0009 %   GLPK solver suffices (no MILP assumption needed).
    -0010 
    -0011     methods (Test)
    -0012 
    -0013         % ---- sampleMaxVolEllipse: analytic validation -------------------
    -0014 
    -0015         function sampleMaxVolEllipseUnitBox(testCase)
    -0016             % Box {-1 <= x_i <= 1} -> unit ball: centre 0, E*E' = I.
    -0017             A = [eye(3); -eye(3)];
    -0018             b = ones(6, 1);
    -0019             [x, E, converged] = sampleMaxVolEllipse(A, b, zeros(3, 1));
    -0020             testCase.verifyTrue(converged);
    -0021             testCase.verifyLessThan(norm(x), 1e-4);
    -0022             testCase.verifyLessThan(norm(E*E' - eye(3)), 1e-3);
    -0023         end
    -0024 
    -0025         function sampleMaxVolEllipseScaledBox(testCase)
    -0026             % Box {-r_i <= x_i <= r_i} -> E*E' = diag(r_i^2).
    -0027             r = [2; 0.5; 3];
    -0028             A = [eye(3); -eye(3)];
    -0029             b = [r; r];
    -0030             [x, E, converged] = sampleMaxVolEllipse(A, b, zeros(3, 1));
    -0031             testCase.verifyTrue(converged);
    -0032             testCase.verifyLessThan(norm(x), 1e-4);
    -0033             testCase.verifyLessThan(norm(E*E' - diag(r.^2)), 1e-3);
    -0034         end
    -0035 
    -0036         function sampleMaxVolEllipseShearedBox(testCase)
    -0037             % {-1 <= M*x <= 1} -> E*E' = inv(M)*inv(M)'.
    -0038             M = [1 0.5; 0 1];
    -0039             A = [M; -M];
    -0040             b = ones(4, 1);
    -0041             [x, E, converged] = sampleMaxVolEllipse(A, b, zeros(2, 1));
    -0042             Minv = inv(M);
    -0043             testCase.verifyTrue(converged);
    -0044             testCase.verifyLessThan(norm(x), 1e-4);
    -0045             testCase.verifyLessThan(norm(E*E' - Minv*Minv'), 1e-3);
    -0046         end
    -0047 
    -0048         function sampleMaxVolEllipseTriangle(testCase)
    -0049             % Standard simplex {x>=0, x1+x2<=1}: the max-area inscribed ellipse is
    -0050             % the Steiner inellipse, centred at the centroid (1/3,1/3) with
    -0051             % E2 = [[1/9,-1/18],[-1/18,1/9]] (det 1/108) -- NOT the incircle. This
    -0052             % confirms the solver maximises volume, not just the largest ball.
    -0053             A = [-1 0; 0 -1; 1 1];
    -0054             b = [0; 0; 1];
    -0055             [x, E, converged] = sampleMaxVolEllipse(A, b, [0.25; 0.25]);
    -0056             E2exp = [1/9 -1/18; -1/18 1/9];
    -0057             testCase.verifyTrue(converged);
    -0058             testCase.verifyLessThan(norm(x - [1/3; 1/3]), 1e-3);
    -0059             testCase.verifyLessThan(norm(E*E' - E2exp), 1e-3);
    -0060             testCase.verifyLessThan(abs(det(E*E') - 1/108), 1e-4);
    -0061         end
    -0062 
    -0063         function sampleMaxVolEllipseRejectsExterior(testCase)
    -0064             A = [eye(2); -eye(2)];
    -0065             b = ones(4, 1);
    -0066             testCase.verifyError(@() sampleMaxVolEllipse(A, b, [5; 0]), ...
    -0067                 'RAVEN:sampling');
    -0068         end
    -0069 
    -0070         % ---- sampler implementations return diagnostics -----------------
    -0071 
    -0072         function sampleCHRRReturnsInfo(testCase)
    -0073             evalc('[sols, sInfo] = sampleCHRR(testCase.model, 10, 5, 20, 1);');
    -0074             nRxns = numel(testCase.model.rxns);
    -0075             testCase.verifySize(sols, [nRxns, 10]);
    -0076             testCase.verifyTrue(isfield(sInfo, 'nDimensions'));
    -0077             testCase.verifyTrue(isfield(sInfo, 'mveConverged'));
    -0078             % Samples must satisfy steady state and bounds.
    -0079             resid = testCase.model.S * sols;
    -0080             testCase.verifyLessThan(max(abs(resid(:))), 1e-6);
    -0081             testCase.verifyGreaterThanOrEqual(min(sols, [], 2), testCase.model.lb - 1e-6);
    -0082             testCase.verifyLessThanOrEqual(max(sols, [], 2), testCase.model.ub + 1e-6);
    -0083         end
    -0084 
    -0085         function sampleACHRReturnsSteadyState(testCase)
    -0086             evalc('sols = sampleACHR(testCase.model, 10, 5, [], 1);');
    -0087             nRxns = numel(testCase.model.rxns);
    -0088             testCase.verifySize(sols, [nRxns, 10]);
    -0089             resid = testCase.model.S * sols;
    -0090             testCase.verifyLessThan(max(abs(resid(:))), 1e-6);
    -0091         end
    -0092 
    -0093         % ---- randomSampling dispatch ------------------------------------
    -0094 
    -0095         function randomSamplingDefaultsToACHR(testCase)
    -0096             % The default method must be ACHR: with a fixed seed the default call
    -0097             % must equal an explicit 'achr' call.
    -0098             evalc('sDefault = randomSampling(testCase.model, 8, ''thinning'', 5, ''seed'', 3);');
    -0099             evalc('sAchr = randomSampling(testCase.model, 8, ''method'', ''achr'', ''thinning'', 5, ''seed'', 3);');
    -0100             testCase.verifyEqual(sDefault, sAchr);
    -0101         end
    -0102 
    -0103         function randomSamplingCHRRDispatch(testCase)
    -0104             evalc(['sols = randomSampling(testCase.model, 10, ''method'', ''chrr'', ' ...
    -0105                 '''thinning'', 5, ''nBurnin'', 20, ''seed'', 1);']);
    -0106             nRxns = numel(testCase.model.rxns);
    -0107             testCase.verifySize(sols, [nRxns, 10]);
    -0108             resid = testCase.model.S * sols;
    -0109             testCase.verifyLessThan(max(abs(resid(:))), 1e-6);
    -0110         end
    -0111 
    -0112         function randomSamplingCHRRExposesInfo(testCase)
    -0113             % The CHRR convergence diagnostics must be reachable through the
    -0114             % documented randomSampling entry point, not only sampleCHRR.
    -0115             evalc(['[sols, gr, sInfo] = randomSampling(testCase.model, 10, ' ...
    -0116                 '''method'', ''chrr'', ''thinning'', 5, ''nBurnin'', 20, ''seed'', 1);']);
    -0117             testCase.verifyTrue(isfield(sInfo, 'mveConverged'));
    -0118         end
    -0119 
    -0120         function randomSamplingCHRRDeterministic(testCase)
    -0121             evalc(['s1 = randomSampling(testCase.model, 8, ''method'', ''chrr'', ' ...
    -0122                 '''thinning'', 5, ''nBurnin'', 20, ''seed'', 42);']);
    -0123             evalc(['s2 = randomSampling(testCase.model, 8, ''method'', ''chrr'', ' ...
    -0124                 '''thinning'', 5, ''nBurnin'', 20, ''seed'', 42);']);
    -0125             testCase.verifyEqual(s1, s2);
    -0126         end
    -0127 
    -0128         function randomSamplingCHRRVaries(testCase)
    -0129             % A correct CHRR run must mix: at least one reaction varies, and two
    -0130             % seeds give distinct sample matrices. Guards against a degenerate
    -0131             % sampler that collapses to a single point (which would still satisfy
    -0132             % S*v=0 and the bounds, and remain deterministic).
    -0133             evalc(['s1 = randomSampling(testCase.model, 50, ''method'', ''chrr'', ' ...
    -0134                 '''thinning'', 5, ''nBurnin'', 20, ''seed'', 1);']);
    -0135             evalc(['s2 = randomSampling(testCase.model, 50, ''method'', ''chrr'', ' ...
    -0136                 '''thinning'', 5, ''nBurnin'', 20, ''seed'', 2);']);
    -0137             testCase.verifyGreaterThan(max(std(s1, 0, 2)), 1e-3);
    -0138             testCase.verifyNotEqual(s1, s2);
    -0139         end
    -0140 
    -0141         function randomSamplingUnknownMethodErrors(testCase)
    -0142             testCase.verifyError( ...
    -0143                 @() randomSampling(testCase.model, 5, 'method', 'gibbs'), ...
    -0144                 'RAVEN:badInput');
    -0145         end
    -0146 
    -0147         function randomSamplingLoopDetectionUsesModelBounds(testCase)
    -0148             % Loop detection must take its threshold from the model. R2/R3
    -0149             % form an a -> b -> a loop that runs up to this model's 100 cap,
    -0150             % while the linear path R1 -> R4 is held to 10. A threshold that
    -0151             % assumes ±1000 bounds matches neither, leaving the loop
    -0152             % reactions to be sampled as if they were loop-free.
    -0153             m = tSampling.loopModel();
    -0154             evalc(['[~, goodRxns] = randomSampling(m, 2, ''method'', ' ...
    -0155                 '''randomObjective'', ''seed'', 1);']);
    -0156             testCase.verifyEqual(sort(goodRxns(:))', [1 4]);
    -0157         end
    -0158 
    -0159     end
    -0160 
    -0161     methods (Static, Access = private)
    -0162 
    -0163         function m = loopModel()
    -0164             m = struct();
    -0165             m.id       = 'loop';
    -0166             m.rxns     = {'R1';'R2';'R3';'R4'};
    -0167             m.rxnNames = m.rxns;
    -0168             m.mets     = {'a';'b'};
    -0169             m.metNames = {'a';'b'};
    -0170             m.metComps = [1;1];
    -0171             m.comps    = {'c'};
    -0172             m.compNames= {'cytosol'};
    -0173             %             R1  R2  R3  R4
    -0174             m.S = sparse([ 1  -1   1   0;    % a
    -0175                            0   1  -1  -1]);  % b
    -0176             m.lb  = [0;0;0;0];
    -0177             m.ub  = [10;100;100;100];
    -0178             m.rev = [0;0;0;0];
    -0179             m.c   = [0;0;0;1];
    -0180             m.b   = zeros(2,1);
    -0181             m.genes = {}; m.grRules = {'';'';'';''}; m.rxnGeneMat = sparse(4,0);
    -0182             m.metFormulas = {'C';'C'};
    -0183         end
    -0184 
    -0185     end
    -0186 end
    -
    Generated by m2html © 2005
    - - \ No newline at end of file diff --git a/docs/html/testing/function_tests/tSolver.html b/docs/html/testing/function_tests/tSolver.html deleted file mode 100644 index eaaddb1e..00000000 --- a/docs/html/testing/function_tests/tSolver.html +++ /dev/null @@ -1,121 +0,0 @@ - - - - Description of tSolver - - - - - - - - - -
    Home > testing > function_tests > tSolver.m
    - - - -

    tSolver -

    - -

    PURPOSE ^

    -
    - -

    SYNOPSIS ^

    -
    This is a script file.
    - -

    DESCRIPTION ^

    -
    - - -

    CROSS-REFERENCE INFORMATION ^

    -This function calls: - -This function is called by: - - - -

    SUBFUNCTIONS ^

    - - -

    SOURCE CODE ^

    -
    0001 classdef tSolver < RavenTestCase
    -0002 % tSolver  Tests for the LP/QP solving and solver-configuration functions in solver/.
    -0003 
    -0004     methods (Test)
    -0005 
    -0006         function setRavenSolverAcceptsGlpk(testCase)
    -0007             testCase.verifyWarningFree(@() setRavenSolver('glpk'));
    -0008         end
    -0009 
    -0010         function setRavenSolverRejectsUnknown(testCase)
    -0011             testCase.verifyError(@() setRavenSolver('notarealsolver'), ?MException);
    -0012         end
    -0013 
    -0014         function solveLPFindsOptimum(testCase)
    -0015             sol = solveLP(testCase.model);
    -0016             testCase.verifyEqual(sol.stat, 1);
    -0017             testCase.verifyNumElements(sol.x, numel(testCase.model.rxns));
    -0018             testCase.verifyEqual(sol.f, 0.87392, 'AbsTol', 1e-3);
    -0019         end
    -0020 
    -0021         function solveLPMinimiseFluxes(testCase)
    -0022             sol = solveLP(testCase.model, 1);
    -0023             testCase.verifyEqual(sol.stat, 1);
    -0024         end
    -0025 
    -0026         function optimizeProbSolvesLP(testCase)
    -0027             res = optimizeProb(testCase.modelLP());
    -0028             testCase.verifyClass(res, 'struct');
    -0029             testCase.verifyTrue(isfield(res, 'obj'));
    -0030         end
    -0031 
    -0032         function optimizeProbReportsTimeLimit(testCase)
    -0033             res = optimizeProb(testCase.modelLP());
    -0034             testCase.verifyTrue(isfield(res, 'hitTimeLimit'));
    -0035             testCase.verifyTrue(islogical(res.hitTimeLimit));
    -0036             %The problem is solved long before the time limit is reached
    -0037             testCase.verifyFalse(res.hitTimeLimit);
    -0038         end
    -0039 
    -0040         function checkSolutionOnFeasibleProblem(testCase)
    -0041             res = optimizeProb(testCase.modelLP());
    -0042             [isFeasible, isOptimal] = checkSolution(res);
    -0043             testCase.verifyTrue(logical(isFeasible));
    -0044             testCase.verifyTrue(logical(isOptimal));
    -0045         end
    -0046 
    -0047         function splitProbForConditioningReturnsProb(testCase)
    -0048             [p2, condInfo] = splitProbForConditioning(testCase.modelLP(), 1e6); %#ok<ASGLU>
    -0049             testCase.verifyClass(p2, 'struct');
    -0050         end
    -0051 
    -0052     end
    -0053 
    -0054     methods (Access = private)
    -0055         function prob = modelLP(testCase)
    -0056             % Build the COBRA-style LP problem for the test model exactly as
    -0057             % solveLP does, so the low-level solver functions get a valid input.
    -0058             m = testCase.model;
    -0059             nMets = size(m.S, 1);
    -0060             prob = [];
    -0061             prob.c = [m.c * -1; zeros(nMets, 1)];
    -0062             prob.b = zeros(nMets, 1);
    -0063             prob.lb = [m.lb; m.b(:, 1)];
    -0064             prob.ub = [m.ub; m.b(:, min(size(m.b, 2), 2))];
    -0065             prob.csense = repmat('E', 1, numel(prob.b));
    -0066             prob.osense = 1;
    -0067             prob.vartype = repmat('C', 1, numel(prob.c));
    -0068             prob.A = [m.S, -speye(nMets)];
    -0069             prob.a = m.S;
    -0070         end
    -0071     end
    -0072 end
    -
    Generated by m2html © 2005
    - - \ No newline at end of file diff --git a/docs/html/testing/function_tests/tSyntax.html b/docs/html/testing/function_tests/tSyntax.html deleted file mode 100644 index ccbb1fbf..00000000 --- a/docs/html/testing/function_tests/tSyntax.html +++ /dev/null @@ -1,97 +0,0 @@ - - - - Description of tSyntax - - - - - - - - - -
    Home > testing > function_tests > tSyntax.m
    - - - -

    tSyntax -

    - -

    PURPOSE ^

    -
    - -

    SYNOPSIS ^

    -
    This is a script file.
    - -

    DESCRIPTION ^

    -
    - - -

    CROSS-REFERENCE INFORMATION ^

    -This function calls: - -This function is called by: - - - -

    SUBFUNCTIONS ^

    - - -

    SOURCE CODE ^

    -
    0001 classdef tSyntax < RavenTestCase
    -0002 % tSyntax  Every RAVEN source file must parse.
    -0003 %
    -0004 %   A MATLAB file with unbalanced block keywords still sits in the repository
    -0005 %   looking perfectly ordinary: the error only surfaces when something calls
    -0006 %   it, so a file behind an unconditional skip (e.g. assumeFail) can carry a
    -0007 %   broken parse indefinitely without any test catching it.
    -0008 %
    -0009 %   This test is the cheap backstop: it needs no data, no solver and no
    -0010 %   network, and it runs over the entire source tree in seconds.
    -0011 
    -0012     methods (Test)
    -0013         function allSourceFilesParse(testCase)
    -0014             files = dir(fullfile(testCase.ravenRoot,'**','*.m'));
    -0015             paths = fullfile({files.folder}, {files.name});
    -0016 
    -0017             % software/ is vendored third-party code (GLPKmex, libSBML). It is
    -0018             % not ours to fix, so a syntax complaint there is not a RAVEN bug.
    -0019             paths = paths(~contains(paths, [filesep 'software' filesep]));
    -0020 
    -0021             testCase.assertNotEmpty(paths, 'Found no .m files to check.');
    -0022 
    -0023             msgs = checkcode(paths{:}, '-id');
    -0024             if ~iscell(msgs)
    -0025                 msgs = {msgs};   % checkcode returns a bare struct for one file
    -0026             end
    -0027 
    -0028             offenders = {};
    -0029             for i = 1:numel(paths)
    -0030                 if isempty(msgs{i})
    -0031                     continue
    -0032                 end
    -0033                 % SYNER is checkcode's parse-error identifier. Only syntax is
    -0034                 % checked here; style warnings are deliberately ignored so the
    -0035                 % test stays a hard gate rather than a lint backlog.
    -0036                 bad = strcmp({msgs{i}.id}, 'SYNER');
    -0037                 for k = find(bad)
    -0038                     offenders{end+1} = sprintf('%s (line %d): %s', ...
    -0039                         strrep(paths{i}, testCase.ravenRoot, ''), ...
    -0040                         msgs{i}(k).line, msgs{i}(k).message); %#ok<AGROW>
    -0041                 end
    -0042             end
    -0043 
    -0044             testCase.verifyEmpty(offenders, ...
    -0045                 sprintf('Files with parse errors:\n%s', strjoin(offenders, newline)));
    -0046         end
    -0047     end
    -0048 end
    -
    Generated by m2html © 2005
    - - \ No newline at end of file diff --git a/docs/html/testing/function_tests/tTasks.html b/docs/html/testing/function_tests/tTasks.html deleted file mode 100644 index f5e55eca..00000000 --- a/docs/html/testing/function_tests/tTasks.html +++ /dev/null @@ -1,140 +0,0 @@ - - - - Description of tTasks - - - - - - - - - -
    Home > testing > function_tests > tTasks.m
    - - - -

    tTasks -

    - -

    PURPOSE ^

    -
    - -

    SYNOPSIS ^

    -
    This is a script file.
    - -

    DESCRIPTION ^

    -
    - - -

    CROSS-REFERENCE INFORMATION ^

    -This function calls: - -This function is called by: - - - -

    SUBFUNCTIONS ^

    - - -

    SOURCE CODE ^

    -
    0001 classdef tTasks < RavenTestCase
    -0002 % tTasks  Tests for the metabolic-task functions in tasks/.
    -0003 
    -0004     methods (Test)
    -0005 
    -0006         function checkTasksSucceedsOnFullModel(testCase)
    -0007             m = testCase.taskTestModel();
    -0008             task = testCase.taskTestStruct();
    -0009             [~, report] = evalc('checkTasks(m, [], true, false, false, task);');
    -0010             testCase.verifyTrue(report.ok == 1);
    -0011         end
    -0012 
    -0013         function checkTasksFailsWithoutKeyReaction(testCase)
    -0014             m = removeReactions(testCase.taskTestModel(), {'R2'});
    -0015             task = testCase.taskTestStruct();
    -0016             [~, report] = evalc('checkTasks(m, [], true, false, false, task);');
    -0017             testCase.verifyTrue(report.ok == 0);
    -0018         end
    -0019 
    -0020         function parseTaskListReturnsStruct(testCase)
    -0021             taskFile = fullfile(testCase.ravenRoot,'testing','function_tests', ...
    -0022                 'test_data','test_tasks.txt');
    -0023             testCase.assumeDependency(exist(taskFile,'file')==2, 'test_tasks.txt');
    -0024             ts = parseTaskList(taskFile);
    -0025             testCase.verifyClass(ts, 'struct');
    -0026             testCase.verifyNotEmpty(ts);
    -0027         end
    -0028 
    -0029         function parseTaskListMatchesExpected(testCase)
    -0030             % The text and Excel task definitions must parse to identical
    -0031             % structs with exactly the expected fields and values.
    -0032             dataDir   = fullfile(testCase.ravenRoot,'testing','function_tests','test_data');
    -0033             txtFile   = fullfile(dataDir,'test_tasks.txt');
    -0034             xlsFile   = fullfile(dataDir,'test_tasks.xls');
    -0035             testCase.assumeDependency(exist(txtFile,'file')==2, 'test_tasks.txt');
    -0036             testCase.assumeDependency(exist(xlsFile,'file')==2, 'test_tasks.xls');
    -0037 
    -0038             taskStruct      = parseTaskList(txtFile);
    -0039             taskStructExcel = parseTaskList(xlsFile);
    -0040 
    -0041             expTask1.id          = 'ER';
    -0042             expTask1.description = 'Aerobic rephosphorylation of ATP from glucose';
    -0043             expTask1.shouldFail  = true;
    -0044             expTask1.printFluxes = true;
    -0045             expTask1.comments    = 'Messed up reaction';
    -0046             expTask1.inputs      = {'O2[s]';'glucose[s]'};
    -0047             expTask1.LBin        = [23.6;23.6];
    -0048             expTask1.UBin        = [23.8;23.8];
    -0049             expTask1.outputs     = {'H2O[s]';'CO2[s]'};
    -0050             expTask1.LBout       = [26.1;26.1];
    -0051             expTask1.UBout       = [26.2;26.2];
    -0052             expTask1.equations   = {'ATP[c] + H2O[c] => ADP[c] + Pi[c] + H+[c]'};
    -0053             expTask1.LBequ       = 30.2;
    -0054             expTask1.UBequ       = 30.6;
    -0055             expTask1.changed     = {'ATP[a] + H2O[a] => ADP[a] + Pi[a] + H+[a]'};
    -0056             expTask1.LBrxn       = 56.2;
    -0057             % incomplete struct must not match
    -0058             testCase.verifyNotEqual(taskStruct(1), expTask1);
    -0059             expTask1.UBrxn = 60;
    -0060             testCase.verifyEqual(taskStruct(1), expTask1);
    -0061             testCase.verifyEqual(taskStructExcel(1), expTask1);
    -0062 
    -0063             testCase.verifyEqual(length(taskStruct), 2);
    -0064             testCase.verifyEqual(length(taskStructExcel), 2);
    -0065 
    -0066             expTask2.id          = 'BS';
    -0067             expTask2.description = 'ATP de novo synthesis';
    -0068             expTask2.shouldFail  = false;
    -0069             expTask2.printFluxes = false;
    -0070             expTask2.comments    = '';
    -0071             expTask2.inputs      = {'O2[s]';'glucose[s]';'NH3[s]';'Pi[s]'};
    -0072             expTask2.LBin        = [0;0;0;0];
    -0073             expTask2.UBin        = [1000;1000;1000;1000];
    -0074             expTask2.outputs     = {'H2O[s]';'CO2[s]';'ATP[c]'};
    -0075             expTask2.LBout       = [0;0;1];
    -0076             expTask2.UBout       = [1.1;1.1;1.3];
    -0077             expTask2.equations   = {'ATP[c] + H2O[c] <=> ADP[c] + Pi[c] + H+[c]'};
    -0078             expTask2.LBequ       = -1000;
    -0079             expTask2.UBequ       = 1000;
    -0080             expTask2.changed     = {};
    -0081             expTask2.LBrxn       = [];
    -0082             expTask2.UBrxn       = [];
    -0083             testCase.verifyEqual(taskStruct(2), expTask2);
    -0084             testCase.verifyEqual(taskStructExcel(2), expTask2);
    -0085 
    -0086             % the two formats must produce identical results
    -0087             testCase.verifyEqual(taskStruct, taskStructExcel);
    -0088         end
    -0089 
    -0090     end
    -0091 end
    -
    Generated by m2html © 2005
    - - \ No newline at end of file diff --git a/docs/html/testing/function_tests/tUtils.html b/docs/html/testing/function_tests/tUtils.html deleted file mode 100644 index 4341bfec..00000000 --- a/docs/html/testing/function_tests/tUtils.html +++ /dev/null @@ -1,248 +0,0 @@ - - - - Description of tUtils - - - - - - - - - -
    Home > testing > function_tests > tUtils.m
    - - - -

    tUtils -

    - -

    PURPOSE ^

    -
    - -

    SYNOPSIS ^

    -
    This is a script file.
    - -

    DESCRIPTION ^

    -
    - - -

    CROSS-REFERENCE INFORMATION ^

    -This function calls: - -This function is called by: - - - -

    SUBFUNCTIONS ^

    - - -

    SOURCE CODE ^

    -
    0001 classdef tUtils < RavenTestCase
    -0002 % tUtils  Tests for the low-level helpers in utils/.
    -0003 
    -0004     methods (Test)
    -0005 
    -0006         function deprecationWarningWarnsOncePerSession(testCase)
    -0007             % A name no deprecated wrapper uses, so this test does not
    -0008             % depend on whether some other test warned first.
    -0009             name = 'tUtilsDeprecationDummy';
    -0010             testCase.verifyWarning( ...
    -0011                 @() deprecationWarning(name,'somethingElse(model)'), ...
    -0012                 'RAVEN:deprecated');
    -0013             % Second call in the same session stays quiet.
    -0014             testCase.verifyWarningFree( ...
    -0015                 @() deprecationWarning(name,'somethingElse(model)'));
    -0016         end
    -0017 
    -0018         function deprecationWarningNamesTheReplacement(testCase)
    -0019             name = 'tUtilsDeprecationNamed';
    -0020             lastwarn('');
    -0021             deprecationWarning(name,'findLeakMetabolite(model,''produce'',...)');
    -0022             testCase.verifySubstring(lastwarn, 'findLeakMetabolite');
    -0023         end
    -0024 
    -0025         function legacyMethodNoticeWarnsOncePerSession(testCase)
    -0026             % A name no legacy function uses, so this test does not depend on
    -0027             % whether some other test noticed first.
    -0028             name = 'tUtilsLegacyDummy';
    -0029             testCase.verifyWarning( ...
    -0030                 @() legacyMethodNotice(name,'somethingElse'), ...
    -0031                 'RAVEN:legacyMethod');
    -0032             % Second call in the same session stays quiet.
    -0033             testCase.verifyWarningFree( ...
    -0034                 @() legacyMethodNotice(name,'somethingElse'));
    -0035         end
    -0036 
    -0037         function legacyMethodNoticePromisesNoRemoval(testCase)
    -0038             % The point of a separate identifier from RAVEN:deprecated is that
    -0039             % it commits to nothing being removed.
    -0040             name = 'tUtilsLegacyNamed';
    -0041             lastwarn('');
    -0042             legacyMethodNotice(name,'ftINIT');
    -0043             testCase.verifySubstring(lastwarn, 'ftINIT');
    -0044             testCase.verifySubstring(lastwarn, 'remains supported');
    -0045             testCase.verifyEmpty(strfind(lastwarn, 'will be removed'));
    -0046         end
    -0047 
    -0048         function convertCharArrayFromChar(testCase)
    -0049             testCase.verifyEqual(convertCharArray('abc'), {'abc'});
    -0050         end
    -0051 
    -0052         function convertCharArrayFromCell(testCase)
    -0053             testCase.verifyEqual(convertCharArray({'a','b'}), {'a','b'});
    -0054         end
    -0055 
    -0056         function convertCharArrayFromString(testCase)
    -0057             testCase.verifyEqual(convertCharArray("str"), {'str'});
    -0058         end
    -0059 
    -0060         function ravenListFormatsItems(testCase)
    -0061             msg = ravenList('Bad genes:', {'G1','G2'});
    -0062             testCase.verifySubstring(msg, 'G1');
    -0063             testCase.verifySubstring(msg, 'G2');
    -0064         end
    -0065 
    -0066         function ravenListIndentsItemsWithATab(testCase)
    -0067             % strcat(char(9), items) strips the tab entirely, since strcat
    -0068             % removes trailing whitespace from a plain char argument and a
    -0069             % lone tab is entirely trailing whitespace.
    -0070             msg = ravenList('Bad genes:', {'G1'});
    -0071             lines = strsplit(msg, newline);
    -0072             testCase.verifyEqual(lines{2}(1), char(9));
    -0073         end
    -0074 
    -0075         function ravenListTrimsToTenByDefault(testCase)
    -0076             items = arrayfun(@(n) sprintf('G%d',n), 1:12, 'UniformOutput', false);
    -0077             msg = ravenList('Too many:', items);
    -0078             testCase.verifySubstring(msg, '...and 3 more');
    -0079         end
    -0080 
    -0081         function ravenListNoTrimWhenFalse(testCase)
    -0082             items = arrayfun(@(n) sprintf('G%d',n), 1:12, 'UniformOutput', false);
    -0083             msg = ravenList('All:', items, false);
    -0084             testCase.verifySubstring(msg, 'G12');
    -0085             testCase.verifyFalse(contains(msg, '...and'));
    -0086         end
    -0087 
    -0088         function printOrangeReturnsTextContainingInput(testCase)
    -0089             evalc('s = printOrange(''hello'');');
    -0090             testCase.verifySubstring(s, 'hello');
    -0091         end
    -0092 
    -0093         function printOrangeKeepsPercentWhenPrinting(testCase)
    -0094             % With nargout==0, printOrange prints via fprintf; a literal
    -0095             % "%" in the input must not be reinterpreted as a format
    -0096             % directive, which would truncate the rest of the text.
    -0097             out = evalc('printOrange(''50% complete'');');
    -0098             testCase.verifySubstring(out, '50% complete');
    -0099         end
    -0100 
    -0101         function printOrangeExpandsNewlineWhenPrinting(testCase)
    -0102             % "\n" in the input is a line break, as with fprintf.
    -0103             out = evalc('printOrange(''first\nsecond\n'');');
    -0104             testCase.verifySubstring(out, ['first' newline 'second' newline]);
    -0105             testCase.verifyFalse(contains(out, '\n'));
    -0106         end
    -0107 
    -0108         function parallelWorkersRAVENFalseReturnsZero(testCase)
    -0109             testCase.verifyEqual(parallelWorkersRAVEN(false), 0);
    -0110         end
    -0111 
    -0112         function parallelWorkersRAVENTrueNoPCTReturnsZero(testCase)
    -0113             testCase.assumeFalse(license('test','Distrib_Computing_Toolbox'), ...
    -0114                 'Parallel Computing Toolbox present; skipping no-PCT path.');
    -0115             testCase.verifyEqual(parallelWorkersRAVEN(true), 0);
    -0116         end
    -0117 
    -0118         function parallelWorkersRAVENTruePCTReturnsInf(testCase)
    -0119             testCase.assumeTrue(license('test','Distrib_Computing_Toolbox'), ...
    -0120                 'Parallel Computing Toolbox not present; skipping PCT path.');
    -0121             testCase.verifyEqual(parallelWorkersRAVEN(true), Inf);
    -0122         end
    -0123 
    -0124         function runRAVENtestsExists(testCase)
    -0125             % Do not execute it (would run the legacy suite); just confirm presence.
    -0126             testCase.verifyEqual(exist('runRAVENtests','file'), 2);
    -0127         end
    -0128 
    -0129         function parseGrRuleBuildsTree(testCase)
    -0130             t = parseGrRule('(G1 and G2) or G3');
    -0131             testCase.verifyEqual(t.type, 'or');
    -0132             testCase.verifyEqual(numel(t.children), 2);
    -0133             testCase.verifyEqual(t.children{1}.type, 'and');
    -0134             testCase.verifyEqual(t.children{2}.id, 'G3');
    -0135         end
    -0136 
    -0137         function parseGrRuleEmptyIsEmpty(testCase)
    -0138             testCase.verifyEmpty(parseGrRule(''));
    -0139             testCase.verifyEmpty(parseGrRule('   '));
    -0140         end
    -0141 
    -0142         function parseGrRuleTakesWholeWordOperators(testCase)
    -0143             % Gene IDs containing "and"/"or" are genes, not operators.
    -0144             t = parseGrRule('RAND1 or ORF2');
    -0145             testCase.verifyEqual(t.type, 'or');
    -0146             testCase.verifyEqual(t.children{1}.id, 'RAND1');
    -0147             testCase.verifyEqual(t.children{2}.id, 'ORF2');
    -0148         end
    -0149 
    -0150         function parseGrRuleAcceptsSymbolOperators(testCase)
    -0151             testCase.verifyEqual(parseGrRule('G1 & G2').type, 'and');
    -0152             testCase.verifyEqual(parseGrRule('G1 | G2').type, 'or');
    -0153             testCase.verifyEqual(parseGrRule('G1 AND G2').type, 'and');
    -0154         end
    -0155 
    -0156         function parseGrRuleRejectsMalformed(testCase)
    -0157             testCase.verifyError(@() parseGrRule('(G1 and G2'), 'RAVEN:badGrRule');
    -0158             testCase.verifyError(@() parseGrRule('G1 and'), 'RAVEN:badGrRule');
    -0159             testCase.verifyError(@() parseGrRule('G1 G2'), 'RAVEN:badGrRule');
    -0160         end
    -0161 
    -0162         function isDnfGrRuleClassifies(testCase)
    -0163             testCase.verifyTrue(isDnfGrRule('(G1 and G2) or G3'));
    -0164             testCase.verifyTrue(isDnfGrRule('G1'));
    -0165             testCase.verifyTrue(isDnfGrRule(''));
    -0166             testCase.verifyFalse(isDnfGrRule('(G1 or G2) and G3'));
    -0167             testCase.verifyFalse(isDnfGrRule('G1 and (G2 or G3)'));
    -0168         end
    -0169 
    -0170         function isDnfGrRuleIgnoresRedundantBrackets(testCase)
    -0171             % Bracketing alone never makes a rule non-DNF.
    -0172             testCase.verifyTrue(isDnfGrRule('((G1 and G2) or G3)'));
    -0173             testCase.verifyTrue(isDnfGrRule('(G1 or G2) or (G3 and G4)'));
    -0174         end
    -0175 
    -0176         function grRuleToDNFDistributes(testCase)
    -0177             testCase.verifyEqual(grRuleToDNF('g1 and (g2 or g3)'), ...
    -0178                 {{'g1','g2'}, {'g1','g3'}});
    -0179             % Two or:s, but four isozymes: the count of ' or ' is not the
    -0180             % count of clauses.
    -0181             testCase.verifyEqual(numel(grRuleToDNF('(g1 or g2) and (g3 or g4)')), 4);
    -0182         end
    -0183 
    -0184         function grRuleToDNFSimpleCases(testCase)
    -0185             testCase.verifyEqual(grRuleToDNF(''), {});
    -0186             testCase.verifyEqual(grRuleToDNF('G1'), {{'G1'}});
    -0187             testCase.verifyEqual(grRuleToDNF('(G1 and G2) or G3'), {{'G1','G2'},{'G3'}});
    -0188         end
    -0189 
    -0190         function grRuleToStringRoundTrips(testCase)
    -0191             rules = {'(G1 and G2) or G3', 'G1', 'G1 and G2', 'G1 or G2'};
    -0192             for k = 1:numel(rules)
    -0193                 testCase.verifyEqual(grRuleToString(parseGrRule(rules{k})), rules{k});
    -0194             end
    -0195             testCase.verifyEqual(grRuleToString(parseGrRule('')), '');
    -0196         end
    -0197 
    -0198     end
    -0199 end
    -
    Generated by m2html © 2005
    - - \ No newline at end of file diff --git a/docs/html/tutorial/index.html b/docs/html/tutorial/index.html deleted file mode 100644 index 51323021..00000000 --- a/docs/html/tutorial/index.html +++ /dev/null @@ -1,31 +0,0 @@ - - - - Index for Directory tutorial - - - - - - - - - - -
    < Master indexIndex for tutorial >
    - -

    Index for tutorial

    - -

    Matlab files in this directory:

    - -
     tutorial1tutorial1
     tutorial2tutorial2
     tutorial2_solutionstutorial2_solutions
     tutorial3tutorial3
     tutorial3_solutionstutorial3_solutions
     tutorial4tutorial4
     tutorial4_solutionstutorial4_solutions
     tutorial5tutorial5
     tutorial7tutorial7
    - -

    Other Matlab-specific files in this directory:

    -
      -
    • empty.mat
    - - -
    Generated by m2html © 2005
    - - \ No newline at end of file diff --git a/docs/html/tutorial/tutorial1.html b/docs/html/tutorial/tutorial1.html deleted file mode 100644 index ba2a793a..00000000 --- a/docs/html/tutorial/tutorial1.html +++ /dev/null @@ -1,183 +0,0 @@ - - - - Description of tutorial1 - - - - - - - - - -
    Home > tutorial > tutorial1.m
    - - - -

    tutorial1 -

    - -

    PURPOSE ^

    -
    tutorial1
    - -

    SYNOPSIS ^

    -
    This is a script file.
    - -

    DESCRIPTION ^

    -
     tutorial1
    -   This is a short introduction that shows how to load a genome-scale
    -   metabolic model (GEM), set reaction constraints, objective function and
    -   perform an optimization through flux balance analysis (FBA). The
    -   resulting fluxes are visualized and exported to a PDF file.
    -   For a more detailed description of the individual functions, see
    -   [raven_directory]/doc/index.html.
    -   A GEM for the filamentous fungus Penicillium chrysogenum is used in
    -   this tutorial. The model is provided as the SBML file
    -   "iAL1006 v1.00.xml".
    -   See Tutorial 1 on the RAVEN wiki for more details:
    -   https://raven-docs.readthedocs.io/en/latest/tutorials/
    - - -

    CROSS-REFERENCE INFORMATION ^

    -This function calls: -
      -
    -This function is called by: -
      -
    - - - - -

    SOURCE CODE ^

    -
    0001 % tutorial1
    -0002 %   This is a short introduction that shows how to load a genome-scale
    -0003 %   metabolic model (GEM), set reaction constraints, objective function and
    -0004 %   perform an optimization through flux balance analysis (FBA). The
    -0005 %   resulting fluxes are visualized and exported to a PDF file.
    -0006 %   For a more detailed description of the individual functions, see
    -0007 %   [raven_directory]/doc/index.html.
    -0008 %   A GEM for the filamentous fungus Penicillium chrysogenum is used in
    -0009 %   this tutorial. The model is provided as the SBML file
    -0010 %   "iAL1006 v1.00.xml".
    -0011 %   See Tutorial 1 on the RAVEN wiki for more details:
    -0012 %   https://raven-docs.readthedocs.io/en/latest/tutorials/
    -0013 
    -0014 %Import the model from SBML. This function performs a number of checks
    -0015 %regarding the model structure (such as for incorrectly written equations
    -0016 %or illegal characters). In this structure there is only one warning; that
    -0017 %the formula for the metabolite LPE could not be parsed. The "false" flag
    -0018 %imports a model with exchange reactions in their "closed" form. This makes
    -0019 %the model unsuited for modelling, but it is useful for some quality
    -0020 %control steps.
    -0021 model=importModel('iAL1006 v1.00.xml',false);
    -0022 
    -0023 %The following function prints some properties of the model. The two "true"
    -0024 %flags say that it should also list potential problems such as dead-end
    -0025 %reactions or unconnected metabolites.
    -0026 printModelStats(model,true,true);
    -0027 
    -0028 %As can be seen the model contains 1632 reactions, 1395 metabolites and
    -0029 %1006 genes
    -0030 
    -0031 %Most modelling approaches using GEMs are based on the mass balancing
    -0032 %around the internal metabolites in the system. However, for the system to
    -0033 %uptake or excrete metabolites, some metabolites have been defined as
    -0034 %"unconstrained". To simulate anything, those metabolites have to be
    -0035 %removed from the model. The function simplifyModel
    -0036 %is a general-purpose function for making models smaller. This includes the
    -0037 %options such as grouping linear reactions and deleting reactions which
    -0038 %cannot carry flux. Here it is chosen to delete the exchange metabolites,
    -0039 %all reactions that are constrained to zero (mainly uptake of non-standard
    -0040 %carbon sources), and all reactions that cannot carry flux (mainly
    -0041 %reactions that were dependent on any of those non-standard carbons
    -0042 %sources).
    -0043 model=simplifyModel(model,true,false,true,true);
    -0044 
    -0045 %As can be seen the model now contains only 1305 reactions, 1037
    -0046 %metabolites and 1006 genes
    -0047 
    -0048 %As a first way of validating the model, calculate the theoretical yield of
    -0049 %carbon dioxide from glucose. The supplied model already allows for uptake
    -0050 %of phosphate, sulfate, NH3, O2 and the co-factor precursors thiamin and
    -0051 %pimelate. The setParam function is used for setting constraints,
    -0052 %reversibility and objective function coefficients.
    -0053 %Set the uptake of glucose to be no more than 1 mmol/gDW/h and no uptake of
    -0054 %ethanol.
    -0055 model=setParam(model,'ub',{'glcIN' 'etohIN'},[1 0]);
    -0056 
    -0057 %Set the objective for the simulation to maximize CO2 production
    -0058 model=setParam(model,'obj',{'co2OUT'},1);
    -0059 
    -0060 %The problem can now be solved using linear programming. The solveLP
    -0061 %function takes a model and solves the linear programming problem defined
    -0062 %by the constraints and the objective value coefficients.
    -0063 sol=solveLP(model);
    -0064 
    -0065 %If everything worked fine one should see a structure that contains the
    -0066 %fields .f which is the objective value, .stat which is 1 if the
    -0067 %optimization terminated successfully and .x which contains the fluxes
    -0068 %through each of the reactions in this particular solution of the problem.
    -0069 %sol.x contains 1305 values which makes it rather difficult to interpret. A
    -0070 %first approach is to look at only the reactions that transports
    -0071 %metabolites in to or out from the system. The printFluxes function prints
    -0072 %(parts of) the flux distribution to the console.
    -0073 printFluxes(model, sol.x, true, 10^-7); %true means only print exchange fluxes
    -0074 
    -0075 %One can see that the system took up one unit of glucose and 6 units of O2
    -0076 %while producing 6 units of CO2 and 6 units of H2O. To get more detailed
    -0077 %insight how this happens, one can use the same function to plot all fluxes
    -0078 %(above 10^-7).
    -0079 printFluxes(model, sol.x, false, 10^-7);
    -0080 
    -0081 %The results show many reactions that have -1000 or 1000 flux. This is
    -0082 %because there are loops in the solution. In order to clean up the solution
    -0083 %one can minimize the sum of all the fluxes. This is done by setting the
    -0084 %third flag to solveLP to true (take a look at solveLP, there are other
    -0085 %options as well).
    -0086 sol=solveLP(model,1);
    -0087 printFluxes(model, sol.x, false, 10^-7);
    -0088 
    -0089 %Now there are much fewer reactions that are active. Some of them will
    -0090 %probably appear to be a little "weird" but that is because the ATP and
    -0091 %NAD(P)H produced has to go somewhere to be mass balanced.
    -0092 
    -0093 %If one wants to study the growth instead, change the objective to be the
    -0094 %production of biomass instead of CO2
    -0095 model=setParam(model,'obj',{'bmOUT'},1);
    -0096 sol=solveLP(model,1);
    -0097 printFluxes(model, sol.x, true, 10^-7);
    -0098 
    -0099 %The results show that the growth rate is 0.084/h and that the system now
    -0100 %also requires sulfate, phosphate, NH3, thiamin and pimelate. Compare this
    -0101 %to the growth on ethanol instead of glucose. Use three times the molar
    -0102 %flux of ethanol since it contains 2 carbons rather than 6. 'eq' means
    -0103 %'equal to' and sets the lower and upper bound to the same value.
    -0104 modelETH=setParam(model,'eq',{'glcIN' 'etohIN'},[0 3]);
    -0105 solETH=solveLP(modelETH,1);
    -0106 printFluxes(modelETH, solETH.x, true, 10^-7);
    -0107 
    -0108 %Investigate how metabolism changes between the two carbon sources.
    -0109 %compareFluxes takes two flux distributions and reports every reaction whose
    -0110 %flux changed, largest change first, labelling those that were turned on,
    -0111 %turned off or reversed direction. Here only changes above 0.5 mmol/gDW/h
    -0112 %are considered.
    -0113 res=compareFluxes(modelETH,sol.x,solETH.x,'cutoff',0.5);
    -0114 
    -0115 %The printed table is capped at 20 rows, but res.changed holds every changed
    -0116 %reaction and res.turnedOn/turnedOff/flipped list those that switched state.
    -0117 %By studying them one can start to get an idea about where the key changes
    -0118 %occur. Visualization can help a lot in this regard. One can restrict the
    -0119 %comparison to ATP metabolism by naming the metabolites of interest:
    -0120 compareFluxes(modelETH,sol.x,solETH.x,'cutoff',0.4,'metaboliteList',{'ATP'});
    -0121 
    -0122 %See that on glucose ATP is generated in glycolysis but on ethanol it seems
    -0123 %to have to do with acetate and so on. This allows the user to look further
    -0124 %and further down until one understands the underlying flux redistributions
    -0125 %that give rise to different phenotypes.
    -
    Generated by m2html © 2005
    - - \ No newline at end of file diff --git a/docs/html/tutorial/tutorial2.html b/docs/html/tutorial/tutorial2.html deleted file mode 100644 index 0d28c451..00000000 --- a/docs/html/tutorial/tutorial2.html +++ /dev/null @@ -1,75 +0,0 @@ - - - - Description of tutorial2 - - - - - - - - - -
    Home > tutorial > tutorial2.m
    - - - -

    tutorial2 -

    - -

    PURPOSE ^

    -
    tutorial2
    - -

    SYNOPSIS ^

    -
    This is a script file.
    - -

    DESCRIPTION ^

    -
     tutorial2
    -   This exercise deals with the a small glycolysis model in RAVEN
    -   compatible Excel format and shows the most basic aspects of the
    -   stoichiometric modelling. It is shown how to build a simple model from
    -   scratch, set parameters and perform simple simulations.
    -   See Tutorial 2 on the RAVEN wiki for more details:
    -   https://raven-docs.readthedocs.io/en/latest/tutorials/
    - - -

    CROSS-REFERENCE INFORMATION ^

    -This function calls: -
      -
    -This function is called by: -
      -
    - - - - -

    SOURCE CODE ^

    -
    0001 % tutorial2
    -0002 %   This exercise deals with the a small glycolysis model in RAVEN
    -0003 %   compatible Excel format and shows the most basic aspects of the
    -0004 %   stoichiometric modelling. It is shown how to build a simple model from
    -0005 %   scratch, set parameters and perform simple simulations.
    -0006 %   See Tutorial 2 on the RAVEN wiki for more details:
    -0007 %   https://raven-docs.readthedocs.io/en/latest/tutorials/
    -0008 
    -0009 %Import the model into a RAVEN model structure
    -0010 smallModel=importModel('empty.xml');
    -0011 
    -0012 %This solves the linear programming problem.
    -0013 %NOTE: if sol.f is equal to zero then the problem is not solvable. Ensure
    -0014 %that uptake and excretion of all necessary stuff are added to the model
    -0015 %and run the solveLP again.
    -0016 sol=solveLP(smallModel);
    -0017 
    -0018 %Print the resulting exchange fluxes
    -0019 printFluxes(smallModel,sol.x,true);
    -0020 
    -0021 %Print all fluxes
    -0022 printFluxes(smallModel,sol.x,false,10^-5,[],'%rxnID (%rxnName):\n\t%eqn\n\t%flux\n');
    -
    Generated by m2html © 2005
    - - \ No newline at end of file diff --git a/docs/html/tutorial/tutorial2_solutions.html b/docs/html/tutorial/tutorial2_solutions.html deleted file mode 100644 index a9407206..00000000 --- a/docs/html/tutorial/tutorial2_solutions.html +++ /dev/null @@ -1,69 +0,0 @@ - - - - Description of tutorial2_solutions - - - - - - - - - -
    Home > tutorial > tutorial2_solutions.m
    - - - -

    tutorial2_solutions -

    - -

    PURPOSE ^

    -
    tutorial2_solutions
    - -

    SYNOPSIS ^

    -
    This is a script file.
    - -

    DESCRIPTION ^

    -
     tutorial2_solutions
    -   This script contains the solutions for Tutorial 2, see Tutorial 2 on
    -   the RAVEN wiki for more details:
    -   https://raven-docs.readthedocs.io/en/latest/tutorials/
    - - -

    CROSS-REFERENCE INFORMATION ^

    -This function calls: -
      -
    -This function is called by: -
      -
    - - - - -

    SOURCE CODE ^

    -
    0001 % tutorial2_solutions
    -0002 %   This script contains the solutions for Tutorial 2, see Tutorial 2 on
    -0003 %   the RAVEN wiki for more details:
    -0004 %   https://raven-docs.readthedocs.io/en/latest/tutorials/
    -0005 
    -0006 %Import the model into a RAVEN model structure
    -0007 smallModel=readYAMLmodel('small.yml');
    -0008 
    -0009 %This solves the linear programming problem.
    -0010 %NOTE: if sol.f is equal to zero then the problem is not solvable. Ensure
    -0011 %that uptake and excretion of all necessary stuff are added to the model
    -0012 %and run the solveLP again.
    -0013 sol=solveLP(smallModel);
    -0014 
    -0015 %Print the resulting exchange fluxes
    -0016 printFluxes(smallModel,sol.x,true);
    -0017 
    -0018 %Print all fluxes
    -0019 printFluxes(smallModel,sol.x,false,10^-5,[],'%rxnID (%rxnName):\n\t%eqn\n\t%flux\n');
    -
    Generated by m2html © 2005
    - - \ No newline at end of file diff --git a/docs/html/tutorial/tutorial3.html b/docs/html/tutorial/tutorial3.html deleted file mode 100644 index 3c3dfdab..00000000 --- a/docs/html/tutorial/tutorial3.html +++ /dev/null @@ -1,112 +0,0 @@ - - - - Description of tutorial3 - - - - - - - - - -
    Home > tutorial > tutorial3.m
    - - - -

    tutorial3 -

    - -

    PURPOSE ^

    -
    tutorial3
    - -

    SYNOPSIS ^

    -
    This is a script file.
    - -

    DESCRIPTION ^

    -
     tutorial3
    -   This exercise shows how to run FBA simulations, gene deletion analysis,
    -   and how one can use GEMs as a scaffold for interpreting microarray
    -   data. A simplified model of yeast metabolism is used in this approach
    -   as an example.
    -   See Tutorial 3 on the RAVEN wiki for more details:
    -   https://raven-docs.readthedocs.io/en/latest/tutorials/
    -
    -   It is assumed that the user has already completed Tutorial 2
    - - -

    CROSS-REFERENCE INFORMATION ^

    -This function calls: -
      -
    -This function is called by: -
      -
    - - - - -

    SOURCE CODE ^

    -
    0001 % tutorial3
    -0002 %   This exercise shows how to run FBA simulations, gene deletion analysis,
    -0003 %   and how one can use GEMs as a scaffold for interpreting microarray
    -0004 %   data. A simplified model of yeast metabolism is used in this approach
    -0005 %   as an example.
    -0006 %   See Tutorial 3 on the RAVEN wiki for more details:
    -0007 %   https://raven-docs.readthedocs.io/en/latest/tutorials/
    -0008 %
    -0009 %   It is assumed that the user has already completed Tutorial 2
    -0010 
    -0011 %Import the model
    -0012 model=readYAMLmodel('smallYeast.yml');
    -0013 
    -0014 %Set the upper bound of glucose uptake to 1 and O2 uptake to unlimited
    -0015 model=setParam(model,'ub',{'glcIN' 'o2IN'},[1 1000]);
    -0016 
    -0017 %Set the objective to be ethanol production
    -0018 model=setParam(model,'obj',{'ethOUT'},1);
    -0019 
    -0020 %Solve the model
    -0021 sol=solveLP(model);
    -0022 
    -0023 %Print the resulting exchange fluxes
    -0024 printFluxes(model,sol.x,true);
    -0025 
    -0026 %Run a single gene deletion
    -0027 [genes, fluxes, originalGenes, details]=findGeneDeletions(model,'sgd');
    -0028 
    -0029 %Get the indexes of these reactions
    -0030 I=getIndexes(model,{'biomassOUT'},'rxns');
    -0031 J=getIndexes(model,{'glyOUT'},'rxns');
    -0032 
    -0033 okSolutions=find(fluxes(I,:)>10^-2); %Only look at solutions which are still growing
    -0034 [maxGlycerol, J]=max(fluxes(J,okSolutions));
    -0035 disp(maxGlycerol);
    -0036 disp(originalGenes(genes(okSolutions(J),:)));
    -0037 
    -0038 %Compare the ZWF1 deletion strain to wild type, looking only at the
    -0039 %reactions that involve the redox cofactors
    -0040 model2=setParam(model,'eq',{'ZWF'},0);
    -0041 sol2=solveLP(model2);
    -0042 compareFluxes(model,sol.x,sol2.x,'cutoff',10^-2, ...
    -0043     'metaboliteList',{'NADPH' 'NADH' 'NAD' 'NADP'});
    -0044 
    -0045 %Reload the model, since earlier steps modified its bounds and objective
    -0046 model=readYAMLmodel('smallYeast.yml');
    -0047 
    -0048 %Read microarray results and calculate reporter metabolites (metabolites
    -0049 %around which there are significant transcriptional changes)
    -0050 [orfs, pvalues]=textread('expression.txt','%s%f');
    -0051 repMets=reporterMetabolites(model,orfs,pvalues);
    -0052 [I, J]=sort(repMets.metPValues);
    -0053 
    -0054 fprintf('TOP 10 REPORTER METABOLITES:\n');
    -0055 for i=1:min(numel(J),10)
    -0056     fprintf([repMets.mets{J(i)} '\t' num2str(I(i)) '\n']);
    -0057 end
    -
    Generated by m2html © 2005
    - - \ No newline at end of file diff --git a/docs/html/tutorial/tutorial3_solutions.html b/docs/html/tutorial/tutorial3_solutions.html deleted file mode 100644 index b3ac081a..00000000 --- a/docs/html/tutorial/tutorial3_solutions.html +++ /dev/null @@ -1,137 +0,0 @@ - - - - Description of tutorial3_solutions - - - - - - - - - -
    Home > tutorial > tutorial3_solutions.m
    - - - -

    tutorial3_solutions -

    - -

    PURPOSE ^

    -
    tutorial3_solutions
    - -

    SYNOPSIS ^

    -
    This is a script file.
    - -

    DESCRIPTION ^

    -
     tutorial3_solutions
    -   This script contains the solutions for Tutorial 3, see Tutorial 3 on
    -   the RAVEN wiki for more details:
    -   https://raven-docs.readthedocs.io/en/latest/tutorials/
    -   All the parameters are set in this script, rather than modifying the
    -   Excel model file.
    - - -

    CROSS-REFERENCE INFORMATION ^

    -This function calls: -
      -
    -This function is called by: -
      -
    - - - - -

    SOURCE CODE ^

    -
    0001 % tutorial3_solutions
    -0002 %   This script contains the solutions for Tutorial 3, see Tutorial 3 on
    -0003 %   the RAVEN wiki for more details:
    -0004 %   https://raven-docs.readthedocs.io/en/latest/tutorials/
    -0005 %   All the parameters are set in this script, rather than modifying the
    -0006 %   Excel model file.
    -0007 
    -0008 %Import the model
    -0009 model=readYAMLmodel('smallYeast.yml');
    -0010 
    -0011 %Step 1
    -0012 %Set the upper bound of glucose uptake to 1 and O2 uptake to zero
    -0013 model=setParam(model,'ub',{'glcIN' 'o2IN'},[1 0]);
    -0014 
    -0015 %Set the objective to be ATP hydrolysis
    -0016 model=setParam(model,'obj',{'ATPX'},1);
    -0017 
    -0018 %Solve the model
    -0019 sol=solveLP(model);
    -0020 
    -0021 %Print the resulting fluxes. The ATP production rate should be 2.0. It was
    -0022 %4.0 in Tutorial 2, but there sucrose was used instead of glucose.
    -0023 printFluxes(model,sol.x,false);
    -0024 
    -0025 %Step 2
    -0026 %Check the yield of different products and print the results
    -0027 %Change to fully aerobic
    -0028 model=setParam(model,'ub',{'glcIN' 'o2IN'},[1 1000]);
    -0029 model=setParam(model,'obj',{'ethOUT'},1);
    -0030 sol=solveLP(model);
    -0031 fprintf(['Yield of ethanol is ' num2str(sol.f) ' mol/mol\n']);
    -0032 model=setParam(model,'obj',{'acOUT'},1);
    -0033 sol=solveLP(model);
    -0034 fprintf(['Yield of acetate is ' num2str(sol.f) ' mol/mol\n']);
    -0035 model=setParam(model,'obj',{'glyOUT'},1);
    -0036 sol=solveLP(model);
    -0037 fprintf(['Yield of glycerol is ' num2str(sol.f) ' mol/mol\n']);
    -0038 model=setParam(model,'obj',{'biomassOUT'},1);
    -0039 sol=solveLP(model);
    -0040 fprintf(['Yield of biomass is ' num2str(sol.f) '/h\n']);
    -0041 
    -0042 %Step 3
    -0043 %Solve for both aerobic and anaerobic growth
    -0044 solA=solveLP(model);
    -0045 model=setParam(model,'ub',{'o2IN'},0.5);
    -0046 solB=solveLP(model);
    -0047 
    -0048 %Step 4
    -0049 %Change to anaerobic growth and maximize for biomass
    -0050 model=setParam(model,'eq',{'o2IN'},0);
    -0051 model=setParam(model,'obj',{'biomassOUT'},1);
    -0052 sol=solveLP(model);
    -0053 printFluxes(model,sol.x,true);
    -0054 %One can see that the model predicts a glycerol production of 0.23
    -0055 %mmol/gDW/h
    -0056 
    -0057 %Run a single gene deletion
    -0058 [genes, fluxes, originalGenes, details]=findGeneDeletions(model,'sgd');
    -0059 
    -0060 %Get the indexes of these reactions
    -0061 I=getIndexes(model,{'biomassOUT'},'rxns');
    -0062 J=getIndexes(model,{'glyOUT'},'rxns');
    -0063 
    -0064 okSolutions=find(fluxes(I,:)>10^-2); %Only look at solutions which are still growing
    -0065 [maxGlycerol, J]=max(fluxes(J,okSolutions));
    -0066 fprintf(['Glycerol production is ' num2str(maxGlycerol) ' after deletion of ' originalGenes{genes(okSolutions(J),:)} '\n']);
    -0067 
    -0068 %The best gene deletion corresponds to turning off the ZWF1 reaction
    -0069 %(YNL241C)
    -0070 model2=setParam(model,'eq',{'ZWF'},0);
    -0071 sol2=solveLP(model2);
    -0072 compareFluxes(model,sol.x,sol2.x,'cutoff',10^-2, ...
    -0073     'metaboliteList',{'NADPH' 'NADH' 'NAD' 'NADP'});
    -0074 
    -0075 %Step 5
    -0076 %Read microarray results and calculate reporter metabolites (metabolites
    -0077 %around which there are significant transcriptional changes)
    -0078 [orfs, pvalues]=textread('expression.txt','%s%f');
    -0079 repMets=reporterMetabolites(model,orfs,pvalues);
    -0080 [I, J]=sort(repMets.metPValues);
    -0081 
    -0082 fprintf('TOP 10 REPORTER METABOLITES:\n');
    -0083 for i=1:min(numel(J),10)
    -0084     fprintf([repMets.mets{J(i)} '\t' num2str(I(i)) '\n']);
    -0085 end
    -
    Generated by m2html © 2005
    - - \ No newline at end of file diff --git a/docs/html/tutorial/tutorial4.html b/docs/html/tutorial/tutorial4.html deleted file mode 100644 index 2da3b58c..00000000 --- a/docs/html/tutorial/tutorial4.html +++ /dev/null @@ -1,79 +0,0 @@ - - - - Description of tutorial4 - - - - - - - - - -
    Home > tutorial > tutorial4.m
    - - - -

    tutorial4 -

    - -

    PURPOSE ^

    -
    tutorial4
    - -

    SYNOPSIS ^

    -
    This is a script file.
    - -

    DESCRIPTION ^

    -
     tutorial4
    -   This script contains the list of functions necessary for running
    -   Tutorial 4, see Tutorial 4 on the RAVEN wiki for more details:
    -   https://raven-docs.readthedocs.io/en/latest/tutorials/
    -   Several key stages may be missing, try to fill these gaps before
    -   checking the solutions in tutorial4_solutions. It is assumed that the
    -   user is somewhat familiar with linear programming.
    -
    -   It is assumed that the user has already completed Tutorials 2-3
    - - -

    CROSS-REFERENCE INFORMATION ^

    -This function calls: -
      -
    -This function is called by: -
      -
    - - - - -

    SOURCE CODE ^

    -
    0001 % tutorial4
    -0002 %   This script contains the list of functions necessary for running
    -0003 %   Tutorial 4, see Tutorial 4 on the RAVEN wiki for more details:
    -0004 %   https://raven-docs.readthedocs.io/en/latest/tutorials/
    -0005 %   Several key stages may be missing, try to fill these gaps before
    -0006 %   checking the solutions in tutorial4_solutions. It is assumed that the
    -0007 %   user is somewhat familiar with linear programming.
    -0008 %
    -0009 %   It is assumed that the user has already completed Tutorials 2-3
    -0010 
    -0011 %Import the model to gap-fill and the reference model to draw reactions from
    -0012 model=readYAMLmodel('smallYeastBad.yml');
    -0013 refModel=readYAMLmodel('smallYeast.yml');
    -0014 model.b=[model.b inf(numel(model.b),1)];
    -0015 sol=solveLP(model,1);
    -0016 printFluxes(model,sol.x,false,10^-5,[],'%rxnID (%rxnName):\n\t%eqn\n\t%flux\n');
    -0017 I=canExchange(model,'consume');
    -0018 disp(model.mets(I));
    -0019 gapReport(model,{refModel});
    -0020 
    -0021 [newConnected, cannotConnect, addedRxns, newModel]=fillGaps(model,{refModel},false);
    -0022 disp(addedRxns);
    -0023 disp(newConnected);
    -0024 disp(cannotConnect);
    -
    Generated by m2html © 2005
    - - \ No newline at end of file diff --git a/docs/html/tutorial/tutorial4_solutions.html b/docs/html/tutorial/tutorial4_solutions.html deleted file mode 100644 index 3e746cc8..00000000 --- a/docs/html/tutorial/tutorial4_solutions.html +++ /dev/null @@ -1,137 +0,0 @@ - - - - Description of tutorial4_solutions - - - - - - - - - -
    Home > tutorial > tutorial4_solutions.m
    - - - -

    tutorial4_solutions -

    - -

    PURPOSE ^

    -
    tutorial4_solutions
    - -

    SYNOPSIS ^

    -
    This is a script file.
    - -

    DESCRIPTION ^

    -
     tutorial4_solutions
    -   This script contains the solutions for Tutorial 4, see Tutorial 4 on
    -   the RAVEN wiki for more details:
    -   https://raven-docs.readthedocs.io/en/latest/tutorials/
    -
    -   NOTE: Many of these changes are easier to do in the Excel sheet. They
    -   are done here in code just to avoid having several model files.
    - - -

    CROSS-REFERENCE INFORMATION ^

    -This function calls: -
      -
    -This function is called by: -
      -
    - - - - -

    SOURCE CODE ^

    -
    0001 % tutorial4_solutions
    -0002 %   This script contains the solutions for Tutorial 4, see Tutorial 4 on
    -0003 %   the RAVEN wiki for more details:
    -0004 %   https://raven-docs.readthedocs.io/en/latest/tutorials/
    -0005 %
    -0006 %   NOTE: Many of these changes are easier to do in the Excel sheet. They
    -0007 %   are done here in code just to avoid having several model files.
    -0008 
    -0009 %Import the model
    -0010 model=readYAMLmodel('smallYeastBad.yml');
    -0011 
    -0012 %Close all uptake and maximize for production
    -0013 model=setParam(model,'eq',{'glcIN', 'o2IN'},[0 0]);
    -0014 model=setParam(model,'obj',{'acOUT' 'biomassOUT' 'co2OUT' 'ethOUT' 'glyOUT'},[1 1 1 1 1]);
    -0015 sol=solveLP(model);
    -0016 printFluxes(model,sol.x,true); %Nothing produced, good
    -0017 
    -0018 %Add some fake reactions
    -0019 rxns.rxns={'FREE_ATP';'FREE_NADH';'FREE_NADPH'};
    -0020 rxns.equations={'ATP <=> ADP + phosphate';'NAD(+) <=> NADH';'NADP(+) <=> NADPH'};
    -0021 model=addRxns(model,rxns,2,'c');
    -0022 sol=solveLP(model,1);
    -0023 
    -0024 %Lots of ethanol produced. Also plot the equations to make the error easier
    -0025 %to find
    -0026 printFluxes(model,sol.x,false,[],[],'%rxnID (%rxnName):%flux\n\t%eqn\n');
    -0027 
    -0028 %See that ADH1 should only produce one unit of ethanol. Change the reaction
    -0029 %equation
    -0030 model=changeRxns(model,'ADH1','acetaldehyde[c] + NADH[c] => ethanol[c] + NAD(+)[c]',3);
    -0031 sol=solveLP(model,1);
    -0032 printFluxes(model,sol.x,true); %Nothing produced, good
    -0033 
    -0034 %Add excretion of all metabolites
    -0035 model.b=[model.b inf(numel(model.b),1)];
    -0036 sol=solveLP(model,1);
    -0037 printFluxes(model,sol.x,false,10^-5,[],'%rxnID (%rxnName):\n\t%eqn\n\t%flux\n');
    -0038 
    -0039 %By looking at the reactions which were unbalanced and that were in the
    -0040 %flux list one can see that FBP should be changed to result in only one unit
    -0041 %of F6P
    -0042 model=changeRxns(model,'FBP','beta-D-fructofuranose 1,6-bisphosphate[c] => beta-D-fructofuranose 6-phosphate[c] + phosphate[c]',3);
    -0043 sol=solveLP(model,1);
    -0044 printFluxes(model,sol.x,false,10^-5,[],'%rxnID (%rxnName):\n\t%eqn\n\t%flux\n');
    -0045 
    -0046 %The same thing again and one should change PFK to only give one unit of
    -0047 %F16P
    -0048 model=changeRxns(model,'PFK','ATP[c] + beta-D-fructofuranose 6-phosphate[c] => ADP[c] + beta-D-fructofuranose 1,6-bisphosphate[c]',3);
    -0049 sol=solveLP(model,1);
    -0050 printFluxes(model,sol.x,false,10^-5,[],'%rxnID (%rxnName):\n\t%eqn\n\t%flux\n'); %Now it works
    -0051 
    -0052 %Set all uptakes and production to 0
    -0053 model=setParam(model,'eq',getExchangeRxns(model),0);
    -0054 
    -0055 %Since it is checked which metabolites could be consumed without
    -0056 %production, one can no longer have free production of all metabolites
    -0057 model.b=model.b(:,1);
    -0058 I=canExchange(model,'consume');
    -0059 disp(model.mets(I)); %These 12 metabolites can be consumed without any production
    -0060 
    -0061 %Allow all uptake
    -0062 model.b=[ones(numel(model.b),1)*-1000 model.b];
    -0063 
    -0064 %Pick CO2 and force uptake of it
    -0065 model=setParam(model,'eq',{'co2OUT'},-1); %Negative output means input
    -0066 sol=solveLP(model);
    -0067 printFluxes(model,sol.x,false,10^-5,[],'%rxnID (%rxnName):\n\t%eqn\n\t%flux\n'); %Now it works
    -0068 
    -0069 %See that PDC converts pyruvate (3 carbons) to acetaldehyde (2 carbons)
    -0070 %without any other products. If one googles, one may realize that CO2 is
    -0071 %missing. This would be simpler to change in the Excel file (or using
    -0072 %changeRxns), but one can change it here as an exercise. One therefore
    -0073 %needs to find the index of the reactions and the index of cytosolic CO2 in
    -0074 %order to change the reaction
    -0075 Irxn=ismember(model.rxns,'PDC');
    -0076 Imet=ismember(model.mets,'CO2_c');
    -0077 model.S(Imet,Irxn)=1; %The coefficient is 1.0
    -0078 
    -0079 %Display the new equation just to be sure
    -0080 constructEquations(model,Irxn)
    -0081 
    -0082 %The solution is now not feasible, meaning that it is no longer possible to
    -0083 %force uptake of CO2 without any output
    -0084 sol=solveLP(model);
    -
    Generated by m2html © 2005
    - - \ No newline at end of file diff --git a/docs/html/tutorial/tutorial5.html b/docs/html/tutorial/tutorial5.html deleted file mode 100644 index b6f84795..00000000 --- a/docs/html/tutorial/tutorial5.html +++ /dev/null @@ -1,254 +0,0 @@ - - - - Description of tutorial5 - - - - - - - - - -
    Home > tutorial > tutorial5.m
    - - - -

    tutorial5 -

    - -

    PURPOSE ^

    -
    tutorial5
    - -

    SYNOPSIS ^

    -
    This is a script file.
    - -

    DESCRIPTION ^

    -
     tutorial5
    -   This exercise is about creating a model from KEGG, based on protein
    -   sequences in a FASTA file, and doing some functionality checks on the
    -   model. The example case is for the yeast Saccharomyces cerevisiae. This
    -   tutorial is more of a showcase than the previous four, and its main
    -   purpose is to serve as a scaffold to reconstruct a GEM for any
    -   organism.
    -   This refers to Tutorial 5 on the RAVEN wiki:
    -   https://raven-docs.readthedocs.io/en/latest/tutorials/
    - 
    - Start by downloading trained Hidden Markov Models for eukaryotes. This can
    - be done automatically or manually from the RAVEN Wiki in its GitHub
    - repository. In this tutorial, the archive "kegg118_eukaryotes" is picked for
    - the automatic download. See the documentation in the RAVEN Wiki for more
    - information regarding preparation of such archive.
    - 
    - This creates a model for S. cerevisiae. The parameters are set to exclude
    - general or unclear reactions and reactions with undefined stoichiometry.
    - Type "help getKEGGModelForOrganism" to see what the different parameters
    - are for. This process takes up to 20-35 minutes in macOS, Unix systems and
    - 40-55 minutes in Windows, depending on your hardware and the size of
    - target organism proteome
    - - -

    CROSS-REFERENCE INFORMATION ^

    -This function calls: -
      -
    -This function is called by: -
      -
    - - - - -

    SOURCE CODE ^

    -
    0001 % tutorial5
    -0002 %   This exercise is about creating a model from KEGG, based on protein
    -0003 %   sequences in a FASTA file, and doing some functionality checks on the
    -0004 %   model. The example case is for the yeast Saccharomyces cerevisiae. This
    -0005 %   tutorial is more of a showcase than the previous four, and its main
    -0006 %   purpose is to serve as a scaffold to reconstruct a GEM for any
    -0007 %   organism.
    -0008 %   This refers to Tutorial 5 on the RAVEN wiki:
    -0009 %   https://raven-docs.readthedocs.io/en/latest/tutorials/
    -0010 %
    -0011 % Start by downloading trained Hidden Markov Models for eukaryotes. This can
    -0012 % be done automatically or manually from the RAVEN Wiki in its GitHub
    -0013 % repository. In this tutorial, the archive "kegg118_eukaryotes" is picked for
    -0014 % the automatic download. See the documentation in the RAVEN Wiki for more
    -0015 % information regarding preparation of such archive.
    -0016 %
    -0017 % This creates a model for S. cerevisiae. The parameters are set to exclude
    -0018 % general or unclear reactions and reactions with undefined stoichiometry.
    -0019 % Type "help getKEGGModelForOrganism" to see what the different parameters
    -0020 % are for. This process takes up to 20-35 minutes in macOS, Unix systems and
    -0021 % 40-55 minutes in Windows, depending on your hardware and the size of
    -0022 % target organism proteome
    -0023 model=getKEGGModelForOrganism('sce','sce.fa','kegg118_eukaryotes','output',false,false,false,false,10^-30,0.8,0.3,-1);
    -0024 
    -0025 % The resulting model should contain around 1589 reactions, 1600
    -0026 % metabolites and 836 genes. Small variations are possible since it is an
    -0027 % heuristic algorithm and different KEGG versions will give slightly
    -0028 % different results.
    -0029 disp(model);
    -0030 
    -0031 % A first control is that the model should not be able to produce any
    -0032 % metabolites without uptake of some metabolites. This commonly happens when
    -0033 % metabolites have a different meaning in different reactions. The best way
    -0034 % to find such reactions is to run findLeakMetabolite and analyze the resulting
    -0035 % solution for bad reactions. An automated approach is to use removeBadRxns,
    -0036 % which tries to do the same thing in an automated manner. Type
    -0037 % "help removeBadRxns" for details.
    -0038 [newModel, removedRxns]=removeBadRxns(model);
    -0039 
    -0040 % One can see an error about that H+ can be made even if no reactions were
    -0041 % unbalanced. Protons are particularly problematic since it is rather
    -0042 % arbitary at which pH the formulas are written for. For the purpose of this
    -0043 % analysis, the protons can be ignored and fixed later.
    -0044 [newModel, removedRxns]=removeBadRxns(model,1,{'H+'},true);
    -0045 
    -0046 % Only one reaction was removed because it enabled the model to produce
    -0047 % something from nothing. Since it is only one reaction, it might be
    -0048 % worthwhile to look into this in more detail.
    -0049 disp(removedRxns);
    -0050 
    -0051 % According to the information in KEGG about this reaction it is a general
    -0052 % polymer reaction. One might want to look at the flux distributions
    -0053 % in more detail to try to find out if there is any better alternative to
    -0054 % delete. Use findLeakMetabolite to do this.
    -0055 [fluxes, metabolite]=findLeakMetabolite(model,'produce',{'H+'},true);
    -0056 model.metNames(metabolite)
    -0057 
    -0058 % The model could produce H2O using the following reactions
    -0059 printFluxes(model, fluxes, false, [], [],'%rxnID (%rxnName):\n\t%eqn: %flux\n')
    -0060 
    -0061 % That resulted in quite a lot of fluxes to look through. Maybe it is easier
    -0062 % if the elementally balanced reactions are excluded.
    -0063 balanceStructure=getElementalBalance(model);
    -0064 
    -0065 % The hydrogen balancing is a bit tricky, so this time it seems more
    -0066 % relevant to only look at the ones unbalanced for oxygen (since water was
    -0067 % produced)
    -0068 goodOnes=balanceStructure.leftComp(:,6)==balanceStructure.rightComp(:,6);
    -0069 printFluxes(removeReactions(model,goodOnes), fluxes(~goodOnes), false, [], [],'%rxnID (%rxnName):\n\t%eqn: %flux\n');
    -0070 
    -0071 % There is still a good number of reactions. Leave only the reactions which
    -0072 % involve amylose or starch, from one of the problematic reactions that was
    -0073 % identified earlier.
    -0074 printFluxes(model, fluxes, false, [], [],'%rxnID (%rxnName):\n\t%eqn: %flux\n',{'Amylose';'Starch'});
    -0075 
    -0076 % There are two elementally unbalanced reactions, including, the reaction
    -0077 % which was identified by removeBadRxns. When looking to these reactions
    -0078 % closer, one can notice the contradiction between the reactions. The first
    -0079 % one shows that amylose and starch are interconvertible, while the second
    -0080 % reaction shows that amylose contains one less glucose unit than starch.
    -0081 % This type of general reactions are problematic and should be fixed
    -0082 % manually. It is hereby chosen to trust removeBadRxns and delete R02110.
    -0083 model=removeReactions(model,'R02110');
    -0084 
    -0085 % The model can no longer make something from nothing. Can it consume
    -0086 % something without any output?
    -0087 [solution, metabolite]=findLeakMetabolite(model,'consume',{'H+'},true);
    -0088 model.metNames(metabolite)
    -0089 
    -0090 % Nope, so that was good. Add some uptakes and see what it can produce.
    -0091 [~, J]=ismember({'D-Glucose';'H2O';'Orthophosphate';'Oxygen';'NH3';'Sulfate'},model.metNames);
    -0092 [model, addedRxns]=addExchangeRxns(model,'in',J);
    -0093 
    -0094 % Check which metabolites can be produced given these uptakes. The
    -0095 % canExchange function allows for output of all metabolites. This will not
    -0096 % happen in the real cell, but it is very useful for functionality testing
    -0097 % of the model. Once it is functional, the excretion reactions based on
    -0098 % evidence can be added as well.
    -0099 I=canExchange(model,'produce');
    -0100 
    -0101 fprintf('%d%%\n', round(sum(I)/numel(model.mets)*100));
    -0102 % It seems that around 31% of the metabolites could be synthesized. It is
    -0103 % not directly clear whether this is a high or low number, many metabolites
    -0104 % should not be possible to synthesize from those simple precursors.
    -0105 
    -0106 % Try to fill gaps using the full KEGG model to see if that gives a
    -0107 % significantly higher number
    -0108 keggModel=getModelFromKEGG(false,false,false,false);
    -0109 
    -0110 % The KEGG model is associated to more than 6,400,000 genes. They will not
    -0111 % be used for the gapfilling, so they are removed to make this a little
    -0112 % faster
    -0113 keggModel=rmfield(keggModel,'genes');
    -0114 keggModel=rmfield(keggModel,'rxnGeneMat');
    -0115 
    -0116 % It is already known from the previous commands that there are some
    -0117 % unbalanced reactions in KEGG. Only use the balanced ones for the gap
    -0118 % filling
    -0119 balanceStructure=getElementalBalance(keggModel);
    -0120 keggModel=removeReactions(keggModel,balanceStructure.balanceStatus~=1,true,true);
    -0121 
    -0122 % The function fillGaps with these settings will try to include reactions in
    -0123 % order to have flux through all reactions in the model. There are other
    -0124 % settings as well. The first flag says that production of all metabolites
    -0125 % should be allowed.
    -0126 params.relGap=0.6; %Lower number for a more exhaustive search
    -0127 params.printReport=true;
    -0128 [newConnected, cannotConnect, addedRxns, newModel, exitFlag]=fillGaps(model,keggModel,true,false,false,[],params);
    -0129 
    -0130 % The results show that fillGaps could connect around 29 reactions
    -0131 % (newConnected) by including around 41 reactions from the KEGG model
    -0132 % (addedRxns). Those should of course be checked manually to confirm that
    -0133 % they exist in yeast, but in this tutorial it is assumed that all these
    -0134 % reactions indeed occur in yeast.
    -0135 
    -0136 % Continue to improve the connectivity of the model by identifying
    -0137 % metabolites that should be connected. A convenient way to get an overview
    -0138 % of how connected the model is, and at the same time getting a lot of
    -0139 % useful data, is to use gapReport. Note that it can take several to many
    -0140 % hours to run, depending on the number of gaps in the model.
    -0141 [noFluxRxns, noFluxRxnsRelaxed, subGraphs, notProducedMets, minToConnect,...
    -0142     neededForProductionMat]=gapReport(newModel);
    -0143 
    -0144 % The results show that 532 from 1634 reactions cannot carry flux. Remember
    -0145 % that an output is allowed for all metabolites, that is why it calculates
    -0146 % 532 in both cases. 543 from 1617 metabolites cannot be synthesized from
    -0147 % the precursors which are supplied. There are 6 subnetworks in the model,
    -0148 % of which 1605 from 1617 metabolites belong to the first one.
    -0149 
    -0150 % It will also print something similar to:
    -0151 
    -0152 % To enable net production of all metabolites, a total of 322 metabolites
    -0153 % must be connected
    -0154 % Top 10 metabolites to connect:
    -0155 %     1. Acyl-CoA[s] (connects 162 metabolites)
    -0156 %     2. Thiamin diphosphate[s] (connects 17 metabolites)
    -0157 %     3. G00003[s] (connects 14 metabolites)
    -0158 %     4. Androstenediol[s] (connects 13 metabolites)
    -0159 %     5. [Dihydrolipoyllysine-residue succinyltransferase] S-glutaryldihydrolipoyllysine[s] (connects 13 metabolites)
    -0160 %     6. Selenomethionyl-tRNA(Met)[s] (connects 12 metabolites)
    -0161 %     7. Retinyl ester[s] (connects 11 metabolites)
    -0162 %     8. 1-Alkyl-2-acylglycerol[s] (connects 10 metabolites)
    -0163 %     9. G00146[s] (connects 10 metabolites)
    -0164 %     10. Progesterone[s] (connects 9 metabolites)
    -0165 
    -0166 % This is a very useful way of directing the gap-filling tasks to where they
    -0167 % are of the greatest use. In this case it says that to have net
    -0168 % production of all metabolites in the model from the simple inputs which
    -0169 % have been defined (543 metabolites can currently not have net production)
    -0170 % one needs to connect 322 unconnected metabolites in. However, by
    -0171 % connecting only the top 10 in the list 271/543 (50%) of them will now be
    -0172 % connected. When looking at the list one can see that the top ranking one
    -0173 % is Acyl-CoA, which represents many different fatty acid chains. The second
    -0174 % metabolite, thiamine disphosphate, is an enzyme co-factor. It turns out
    -0175 % that yeast cannot grow on only the substrates that had been defined, but
    -0176 % that it requires some other precursors for co-factor synthesis as well.
    -0177 
    -0178 % Add uptake reactions for the minimal media constituents needed for yeast
    -0179 % to grow.
    -0180 [~, J]=ismember({'4-Aminobenzoate';'Riboflavin';'Thiamine';'Biotin';'Folate';'Nicotinate';'Zymosterol';'Choline'},newModel.metNames);
    -0181 [newModel, addedRxns]=addExchangeRxns(newModel,'in',J);
    -0182 
    -0183 % Rerun gapReport and use the output for targeting the gap-filling efforts.
    -0184 % Note that only some info is printed; most of it is available in the output
    -0185 % structures. Work like this in an iterative manner until the model is of
    -0186 % sufficient quality.
    -
    Generated by m2html © 2005
    - - \ No newline at end of file diff --git a/docs/html/tutorial/tutorial7.html b/docs/html/tutorial/tutorial7.html deleted file mode 100644 index dbdf6b4e..00000000 --- a/docs/html/tutorial/tutorial7.html +++ /dev/null @@ -1,209 +0,0 @@ - - - - Description of tutorial7 - - - - - - - - - -
    Home > tutorial > tutorial7.m
    - - - -

    tutorial7 -

    - -

    PURPOSE ^

    -
    tutorial7
    - -

    SYNOPSIS ^

    -
    This is a script file.
    - -

    DESCRIPTION ^

    -
     tutorial7
    -   This exercise shows how to use the biomass/ functions to calibrate a
    -   model's biomass pseudoreaction against measured condition-specific
    -   composition data: getBiomassFractions to see what the model currently
    -   predicts, scaleBiomassFraction to rescale individual components to
    -   measured values (with one component left to absorb whatever is left,
    -   so the total stays at 1 g/gDW), and setGAM to apply a
    -   growth-associated maintenance value. See Tutorial 7 on the RAVEN wiki
    -   for more details: https://raven-docs.readthedocs.io/en/latest/tutorials/
    -
    -   A small hand-built model is used instead of a real GEM, so the
    -   pseudoreaction layout is visible in full below. Real GEMs lay this out
    -   the same way -- one pseudoreaction per macromolecular component,
    -   feeding into a top-level biomass pseudoreaction -- and the file
    -   biomassConditions_template.tsv in this folder is a template for the
    -   condition data itself: one row per component, one column per named
    -   condition, values in g/gDW. Real, published examples of the same
    -   shape of data (per-metabolite abundances a modeller derives such
    -   fractions from) are yeast-GEM's data/physiology/biomassComposition_*.tsv
    -   files; the numbers used here are illustrative, not measured.
    - - -

    CROSS-REFERENCE INFORMATION ^

    -This function calls: -
      -
    -This function is called by: -
      -
    - - -

    SUBFUNCTIONS ^

    - - -

    SOURCE CODE ^

    -
    0001 % tutorial7
    -0002 %   This exercise shows how to use the biomass/ functions to calibrate a
    -0003 %   model's biomass pseudoreaction against measured condition-specific
    -0004 %   composition data: getBiomassFractions to see what the model currently
    -0005 %   predicts, scaleBiomassFraction to rescale individual components to
    -0006 %   measured values (with one component left to absorb whatever is left,
    -0007 %   so the total stays at 1 g/gDW), and setGAM to apply a
    -0008 %   growth-associated maintenance value. See Tutorial 7 on the RAVEN wiki
    -0009 %   for more details: https://raven-docs.readthedocs.io/en/latest/tutorials/
    -0010 %
    -0011 %   A small hand-built model is used instead of a real GEM, so the
    -0012 %   pseudoreaction layout is visible in full below. Real GEMs lay this out
    -0013 %   the same way -- one pseudoreaction per macromolecular component,
    -0014 %   feeding into a top-level biomass pseudoreaction -- and the file
    -0015 %   biomassConditions_template.tsv in this folder is a template for the
    -0016 %   condition data itself: one row per component, one column per named
    -0017 %   condition, values in g/gDW. Real, published examples of the same
    -0018 %   shape of data (per-metabolite abundances a modeller derives such
    -0019 %   fractions from) are yeast-GEM's data/physiology/biomassComposition_*.tsv
    -0020 %   files; the numbers used here are illustrative, not measured.
    -0021 
    -0022 model = buildBiomassTemplateModel();
    -0023 biomassConfig = buildBiomassTemplateConfig();
    -0024 
    -0025 %What the template model's biomass pseudoreaction predicts before any
    -0026 %calibration
    -0027 fractions = getBiomassFractions(model, biomassConfig);
    -0028 fprintf('Before calibration:\n');
    -0029 disp(fractions);
    -0030 
    -0031 %Load the condition data. Each column after "component" is a named
    -0032 %condition; "carbohydrate" is deliberately not a row in the file -- it is
    -0033 %the component left to balance the total back to 1 g/gDW for whichever
    -0034 %condition is applied.
    -0035 condData = readtable('biomassConditions_template.tsv', 'FileType', 'text', ...
    -0036     'Delimiter', '\t');
    -0037 conditionNames = condData.Properties.VariableNames(2:end);
    -0038 
    -0039 for c = 1:numel(conditionNames)
    -0040     conditionName = conditionNames{c};
    -0041     condModel = model; %Start each condition from the same unscaled template
    -0042 
    -0043     fprintf('\n--- Condition: %s ---\n', conditionName);
    -0044     for i = 1:height(condData)
    -0045         component = condData.component{i};
    -0046         target = condData.(conditionName)(i);
    -0047         %Balance the total back to 1 g/gDW on the last explicit component,
    -0048         %so carbohydrate absorbs whatever measuring the other four leaves.
    -0049         if i == height(condData)
    -0050             condModel = scaleBiomassFraction(condModel, biomassConfig, ...
    -0051                 component, target, 'balanceOut', 'carbohydrate');
    -0052         else
    -0053             condModel = scaleBiomassFraction(condModel, biomassConfig, ...
    -0054                 component, target);
    -0055         end
    -0056     end
    -0057 
    -0058     fractions = getBiomassFractions(condModel, biomassConfig);
    -0059     fprintf('carbohydrate (balanced): %.4g g/gDW\n', fractions.carbohydrate);
    -0060     fprintf('total: %.4g g/gDW\n', fractions.total);
    -0061 end
    -0062 
    -0063 %setGAM applies a known growth-associated maintenance value directly,
    -0064 %rather than deriving one from data -- use fitParameters for that instead,
    -0065 %against measured exchange fluxes at a fixed growth rate (see its "See
    -0066 %also" cross-reference to setGAM).
    -0067 before = full(model.S(strcmp(model.mets, 'atp_c'), strcmp(model.rxns, biomassConfig.biomass_rxn)));
    -0068 model = setGAM(model, 35, biomassConfig.biomass_rxn, ...
    -0069     {'ATP', 'ADP', 'H2O', 'H+', 'phosphate'});
    -0070 after = full(model.S(strcmp(model.mets, 'atp_c'), strcmp(model.rxns, biomassConfig.biomass_rxn)));
    -0071 fprintf('\nATP coefficient in the biomass reaction: %.4g -> %.4g\n', before, after);
    -0072 
    -0073 
    -0074 function model = buildBiomassTemplateModel()
    -0075 %A minimal model with one pseudoreaction per biomass component, feeding
    -0076 %into a top-level biomass pseudoreaction -- the layout getBiomassFractions,
    -0077 %scaleBiomassFraction, scaleBiomassPseudoreaction and setGAM all expect.
    -0078 model.id = 'biomassTemplateModel';
    -0079 model.name = 'Template model for the biomass-composition tutorial';
    -0080 
    -0081 model.mets = {'aa_c'; 'glycogen_c'; 'ntp_c'; 'dntp_c'; 'fa_c'; ...
    -0082     'protein_c'; 'carbohydrate_c'; 'rna_c'; 'dna_c'; 'lipid_c'; ...
    -0083     'atp_c'; 'adp_c'; 'h2o_c'; 'h_c'; 'pi_c'};
    -0084 model.metNames = {'amino acid pool'; 'glycogen'; 'NTP pool'; 'dNTP pool'; ...
    -0085     'fatty acid pool'; 'protein'; 'carbohydrate'; 'RNA'; 'DNA'; ...
    -0086     'lipid_backbone'; 'ATP'; 'ADP'; 'H2O'; 'H+'; 'phosphate'};
    -0087 model.metFormulas = {'C5H9NO2'; 'C6H10O5'; 'C10H12N5O10P'; 'C10H12N5O9P'; ...
    -0088     'C16H30O2'; ''; ''; ''; ''; ''; ...
    -0089     'C10H12N5O13P3'; 'C10H12N5O10P2'; 'H2O'; 'H'; 'HO4P'};
    -0090 model.metCharges = [0; 0; -2; -2; 0; 0; 0; 0; 0; 0; -4; -3; 0; 1; -2];
    -0091 model.comps = {'c'};
    -0092 model.compNames = {'cytosol'};
    -0093 model.metComps = ones(numel(model.mets), 1);
    -0094 
    -0095 model.rxns = {'R_protein'; 'R_carb'; 'R_rna'; 'R_dna'; 'R_lipid'; 'R_biomass'};
    -0096 model.rxnNames = {'protein pseudoreaction'; 'carbohydrate pseudoreaction'; ...
    -0097     'RNA pseudoreaction'; 'DNA pseudoreaction'; ...
    -0098     'lipid backbone pseudoreaction'; 'biomass pseudoreaction'};
    -0099 
    -0100 nMets = numel(model.mets);
    -0101 nRxns = numel(model.rxns);
    -0102 model.S = sparse(nMets, nRxns);
    -0103 met = @(id) find(strcmp(model.mets, id));
    -0104 model.S(met('aa_c'), 1) = -20;         model.S(met('protein_c'), 1) = 1;
    -0105 model.S(met('glycogen_c'), 2) = -5;    model.S(met('carbohydrate_c'), 2) = 1;
    -0106 model.S(met('ntp_c'), 3) = -10;        model.S(met('rna_c'), 3) = 1;
    -0107 model.S(met('dntp_c'), 4) = -2;        model.S(met('dna_c'), 4) = 1;
    -0108 model.S(met('fa_c'), 5) = -0.08;       model.S(met('lipid_c'), 5) = 1;
    -0109 model.S(met('protein_c'), 6) = -1;     model.S(met('carbohydrate_c'), 6) = -1;
    -0110 model.S(met('rna_c'), 6) = -1;         model.S(met('dna_c'), 6) = -1;
    -0111 model.S(met('lipid_c'), 6) = -1;
    -0112 model.S(met('atp_c'), 6) = -30;        model.S(met('h2o_c'), 6) = -30;
    -0113 model.S(met('adp_c'), 6) = 30;         model.S(met('h_c'), 6) = 30;
    -0114 model.S(met('pi_c'), 6) = 30;
    -0115 
    -0116 model.lb = zeros(nRxns, 1);
    -0117 model.ub = repmat(1000, nRxns, 1);
    -0118 model.rev = zeros(nRxns, 1);
    -0119 model.c = [0; 0; 0; 0; 0; 1];
    -0120 model.b = zeros(nMets, 1);
    -0121 model.genes = {};
    -0122 model.rxnGeneMat = sparse(nRxns, 0);
    -0123 model.grRules = repmat({''}, nRxns, 1);
    -0124 end
    -0125 
    -0126 
    -0127 function biomassConfig = buildBiomassTemplateConfig()
    -0128 biomassConfig.biomass_rxn = 'R_biomass';
    -0129 biomassConfig.proton_met = 'h_c';
    -0130 biomassConfig.components = { ...
    -0131     struct('name', 'protein', 'pseudoreaction_name', 'protein pseudoreaction', ...
    -0132         'mass_strategy', 'mw_minus_2h'), ...
    -0133     struct('name', 'carbohydrate', 'pseudoreaction_name', 'carbohydrate pseudoreaction', ...
    -0134         'mass_strategy', 'mw'), ...
    -0135     struct('name', 'RNA', 'pseudoreaction_name', 'RNA pseudoreaction', ...
    -0136         'mass_strategy', 'mw_minus_water'), ...
    -0137     struct('name', 'DNA', 'pseudoreaction_name', 'DNA pseudoreaction', ...
    -0138         'mass_strategy', 'mw_minus_water'), ...
    -0139     struct('name', 'lipid_backbone', 'pseudoreaction_name', 'lipid backbone pseudoreaction', ...
    -0140         'mass_strategy', 'grams')};
    -0141 end
    -
    Generated by m2html © 2005
    - - \ No newline at end of file diff --git a/docs/html/utils/runRAVENtests.html b/docs/html/utils/runRAVENtests.html index e0dcbe86..bc80ee9c 100644 --- a/docs/html/utils/runRAVENtests.html +++ b/docs/html/utils/runRAVENtests.html @@ -71,7 +71,7 @@

    SOURCE CODE ^'testing','function_tests')); +0018 cd(fullfile(ravenPath,'tests','function_tests')); 0019 0020 % Collect the concrete test classes only. RavenTestCase is an abstract base 0021 % class that the test classes subclass, and runtests cannot build a suite From ba608950c482ac3d6f6de70700cfd5dc57abcddb Mon Sep 17 00:00:00 2001 From: Eduard Kerkhoven Date: Sat, 17 Oct 2026 08:12:14 +0200 Subject: [PATCH 17/48] fix: reject gene ids that split in grRules in getModelFromHomology (#3) New-organism gene ids become gene ids of the draft model, and grRules are split at whitespace and parentheses, and by COBRA also at "|" and "&". UniProt FASTA headers such as sp|P33284|HXK_KLULA passed through and broke the draft downstream. getModelFromHomology now raises an error naming the offending ids, as raven-toolbox does. Original-commit: https://github.com/SysBioChalmers/RAVEN/commit/48b3c525792caea7ff478a5719e22faf04fb06b2 --- .../homology/getModelFromHomology.m | 16 ++++++++++++++++ tests/function_tests/tReconstruction.m | 7 +++++++ 2 files changed, 23 insertions(+) diff --git a/src/reconstruction/homology/getModelFromHomology.m b/src/reconstruction/homology/getModelFromHomology.m index ca26e70a..00866bdf 100755 --- a/src/reconstruction/homology/getModelFromHomology.m +++ b/src/reconstruction/homology/getModelFromHomology.m @@ -93,6 +93,11 @@ % unneccesary old genes, the models should not have "or"-relations in their % grRules (use expandModel). % +% The gene ids of the new organism in blastStructure become its gene ids in +% the draft model, so they cannot contain whitespace, parentheses, "|" or +% "&". FASTA headers from UniProt, such as "sp|P33284|HXK_KLULA", should be +% reduced to the accession first. +% % The resulting draft model contains only reactions associated with % orthologous genes, plus the gene-free ones with keepGeneFree. With % complexPolicy "flag", the old (original) genes involved in "and" relations @@ -423,6 +428,17 @@ finalMappings{i}=finalMappings{i}(usedNewGenes,:); end +%Gene ids end up in grRules, which are split at whitespace and parentheses, +%and which COBRA also splits at "|" and "&" +unusable=allGenes{1}(~cellfun(@isempty,regexp(allGenes{1},'[\s()|&]','once'))); +if ~isempty(unusable) + error('RAVEN:badInput',['%d gene ids of "%s" cannot be written into a grRule, '... + 'because they contain whitespace, parentheses, "|" or "&": %s. Map them '... + 'to gene ids first, or use the accession alone for UniProt headers such '... + 'as "sp|P33284|HXK_KLULA".'],numel(unusable),getModelFor,... + strjoin(unusable(1:min(3,end))',', ')); +end + %Now is it time to choose which reactions should be included from which %models. If there is a preferred order specified then each gene can only %result in reactions from one model, otherwise they should all be included diff --git a/tests/function_tests/tReconstruction.m b/tests/function_tests/tReconstruction.m index 71a07e5e..cdc7d5ff 100644 --- a/tests/function_tests/tReconstruction.m +++ b/tests/function_tests/tReconstruction.m @@ -85,6 +85,13 @@ function getModelFromHomologyComplexPolicyAndOptions(testCase) testCase.verifyEqual(flag.rxnNotes{1}, 'Included by getModelFromHomology'); end + function getModelFromHomologyRejectsGeneIdsThatSplit(testCase) + src = testCase.model; src.id = 'srcModel'; + newGenes = cellstr(strcat('sp|P', string(1:10)', '|X_BUG')); + bs = makeFakeBlastStructure([src.genes(1:10), newGenes], 'srcModel', 'tgtOrg'); + testCase.verifyError(@() getModelFromHomology({src}, bs, 'tgtOrg'), 'RAVEN:badInput'); + end + function getModelFromHomologyRejectsUnknownComplexPolicy(testCase) src = testCase.model; src.id = 'srcModel'; bs = makeFakeBlastStructure([src.genes(1:10), strcat('t_', src.genes(1:10))], 'srcModel', 'tgtOrg'); From 7efdbd61bc502753d0b67ccf165d907cc3209c6f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 17 Oct 2026 06:13:39 +0000 Subject: [PATCH 18/48] doc: regenerate HTML documentation for ba60895 [skip ci] --- .../homology/getModelFromHomology.html | 1459 +++++++++-------- 1 file changed, 740 insertions(+), 719 deletions(-) diff --git a/docs/html/reconstruction/homology/getModelFromHomology.html b/docs/html/reconstruction/homology/getModelFromHomology.html index cee939ad..185ca131 100644 --- a/docs/html/reconstruction/homology/getModelFromHomology.html +++ b/docs/html/reconstruction/homology/getModelFromHomology.html @@ -120,6 +120,11 @@

    DESCRIPTION ^SOURCE CODE ^% unneccesary old genes, the models should not have "or"-relations in their 0094 % grRules (use expandModel). 0095 % -0096 % The resulting draft model contains only reactions associated with -0097 % orthologous genes, plus the gene-free ones with keepGeneFree. With -0098 % complexPolicy "flag", the old (original) genes involved in "and" relations -0099 % in grRules without any orthologs are still included in the draft model as -0100 % OLD_MODELID_geneName. A gene that several template genes map to appears -0101 % once in a grRule. The draft does not keep the version, date or -0102 % descriptive annotation of the templates. -0103 % -0104 % "to" and "from" means relative to the new organism. -0105 -0106 hitGenes.oldGenes = []; % collect the old genes from the template model (organism) -0107 hitGenes.newGenes = []; % collect the new genes of the draft model (target organism) -0108 -0109 getModelFor=char(getModelFor); +0096 % The gene ids of the new organism in blastStructure become its gene ids in +0097 % the draft model, so they cannot contain whitespace, parentheses, "|" or +0098 % "&". FASTA headers from UniProt, such as "sp|P33284|HXK_KLULA", should be +0099 % reduced to the accession first. +0100 % +0101 % The resulting draft model contains only reactions associated with +0102 % orthologous genes, plus the gene-free ones with keepGeneFree. With +0103 % complexPolicy "flag", the old (original) genes involved in "and" relations +0104 % in grRules without any orthologs are still included in the draft model as +0105 % OLD_MODELID_geneName. A gene that several template genes map to appears +0106 % once in a grRule. The draft does not keep the version, date or +0107 % descriptive annotation of the templates. +0108 % +0109 % "to" and "from" means relative to the new organism. 0110 -0111 p=parseRAVENargs(varargin, {'preferredOrder',[]; 'strictness',1; ... -0112 'onlyGenesInModels',false; 'maxE',10^-30; 'minLen',100; 'minIde',40; ... -0113 'mapNewGenesToOld',true; 'complexPolicy','flag'; 'keepGeneFree',false; ... -0114 'preserveNotes',false}); -0115 complexPolicy=lower(char(p.complexPolicy)); -0116 if ~ismember(complexPolicy,{'flag','keep','drop'}) -0117 error('complexPolicy must be "flag", "keep" or "drop", not "%s"',complexPolicy); -0118 end -0119 keepGeneFree=p.keepGeneFree; -0120 preserveNotes=p.preserveNotes; -0121 preferredOrder=p.preferredOrder; -0122 if isempty(preferredOrder) -0123 preferredOrder=[]; -0124 else -0125 preferredOrder=convertCharArray(preferredOrder); -0126 [row,col]=size(preferredOrder); -0127 if col>row -0128 preferredOrder=transpose(preferredOrder); -0129 end -0130 end -0131 switch p.strictness -0132 case 2 -0133 bidirectional=false; bestHitsOnly=false; -0134 case 3 -0135 bidirectional=true; bestHitsOnly=true; -0136 otherwise -0137 bidirectional=true; bestHitsOnly=false; -0138 end -0139 onlyGenesInModels=p.onlyGenesInModels; -0140 maxE=p.maxE; -0141 minLen=p.minLen; -0142 minIde=p.minIde; -0143 mapNewGenesToOld=p.mapNewGenesToOld; -0144 -0145 if isfield(models,'S') -0146 models={models}; -0147 end -0148 -0149 %Check that all the information is in the blast structure -0150 modelNames=cell(numel(models),1); -0151 for i=1:numel(models) -0152 modelNames{i}=models{i}.id; -0153 %Gene short names, geneMiriams and proteins are often different -0154 %between species, safer not to include them -0155 if isfield(models{i},'geneShortNames') -0156 models{i}=rmfield(models{i},'geneShortNames'); -0157 end -0158 if isfield(models{i},'geneMiriams') -0159 models{i}=rmfield(models{i},'geneMiriams'); -0160 end -0161 if isfield(models{i},'proteins') -0162 models{i}=rmfield(models{i},'proteins'); -0163 end -0164 %The geneFrom field also loses meaning if the genes are replaced by -0165 %orthologs -0166 if isfield(models{i},'geneFrom') -0167 models{i}=rmfield(models{i},'geneFrom'); +0111 hitGenes.oldGenes = []; % collect the old genes from the template model (organism) +0112 hitGenes.newGenes = []; % collect the new genes of the draft model (target organism) +0113 +0114 getModelFor=char(getModelFor); +0115 +0116 p=parseRAVENargs(varargin, {'preferredOrder',[]; 'strictness',1; ... +0117 'onlyGenesInModels',false; 'maxE',10^-30; 'minLen',100; 'minIde',40; ... +0118 'mapNewGenesToOld',true; 'complexPolicy','flag'; 'keepGeneFree',false; ... +0119 'preserveNotes',false}); +0120 complexPolicy=lower(char(p.complexPolicy)); +0121 if ~ismember(complexPolicy,{'flag','keep','drop'}) +0122 error('complexPolicy must be "flag", "keep" or "drop", not "%s"',complexPolicy); +0123 end +0124 keepGeneFree=p.keepGeneFree; +0125 preserveNotes=p.preserveNotes; +0126 preferredOrder=p.preferredOrder; +0127 if isempty(preferredOrder) +0128 preferredOrder=[]; +0129 else +0130 preferredOrder=convertCharArray(preferredOrder); +0131 [row,col]=size(preferredOrder); +0132 if col>row +0133 preferredOrder=transpose(preferredOrder); +0134 end +0135 end +0136 switch p.strictness +0137 case 2 +0138 bidirectional=false; bestHitsOnly=false; +0139 case 3 +0140 bidirectional=true; bestHitsOnly=true; +0141 otherwise +0142 bidirectional=true; bestHitsOnly=false; +0143 end +0144 onlyGenesInModels=p.onlyGenesInModels; +0145 maxE=p.maxE; +0146 minLen=p.minLen; +0147 minIde=p.minIde; +0148 mapNewGenesToOld=p.mapNewGenesToOld; +0149 +0150 if isfield(models,'S') +0151 models={models}; +0152 end +0153 +0154 %Check that all the information is in the blast structure +0155 modelNames=cell(numel(models),1); +0156 for i=1:numel(models) +0157 modelNames{i}=models{i}.id; +0158 %Gene short names, geneMiriams and proteins are often different +0159 %between species, safer not to include them +0160 if isfield(models{i},'geneShortNames') +0161 models{i}=rmfield(models{i},'geneShortNames'); +0162 end +0163 if isfield(models{i},'geneMiriams') +0164 models{i}=rmfield(models{i},'geneMiriams'); +0165 end +0166 if isfield(models{i},'proteins') +0167 models{i}=rmfield(models{i},'proteins'); 0168 end -0169 end -0170 -0171 %Check that genes do not begin with ( or end with ), as this makes problematic grRules -0172 for i=1:numel(blastStructure) -0173 problemGenes = startsWith(blastStructure(i).fromGenes,'(') | endsWith(blastStructure(i).fromGenes,')'); -0174 if any(problemGenes) -0175 error(['One or multiple gene identifiers from ' blastStructure(i).fromId ... -0176 ' starts with ''('' and/or ends with '')'', which is not allowed']) -0177 end -0178 end -0179 -0180 %Assume for now that all information is there and that it is correct. This -0181 %is important to fix since no further checks are being made! -0182 -0183 %Check whether provided fasta files use the same gene identifiers as -0184 %provided template models -0185 for i=1:numel(blastStructure) -0186 if ~strcmp(blastStructure(i).fromId,getModelFor) -0187 j=strcmpi(blastStructure(i).fromId,modelNames); -0188 if j==0 -0189 error(['While the blastStructure contains sequences from '... -0190 'organismID "%s" (as\nprovided in getBlast), none of '... -0191 'template models have this id (as model.id)'],... -0192 string(blastStructure(i).fromId)); -0193 end -0194 k=sum(ismember(blastStructure(i).fromGenes,models{j}.genes)); -0195 if k<(numel(models{j}.genes)*0.05) -0196 error(['Less than 5%% of the genes in the template model '... -0197 'with model.id "%s"\ncan be found in the blastStructure. '... -0198 'Ensure that the protein FASTA\nused in getBlast and '... -0199 'the template model used in getModelFromHomology\nuse '... -0200 'the same style of gene identifiers'],models{j}.id) -0201 end -0202 end -0203 end -0204 -0205 %Standardize grRules of template models -0206 for i=1:length(models) -0207 fprintf('\nStandardizing grRules of template model with ID "%s" ...',models{i}.id); -0208 [models{i}.grRules,models{i}.rxnGeneMat]=standardizeGrRules(models{i},false); -0209 end -0210 fprintf(' done\n'); -0211 -0212 %Remove all gene matches that are below the cutoffs -0213 for i=1:numel(blastStructure) -0214 indexes=blastStructure(i).evalue<maxE & blastStructure(i).aligLen>=minLen & blastStructure(i).identity>=minIde; %Do it in this direction to lose NaNs -0215 blastStructure(i).fromGenes(~indexes)=[]; -0216 blastStructure(i).toGenes(~indexes)=[]; -0217 blastStructure(i).evalue(~indexes)=[]; -0218 blastStructure(i).identity(~indexes)=[]; -0219 blastStructure(i).aligLen(~indexes)=[]; -0220 blastStructure(i).bitscore(~indexes)=[]; -0221 blastStructure(i).ppos(~indexes)=[]; -0222 end -0223 -0224 %Remove all reactions from the models that have no genes encoding for them, -0225 %unless keepGeneFree. Also remove all genes that encode for no reactions. -0226 %There should not be any but there might be mistakes -0227 geneFreeRxns=cell(numel(models),1); -0228 geneFreeRxns(:)={{}}; -0229 for i=1:numel(models) -0230 [hasGenes, ~]=find(models{i}.rxnGeneMat); -0231 hasNoGenes=1:numel(models{i}.rxns); -0232 hasNoGenes(hasGenes)=[]; -0233 if keepGeneFree -0234 geneFreeRxns{i}=models{i}.rxns(hasNoGenes); -0235 else -0236 models{i}=removeReactions(models{i},hasNoGenes,true,true); -0237 end -0238 end -0239 -0240 %Create a structure that contains all genes used in the blasts in any -0241 %direction for each of the models in "models" and for the new organism. The -0242 %first cell is for the new organism and then according to the preferred -0243 %order. If no such order is supplied, then according to the order in -0244 %"models" -0245 allGenes=cell(numel(models)+1,1); -0246 if isempty(preferredOrder) -0247 useOrder=modelNames; -0248 else -0249 useOrder=preferredOrder; -0250 end -0251 -0252 %Get the corresponding indexes for those models in the "models" structure -0253 useOrderIndexes=zeros(numel(models),1); -0254 for i=1:numel(models) -0255 [~, index]=ismember(models{i}.id,useOrder); -0256 useOrderIndexes(index)=i; -0257 end -0258 -0259 %Remove all genes from the blast structure that have no genes in the models -0260 if onlyGenesInModels==true -0261 modelGenes={}; -0262 for i=1:numel(models) -0263 modelGenes=[modelGenes;models{i}.genes]; -0264 end -0265 for i=1:numel(blastStructure) -0266 %Check to see if it should match the toId or fromId -0267 if strcmpi(blastStructure(i).fromId,getModelFor) -0268 I=ismember(blastStructure(i).toGenes,modelGenes); -0269 else -0270 I=ismember(blastStructure(i).fromGenes,modelGenes); -0271 end -0272 blastStructure(i).fromGenes(~I)=[]; -0273 blastStructure(i).toGenes(~I)=[]; -0274 blastStructure(i).evalue(~I)=[]; -0275 blastStructure(i).identity(~I)=[]; -0276 blastStructure(i).aligLen(~I)=[]; -0277 blastStructure(i).bitscore(~I)=[]; -0278 blastStructure(i).ppos(~I)=[]; -0279 -0280 %Check that no matching in blastStructure is empty. This happens if -0281 %no genes in the models are present in the corresponding sheet -0282 if isempty(blastStructure(i).fromGenes) -0283 EM=['No genes in matching from ' blastStructure(i).fromId ' to ' blastStructure(i).toId ' are present in the corresponding model']; -0284 error('RAVEN:badInput', '%s', EM); -0285 end -0286 end -0287 end -0288 -0289 %If only best orthologs are to be used then all other measurements are -0290 %deleted from the blastStructure. All code after this stays the same. This -0291 %means that preferred order can still matter. The best ortholog is selected -0292 %by bitscore, which unlike E-value does not shift when the search database -0293 %changes size. -0294 if bestHitsOnly -0295 for i=1:numel(blastStructure) -0296 keep=false(numel(blastStructure(i).toGenes),1); -0297 [allFromGenes, ~, I]=unique(blastStructure(i).fromGenes); -0298 -0299 %It would be nice to get rid of this loop -0300 for j=1:numel(allFromGenes) -0301 allMatches=find(I==j); -0302 bestMatches=allMatches(blastStructure(i).bitscore(allMatches)==max(blastStructure(i).bitscore(allMatches))); +0169 %The geneFrom field also loses meaning if the genes are replaced by +0170 %orthologs +0171 if isfield(models{i},'geneFrom') +0172 models{i}=rmfield(models{i},'geneFrom'); +0173 end +0174 end +0175 +0176 %Check that genes do not begin with ( or end with ), as this makes problematic grRules +0177 for i=1:numel(blastStructure) +0178 problemGenes = startsWith(blastStructure(i).fromGenes,'(') | endsWith(blastStructure(i).fromGenes,')'); +0179 if any(problemGenes) +0180 error(['One or multiple gene identifiers from ' blastStructure(i).fromId ... +0181 ' starts with ''('' and/or ends with '')'', which is not allowed']) +0182 end +0183 end +0184 +0185 %Assume for now that all information is there and that it is correct. This +0186 %is important to fix since no further checks are being made! +0187 +0188 %Check whether provided fasta files use the same gene identifiers as +0189 %provided template models +0190 for i=1:numel(blastStructure) +0191 if ~strcmp(blastStructure(i).fromId,getModelFor) +0192 j=strcmpi(blastStructure(i).fromId,modelNames); +0193 if j==0 +0194 error(['While the blastStructure contains sequences from '... +0195 'organismID "%s" (as\nprovided in getBlast), none of '... +0196 'template models have this id (as model.id)'],... +0197 string(blastStructure(i).fromId)); +0198 end +0199 k=sum(ismember(blastStructure(i).fromGenes,models{j}.genes)); +0200 if k<(numel(models{j}.genes)*0.05) +0201 error(['Less than 5%% of the genes in the template model '... +0202 'with model.id "%s"\ncan be found in the blastStructure. '... +0203 'Ensure that the protein FASTA\nused in getBlast and '... +0204 'the template model used in getModelFromHomology\nuse '... +0205 'the same style of gene identifiers'],models{j}.id) +0206 end +0207 end +0208 end +0209 +0210 %Standardize grRules of template models +0211 for i=1:length(models) +0212 fprintf('\nStandardizing grRules of template model with ID "%s" ...',models{i}.id); +0213 [models{i}.grRules,models{i}.rxnGeneMat]=standardizeGrRules(models{i},false); +0214 end +0215 fprintf(' done\n'); +0216 +0217 %Remove all gene matches that are below the cutoffs +0218 for i=1:numel(blastStructure) +0219 indexes=blastStructure(i).evalue<maxE & blastStructure(i).aligLen>=minLen & blastStructure(i).identity>=minIde; %Do it in this direction to lose NaNs +0220 blastStructure(i).fromGenes(~indexes)=[]; +0221 blastStructure(i).toGenes(~indexes)=[]; +0222 blastStructure(i).evalue(~indexes)=[]; +0223 blastStructure(i).identity(~indexes)=[]; +0224 blastStructure(i).aligLen(~indexes)=[]; +0225 blastStructure(i).bitscore(~indexes)=[]; +0226 blastStructure(i).ppos(~indexes)=[]; +0227 end +0228 +0229 %Remove all reactions from the models that have no genes encoding for them, +0230 %unless keepGeneFree. Also remove all genes that encode for no reactions. +0231 %There should not be any but there might be mistakes +0232 geneFreeRxns=cell(numel(models),1); +0233 geneFreeRxns(:)={{}}; +0234 for i=1:numel(models) +0235 [hasGenes, ~]=find(models{i}.rxnGeneMat); +0236 hasNoGenes=1:numel(models{i}.rxns); +0237 hasNoGenes(hasGenes)=[]; +0238 if keepGeneFree +0239 geneFreeRxns{i}=models{i}.rxns(hasNoGenes); +0240 else +0241 models{i}=removeReactions(models{i},hasNoGenes,true,true); +0242 end +0243 end +0244 +0245 %Create a structure that contains all genes used in the blasts in any +0246 %direction for each of the models in "models" and for the new organism. The +0247 %first cell is for the new organism and then according to the preferred +0248 %order. If no such order is supplied, then according to the order in +0249 %"models" +0250 allGenes=cell(numel(models)+1,1); +0251 if isempty(preferredOrder) +0252 useOrder=modelNames; +0253 else +0254 useOrder=preferredOrder; +0255 end +0256 +0257 %Get the corresponding indexes for those models in the "models" structure +0258 useOrderIndexes=zeros(numel(models),1); +0259 for i=1:numel(models) +0260 [~, index]=ismember(models{i}.id,useOrder); +0261 useOrderIndexes(index)=i; +0262 end +0263 +0264 %Remove all genes from the blast structure that have no genes in the models +0265 if onlyGenesInModels==true +0266 modelGenes={}; +0267 for i=1:numel(models) +0268 modelGenes=[modelGenes;models{i}.genes]; +0269 end +0270 for i=1:numel(blastStructure) +0271 %Check to see if it should match the toId or fromId +0272 if strcmpi(blastStructure(i).fromId,getModelFor) +0273 I=ismember(blastStructure(i).toGenes,modelGenes); +0274 else +0275 I=ismember(blastStructure(i).fromGenes,modelGenes); +0276 end +0277 blastStructure(i).fromGenes(~I)=[]; +0278 blastStructure(i).toGenes(~I)=[]; +0279 blastStructure(i).evalue(~I)=[]; +0280 blastStructure(i).identity(~I)=[]; +0281 blastStructure(i).aligLen(~I)=[]; +0282 blastStructure(i).bitscore(~I)=[]; +0283 blastStructure(i).ppos(~I)=[]; +0284 +0285 %Check that no matching in blastStructure is empty. This happens if +0286 %no genes in the models are present in the corresponding sheet +0287 if isempty(blastStructure(i).fromGenes) +0288 EM=['No genes in matching from ' blastStructure(i).fromId ' to ' blastStructure(i).toId ' are present in the corresponding model']; +0289 error('RAVEN:badInput', '%s', EM); +0290 end +0291 end +0292 end +0293 +0294 %If only best orthologs are to be used then all other measurements are +0295 %deleted from the blastStructure. All code after this stays the same. This +0296 %means that preferred order can still matter. The best ortholog is selected +0297 %by bitscore, which unlike E-value does not shift when the search database +0298 %changes size. +0299 if bestHitsOnly +0300 for i=1:numel(blastStructure) +0301 keep=false(numel(blastStructure(i).toGenes),1); +0302 [allFromGenes, ~, I]=unique(blastStructure(i).fromGenes); 0303 -0304 %Keep the best matches -0305 keep(bestMatches)=true; -0306 end -0307 -0308 %Delete all matches that were not best matches -0309 blastStructure(i).fromGenes(~keep)=[]; -0310 blastStructure(i).toGenes(~keep)=[]; -0311 blastStructure(i).evalue(~keep)=[]; -0312 blastStructure(i).identity(~keep)=[]; -0313 blastStructure(i).aligLen(~keep)=[]; -0314 blastStructure(i).bitscore(~keep)=[]; -0315 blastStructure(i).ppos(~keep)=[]; -0316 end -0317 end -0318 -0319 useOrder=[{getModelFor};useOrder]; -0320 -0321 for i=1:numel(blastStructure) -0322 [~, toIndex]=ismember(blastStructure(i).toId,useOrder); -0323 [~, fromIndex]=ismember(blastStructure(i).fromId,useOrder); -0324 -0325 %Add all genes to the corresponding list in allGenes -0326 allGenes{toIndex}=[allGenes{toIndex};blastStructure(i).toGenes]; -0327 allGenes{fromIndex}=[allGenes{fromIndex};blastStructure(i).fromGenes]; -0328 end -0329 -0330 %Keep only the unique gene names -0331 maxOtherGeneNr=0; %Determines the dimension of the connectivity matrixes -0332 for i=1:numel(allGenes) -0333 allGenes{i}=unique(allGenes{i}); -0334 if i>1 -0335 if numel(allGenes{i})>maxOtherGeneNr -0336 maxOtherGeneNr=numel(allGenes{i}); -0337 end -0338 end -0339 end -0340 -0341 %Generate a cell array of matrixes that describes how the genes in the new -0342 %organism map to the models. Each cell matches to the corresponding model -0343 %in useOrder (starting at 2 of course). First dimension is gene in new -0344 %organism, second which gene it is in the other organism. The second matrix -0345 %describes how they map back. -0346 -0347 %As it is now, a significant match is indicated by a 1. This could be -0348 %expanded to contain some kind of significance level. The first dimension -0349 %is still the genes in the new model. -0350 -0351 allTo=cell(numel(useOrder)-1,1); -0352 allFrom=cell(numel(useOrder)-1,1); -0353 -0354 for i=1:numel(useOrder)-1 -0355 allTo{i}=sparse(numel(allGenes{1}),numel(allGenes{i+1})); -0356 allFrom{i}=sparse(numel(allGenes{1}),numel(allGenes{i+1})); -0357 end +0304 %It would be nice to get rid of this loop +0305 for j=1:numel(allFromGenes) +0306 allMatches=find(I==j); +0307 bestMatches=allMatches(blastStructure(i).bitscore(allMatches)==max(blastStructure(i).bitscore(allMatches))); +0308 +0309 %Keep the best matches +0310 keep(bestMatches)=true; +0311 end +0312 +0313 %Delete all matches that were not best matches +0314 blastStructure(i).fromGenes(~keep)=[]; +0315 blastStructure(i).toGenes(~keep)=[]; +0316 blastStructure(i).evalue(~keep)=[]; +0317 blastStructure(i).identity(~keep)=[]; +0318 blastStructure(i).aligLen(~keep)=[]; +0319 blastStructure(i).bitscore(~keep)=[]; +0320 blastStructure(i).ppos(~keep)=[]; +0321 end +0322 end +0323 +0324 useOrder=[{getModelFor};useOrder]; +0325 +0326 for i=1:numel(blastStructure) +0327 [~, toIndex]=ismember(blastStructure(i).toId,useOrder); +0328 [~, fromIndex]=ismember(blastStructure(i).fromId,useOrder); +0329 +0330 %Add all genes to the corresponding list in allGenes +0331 allGenes{toIndex}=[allGenes{toIndex};blastStructure(i).toGenes]; +0332 allGenes{fromIndex}=[allGenes{fromIndex};blastStructure(i).fromGenes]; +0333 end +0334 +0335 %Keep only the unique gene names +0336 maxOtherGeneNr=0; %Determines the dimension of the connectivity matrixes +0337 for i=1:numel(allGenes) +0338 allGenes{i}=unique(allGenes{i}); +0339 if i>1 +0340 if numel(allGenes{i})>maxOtherGeneNr +0341 maxOtherGeneNr=numel(allGenes{i}); +0342 end +0343 end +0344 end +0345 +0346 %Generate a cell array of matrixes that describes how the genes in the new +0347 %organism map to the models. Each cell matches to the corresponding model +0348 %in useOrder (starting at 2 of course). First dimension is gene in new +0349 %organism, second which gene it is in the other organism. The second matrix +0350 %describes how they map back. +0351 +0352 %As it is now, a significant match is indicated by a 1. This could be +0353 %expanded to contain some kind of significance level. The first dimension +0354 %is still the genes in the new model. +0355 +0356 allTo=cell(numel(useOrder)-1,1); +0357 allFrom=cell(numel(useOrder)-1,1); 0358 -0359 %Fill the matches to other species -0360 for i=1:numel(blastStructure) -0361 if strcmp(blastStructure(i).toId,getModelFor) -0362 %This was "to" the new organism. They should all match so no checks -0363 %are being made -0364 [~, a]=ismember(blastStructure(i).toGenes,allGenes{1}); -0365 [~, fromModel]=ismember(blastStructure(i).fromId,useOrder); -0366 [~, b]=ismember(blastStructure(i).fromGenes,allGenes{fromModel}); -0367 idx = sub2ind(size(allTo{fromModel-1}), a, b); -0368 allTo{fromModel-1}(idx)=1; -0369 else -0370 %This was "from" the new organism -0371 [~, a]=ismember(blastStructure(i).fromGenes,allGenes{1}); -0372 [~, toModel]=ismember(blastStructure(i).toId,useOrder); -0373 [~, b]=ismember(blastStructure(i).toGenes,allGenes{toModel}); -0374 idx = sub2ind(size(allFrom{toModel-1}), a, b); -0375 allFrom{toModel-1}(idx)=1; -0376 end -0377 end -0378 -0379 %Now we have all the gene matches in a convenient way. For all the genes in -0380 %the new organism get the genes that should be included from other -0381 %organisms. If all genes should be included this simply means keep the -0382 %allFrom matrix as it is. If only orthologs which could be mapped in both -0383 %BLAST directions are to be included then only those elements are kept. -0384 -0385 finalMappings=cell(numel(useOrder)-1,1); -0386 if bidirectional -0387 for j=1:numel(allFrom) -0388 finalMappings{j}=allTo{j}~=0 & allFrom{j}~=0; -0389 end -0390 else -0391 if mapNewGenesToOld==true -0392 finalMappings=allFrom; -0393 else -0394 finalMappings=allTo; -0395 end -0396 end -0397 -0398 %Remove all genes from the mapping that are not in the models. This does not -0399 %do much if only genes in the models were used for the original mapping. -0400 %Also simplify the finalMapping and allGenes structures so that they only -0401 %contain mappings that exist -0402 usedNewGenes=false(numel(allGenes{1}),1); -0403 -0404 for i=1:numel(allGenes)-1 -0405 %First remove mappings for those genes that are not in the model -0406 if onlyGenesInModels==false -0407 a=ismember(allGenes{i+1},models{useOrderIndexes(i)}.genes); -0408 finalMappings{i}(:,~a)=false; -0409 end -0410 -0411 %Then remove unused ones and simplify -0412 [a, b]=find(finalMappings{i}); -0413 usedGenes=false(numel(allGenes{i+1}),1); -0414 usedNewGenes(a)=true; -0415 usedGenes(b)=true; -0416 finalMappings{i}=finalMappings{i}(:,usedGenes); -0417 allGenes{i+1}=allGenes{i+1}(usedGenes); -0418 end -0419 -0420 %Remove all new genes that have not been mapped to anything -0421 allGenes{1}=allGenes{1}(usedNewGenes); -0422 for i=1:numel(finalMappings) -0423 finalMappings{i}=finalMappings{i}(usedNewGenes,:); -0424 end -0425 -0426 %Now is it time to choose which reactions should be included from which -0427 %models. If there is a preferred order specified then each gene can only -0428 %result in reactions from one model, otherwise they should all be included -0429 -0430 %Start by simplifying the models by removing genes/reactions that are not -0431 %used. This is where it gets weird with complexes, especially "or" -0432 %complexes. In this step only reactions which are encoded by one single -0433 %gene, or where all genes should be deleted, are deleted. The info on the -0434 %full complex is still present in the grRules -0435 -0436 for i=1:numel(models) -0437 a=ismember(models{useOrderIndexes(i)}.genes,allGenes{i+1}); -0438 -0439 %Remove reactions that are not associated to any of the genes in -0440 %allGenes, thereby also keeping complexes where only for one of the -0441 %genes was matched -0442 [rxnsToKeep,~] = find(models{useOrderIndexes(i)}.rxnGeneMat(:,a)); -0443 rxnsToRemove = repmat(1,numel(models{useOrderIndexes(i)}.rxns),1); -0444 rxnsToRemove(rxnsToKeep) = 0; -0445 rxnsToRemove(ismember(models{useOrderIndexes(i)}.rxns,geneFreeRxns{useOrderIndexes(i)})) = 0; -0446 rxnsToRemove = find(rxnsToRemove); -0447 models{useOrderIndexes(i)}=removeReactions(models{useOrderIndexes(i)},rxnsToRemove,true,true,true); -0448 end -0449 -0450 %Since mergeModels function will be used in the end, the models are -0451 %simplified further by deleting genes/reactions in the order specified by -0452 %preferredOrder. This means that the last model will only contain reactions -0453 %for genes that mapped only to that model -0454 -0455 allUsedGenes=false(numel(allGenes{1}),1); -0456 -0457 if ~isempty(preferredOrder) && numel(models)>1 -0458 [usedGenes, ~]=find(finalMappings{1}); %All that are used in the first model in preferredOrder -0459 allUsedGenes(usedGenes)=true; -0460 for i=2:numel(finalMappings) -0461 [usedGenes, ~]=find(finalMappings{i}); -0462 usedGenes=unique(usedGenes); -0463 a=ismember(usedGenes,find(allUsedGenes)); -0464 -0465 [~, genesToDelete]=find(finalMappings{i}(usedGenes(a),:)); %IMPORTANT! IS it really correct to remove all genes that map? -0466 genesToDelete=unique(genesToDelete); %Maybe not needed, but for clarity if nothing else -0467 -0468 %Remove all the genes that were already found and add the other -0469 %ones to allUsedGenes -0470 models{useOrderIndexes(i)}=removeGenes(models{useOrderIndexes(i)},allGenes{i+1}(genesToDelete),true,true,false); -0471 allUsedGenes(usedGenes)=true; -0472 -0473 %Remove the deleted genes from finalMappings and allGenes. -0474 finalMappings{i}(:,genesToDelete)=[]; -0475 allGenes{i+1}(genesToDelete)=[]; -0476 end -0477 end -0478 -0479 %Now loop through the models and update the gene associations. Genes not -0480 %belonging to the new organism will be renamed as "OLD_MODELID_gene" -0481 for i=1:numel(models) -0482 %Find all the new genes that should be used for this model -0483 [newGenes, oldGenes]=find(finalMappings{i}); -0484 -0485 %Create a new gene list with the genes from the new organism and those -0486 %genes that could not be removed -0487 replaceableGenes=allGenes{i+1}(unique(oldGenes)); -0488 nonReplaceableGenes=setdiff(models{useOrderIndexes(i)}.genes,replaceableGenes); -0489 fullGeneList=[allGenes{1}(unique(newGenes));nonReplaceableGenes]; -0490 -0491 %Just to save some indexing later. This is the LAST index of -0492 %replaceable ones -0493 nonRepStartIndex=numel(unique(newGenes)); -0494 -0495 %Construct a new rxnGeneMat -0496 newRxnGeneMat=sparse(numel(models{useOrderIndexes(i)}.rxns),numel(fullGeneList)); -0497 -0498 %Now update the rxnGeneMat. This is a little tricky and could -0499 %probably be done in a more efficient way, but I just loop through the -0500 %reactions and add them one by one -0501 for j=1:numel(models{useOrderIndexes(i)}.rxns) -0502 %Get the old genes encoding for this reaction -0503 [~, oldGeneIds]=find(models{useOrderIndexes(i)}.rxnGeneMat(j,:)); -0504 -0505 %Tokenize the grRule once; all gene substitutions operate on the -0506 %token list so that no gene ID can partially match another. -0507 tokens=gprTokenize(models{useOrderIndexes(i)}.grRules{j}); -0508 -0509 %Update the matrix for each gene. This includes replacing one gene -0510 %with several new ones if there were several matches -0511 for k=1:numel(oldGeneIds) -0512 %Match the gene to one in the gene list. This is done as a text -0513 %match. Could probably be done better, but the indexing is -0514 %complex -0515 -0516 geneName=models{useOrderIndexes(i)}.genes(oldGeneIds(k)); -0517 -0518 %First search in the mappable genes -0519 mapIndex=find(ismember(allGenes{i+1},geneName)); +0359 for i=1:numel(useOrder)-1 +0360 allTo{i}=sparse(numel(allGenes{1}),numel(allGenes{i+1})); +0361 allFrom{i}=sparse(numel(allGenes{1}),numel(allGenes{i+1})); +0362 end +0363 +0364 %Fill the matches to other species +0365 for i=1:numel(blastStructure) +0366 if strcmp(blastStructure(i).toId,getModelFor) +0367 %This was "to" the new organism. They should all match so no checks +0368 %are being made +0369 [~, a]=ismember(blastStructure(i).toGenes,allGenes{1}); +0370 [~, fromModel]=ismember(blastStructure(i).fromId,useOrder); +0371 [~, b]=ismember(blastStructure(i).fromGenes,allGenes{fromModel}); +0372 idx = sub2ind(size(allTo{fromModel-1}), a, b); +0373 allTo{fromModel-1}(idx)=1; +0374 else +0375 %This was "from" the new organism +0376 [~, a]=ismember(blastStructure(i).fromGenes,allGenes{1}); +0377 [~, toModel]=ismember(blastStructure(i).toId,useOrder); +0378 [~, b]=ismember(blastStructure(i).toGenes,allGenes{toModel}); +0379 idx = sub2ind(size(allFrom{toModel-1}), a, b); +0380 allFrom{toModel-1}(idx)=1; +0381 end +0382 end +0383 +0384 %Now we have all the gene matches in a convenient way. For all the genes in +0385 %the new organism get the genes that should be included from other +0386 %organisms. If all genes should be included this simply means keep the +0387 %allFrom matrix as it is. If only orthologs which could be mapped in both +0388 %BLAST directions are to be included then only those elements are kept. +0389 +0390 finalMappings=cell(numel(useOrder)-1,1); +0391 if bidirectional +0392 for j=1:numel(allFrom) +0393 finalMappings{j}=allTo{j}~=0 & allFrom{j}~=0; +0394 end +0395 else +0396 if mapNewGenesToOld==true +0397 finalMappings=allFrom; +0398 else +0399 finalMappings=allTo; +0400 end +0401 end +0402 +0403 %Remove all genes from the mapping that are not in the models. This does not +0404 %do much if only genes in the models were used for the original mapping. +0405 %Also simplify the finalMapping and allGenes structures so that they only +0406 %contain mappings that exist +0407 usedNewGenes=false(numel(allGenes{1}),1); +0408 +0409 for i=1:numel(allGenes)-1 +0410 %First remove mappings for those genes that are not in the model +0411 if onlyGenesInModels==false +0412 a=ismember(allGenes{i+1},models{useOrderIndexes(i)}.genes); +0413 finalMappings{i}(:,~a)=false; +0414 end +0415 +0416 %Then remove unused ones and simplify +0417 [a, b]=find(finalMappings{i}); +0418 usedGenes=false(numel(allGenes{i+1}),1); +0419 usedNewGenes(a)=true; +0420 usedGenes(b)=true; +0421 finalMappings{i}=finalMappings{i}(:,usedGenes); +0422 allGenes{i+1}=allGenes{i+1}(usedGenes); +0423 end +0424 +0425 %Remove all new genes that have not been mapped to anything +0426 allGenes{1}=allGenes{1}(usedNewGenes); +0427 for i=1:numel(finalMappings) +0428 finalMappings{i}=finalMappings{i}(usedNewGenes,:); +0429 end +0430 +0431 %Gene ids end up in grRules, which are split at whitespace and parentheses, +0432 %and which COBRA also splits at "|" and "&" +0433 unusable=allGenes{1}(~cellfun(@isempty,regexp(allGenes{1},'[\s()|&]','once'))); +0434 if ~isempty(unusable) +0435 error('RAVEN:badInput',['%d gene ids of "%s" cannot be written into a grRule, '... +0436 'because they contain whitespace, parentheses, "|" or "&": %s. Map them '... +0437 'to gene ids first, or use the accession alone for UniProt headers such '... +0438 'as "sp|P33284|HXK_KLULA".'],numel(unusable),getModelFor,... +0439 strjoin(unusable(1:min(3,end))',', ')); +0440 end +0441 +0442 %Now is it time to choose which reactions should be included from which +0443 %models. If there is a preferred order specified then each gene can only +0444 %result in reactions from one model, otherwise they should all be included +0445 +0446 %Start by simplifying the models by removing genes/reactions that are not +0447 %used. This is where it gets weird with complexes, especially "or" +0448 %complexes. In this step only reactions which are encoded by one single +0449 %gene, or where all genes should be deleted, are deleted. The info on the +0450 %full complex is still present in the grRules +0451 +0452 for i=1:numel(models) +0453 a=ismember(models{useOrderIndexes(i)}.genes,allGenes{i+1}); +0454 +0455 %Remove reactions that are not associated to any of the genes in +0456 %allGenes, thereby also keeping complexes where only for one of the +0457 %genes was matched +0458 [rxnsToKeep,~] = find(models{useOrderIndexes(i)}.rxnGeneMat(:,a)); +0459 rxnsToRemove = repmat(1,numel(models{useOrderIndexes(i)}.rxns),1); +0460 rxnsToRemove(rxnsToKeep) = 0; +0461 rxnsToRemove(ismember(models{useOrderIndexes(i)}.rxns,geneFreeRxns{useOrderIndexes(i)})) = 0; +0462 rxnsToRemove = find(rxnsToRemove); +0463 models{useOrderIndexes(i)}=removeReactions(models{useOrderIndexes(i)},rxnsToRemove,true,true,true); +0464 end +0465 +0466 %Since mergeModels function will be used in the end, the models are +0467 %simplified further by deleting genes/reactions in the order specified by +0468 %preferredOrder. This means that the last model will only contain reactions +0469 %for genes that mapped only to that model +0470 +0471 allUsedGenes=false(numel(allGenes{1}),1); +0472 +0473 if ~isempty(preferredOrder) && numel(models)>1 +0474 [usedGenes, ~]=find(finalMappings{1}); %All that are used in the first model in preferredOrder +0475 allUsedGenes(usedGenes)=true; +0476 for i=2:numel(finalMappings) +0477 [usedGenes, ~]=find(finalMappings{i}); +0478 usedGenes=unique(usedGenes); +0479 a=ismember(usedGenes,find(allUsedGenes)); +0480 +0481 [~, genesToDelete]=find(finalMappings{i}(usedGenes(a),:)); %IMPORTANT! IS it really correct to remove all genes that map? +0482 genesToDelete=unique(genesToDelete); %Maybe not needed, but for clarity if nothing else +0483 +0484 %Remove all the genes that were already found and add the other +0485 %ones to allUsedGenes +0486 models{useOrderIndexes(i)}=removeGenes(models{useOrderIndexes(i)},allGenes{i+1}(genesToDelete),true,true,false); +0487 allUsedGenes(usedGenes)=true; +0488 +0489 %Remove the deleted genes from finalMappings and allGenes. +0490 finalMappings{i}(:,genesToDelete)=[]; +0491 allGenes{i+1}(genesToDelete)=[]; +0492 end +0493 end +0494 +0495 %Now loop through the models and update the gene associations. Genes not +0496 %belonging to the new organism will be renamed as "OLD_MODELID_gene" +0497 for i=1:numel(models) +0498 %Find all the new genes that should be used for this model +0499 [newGenes, oldGenes]=find(finalMappings{i}); +0500 +0501 %Create a new gene list with the genes from the new organism and those +0502 %genes that could not be removed +0503 replaceableGenes=allGenes{i+1}(unique(oldGenes)); +0504 nonReplaceableGenes=setdiff(models{useOrderIndexes(i)}.genes,replaceableGenes); +0505 fullGeneList=[allGenes{1}(unique(newGenes));nonReplaceableGenes]; +0506 +0507 %Just to save some indexing later. This is the LAST index of +0508 %replaceable ones +0509 nonRepStartIndex=numel(unique(newGenes)); +0510 +0511 %Construct a new rxnGeneMat +0512 newRxnGeneMat=sparse(numel(models{useOrderIndexes(i)}.rxns),numel(fullGeneList)); +0513 +0514 %Now update the rxnGeneMat. This is a little tricky and could +0515 %probably be done in a more efficient way, but I just loop through the +0516 %reactions and add them one by one +0517 for j=1:numel(models{useOrderIndexes(i)}.rxns) +0518 %Get the old genes encoding for this reaction +0519 [~, oldGeneIds]=find(models{useOrderIndexes(i)}.rxnGeneMat(j,:)); 0520 -0521 if ~isempty(mapIndex) -0522 % add the old genes -0523 hitGenes.oldGenes = [hitGenes.oldGenes, {geneName}]; +0521 %Tokenize the grRule once; all gene substitutions operate on the +0522 %token list so that no gene ID can partially match another. +0523 tokens=gprTokenize(models{useOrderIndexes(i)}.grRules{j}); 0524 -0525 %Get the new genes for that gene -0526 a=find(finalMappings{i}(:,mapIndex)); -0527 -0528 %Find the positions of these genes in the final gene list -0529 [~, b]=ismember(allGenes{1}(a),fullGeneList); -0530 -0531 %Update the matrix -0532 newRxnGeneMat(j,b)=1; +0525 %Update the matrix for each gene. This includes replacing one gene +0526 %with several new ones if there were several matches +0527 for k=1:numel(oldGeneIds) +0528 %Match the gene to one in the gene list. This is done as a text +0529 %match. Could probably be done better, but the indexing is +0530 %complex +0531 +0532 geneName=models{useOrderIndexes(i)}.genes(oldGeneIds(k)); 0533 -0534 %Build repString for hitGenes (display only) -0535 repString=fullGeneList{b(1)}; -0536 if numel(b)>1 -0537 for l=2:numel(b) -0538 repString=[repString ' or ' fullGeneList{b(l)}]; %#ok<AGROW> -0539 end -0540 repString=['(' repString ')']; -0541 end -0542 hitGenes.newGenes = [hitGenes.newGenes, {repString}]; +0534 %First search in the mappable genes +0535 mapIndex=find(ismember(allGenes{i+1},geneName)); +0536 +0537 if ~isempty(mapIndex) +0538 % add the old genes +0539 hitGenes.oldGenes = [hitGenes.oldGenes, {geneName}]; +0540 +0541 %Get the new genes for that gene +0542 a=find(finalMappings{i}(:,mapIndex)); 0543 -0544 %Replace the old gene token with the new gene(s) -0545 tokens=gprReplaceToken(tokens,geneName{1},fullGeneList(b)); -0546 else -0547 %Then search in the non-replaceable genes. There could only -0548 %be one match here -0549 index=find(ismember(nonReplaceableGenes,geneName)); -0550 -0551 %Update the matrix -0552 newRxnGeneMat(j,nonRepStartIndex+index)=1; -0553 -0554 oldName=strcat('OLD_',models{useOrderIndexes(i)}.id,'_',geneName{1}); -0555 tokens=gprReplaceToken(tokens,geneName{1},{oldName}); -0556 end -0557 end -0558 -0559 models{useOrderIndexes(i)}.grRules{j}=gprBuildRule(tokens); -0560 end -0561 -0562 %Add the new list of genes -0563 models{useOrderIndexes(i)}.rxnGeneMat=newRxnGeneMat; -0564 if ~isempty(nonReplaceableGenes) -0565 models{useOrderIndexes(i)}.genes=[allGenes{1}(unique(newGenes));strcat('OLD_',models{useOrderIndexes(i)}.id,'_',nonReplaceableGenes)]; -0566 else -0567 models{useOrderIndexes(i)}.genes=allGenes{1}(unique(newGenes)); -0568 end -0569 if isfield(models{useOrderIndexes(i)},'geneComps') -0570 geneComps=models{useOrderIndexes(i)}.geneComps(1); -0571 models{useOrderIndexes(i)}.geneComps=zeros(numel(models{useOrderIndexes(i)}.genes),1); -0572 %Assume that all genes are in the same compartment, and this -0573 %compartment is specified for the first gene -0574 models{useOrderIndexes(i)}.geneComps(:)=geneComps; -0575 end -0576 end -0577 -0578 %Now merge the models. All information should be correct except for "or" -0579 %complexes -0580 draftModel=mergeModels(models,'metNames'); -0581 -0582 %Remove OLD_ genes that appear only in OR branches (they were added for -0583 %AND-complex genes with no orthologs; in OR branches they are redundant). -0584 %Uses token-level removal to avoid partial-match hazards. -0585 for oldI=1:numel(draftModel.grRules) -0586 if ~contains(draftModel.grRules{oldI},'OLD_') -0587 continue -0588 end -0589 tkns=gprTokenize(draftModel.grRules{oldI}); -0590 again=true; -0591 while again -0592 again=false; -0593 for kk=1:numel(tkns) -0594 if startsWith(tkns{kk},'OLD_') -0595 if kk>1 && strcmp(tkns{kk-1},'or') -0596 tkns(kk-1:kk)=[]; -0597 again=true; break -0598 elseif kk<numel(tkns) && strcmp(tkns{kk+1},'or') -0599 tkns(kk:kk+1)=[]; -0600 again=true; break -0601 end -0602 end -0603 end +0544 %Find the positions of these genes in the final gene list +0545 [~, b]=ismember(allGenes{1}(a),fullGeneList); +0546 +0547 %Update the matrix +0548 newRxnGeneMat(j,b)=1; +0549 +0550 %Build repString for hitGenes (display only) +0551 repString=fullGeneList{b(1)}; +0552 if numel(b)>1 +0553 for l=2:numel(b) +0554 repString=[repString ' or ' fullGeneList{b(l)}]; %#ok<AGROW> +0555 end +0556 repString=['(' repString ')']; +0557 end +0558 hitGenes.newGenes = [hitGenes.newGenes, {repString}]; +0559 +0560 %Replace the old gene token with the new gene(s) +0561 tokens=gprReplaceToken(tokens,geneName{1},fullGeneList(b)); +0562 else +0563 %Then search in the non-replaceable genes. There could only +0564 %be one match here +0565 index=find(ismember(nonReplaceableGenes,geneName)); +0566 +0567 %Update the matrix +0568 newRxnGeneMat(j,nonRepStartIndex+index)=1; +0569 +0570 oldName=strcat('OLD_',models{useOrderIndexes(i)}.id,'_',geneName{1}); +0571 tokens=gprReplaceToken(tokens,geneName{1},{oldName}); +0572 end +0573 end +0574 +0575 models{useOrderIndexes(i)}.grRules{j}=gprBuildRule(tokens); +0576 end +0577 +0578 %Add the new list of genes +0579 models{useOrderIndexes(i)}.rxnGeneMat=newRxnGeneMat; +0580 if ~isempty(nonReplaceableGenes) +0581 models{useOrderIndexes(i)}.genes=[allGenes{1}(unique(newGenes));strcat('OLD_',models{useOrderIndexes(i)}.id,'_',nonReplaceableGenes)]; +0582 else +0583 models{useOrderIndexes(i)}.genes=allGenes{1}(unique(newGenes)); +0584 end +0585 if isfield(models{useOrderIndexes(i)},'geneComps') +0586 geneComps=models{useOrderIndexes(i)}.geneComps(1); +0587 models{useOrderIndexes(i)}.geneComps=zeros(numel(models{useOrderIndexes(i)}.genes),1); +0588 %Assume that all genes are in the same compartment, and this +0589 %compartment is specified for the first gene +0590 models{useOrderIndexes(i)}.geneComps(:)=geneComps; +0591 end +0592 end +0593 +0594 %Now merge the models. All information should be correct except for "or" +0595 %complexes +0596 draftModel=mergeModels(models,'metNames'); +0597 +0598 %Remove OLD_ genes that appear only in OR branches (they were added for +0599 %AND-complex genes with no orthologs; in OR branches they are redundant). +0600 %Uses token-level removal to avoid partial-match hazards. +0601 for oldI=1:numel(draftModel.grRules) +0602 if ~contains(draftModel.grRules{oldI},'OLD_') +0603 continue 0604 end -0605 draftModel.grRules{oldI}=gprBuildRule(tkns); -0606 end -0607 -0608 %Apply the complex policy to the OLD_ genes left in "and" relations, and -0609 %write each grRule without repeated genes or redundant brackets -0610 toRemove=false(numel(draftModel.rxns),1); -0611 for j=1:numel(draftModel.grRules) -0612 rule=draftModel.grRules{j}; -0613 if isempty(rule) -0614 continue -0615 end -0616 if contains(rule,'OLD_') && strcmp(complexPolicy,'drop') -0617 toRemove(j)=true; -0618 elseif contains(rule,'OLD_') && strcmp(complexPolicy,'keep') -0619 draftModel.grRules{j}=gprSimplify(rule,'OLD_'); -0620 toRemove(j)=isempty(draftModel.grRules{j}); -0621 else -0622 draftModel.grRules{j}=gprSimplify(rule,''); -0623 end -0624 end -0625 if any(toRemove) -0626 draftModel=removeReactions(draftModel,draftModel.rxns(toRemove),true,true,true); -0627 end -0628 -0629 %Change name of the resulting model -0630 draftModel.id=getModelFor; -0631 name='Generated by getModelFromHomology using '; -0632 for i=1:numel(models) -0633 if i<numel(models) -0634 name=[name models{i}.id ', ']; -0635 else -0636 name=[name models{i}.id]; -0637 end -0638 end -0639 draftModel.name=name; -0640 %The draft is a new model: the templates' version, date and description are -0641 %not its own -0642 draftModel=rmfield(draftModel,intersect({'version','date','description'},fieldnames(draftModel))); -0643 if isfield(draftModel,'annotation') -0644 descriptive={'taxonomy','sourceUrl','note','givenName','familyName','authors','email','organization'}; -0645 draftModel.annotation=rmfield(draftModel.annotation,intersect(descriptive,fieldnames(draftModel.annotation))); -0646 end -0647 if ~preserveNotes || ~isfield(draftModel,'rxnNotes') -0648 draftModel.rxnNotes=cell(length(draftModel.rxns),1); -0649 draftModel.rxnNotes(:)={'Included by getModelFromHomology'}; -0650 end -0651 if ~preserveNotes || ~isfield(draftModel,'rxnConfidenceScores') -0652 draftModel.rxnConfidenceScores=NaN(length(draftModel.rxns),1); -0653 draftModel.rxnConfidenceScores(:)=2; +0605 tkns=gprTokenize(draftModel.grRules{oldI}); +0606 again=true; +0607 while again +0608 again=false; +0609 for kk=1:numel(tkns) +0610 if startsWith(tkns{kk},'OLD_') +0611 if kk>1 && strcmp(tkns{kk-1},'or') +0612 tkns(kk-1:kk)=[]; +0613 again=true; break +0614 elseif kk<numel(tkns) && strcmp(tkns{kk+1},'or') +0615 tkns(kk:kk+1)=[]; +0616 again=true; break +0617 end +0618 end +0619 end +0620 end +0621 draftModel.grRules{oldI}=gprBuildRule(tkns); +0622 end +0623 +0624 %Apply the complex policy to the OLD_ genes left in "and" relations, and +0625 %write each grRule without repeated genes or redundant brackets +0626 toRemove=false(numel(draftModel.rxns),1); +0627 for j=1:numel(draftModel.grRules) +0628 rule=draftModel.grRules{j}; +0629 if isempty(rule) +0630 continue +0631 end +0632 if contains(rule,'OLD_') && strcmp(complexPolicy,'drop') +0633 toRemove(j)=true; +0634 elseif contains(rule,'OLD_') && strcmp(complexPolicy,'keep') +0635 draftModel.grRules{j}=gprSimplify(rule,'OLD_'); +0636 toRemove(j)=isempty(draftModel.grRules{j}); +0637 else +0638 draftModel.grRules{j}=gprSimplify(rule,''); +0639 end +0640 end +0641 if any(toRemove) +0642 draftModel=removeReactions(draftModel,draftModel.rxns(toRemove),true,true,true); +0643 end +0644 +0645 %Change name of the resulting model +0646 draftModel.id=getModelFor; +0647 name='Generated by getModelFromHomology using '; +0648 for i=1:numel(models) +0649 if i<numel(models) +0650 name=[name models{i}.id ', ']; +0651 else +0652 name=[name models{i}.id]; +0653 end 0654 end -0655 draftModel=deleteUnusedGenes(draftModel,0); -0656 %Standardize grRules and notify if problematic grRules are found -0657 [draftModel.grRules,draftModel.rxnGeneMat]=standardizeGrRules(draftModel,false); -0658 draftModel=deleteUnusedGenes(draftModel,false); -0659 end -0660 -0661 -0662 function tokens=gprTokenize(rule) -0663 % Split a GPR rule into tokens: gene IDs, "and", "or", "(", ")". -0664 % Brackets are separated from adjacent text by whitespace before splitting. -0665 if isempty(rule) -0666 tokens={}; -0667 return -0668 end -0669 r=regexprep(rule,'([\(\)])',' $1 '); -0670 r=regexprep(r,'\s+',' '); -0671 tokens=strsplit(strtrim(r),' '); -0672 tokens=tokens(~cellfun(@isempty,tokens)); -0673 end -0674 -0675 -0676 function rule=gprBuildRule(tokens) -0677 % Reconstruct a GPR rule string from a token list. -0678 if isempty(tokens) -0679 rule=''; -0680 return -0681 end -0682 rule=strjoin(tokens,' '); -0683 rule=strrep(rule,'( ','('); -0684 rule=strrep(rule,' )',')'); -0685 end -0686 -0687 -0688 function tokens=gprReplaceToken(tokens,oldGene,newGenes) -0689 % Replace each occurrence of token oldGene with the gene(s) in newGenes. -0690 % newGenes is a cell array; multiple genes are wrapped in parentheses and -0691 % joined with " or ". -0692 k=1; -0693 while k<=numel(tokens) -0694 if strcmp(tokens{k},oldGene) -0695 if numel(newGenes)==1 -0696 tokens{k}=newGenes{1}; -0697 k=k+1; -0698 else -0699 seq={'('}; -0700 for g=1:numel(newGenes) -0701 seq{end+1}=newGenes{g}; %#ok<AGROW> -0702 if g<numel(newGenes) -0703 seq{end+1}='or'; %#ok<AGROW> -0704 end -0705 end -0706 seq{end+1}=')'; -0707 tokens=[tokens(1:k-1),seq,tokens(k+1:end)]; -0708 k=k+numel(seq); -0709 end -0710 else -0711 k=k+1; -0712 end -0713 end -0714 end -0715 -0716 -0717 function rule=gprSimplify(rule,removePrefix) -0718 % Rewrite a grRule as a tree: genes starting with removePrefix (if any) are -0719 % dropped from their group, groups of the same operator are flattened, -0720 % repeated genes or groups are removed, and brackets appear only around a -0721 % group of the other operator. A rule with no gene left becomes ''. -0722 tokens=gprTokenize(rule); -0723 if isempty(tokens) -0724 rule=''; -0725 return -0726 end -0727 [node,~]=gprParseOr(tokens,1); -0728 node=gprPrune(node,removePrefix); -0729 if isempty(node) -0730 rule=''; -0731 else -0732 rule=gprRender(node,''); -0733 end -0734 end -0735 -0736 -0737 function [node,pos]=gprParseOr(tokens,pos) -0738 [node,pos]=gprParseAnd(tokens,pos); -0739 kids={node}; -0740 while pos<=numel(tokens) && strcmpi(tokens{pos},'or') -0741 [node,pos]=gprParseAnd(tokens,pos+1); -0742 kids{end+1}=node; %#ok<AGROW> -0743 end -0744 if numel(kids)>1 -0745 node=struct('op','or','gene','','kids',{kids}); -0746 end -0747 end -0748 -0749 -0750 function [node,pos]=gprParseAnd(tokens,pos) -0751 [node,pos]=gprParseAtom(tokens,pos); -0752 kids={node}; -0753 while pos<=numel(tokens) && strcmpi(tokens{pos},'and') -0754 [node,pos]=gprParseAtom(tokens,pos+1); -0755 kids{end+1}=node; %#ok<AGROW> -0756 end -0757 if numel(kids)>1 -0758 node=struct('op','and','gene','','kids',{kids}); +0655 draftModel.name=name; +0656 %The draft is a new model: the templates' version, date and description are +0657 %not its own +0658 draftModel=rmfield(draftModel,intersect({'version','date','description'},fieldnames(draftModel))); +0659 if isfield(draftModel,'annotation') +0660 descriptive={'taxonomy','sourceUrl','note','givenName','familyName','authors','email','organization'}; +0661 draftModel.annotation=rmfield(draftModel.annotation,intersect(descriptive,fieldnames(draftModel.annotation))); +0662 end +0663 if ~preserveNotes || ~isfield(draftModel,'rxnNotes') +0664 draftModel.rxnNotes=cell(length(draftModel.rxns),1); +0665 draftModel.rxnNotes(:)={'Included by getModelFromHomology'}; +0666 end +0667 if ~preserveNotes || ~isfield(draftModel,'rxnConfidenceScores') +0668 draftModel.rxnConfidenceScores=NaN(length(draftModel.rxns),1); +0669 draftModel.rxnConfidenceScores(:)=2; +0670 end +0671 draftModel=deleteUnusedGenes(draftModel,0); +0672 %Standardize grRules and notify if problematic grRules are found +0673 [draftModel.grRules,draftModel.rxnGeneMat]=standardizeGrRules(draftModel,false); +0674 draftModel=deleteUnusedGenes(draftModel,false); +0675 end +0676 +0677 +0678 function tokens=gprTokenize(rule) +0679 % Split a GPR rule into tokens: gene IDs, "and", "or", "(", ")". +0680 % Brackets are separated from adjacent text by whitespace before splitting. +0681 if isempty(rule) +0682 tokens={}; +0683 return +0684 end +0685 r=regexprep(rule,'([\(\)])',' $1 '); +0686 r=regexprep(r,'\s+',' '); +0687 tokens=strsplit(strtrim(r),' '); +0688 tokens=tokens(~cellfun(@isempty,tokens)); +0689 end +0690 +0691 +0692 function rule=gprBuildRule(tokens) +0693 % Reconstruct a GPR rule string from a token list. +0694 if isempty(tokens) +0695 rule=''; +0696 return +0697 end +0698 rule=strjoin(tokens,' '); +0699 rule=strrep(rule,'( ','('); +0700 rule=strrep(rule,' )',')'); +0701 end +0702 +0703 +0704 function tokens=gprReplaceToken(tokens,oldGene,newGenes) +0705 % Replace each occurrence of token oldGene with the gene(s) in newGenes. +0706 % newGenes is a cell array; multiple genes are wrapped in parentheses and +0707 % joined with " or ". +0708 k=1; +0709 while k<=numel(tokens) +0710 if strcmp(tokens{k},oldGene) +0711 if numel(newGenes)==1 +0712 tokens{k}=newGenes{1}; +0713 k=k+1; +0714 else +0715 seq={'('}; +0716 for g=1:numel(newGenes) +0717 seq{end+1}=newGenes{g}; %#ok<AGROW> +0718 if g<numel(newGenes) +0719 seq{end+1}='or'; %#ok<AGROW> +0720 end +0721 end +0722 seq{end+1}=')'; +0723 tokens=[tokens(1:k-1),seq,tokens(k+1:end)]; +0724 k=k+numel(seq); +0725 end +0726 else +0727 k=k+1; +0728 end +0729 end +0730 end +0731 +0732 +0733 function rule=gprSimplify(rule,removePrefix) +0734 % Rewrite a grRule as a tree: genes starting with removePrefix (if any) are +0735 % dropped from their group, groups of the same operator are flattened, +0736 % repeated genes or groups are removed, and brackets appear only around a +0737 % group of the other operator. A rule with no gene left becomes ''. +0738 tokens=gprTokenize(rule); +0739 if isempty(tokens) +0740 rule=''; +0741 return +0742 end +0743 [node,~]=gprParseOr(tokens,1); +0744 node=gprPrune(node,removePrefix); +0745 if isempty(node) +0746 rule=''; +0747 else +0748 rule=gprRender(node,''); +0749 end +0750 end +0751 +0752 +0753 function [node,pos]=gprParseOr(tokens,pos) +0754 [node,pos]=gprParseAnd(tokens,pos); +0755 kids={node}; +0756 while pos<=numel(tokens) && strcmpi(tokens{pos},'or') +0757 [node,pos]=gprParseAnd(tokens,pos+1); +0758 kids{end+1}=node; %#ok<AGROW> 0759 end -0760 end -0761 -0762 -0763 function [node,pos]=gprParseAtom(tokens,pos) -0764 if pos>numel(tokens) -0765 error('Malformed grRule: it ends with an operator'); -0766 end -0767 if strcmp(tokens{pos},'(') -0768 [node,pos]=gprParseOr(tokens,pos+1); -0769 if pos>numel(tokens) || ~strcmp(tokens{pos},')') -0770 error('Malformed grRule: unbalanced brackets'); -0771 end -0772 pos=pos+1; -0773 else -0774 node=struct('op','gene','gene',tokens{pos},'kids',{{}}); -0775 pos=pos+1; +0760 if numel(kids)>1 +0761 node=struct('op','or','gene','','kids',{kids}); +0762 end +0763 end +0764 +0765 +0766 function [node,pos]=gprParseAnd(tokens,pos) +0767 [node,pos]=gprParseAtom(tokens,pos); +0768 kids={node}; +0769 while pos<=numel(tokens) && strcmpi(tokens{pos},'and') +0770 [node,pos]=gprParseAtom(tokens,pos+1); +0771 kids{end+1}=node; %#ok<AGROW> +0772 end +0773 if numel(kids)>1 +0774 node=struct('op','and','gene','','kids',{kids}); +0775 end 0776 end -0777 end +0777 0778 -0779 -0780 function node=gprPrune(node,removePrefix) -0781 if strcmp(node.op,'gene') -0782 if ~isempty(removePrefix) && startsWith(node.gene,removePrefix) -0783 node=[]; -0784 end -0785 return -0786 end -0787 kids={}; -0788 seen={}; -0789 for k=1:numel(node.kids) -0790 kid=gprPrune(node.kids{k},removePrefix); -0791 if isempty(kid) -0792 continue -0793 end -0794 if strcmp(kid.op,node.op) -0795 parts=kid.kids; -0796 else -0797 parts={kid}; -0798 end -0799 for m=1:numel(parts) -0800 key=gprRender(parts{m},node.op); -0801 if ~ismember(key,seen) -0802 seen{end+1}=key; %#ok<AGROW> -0803 kids{end+1}=parts{m}; %#ok<AGROW> -0804 end -0805 end -0806 end -0807 if isempty(kids) -0808 node=[]; -0809 elseif numel(kids)==1 -0810 node=kids{1}; -0811 else -0812 node.kids=kids; -0813 end -0814 end -0815 -0816 -0817 function rule=gprRender(node,parentOp) -0818 if strcmp(node.op,'gene') -0819 rule=node.gene; -0820 return -0821 end -0822 parts=cellfun(@(k) gprRender(k,node.op),node.kids,'UniformOutput',false); -0823 rule=strjoin(parts,[' ' node.op ' ']); -0824 if ~isempty(parentOp) && ~strcmp(parentOp,node.op) -0825 rule=['(' rule ')']; -0826 end -0827 end

    +0779 function [node,pos]=gprParseAtom(tokens,pos) +0780 if pos>numel(tokens) +0781 error('Malformed grRule: it ends with an operator'); +0782 end +0783 if strcmp(tokens{pos},'(') +0784 [node,pos]=gprParseOr(tokens,pos+1); +0785 if pos>numel(tokens) || ~strcmp(tokens{pos},')') +0786 error('Malformed grRule: unbalanced brackets'); +0787 end +0788 pos=pos+1; +0789 else +0790 node=struct('op','gene','gene',tokens{pos},'kids',{{}}); +0791 pos=pos+1; +0792 end +0793 end +0794 +0795 +0796 function node=gprPrune(node,removePrefix) +0797 if strcmp(node.op,'gene') +0798 if ~isempty(removePrefix) && startsWith(node.gene,removePrefix) +0799 node=[]; +0800 end +0801 return +0802 end +0803 kids={}; +0804 seen={}; +0805 for k=1:numel(node.kids) +0806 kid=gprPrune(node.kids{k},removePrefix); +0807 if isempty(kid) +0808 continue +0809 end +0810 if strcmp(kid.op,node.op) +0811 parts=kid.kids; +0812 else +0813 parts={kid}; +0814 end +0815 for m=1:numel(parts) +0816 key=gprRender(parts{m},node.op); +0817 if ~ismember(key,seen) +0818 seen{end+1}=key; %#ok<AGROW> +0819 kids{end+1}=parts{m}; %#ok<AGROW> +0820 end +0821 end +0822 end +0823 if isempty(kids) +0824 node=[]; +0825 elseif numel(kids)==1 +0826 node=kids{1}; +0827 else +0828 node.kids=kids; +0829 end +0830 end +0831 +0832 +0833 function rule=gprRender(node,parentOp) +0834 if strcmp(node.op,'gene') +0835 rule=node.gene; +0836 return +0837 end +0838 parts=cellfun(@(k) gprRender(k,node.op),node.kids,'UniformOutput',false); +0839 rule=strjoin(parts,[' ' node.op ' ']); +0840 if ~isempty(parentOp) && ~strcmp(parentOp,node.op) +0841 rule=['(' rule ')']; +0842 end +0843 end


    Generated by m2html © 2005
    \ No newline at end of file From 27da41214405ad25090b8a122c3c590178d52fa9 Mon Sep 17 00:00:00 2001 From: Eduard Kerkhoven Date: Sat, 17 Oct 2026 08:17:37 +0200 Subject: [PATCH 19/48] feat: hitsFromSuppOrth and runSuppOrth for SuppOrth consensus orthologs (#4) hitsFromSuppOrth reads suppOrth.tsv into a blastStructure for getModelFromHomology, in both directions, with filters on support, predictors, identity and e-value, and mapping of protein ids to gene ids. runSuppOrth runs the suppOrth command and reads its output. Matches hits_from_supporth and run_supporth in raven-toolbox. Original-commit: https://github.com/SysBioChalmers/RAVEN/commit/fb7df0918ca4e20ec4d5cde864bb9662ec69e2eb --- src/reconstruction/homology/getBlast.m | 2 +- src/reconstruction/homology/getDiamond.m | 2 +- .../homology/getModelFromHomology.m | 12 +- .../homology/hitsFromSuppOrth.m | 340 ++++++++++++++++++ .../homology/makeFakeBlastStructure.m | 2 +- src/reconstruction/homology/runSuppOrth.m | 279 ++++++++++++++ tests/function_tests/tReconstruction.m | 144 ++++++++ 7 files changed, 777 insertions(+), 4 deletions(-) create mode 100644 src/reconstruction/homology/hitsFromSuppOrth.m create mode 100644 src/reconstruction/homology/runSuppOrth.m diff --git a/src/reconstruction/homology/getBlast.m b/src/reconstruction/homology/getBlast.m index e74775da..4701b5fd 100755 --- a/src/reconstruction/homology/getBlast.m +++ b/src/reconstruction/homology/getBlast.m @@ -47,7 +47,7 @@ % % See also % -------- -% getModelFromHomology, getDiamond +% getModelFromHomology, getDiamond, runSuppOrth p=parseRAVENargs(varargin, {'developMode',false; 'hideVerbose',false}); developMode=p.developMode; diff --git a/src/reconstruction/homology/getDiamond.m b/src/reconstruction/homology/getDiamond.m index 84f93fbe..7dedb5bd 100755 --- a/src/reconstruction/homology/getDiamond.m +++ b/src/reconstruction/homology/getDiamond.m @@ -49,7 +49,7 @@ % % See also % -------- -% getModelFromHomology, getBlast +% getModelFromHomology, getBlast, runSuppOrth p=parseRAVENargs(varargin, {'developMode',false; 'hideVerbose',false}); developMode=p.developMode; diff --git a/src/reconstruction/homology/getModelFromHomology.m b/src/reconstruction/homology/getModelFromHomology.m index 00866bdf..db277511 100755 --- a/src/reconstruction/homology/getModelFromHomology.m +++ b/src/reconstruction/homology/getModelFromHomology.m @@ -11,7 +11,8 @@ % a cell array of model structures to build the model from. These % models must be sorted by importance in decreasing order. % blastStructure : struct -% a blastStructure as produced by getBlast or makeFakeBlastStructure. +% a blastStructure as produced by getBlast, getDiamond, +% hitsFromSuppOrth, runSuppOrth or makeFakeBlastStructure. % getModelFor : char % a three-four letter abbreviation of the organism to build a model % for. Must have BLASTP hits in both directions to the organisms in @@ -107,6 +108,15 @@ % descriptive annotation of the templates. % % "to" and "from" means relative to the new organism. +% +% The hits can also come from SuppOrth consensus orthologs (hitsFromSuppOrth, +% runSuppOrth). Their e-value, identity and alignment length are +% placeholders that pass the filters, so minSupport and the other filters of +% hitsFromSuppOrth decide which pairs are used. +% +% See also +% -------- +% getBlast, getDiamond, hitsFromSuppOrth, runSuppOrth, makeFakeBlastStructure hitGenes.oldGenes = []; % collect the old genes from the template model (organism) hitGenes.newGenes = []; % collect the new genes of the draft model (target organism) diff --git a/src/reconstruction/homology/hitsFromSuppOrth.m b/src/reconstruction/homology/hitsFromSuppOrth.m new file mode 100644 index 00000000..bc414d49 --- /dev/null +++ b/src/reconstruction/homology/hitsFromSuppOrth.m @@ -0,0 +1,340 @@ +function [blastStructure,suppOrthInfo]=hitsFromSuppOrth(source,organismID,varargin) +% hitsFromSuppOrth Read SuppOrth consensus orthologs into a blastStructure. +% +% SuppOrth (https://github.com/fma91/SuppOrth) runs several ortholog +% predictors (OrthoFinder, SonicParanoid2, Broccoli, and optionally an +% imported OMA run) on a set of proteomes and writes suppOrth.tsv: one row +% per cross-species protein pair, with the number of predictors that +% support it. This function turns that table into a blastStructure for +% getModelFromHomology. +% +% Parameters +% ---------- +% source : char or table +% path to suppOrth.tsv, or a MATLAB table with its columns. Protein +% ids are read as text, so ids such as "NA" or "0123" are kept. +% organismID : char +% SuppOrth species label of the organism to build a model for: the +% FASTA file name without extension, or the --labels value. It becomes +% fromId/toId unless speciesToModel maps it to another id. +% +% Name-Value Arguments +% -------------------- +% speciesToModel : cell or containers.Map +% SuppOrth species label to model id, as an N-by-2 cell array or a +% containers.Map. A label mapped to '' is left out. When given, every +% label in the table must be included. When empty, labels are used as +% model ids (default []). +% minSupport : double +% minimum number of predictors that found the pair (Lv_support). +% SuppOrth has already filtered the table with its own --supportLv and +% --identity_filter; this filter is applied in addition (default 1). +% requireTools : cell +% predictors that must all have found the pair, by column name +% ('orthofinder', 'sonicparanoid', 'broccoli', 'oma') (default {}). +% minIdentity : double +% minimum SuppOrth identity. A pair without an alignment hit fails this +% filter (default [], no filter). +% maxEvalue : double +% maximum SuppOrth e-value. A pair without an alignment hit fails this +% filter (default [], no filter). +% proteinToGene : cell, containers.Map or char +% protein id to model gene id, as an N-by-2 cell array, a +% containers.Map, a JSON file with one object of strings, or a +% two-column tab-separated file (lines starting with # are skipped). +% Proteins missing from the map keep their id, and their number is +% reported in a warning. A gene pair takes the highest support over its +% protein pairs, the union of their predictors, the highest identity +% and bitscore, and the lowest e-value; these values can come from +% different protein pairs (default [], no mapping). +% +% Returns +% ------- +% blastStructure : struct +% one element per search direction between organismID and each other +% species, as from getBlast or getDiamond. Every pair is written in +% both directions, so strictness 1 and 2 of getModelFromHomology give +% the same result. evalue, identity, aligLen and ppos are placeholders +% (0, 100, 1000, 100) that pass the filters of getModelFromHomology; +% bitscore is SuppOrth's value (0 when it has none), which strictness 3 +% ranks by. +% suppOrthInfo : table +% one row per gene pair, with modelId, newGene, templateGene, support, +% supportedBy, identity, evalue and bitscore (SuppOrth's values). +% +% Notes +% ----- +% Only pairs between organismID and another species are kept. Species that +% are not templates in getModelFromHomology have to be left out with +% speciesToModel, because getModelFromHomology requires every id in the +% blastStructure to be the new organism or a template. +% +% Examples +% -------- +% blastStructure = hitsFromSuppOrth('out/suppOrth.tsv','klac',... +% 'minSupport',2,'proteinToGene','klac_genes.tsv'); +% +% See also +% -------- +% runSuppOrth, getModelFromHomology, getDiamond + +p=parseRAVENargs(varargin, {'speciesToModel',[]; 'minSupport',1; ... + 'requireTools',{}; 'minIdentity',[]; 'maxEvalue',[]; 'proteinToGene',[]}); +organismID=char(organismID); +requireTools=cellstr(p.requireTools); +if isempty(p.requireTools) + requireTools={}; +end + +T=readTable(source); +columns=T.Properties.VariableNames; + +labels=unique([T.Species1;T.Species2]); +if ~ismember(organismID,labels) + error('RAVEN:badInput','organismID "%s" is not a species in the table: %s',... + organismID,strjoin(labels,', ')); +end +if isempty(p.speciesToModel) + idOf=containers.Map(labels,labels); +else + idOf=toMap(p.speciesToModel); + unmapped=labels(~isKey(idOf,labels)); + if ~isempty(unmapped) + error('RAVEN:badInput',['speciesToModel has no entry for %s; map each '... + 'label to a model id, or to '''' to leave it out.'],strjoin(unmapped,', ')); + end + if isempty(idOf(organismID)) + error('RAVEN:badInput','speciesToModel maps organismID "%s" to ''''.',organismID); + end +end + +%The per-predictor 0/1 columns are those between SupportedBy and Identity +alignment={'Identity','e_val','Bitscore'}; +start=find(strcmp(columns,'SupportedBy'))+1; +stop=find(strcmp(columns,'Identity'))-1; +if isempty(stop) + stop=numel(columns); +end +toolColumns=setdiff(columns(start:stop),alignment,'stable'); +unknown=setdiff(requireTools,toolColumns,'stable'); +if ~isempty(unknown) + error('RAVEN:badInput','requireTools %s not in the table; its predictors are %s',... + strjoin(unknown,', '),strjoin(toolColumns,', ')); +end + +keep=T.Lv_support>=p.minSupport; +for i=1:numel(requireTools) + keep=keep & toNumber(T.(requireTools{i}))==1; +end +if ~isempty(p.minIdentity) + keep=keep & T.Identity>=p.minIdentity; %NaN fails +end +if ~isempty(p.maxEvalue) + keep=keep & T.e_val<=p.maxEvalue; %NaN fails +end +T=T(keep,:); + +%Orient every pair as (organism protein, template species, template protein) +first=strcmp(T.Species1,organismID) & ~strcmp(T.Species2,organismID); +second=strcmp(T.Species2,organismID) & ~strcmp(T.Species1,organismID); +newProt=[T.Protein1(first);T.Protein2(second)]; +label=[T.Species2(first);T.Species1(second)]; +template=[T.Protein2(first);T.Protein1(second)]; +support=[T.Lv_support(first);T.Lv_support(second)]; +supportedBy=[T.SupportedBy(first);T.SupportedBy(second)]; +identity=[T.Identity(first);T.Identity(second)]; +evalue=[T.e_val(first);T.e_val(second)]; +bitscore=[T.Bitscore(first);T.Bitscore(second)]; +modelId=values(idOf,label); +modelId=modelId(:); +I=~cellfun(@isempty,modelId); +[newProt,template,modelId,support,supportedBy,identity,evalue,bitscore]=... + deal(newProt(I),template(I),modelId(I),support(I),supportedBy(I),... + identity(I),evalue(I),bitscore(I)); + +if ~isempty(p.proteinToGene) + geneOf=toMap(p.proteinToGene); + proteins=unique([newProt;template]); + nMissing=sum(~isKey(geneOf,proteins)); + if nMissing>0 + warning('RAVEN:hitsFromSuppOrth:unmappedProteins',['%d of %d proteins are not '... + 'in proteinToGene and keep their protein id.'],nMissing,numel(proteins)); + end + newProt=mapIds(geneOf,newProt); + template=mapIds(geneOf,template); +end + +if isempty(newProt) + error('RAVEN:badInput',['No SuppOrth pairs between "%s" and a template species '... + 'remain after filtering.'],organismID); +end + +%One row per gene pair, sorted by model, new gene and template gene +key=strcat(modelId,char(31),newProt,char(31),template); +[key,firstRow,group]=unique(key); +info=table(); +info.modelId=modelId(firstRow); +info.newGene=newProt(firstRow); +info.templateGene=template(firstRow); +info.support=accumarray(group,support,[],@max); +rows=accumarray(group,(1:numel(group))',[],@(r) {r}); +info.supportedBy=cellfun(@(r) joinLabels(supportedBy(r)),rows,'UniformOutput',false); +info.identity=accumarray(group,identity,[],@max); +info.evalue=accumarray(group,evalue,[],@min); +info.bitscore=accumarray(group,bitscore,[],@max); +suppOrthInfo=info; + +newId=idOf(organismID); +score=info.bitscore; +score(isnan(score))=0; +models=unique(info.modelId); +blastStructure=[]; +for direction=1:2 + for i=1:numel(models) + r=strcmp(info.modelId,models{i}); + element=[]; + if direction==1 + element.fromId=models{i}; + element.toId=newId; + element.fromGenes=info.templateGene(r); + element.toGenes=info.newGene(r); + else + element.fromId=newId; + element.toId=models{i}; + element.fromGenes=info.newGene(r); + element.toGenes=info.templateGene(r); + end + n=sum(r); + element.evalue=zeros(n,1); + element.identity=100*ones(n,1); + element.aligLen=1000*ones(n,1); + element.bitscore=score(r); + element.ppos=100*ones(n,1); + blastStructure=[blastStructure element]; %#ok + end +end +end + +function T=readTable(source) +required={'Species1','Species2','Protein1','Protein2','Lv_support','SupportedBy'}; +text=[required(1:4),{'SupportedBy'}]; +if istable(source) + T=source; +else + source=char(source); + if ~isfile(source) + error('RAVEN:badInput','SuppOrth table "%s" does not exist.',source); + end + lines=splitlines(fileread(source)); + lines=lines(~cellfun(@isempty,lines)); + header=strsplit(lines{1},'\t','CollapseDelimiters',false); + fields=regexp(lines(2:end),'\t','split'); + width=cellfun(@numel,fields); + bad=find(width~=numel(header),1); + if ~isempty(bad) + error('RAVEN:badInput','%s: line %d has %d fields, the header has %d.',... + source,bad+1,width(bad),numel(header)); + end + fields=vertcat(fields{:}); + if isempty(fields) + fields=cell(0,numel(header)); + end + T=cell2table(fields,'VariableNames',header); +end +missing=setdiff(required,T.Properties.VariableNames,'stable'); +if ~isempty(missing) + error('RAVEN:badInput',['Not a suppOrth.tsv table (SuppOrth 0.4): missing columns %s. '... + 'suppOrth.genes.tsv is not supported; use proteinToGene instead.'],strjoin(missing,', ')); +end +%Protein ids such as "NA" or "0123" stay text +for i=1:numel(text) + T.(text{i})=cellstr(string(T.(text{i}))); +end +for c={'Identity','e_val','Bitscore'} + if ismember(c{1},T.Properties.VariableNames) + T.(c{1})=toNumber(T.(c{1})); + else + T.(c{1})=nan(height(T),1); + end +end +T.Lv_support=toNumber(T.Lv_support); +end + +function x=toNumber(x) +%Read numbers exactly; empty and non-numeric fields become NaN +if ~isnumeric(x) + x=str2double(x); +end +x=double(x(:)); +end + +function m=toMap(x) +if isa(x,'containers.Map') + m=x; +elseif iscell(x) + if size(x,2)~=2 + error('RAVEN:badInput','A cell array map needs two columns.'); + end + m=containers.Map(cellstr(string(x(:,1))),cellstr(string(x(:,2)))); +elseif ischar(x) || isstring(x) + m=readMapFile(char(x)); +else + error('RAVEN:badInput','A map must be a two-column cell array, a containers.Map or a file.'); +end +end + +function m=readMapFile(file) +[~,~,ext]=fileparts(file); +if any(strcmpi(ext,{'.pkl','.pickle'})) + error('RAVEN:badInput',['%s: pickle maps are not read; use a cell array, '... + 'a JSON file or a two-column tab-separated file.'],file); +end +if ~isfile(file) + error('RAVEN:badInput','Map file "%s" does not exist.',file); +end +text=fileread(file); +if strcmpi(ext,'.json') + %Keys are protein ids, which jsondecode would turn into field names + pairs=regexp(text,'"((?:[^"\\]|\\.)*)"\s*:\s*"((?:[^"\\]|\\.)*)"','tokens'); + pairs=vertcat(pairs{:}); + if isempty(pairs) + m=containers.Map('KeyType','char','ValueType','char'); + return + end + pairs=regexprep(pairs,'\\(["\\/])','$1'); + m=containers.Map(pairs(:,1),pairs(:,2)); + return +end +lines=splitlines(text); +lines=lines(~cellfun(@isempty,strtrim(lines)) & ~startsWith(lines,'#')); +fields=regexp(lines,'\t','split'); +short=find(cellfun(@numel,fields)<2,1); +if ~isempty(short) + error('RAVEN:badInput','%s: expected two tab-separated columns, got "%s"',file,lines{short}); +end +keys=strtrim(cellfun(@(f) f{1},fields,'UniformOutput',false)); +vals=strtrim(cellfun(@(f) f{2},fields,'UniformOutput',false)); +if isempty(keys) + m=containers.Map('KeyType','char','ValueType','char'); +else + m=containers.Map(keys,vals); +end +end + +function ids=mapIds(m,ids) +known=isKey(m,ids); +if any(known) + ids(known)=values(m,ids(known)); +end +end + +function joined=joinLabels(labels) +%Union of comma-separated predictor labels, in SuppOrth's F, S, B, O order +found=strtrim(split(strjoin(labels,','),',')); +found=unique(found(~cellfun(@isempty,found))); +order={'F','S','B','O'}; +[isKnown,rank]=ismember(found,order); +known=found(isKnown); +[~,o]=sort(rank(isKnown)); +joined=strjoin([known(o);sort(found(~isKnown))],','); +end diff --git a/src/reconstruction/homology/makeFakeBlastStructure.m b/src/reconstruction/homology/makeFakeBlastStructure.m index e7724cd9..734c70c5 100755 --- a/src/reconstruction/homology/makeFakeBlastStructure.m +++ b/src/reconstruction/homology/makeFakeBlastStructure.m @@ -34,7 +34,7 @@ % % See also % -------- -% getModelFromHomology, getBlast +% getModelFromHomology, getBlast, hitsFromSuppOrth if nargin<3 error('All three parameters should be set'); diff --git a/src/reconstruction/homology/runSuppOrth.m b/src/reconstruction/homology/runSuppOrth.m new file mode 100644 index 00000000..b4039a3e --- /dev/null +++ b/src/reconstruction/homology/runSuppOrth.m @@ -0,0 +1,279 @@ +function [blastStructure,suppOrthInfo]=runSuppOrth(organismID,fastaFile,modelIDs,refFastaFiles,outDir,varargin) +% runSuppOrth Run SuppOrth on an organism and template proteomes. +% +% Runs the suppOrth command (https://github.com/fma91/SuppOrth) and reads +% its consensus orthologs with hitsFromSuppOrth. +% +% Parameters +% ---------- +% organismID : char +% the id of the organism of interest, as in getModelFromHomology. +% fastaFile : char +% a FASTA file (optionally gzipped) with the proteins of the organism. +% modelIDs : cell +% the template model ids, as "model.id" in getModelFromHomology. +% refFastaFiles : cell +% the proteome of each template, in the order of modelIDs, with FASTA +% ids equal to the gene ids of that model (or mapped to them with +% proteinToGene). +% outDir : char +% folder for SuppOrth's output. +% +% Name-Value Arguments +% -------------------- +% tools : char +% comma-separated predictors, or 'all' for orthofinder, sonicparanoid +% and broccoli (default 'all'). +% supportLv : double +% SuppOrth's --supportLv: a pair is kept if at least this many +% predictors found it, or if its identity is above identityFilter +% (default 3, or the number of tools when fewer are selected). +% identityFilter : double +% SuppOrth's --identity_filter (default 80). +% alignment : char +% SuppOrth's --alignment (default 'diamond'). +% threads : double +% number of threads (default: the cores assigned to MATLAB). +% extraArgs : cell +% further arguments for "suppOrth run", for example {'--resume'} or +% {'--oma','path/to/Output'} (default {}). +% suppOrth : char +% the suppOrth command. If empty, the RAVEN_SUPPORTH environment +% variable is used, and then suppOrth on the PATH (default ''). +% +% The name-value arguments of hitsFromSuppOrth (minSupport, requireTools, +% minIdentity, maxEvalue, proteinToGene, speciesToModel) are passed on. +% +% Returns +% ------- +% blastStructure : struct +% as from hitsFromSuppOrth, for getModelFromHomology. +% suppOrthInfo : table +% SuppOrth's values per gene pair, as from hitsFromSuppOrth. +% +% Notes +% ----- +% SuppOrth requires protein ids to be unique over all proteomes. When two +% FASTA files share an id (for example two templates of the same species), +% every id is given a "

    + getModelFromHomology, getDiamond, runSuppOrth

    CROSS-REFERENCE INFORMATION ^

    @@ -140,7 +140,7 @@

    SOURCE CODE ^% 0048 % See also 0049 % -------- -0050 % getModelFromHomology, getDiamond +0050 % getModelFromHomology, getDiamond, runSuppOrth 0051 0052 p=parseRAVENargs(varargin, {'developMode',false; 'hideVerbose',false}); 0053 developMode=p.developMode; diff --git a/docs/html/reconstruction/homology/getDiamond.html b/docs/html/reconstruction/homology/getDiamond.html index 24f24400..c186ab0e 100644 --- a/docs/html/reconstruction/homology/getDiamond.html +++ b/docs/html/reconstruction/homology/getDiamond.html @@ -76,7 +76,7 @@

    DESCRIPTION ^getModelFromHomology, getBlast

    + getModelFromHomology, getBlast, runSuppOrth

    CROSS-REFERENCE INFORMATION ^

    @@ -142,7 +142,7 @@

    SOURCE CODE ^% 0050 % See also 0051 % -------- -0052 % getModelFromHomology, getBlast +0052 % getModelFromHomology, getBlast, runSuppOrth 0053 0054 p=parseRAVENargs(varargin, {'developMode',false; 'hideVerbose',false}); 0055 developMode=p.developMode; diff --git a/docs/html/reconstruction/homology/getModelFromHomology.html b/docs/html/reconstruction/homology/getModelFromHomology.html index 185ca131..d0a4b1da 100644 --- a/docs/html/reconstruction/homology/getModelFromHomology.html +++ b/docs/html/reconstruction/homology/getModelFromHomology.html @@ -38,7 +38,8 @@

    DESCRIPTION ^DESCRIPTION ^getBlast, getDiamond, hitsFromSuppOrth, runSuppOrth, makeFakeBlastStructure

    CROSS-REFERENCE INFORMATION ^

    @@ -163,836 +173,846 @@

    SOURCE CODE ^% a cell array of model structures to build the model from. These 0012 % models must be sorted by importance in decreasing order. 0013 % blastStructure : struct -0014 % a blastStructure as produced by getBlast or makeFakeBlastStructure. -0015 % getModelFor : char -0016 % a three-four letter abbreviation of the organism to build a model -0017 % for. Must have BLASTP hits in both directions to the organisms in -0018 % "models". -0019 % -0020 % Name-Value Arguments -0021 % -------------------- -0022 % preferredOrder : cell -0023 % the order in which reactions should be added from the models. If not -0024 % supplied, reactions will be included from all models, otherwise one -0025 % gene will only result in reactions from one model (default {}). -0026 % strictness : double -0027 % integer that specifies which reactions should be included (default 1): -0028 % -0029 % - 1 : map new genes to old for all pairs which have acceptable BLASTP -0030 % results in both directions. -0031 % - 2 : map new genes to old for all pairs which have acceptable BLASTP -0032 % results in the correspondent direction (mapping can be done in the -0033 % opposite direction, see mapNewGenesToOld below). -0034 % - 3 : check all BLASTP results and retain only the best hit (by -0035 % bitscore) for each gene pair in each direction separately, then map -0036 % new genes to old for all pairs which have acceptable BLASTP results -0037 % in both directions. -0038 % onlyGenesInModels : logical -0039 % consider BLASTP results only for genes that exist in the models. This -0040 % tends to import a larger fraction from the existing models but may -0041 % give less reliable results. Has effect only if strictness=3 -0042 % (default false). -0043 % maxE : double -0044 % only look at genes with E-values <= this value (default 10^-30). -0045 % minLen : double -0046 % only look at genes with alignment length >= this value (default 100). -0047 % Measured against KEGG and OMA orthology across four organisms: any -0048 % value at or below 150 performs the same, while higher values discard -0049 % real orthologs without reducing the number of wrong ones. -0050 % minIde : double -0051 % only look at genes with identity >= this value (default 40 (%)). -0052 % The filter that decides the outcome, and 40 is the measured optimum -0053 % once a wrongly transferred reaction is treated as worse than a missing -0054 % one. maxE, by contrast, changes nothing between 10^-4 and 10^-50. -0055 % mapNewGenesToOld : logical -0056 % determines how to match genes if not looking at only 1-1 orthologs. -0057 % Either map the new genes to the old or old genes to new. The default -0058 % is to map the new genes (default true). -0059 % complexPolicy : char -0060 % what happens to an "and"-linked subunit without an ortholog: -0061 % "flag" keeps the reaction with the subunit renamed -0062 % OLD_MODELID_geneName, for a curator to review; "keep" drops just that -0063 % subunit; "drop" drops the whole reaction (default "flag"). -0064 % keepGeneFree : logical -0065 % also transfer the template reactions that have no grRule (spontaneous, -0066 % transport, exchange, artificial). By default they are dropped, because -0067 % nothing supports them in the new organism. Set it when the template is -0068 % a curated reference whose gene-free reactions belong in every derived -0069 % model. With several templates, a gene-free reaction comes in once per -0070 % template (default false). -0071 % preserveNotes : logical -0072 % keep each transferred reaction's rxnNotes, rxnReferences and -0073 % rxnConfidenceScores from the template. By default the notes say the -0074 % reaction was included by getModelFromHomology and the confidence score -0075 % is 2 (default false). -0076 % -0077 % Returns -0078 % ------- -0079 % draftModel : struct -0080 % a model structure for the new organism. -0081 % hitGenes : struct -0082 % collect the old and new genes. -0083 % -0084 % Examples -0085 % -------- -0086 % draftModel = getModelFromHomology(models, blastStructure, getModelFor); -0087 % -0088 % Notes -0089 % ----- -0090 % The models in the "models" structure should have named the metabolites in -0091 % the same manner, have their reversible reactions in the same direction -0092 % (run sortModel), and use the same compartment names. To avoid keeping -0093 % unneccesary old genes, the models should not have "or"-relations in their -0094 % grRules (use expandModel). -0095 % -0096 % The gene ids of the new organism in blastStructure become its gene ids in -0097 % the draft model, so they cannot contain whitespace, parentheses, "|" or -0098 % "&". FASTA headers from UniProt, such as "sp|P33284|HXK_KLULA", should be -0099 % reduced to the accession first. -0100 % -0101 % The resulting draft model contains only reactions associated with -0102 % orthologous genes, plus the gene-free ones with keepGeneFree. With -0103 % complexPolicy "flag", the old (original) genes involved in "and" relations -0104 % in grRules without any orthologs are still included in the draft model as -0105 % OLD_MODELID_geneName. A gene that several template genes map to appears -0106 % once in a grRule. The draft does not keep the version, date or -0107 % descriptive annotation of the templates. -0108 % -0109 % "to" and "from" means relative to the new organism. -0110 -0111 hitGenes.oldGenes = []; % collect the old genes from the template model (organism) -0112 hitGenes.newGenes = []; % collect the new genes of the draft model (target organism) -0113 -0114 getModelFor=char(getModelFor); -0115 -0116 p=parseRAVENargs(varargin, {'preferredOrder',[]; 'strictness',1; ... -0117 'onlyGenesInModels',false; 'maxE',10^-30; 'minLen',100; 'minIde',40; ... -0118 'mapNewGenesToOld',true; 'complexPolicy','flag'; 'keepGeneFree',false; ... -0119 'preserveNotes',false}); -0120 complexPolicy=lower(char(p.complexPolicy)); -0121 if ~ismember(complexPolicy,{'flag','keep','drop'}) -0122 error('complexPolicy must be "flag", "keep" or "drop", not "%s"',complexPolicy); -0123 end -0124 keepGeneFree=p.keepGeneFree; -0125 preserveNotes=p.preserveNotes; -0126 preferredOrder=p.preferredOrder; -0127 if isempty(preferredOrder) -0128 preferredOrder=[]; -0129 else -0130 preferredOrder=convertCharArray(preferredOrder); -0131 [row,col]=size(preferredOrder); -0132 if col>row -0133 preferredOrder=transpose(preferredOrder); -0134 end -0135 end -0136 switch p.strictness -0137 case 2 -0138 bidirectional=false; bestHitsOnly=false; -0139 case 3 -0140 bidirectional=true; bestHitsOnly=true; -0141 otherwise -0142 bidirectional=true; bestHitsOnly=false; -0143 end -0144 onlyGenesInModels=p.onlyGenesInModels; -0145 maxE=p.maxE; -0146 minLen=p.minLen; -0147 minIde=p.minIde; -0148 mapNewGenesToOld=p.mapNewGenesToOld; -0149 -0150 if isfield(models,'S') -0151 models={models}; -0152 end -0153 -0154 %Check that all the information is in the blast structure -0155 modelNames=cell(numel(models),1); -0156 for i=1:numel(models) -0157 modelNames{i}=models{i}.id; -0158 %Gene short names, geneMiriams and proteins are often different -0159 %between species, safer not to include them -0160 if isfield(models{i},'geneShortNames') -0161 models{i}=rmfield(models{i},'geneShortNames'); -0162 end -0163 if isfield(models{i},'geneMiriams') -0164 models{i}=rmfield(models{i},'geneMiriams'); -0165 end -0166 if isfield(models{i},'proteins') -0167 models{i}=rmfield(models{i},'proteins'); -0168 end -0169 %The geneFrom field also loses meaning if the genes are replaced by -0170 %orthologs -0171 if isfield(models{i},'geneFrom') -0172 models{i}=rmfield(models{i},'geneFrom'); -0173 end -0174 end -0175 -0176 %Check that genes do not begin with ( or end with ), as this makes problematic grRules -0177 for i=1:numel(blastStructure) -0178 problemGenes = startsWith(blastStructure(i).fromGenes,'(') | endsWith(blastStructure(i).fromGenes,')'); -0179 if any(problemGenes) -0180 error(['One or multiple gene identifiers from ' blastStructure(i).fromId ... -0181 ' starts with ''('' and/or ends with '')'', which is not allowed']) -0182 end -0183 end -0184 -0185 %Assume for now that all information is there and that it is correct. This -0186 %is important to fix since no further checks are being made! -0187 -0188 %Check whether provided fasta files use the same gene identifiers as -0189 %provided template models -0190 for i=1:numel(blastStructure) -0191 if ~strcmp(blastStructure(i).fromId,getModelFor) -0192 j=strcmpi(blastStructure(i).fromId,modelNames); -0193 if j==0 -0194 error(['While the blastStructure contains sequences from '... -0195 'organismID "%s" (as\nprovided in getBlast), none of '... -0196 'template models have this id (as model.id)'],... -0197 string(blastStructure(i).fromId)); -0198 end -0199 k=sum(ismember(blastStructure(i).fromGenes,models{j}.genes)); -0200 if k<(numel(models{j}.genes)*0.05) -0201 error(['Less than 5%% of the genes in the template model '... -0202 'with model.id "%s"\ncan be found in the blastStructure. '... -0203 'Ensure that the protein FASTA\nused in getBlast and '... -0204 'the template model used in getModelFromHomology\nuse '... -0205 'the same style of gene identifiers'],models{j}.id) -0206 end -0207 end -0208 end -0209 -0210 %Standardize grRules of template models -0211 for i=1:length(models) -0212 fprintf('\nStandardizing grRules of template model with ID "%s" ...',models{i}.id); -0213 [models{i}.grRules,models{i}.rxnGeneMat]=standardizeGrRules(models{i},false); -0214 end -0215 fprintf(' done\n'); -0216 -0217 %Remove all gene matches that are below the cutoffs -0218 for i=1:numel(blastStructure) -0219 indexes=blastStructure(i).evalue<maxE & blastStructure(i).aligLen>=minLen & blastStructure(i).identity>=minIde; %Do it in this direction to lose NaNs -0220 blastStructure(i).fromGenes(~indexes)=[]; -0221 blastStructure(i).toGenes(~indexes)=[]; -0222 blastStructure(i).evalue(~indexes)=[]; -0223 blastStructure(i).identity(~indexes)=[]; -0224 blastStructure(i).aligLen(~indexes)=[]; -0225 blastStructure(i).bitscore(~indexes)=[]; -0226 blastStructure(i).ppos(~indexes)=[]; -0227 end -0228 -0229 %Remove all reactions from the models that have no genes encoding for them, -0230 %unless keepGeneFree. Also remove all genes that encode for no reactions. -0231 %There should not be any but there might be mistakes -0232 geneFreeRxns=cell(numel(models),1); -0233 geneFreeRxns(:)={{}}; -0234 for i=1:numel(models) -0235 [hasGenes, ~]=find(models{i}.rxnGeneMat); -0236 hasNoGenes=1:numel(models{i}.rxns); -0237 hasNoGenes(hasGenes)=[]; -0238 if keepGeneFree -0239 geneFreeRxns{i}=models{i}.rxns(hasNoGenes); -0240 else -0241 models{i}=removeReactions(models{i},hasNoGenes,true,true); -0242 end -0243 end -0244 -0245 %Create a structure that contains all genes used in the blasts in any -0246 %direction for each of the models in "models" and for the new organism. The -0247 %first cell is for the new organism and then according to the preferred -0248 %order. If no such order is supplied, then according to the order in -0249 %"models" -0250 allGenes=cell(numel(models)+1,1); -0251 if isempty(preferredOrder) -0252 useOrder=modelNames; -0253 else -0254 useOrder=preferredOrder; -0255 end -0256 -0257 %Get the corresponding indexes for those models in the "models" structure -0258 useOrderIndexes=zeros(numel(models),1); -0259 for i=1:numel(models) -0260 [~, index]=ismember(models{i}.id,useOrder); -0261 useOrderIndexes(index)=i; -0262 end -0263 -0264 %Remove all genes from the blast structure that have no genes in the models -0265 if onlyGenesInModels==true -0266 modelGenes={}; -0267 for i=1:numel(models) -0268 modelGenes=[modelGenes;models{i}.genes]; -0269 end -0270 for i=1:numel(blastStructure) -0271 %Check to see if it should match the toId or fromId -0272 if strcmpi(blastStructure(i).fromId,getModelFor) -0273 I=ismember(blastStructure(i).toGenes,modelGenes); -0274 else -0275 I=ismember(blastStructure(i).fromGenes,modelGenes); -0276 end -0277 blastStructure(i).fromGenes(~I)=[]; -0278 blastStructure(i).toGenes(~I)=[]; -0279 blastStructure(i).evalue(~I)=[]; -0280 blastStructure(i).identity(~I)=[]; -0281 blastStructure(i).aligLen(~I)=[]; -0282 blastStructure(i).bitscore(~I)=[]; -0283 blastStructure(i).ppos(~I)=[]; -0284 -0285 %Check that no matching in blastStructure is empty. This happens if -0286 %no genes in the models are present in the corresponding sheet -0287 if isempty(blastStructure(i).fromGenes) -0288 EM=['No genes in matching from ' blastStructure(i).fromId ' to ' blastStructure(i).toId ' are present in the corresponding model']; -0289 error('RAVEN:badInput', '%s', EM); -0290 end -0291 end -0292 end -0293 -0294 %If only best orthologs are to be used then all other measurements are -0295 %deleted from the blastStructure. All code after this stays the same. This -0296 %means that preferred order can still matter. The best ortholog is selected -0297 %by bitscore, which unlike E-value does not shift when the search database -0298 %changes size. -0299 if bestHitsOnly -0300 for i=1:numel(blastStructure) -0301 keep=false(numel(blastStructure(i).toGenes),1); -0302 [allFromGenes, ~, I]=unique(blastStructure(i).fromGenes); +0014 % a blastStructure as produced by getBlast, getDiamond, +0015 % hitsFromSuppOrth, runSuppOrth or makeFakeBlastStructure. +0016 % getModelFor : char +0017 % a three-four letter abbreviation of the organism to build a model +0018 % for. Must have BLASTP hits in both directions to the organisms in +0019 % "models". +0020 % +0021 % Name-Value Arguments +0022 % -------------------- +0023 % preferredOrder : cell +0024 % the order in which reactions should be added from the models. If not +0025 % supplied, reactions will be included from all models, otherwise one +0026 % gene will only result in reactions from one model (default {}). +0027 % strictness : double +0028 % integer that specifies which reactions should be included (default 1): +0029 % +0030 % - 1 : map new genes to old for all pairs which have acceptable BLASTP +0031 % results in both directions. +0032 % - 2 : map new genes to old for all pairs which have acceptable BLASTP +0033 % results in the correspondent direction (mapping can be done in the +0034 % opposite direction, see mapNewGenesToOld below). +0035 % - 3 : check all BLASTP results and retain only the best hit (by +0036 % bitscore) for each gene pair in each direction separately, then map +0037 % new genes to old for all pairs which have acceptable BLASTP results +0038 % in both directions. +0039 % onlyGenesInModels : logical +0040 % consider BLASTP results only for genes that exist in the models. This +0041 % tends to import a larger fraction from the existing models but may +0042 % give less reliable results. Has effect only if strictness=3 +0043 % (default false). +0044 % maxE : double +0045 % only look at genes with E-values <= this value (default 10^-30). +0046 % minLen : double +0047 % only look at genes with alignment length >= this value (default 100). +0048 % Measured against KEGG and OMA orthology across four organisms: any +0049 % value at or below 150 performs the same, while higher values discard +0050 % real orthologs without reducing the number of wrong ones. +0051 % minIde : double +0052 % only look at genes with identity >= this value (default 40 (%)). +0053 % The filter that decides the outcome, and 40 is the measured optimum +0054 % once a wrongly transferred reaction is treated as worse than a missing +0055 % one. maxE, by contrast, changes nothing between 10^-4 and 10^-50. +0056 % mapNewGenesToOld : logical +0057 % determines how to match genes if not looking at only 1-1 orthologs. +0058 % Either map the new genes to the old or old genes to new. The default +0059 % is to map the new genes (default true). +0060 % complexPolicy : char +0061 % what happens to an "and"-linked subunit without an ortholog: +0062 % "flag" keeps the reaction with the subunit renamed +0063 % OLD_MODELID_geneName, for a curator to review; "keep" drops just that +0064 % subunit; "drop" drops the whole reaction (default "flag"). +0065 % keepGeneFree : logical +0066 % also transfer the template reactions that have no grRule (spontaneous, +0067 % transport, exchange, artificial). By default they are dropped, because +0068 % nothing supports them in the new organism. Set it when the template is +0069 % a curated reference whose gene-free reactions belong in every derived +0070 % model. With several templates, a gene-free reaction comes in once per +0071 % template (default false). +0072 % preserveNotes : logical +0073 % keep each transferred reaction's rxnNotes, rxnReferences and +0074 % rxnConfidenceScores from the template. By default the notes say the +0075 % reaction was included by getModelFromHomology and the confidence score +0076 % is 2 (default false). +0077 % +0078 % Returns +0079 % ------- +0080 % draftModel : struct +0081 % a model structure for the new organism. +0082 % hitGenes : struct +0083 % collect the old and new genes. +0084 % +0085 % Examples +0086 % -------- +0087 % draftModel = getModelFromHomology(models, blastStructure, getModelFor); +0088 % +0089 % Notes +0090 % ----- +0091 % The models in the "models" structure should have named the metabolites in +0092 % the same manner, have their reversible reactions in the same direction +0093 % (run sortModel), and use the same compartment names. To avoid keeping +0094 % unneccesary old genes, the models should not have "or"-relations in their +0095 % grRules (use expandModel). +0096 % +0097 % The gene ids of the new organism in blastStructure become its gene ids in +0098 % the draft model, so they cannot contain whitespace, parentheses, "|" or +0099 % "&". FASTA headers from UniProt, such as "sp|P33284|HXK_KLULA", should be +0100 % reduced to the accession first. +0101 % +0102 % The resulting draft model contains only reactions associated with +0103 % orthologous genes, plus the gene-free ones with keepGeneFree. With +0104 % complexPolicy "flag", the old (original) genes involved in "and" relations +0105 % in grRules without any orthologs are still included in the draft model as +0106 % OLD_MODELID_geneName. A gene that several template genes map to appears +0107 % once in a grRule. The draft does not keep the version, date or +0108 % descriptive annotation of the templates. +0109 % +0110 % "to" and "from" means relative to the new organism. +0111 % +0112 % The hits can also come from SuppOrth consensus orthologs (hitsFromSuppOrth, +0113 % runSuppOrth). Their e-value, identity and alignment length are +0114 % placeholders that pass the filters, so minSupport and the other filters of +0115 % hitsFromSuppOrth decide which pairs are used. +0116 % +0117 % See also +0118 % -------- +0119 % getBlast, getDiamond, hitsFromSuppOrth, runSuppOrth, makeFakeBlastStructure +0120 +0121 hitGenes.oldGenes = []; % collect the old genes from the template model (organism) +0122 hitGenes.newGenes = []; % collect the new genes of the draft model (target organism) +0123 +0124 getModelFor=char(getModelFor); +0125 +0126 p=parseRAVENargs(varargin, {'preferredOrder',[]; 'strictness',1; ... +0127 'onlyGenesInModels',false; 'maxE',10^-30; 'minLen',100; 'minIde',40; ... +0128 'mapNewGenesToOld',true; 'complexPolicy','flag'; 'keepGeneFree',false; ... +0129 'preserveNotes',false}); +0130 complexPolicy=lower(char(p.complexPolicy)); +0131 if ~ismember(complexPolicy,{'flag','keep','drop'}) +0132 error('complexPolicy must be "flag", "keep" or "drop", not "%s"',complexPolicy); +0133 end +0134 keepGeneFree=p.keepGeneFree; +0135 preserveNotes=p.preserveNotes; +0136 preferredOrder=p.preferredOrder; +0137 if isempty(preferredOrder) +0138 preferredOrder=[]; +0139 else +0140 preferredOrder=convertCharArray(preferredOrder); +0141 [row,col]=size(preferredOrder); +0142 if col>row +0143 preferredOrder=transpose(preferredOrder); +0144 end +0145 end +0146 switch p.strictness +0147 case 2 +0148 bidirectional=false; bestHitsOnly=false; +0149 case 3 +0150 bidirectional=true; bestHitsOnly=true; +0151 otherwise +0152 bidirectional=true; bestHitsOnly=false; +0153 end +0154 onlyGenesInModels=p.onlyGenesInModels; +0155 maxE=p.maxE; +0156 minLen=p.minLen; +0157 minIde=p.minIde; +0158 mapNewGenesToOld=p.mapNewGenesToOld; +0159 +0160 if isfield(models,'S') +0161 models={models}; +0162 end +0163 +0164 %Check that all the information is in the blast structure +0165 modelNames=cell(numel(models),1); +0166 for i=1:numel(models) +0167 modelNames{i}=models{i}.id; +0168 %Gene short names, geneMiriams and proteins are often different +0169 %between species, safer not to include them +0170 if isfield(models{i},'geneShortNames') +0171 models{i}=rmfield(models{i},'geneShortNames'); +0172 end +0173 if isfield(models{i},'geneMiriams') +0174 models{i}=rmfield(models{i},'geneMiriams'); +0175 end +0176 if isfield(models{i},'proteins') +0177 models{i}=rmfield(models{i},'proteins'); +0178 end +0179 %The geneFrom field also loses meaning if the genes are replaced by +0180 %orthologs +0181 if isfield(models{i},'geneFrom') +0182 models{i}=rmfield(models{i},'geneFrom'); +0183 end +0184 end +0185 +0186 %Check that genes do not begin with ( or end with ), as this makes problematic grRules +0187 for i=1:numel(blastStructure) +0188 problemGenes = startsWith(blastStructure(i).fromGenes,'(') | endsWith(blastStructure(i).fromGenes,')'); +0189 if any(problemGenes) +0190 error(['One or multiple gene identifiers from ' blastStructure(i).fromId ... +0191 ' starts with ''('' and/or ends with '')'', which is not allowed']) +0192 end +0193 end +0194 +0195 %Assume for now that all information is there and that it is correct. This +0196 %is important to fix since no further checks are being made! +0197 +0198 %Check whether provided fasta files use the same gene identifiers as +0199 %provided template models +0200 for i=1:numel(blastStructure) +0201 if ~strcmp(blastStructure(i).fromId,getModelFor) +0202 j=strcmpi(blastStructure(i).fromId,modelNames); +0203 if j==0 +0204 error(['While the blastStructure contains sequences from '... +0205 'organismID "%s" (as\nprovided in getBlast), none of '... +0206 'template models have this id (as model.id)'],... +0207 string(blastStructure(i).fromId)); +0208 end +0209 k=sum(ismember(blastStructure(i).fromGenes,models{j}.genes)); +0210 if k<(numel(models{j}.genes)*0.05) +0211 error(['Less than 5%% of the genes in the template model '... +0212 'with model.id "%s"\ncan be found in the blastStructure. '... +0213 'Ensure that the protein FASTA\nused in getBlast and '... +0214 'the template model used in getModelFromHomology\nuse '... +0215 'the same style of gene identifiers'],models{j}.id) +0216 end +0217 end +0218 end +0219 +0220 %Standardize grRules of template models +0221 for i=1:length(models) +0222 fprintf('\nStandardizing grRules of template model with ID "%s" ...',models{i}.id); +0223 [models{i}.grRules,models{i}.rxnGeneMat]=standardizeGrRules(models{i},false); +0224 end +0225 fprintf(' done\n'); +0226 +0227 %Remove all gene matches that are below the cutoffs +0228 for i=1:numel(blastStructure) +0229 indexes=blastStructure(i).evalue<maxE & blastStructure(i).aligLen>=minLen & blastStructure(i).identity>=minIde; %Do it in this direction to lose NaNs +0230 blastStructure(i).fromGenes(~indexes)=[]; +0231 blastStructure(i).toGenes(~indexes)=[]; +0232 blastStructure(i).evalue(~indexes)=[]; +0233 blastStructure(i).identity(~indexes)=[]; +0234 blastStructure(i).aligLen(~indexes)=[]; +0235 blastStructure(i).bitscore(~indexes)=[]; +0236 blastStructure(i).ppos(~indexes)=[]; +0237 end +0238 +0239 %Remove all reactions from the models that have no genes encoding for them, +0240 %unless keepGeneFree. Also remove all genes that encode for no reactions. +0241 %There should not be any but there might be mistakes +0242 geneFreeRxns=cell(numel(models),1); +0243 geneFreeRxns(:)={{}}; +0244 for i=1:numel(models) +0245 [hasGenes, ~]=find(models{i}.rxnGeneMat); +0246 hasNoGenes=1:numel(models{i}.rxns); +0247 hasNoGenes(hasGenes)=[]; +0248 if keepGeneFree +0249 geneFreeRxns{i}=models{i}.rxns(hasNoGenes); +0250 else +0251 models{i}=removeReactions(models{i},hasNoGenes,true,true); +0252 end +0253 end +0254 +0255 %Create a structure that contains all genes used in the blasts in any +0256 %direction for each of the models in "models" and for the new organism. The +0257 %first cell is for the new organism and then according to the preferred +0258 %order. If no such order is supplied, then according to the order in +0259 %"models" +0260 allGenes=cell(numel(models)+1,1); +0261 if isempty(preferredOrder) +0262 useOrder=modelNames; +0263 else +0264 useOrder=preferredOrder; +0265 end +0266 +0267 %Get the corresponding indexes for those models in the "models" structure +0268 useOrderIndexes=zeros(numel(models),1); +0269 for i=1:numel(models) +0270 [~, index]=ismember(models{i}.id,useOrder); +0271 useOrderIndexes(index)=i; +0272 end +0273 +0274 %Remove all genes from the blast structure that have no genes in the models +0275 if onlyGenesInModels==true +0276 modelGenes={}; +0277 for i=1:numel(models) +0278 modelGenes=[modelGenes;models{i}.genes]; +0279 end +0280 for i=1:numel(blastStructure) +0281 %Check to see if it should match the toId or fromId +0282 if strcmpi(blastStructure(i).fromId,getModelFor) +0283 I=ismember(blastStructure(i).toGenes,modelGenes); +0284 else +0285 I=ismember(blastStructure(i).fromGenes,modelGenes); +0286 end +0287 blastStructure(i).fromGenes(~I)=[]; +0288 blastStructure(i).toGenes(~I)=[]; +0289 blastStructure(i).evalue(~I)=[]; +0290 blastStructure(i).identity(~I)=[]; +0291 blastStructure(i).aligLen(~I)=[]; +0292 blastStructure(i).bitscore(~I)=[]; +0293 blastStructure(i).ppos(~I)=[]; +0294 +0295 %Check that no matching in blastStructure is empty. This happens if +0296 %no genes in the models are present in the corresponding sheet +0297 if isempty(blastStructure(i).fromGenes) +0298 EM=['No genes in matching from ' blastStructure(i).fromId ' to ' blastStructure(i).toId ' are present in the corresponding model']; +0299 error('RAVEN:badInput', '%s', EM); +0300 end +0301 end +0302 end 0303 -0304 %It would be nice to get rid of this loop -0305 for j=1:numel(allFromGenes) -0306 allMatches=find(I==j); -0307 bestMatches=allMatches(blastStructure(i).bitscore(allMatches)==max(blastStructure(i).bitscore(allMatches))); -0308 -0309 %Keep the best matches -0310 keep(bestMatches)=true; -0311 end -0312 -0313 %Delete all matches that were not best matches -0314 blastStructure(i).fromGenes(~keep)=[]; -0315 blastStructure(i).toGenes(~keep)=[]; -0316 blastStructure(i).evalue(~keep)=[]; -0317 blastStructure(i).identity(~keep)=[]; -0318 blastStructure(i).aligLen(~keep)=[]; -0319 blastStructure(i).bitscore(~keep)=[]; -0320 blastStructure(i).ppos(~keep)=[]; -0321 end -0322 end -0323 -0324 useOrder=[{getModelFor};useOrder]; -0325 -0326 for i=1:numel(blastStructure) -0327 [~, toIndex]=ismember(blastStructure(i).toId,useOrder); -0328 [~, fromIndex]=ismember(blastStructure(i).fromId,useOrder); -0329 -0330 %Add all genes to the corresponding list in allGenes -0331 allGenes{toIndex}=[allGenes{toIndex};blastStructure(i).toGenes]; -0332 allGenes{fromIndex}=[allGenes{fromIndex};blastStructure(i).fromGenes]; -0333 end -0334 -0335 %Keep only the unique gene names -0336 maxOtherGeneNr=0; %Determines the dimension of the connectivity matrixes -0337 for i=1:numel(allGenes) -0338 allGenes{i}=unique(allGenes{i}); -0339 if i>1 -0340 if numel(allGenes{i})>maxOtherGeneNr -0341 maxOtherGeneNr=numel(allGenes{i}); -0342 end -0343 end -0344 end -0345 -0346 %Generate a cell array of matrixes that describes how the genes in the new -0347 %organism map to the models. Each cell matches to the corresponding model -0348 %in useOrder (starting at 2 of course). First dimension is gene in new -0349 %organism, second which gene it is in the other organism. The second matrix -0350 %describes how they map back. -0351 -0352 %As it is now, a significant match is indicated by a 1. This could be -0353 %expanded to contain some kind of significance level. The first dimension -0354 %is still the genes in the new model. +0304 %If only best orthologs are to be used then all other measurements are +0305 %deleted from the blastStructure. All code after this stays the same. This +0306 %means that preferred order can still matter. The best ortholog is selected +0307 %by bitscore, which unlike E-value does not shift when the search database +0308 %changes size. +0309 if bestHitsOnly +0310 for i=1:numel(blastStructure) +0311 keep=false(numel(blastStructure(i).toGenes),1); +0312 [allFromGenes, ~, I]=unique(blastStructure(i).fromGenes); +0313 +0314 %It would be nice to get rid of this loop +0315 for j=1:numel(allFromGenes) +0316 allMatches=find(I==j); +0317 bestMatches=allMatches(blastStructure(i).bitscore(allMatches)==max(blastStructure(i).bitscore(allMatches))); +0318 +0319 %Keep the best matches +0320 keep(bestMatches)=true; +0321 end +0322 +0323 %Delete all matches that were not best matches +0324 blastStructure(i).fromGenes(~keep)=[]; +0325 blastStructure(i).toGenes(~keep)=[]; +0326 blastStructure(i).evalue(~keep)=[]; +0327 blastStructure(i).identity(~keep)=[]; +0328 blastStructure(i).aligLen(~keep)=[]; +0329 blastStructure(i).bitscore(~keep)=[]; +0330 blastStructure(i).ppos(~keep)=[]; +0331 end +0332 end +0333 +0334 useOrder=[{getModelFor};useOrder]; +0335 +0336 for i=1:numel(blastStructure) +0337 [~, toIndex]=ismember(blastStructure(i).toId,useOrder); +0338 [~, fromIndex]=ismember(blastStructure(i).fromId,useOrder); +0339 +0340 %Add all genes to the corresponding list in allGenes +0341 allGenes{toIndex}=[allGenes{toIndex};blastStructure(i).toGenes]; +0342 allGenes{fromIndex}=[allGenes{fromIndex};blastStructure(i).fromGenes]; +0343 end +0344 +0345 %Keep only the unique gene names +0346 maxOtherGeneNr=0; %Determines the dimension of the connectivity matrixes +0347 for i=1:numel(allGenes) +0348 allGenes{i}=unique(allGenes{i}); +0349 if i>1 +0350 if numel(allGenes{i})>maxOtherGeneNr +0351 maxOtherGeneNr=numel(allGenes{i}); +0352 end +0353 end +0354 end 0355 -0356 allTo=cell(numel(useOrder)-1,1); -0357 allFrom=cell(numel(useOrder)-1,1); -0358 -0359 for i=1:numel(useOrder)-1 -0360 allTo{i}=sparse(numel(allGenes{1}),numel(allGenes{i+1})); -0361 allFrom{i}=sparse(numel(allGenes{1}),numel(allGenes{i+1})); -0362 end -0363 -0364 %Fill the matches to other species -0365 for i=1:numel(blastStructure) -0366 if strcmp(blastStructure(i).toId,getModelFor) -0367 %This was "to" the new organism. They should all match so no checks -0368 %are being made -0369 [~, a]=ismember(blastStructure(i).toGenes,allGenes{1}); -0370 [~, fromModel]=ismember(blastStructure(i).fromId,useOrder); -0371 [~, b]=ismember(blastStructure(i).fromGenes,allGenes{fromModel}); -0372 idx = sub2ind(size(allTo{fromModel-1}), a, b); -0373 allTo{fromModel-1}(idx)=1; -0374 else -0375 %This was "from" the new organism -0376 [~, a]=ismember(blastStructure(i).fromGenes,allGenes{1}); -0377 [~, toModel]=ismember(blastStructure(i).toId,useOrder); -0378 [~, b]=ismember(blastStructure(i).toGenes,allGenes{toModel}); -0379 idx = sub2ind(size(allFrom{toModel-1}), a, b); -0380 allFrom{toModel-1}(idx)=1; -0381 end -0382 end -0383 -0384 %Now we have all the gene matches in a convenient way. For all the genes in -0385 %the new organism get the genes that should be included from other -0386 %organisms. If all genes should be included this simply means keep the -0387 %allFrom matrix as it is. If only orthologs which could be mapped in both -0388 %BLAST directions are to be included then only those elements are kept. -0389 -0390 finalMappings=cell(numel(useOrder)-1,1); -0391 if bidirectional -0392 for j=1:numel(allFrom) -0393 finalMappings{j}=allTo{j}~=0 & allFrom{j}~=0; -0394 end -0395 else -0396 if mapNewGenesToOld==true -0397 finalMappings=allFrom; -0398 else -0399 finalMappings=allTo; -0400 end -0401 end -0402 -0403 %Remove all genes from the mapping that are not in the models. This does not -0404 %do much if only genes in the models were used for the original mapping. -0405 %Also simplify the finalMapping and allGenes structures so that they only -0406 %contain mappings that exist -0407 usedNewGenes=false(numel(allGenes{1}),1); -0408 -0409 for i=1:numel(allGenes)-1 -0410 %First remove mappings for those genes that are not in the model -0411 if onlyGenesInModels==false -0412 a=ismember(allGenes{i+1},models{useOrderIndexes(i)}.genes); -0413 finalMappings{i}(:,~a)=false; -0414 end -0415 -0416 %Then remove unused ones and simplify -0417 [a, b]=find(finalMappings{i}); -0418 usedGenes=false(numel(allGenes{i+1}),1); -0419 usedNewGenes(a)=true; -0420 usedGenes(b)=true; -0421 finalMappings{i}=finalMappings{i}(:,usedGenes); -0422 allGenes{i+1}=allGenes{i+1}(usedGenes); -0423 end -0424 -0425 %Remove all new genes that have not been mapped to anything -0426 allGenes{1}=allGenes{1}(usedNewGenes); -0427 for i=1:numel(finalMappings) -0428 finalMappings{i}=finalMappings{i}(usedNewGenes,:); -0429 end -0430 -0431 %Gene ids end up in grRules, which are split at whitespace and parentheses, -0432 %and which COBRA also splits at "|" and "&" -0433 unusable=allGenes{1}(~cellfun(@isempty,regexp(allGenes{1},'[\s()|&]','once'))); -0434 if ~isempty(unusable) -0435 error('RAVEN:badInput',['%d gene ids of "%s" cannot be written into a grRule, '... -0436 'because they contain whitespace, parentheses, "|" or "&": %s. Map them '... -0437 'to gene ids first, or use the accession alone for UniProt headers such '... -0438 'as "sp|P33284|HXK_KLULA".'],numel(unusable),getModelFor,... -0439 strjoin(unusable(1:min(3,end))',', ')); -0440 end -0441 -0442 %Now is it time to choose which reactions should be included from which -0443 %models. If there is a preferred order specified then each gene can only -0444 %result in reactions from one model, otherwise they should all be included -0445 -0446 %Start by simplifying the models by removing genes/reactions that are not -0447 %used. This is where it gets weird with complexes, especially "or" -0448 %complexes. In this step only reactions which are encoded by one single -0449 %gene, or where all genes should be deleted, are deleted. The info on the -0450 %full complex is still present in the grRules +0356 %Generate a cell array of matrixes that describes how the genes in the new +0357 %organism map to the models. Each cell matches to the corresponding model +0358 %in useOrder (starting at 2 of course). First dimension is gene in new +0359 %organism, second which gene it is in the other organism. The second matrix +0360 %describes how they map back. +0361 +0362 %As it is now, a significant match is indicated by a 1. This could be +0363 %expanded to contain some kind of significance level. The first dimension +0364 %is still the genes in the new model. +0365 +0366 allTo=cell(numel(useOrder)-1,1); +0367 allFrom=cell(numel(useOrder)-1,1); +0368 +0369 for i=1:numel(useOrder)-1 +0370 allTo{i}=sparse(numel(allGenes{1}),numel(allGenes{i+1})); +0371 allFrom{i}=sparse(numel(allGenes{1}),numel(allGenes{i+1})); +0372 end +0373 +0374 %Fill the matches to other species +0375 for i=1:numel(blastStructure) +0376 if strcmp(blastStructure(i).toId,getModelFor) +0377 %This was "to" the new organism. They should all match so no checks +0378 %are being made +0379 [~, a]=ismember(blastStructure(i).toGenes,allGenes{1}); +0380 [~, fromModel]=ismember(blastStructure(i).fromId,useOrder); +0381 [~, b]=ismember(blastStructure(i).fromGenes,allGenes{fromModel}); +0382 idx = sub2ind(size(allTo{fromModel-1}), a, b); +0383 allTo{fromModel-1}(idx)=1; +0384 else +0385 %This was "from" the new organism +0386 [~, a]=ismember(blastStructure(i).fromGenes,allGenes{1}); +0387 [~, toModel]=ismember(blastStructure(i).toId,useOrder); +0388 [~, b]=ismember(blastStructure(i).toGenes,allGenes{toModel}); +0389 idx = sub2ind(size(allFrom{toModel-1}), a, b); +0390 allFrom{toModel-1}(idx)=1; +0391 end +0392 end +0393 +0394 %Now we have all the gene matches in a convenient way. For all the genes in +0395 %the new organism get the genes that should be included from other +0396 %organisms. If all genes should be included this simply means keep the +0397 %allFrom matrix as it is. If only orthologs which could be mapped in both +0398 %BLAST directions are to be included then only those elements are kept. +0399 +0400 finalMappings=cell(numel(useOrder)-1,1); +0401 if bidirectional +0402 for j=1:numel(allFrom) +0403 finalMappings{j}=allTo{j}~=0 & allFrom{j}~=0; +0404 end +0405 else +0406 if mapNewGenesToOld==true +0407 finalMappings=allFrom; +0408 else +0409 finalMappings=allTo; +0410 end +0411 end +0412 +0413 %Remove all genes from the mapping that are not in the models. This does not +0414 %do much if only genes in the models were used for the original mapping. +0415 %Also simplify the finalMapping and allGenes structures so that they only +0416 %contain mappings that exist +0417 usedNewGenes=false(numel(allGenes{1}),1); +0418 +0419 for i=1:numel(allGenes)-1 +0420 %First remove mappings for those genes that are not in the model +0421 if onlyGenesInModels==false +0422 a=ismember(allGenes{i+1},models{useOrderIndexes(i)}.genes); +0423 finalMappings{i}(:,~a)=false; +0424 end +0425 +0426 %Then remove unused ones and simplify +0427 [a, b]=find(finalMappings{i}); +0428 usedGenes=false(numel(allGenes{i+1}),1); +0429 usedNewGenes(a)=true; +0430 usedGenes(b)=true; +0431 finalMappings{i}=finalMappings{i}(:,usedGenes); +0432 allGenes{i+1}=allGenes{i+1}(usedGenes); +0433 end +0434 +0435 %Remove all new genes that have not been mapped to anything +0436 allGenes{1}=allGenes{1}(usedNewGenes); +0437 for i=1:numel(finalMappings) +0438 finalMappings{i}=finalMappings{i}(usedNewGenes,:); +0439 end +0440 +0441 %Gene ids end up in grRules, which are split at whitespace and parentheses, +0442 %and which COBRA also splits at "|" and "&" +0443 unusable=allGenes{1}(~cellfun(@isempty,regexp(allGenes{1},'[\s()|&]','once'))); +0444 if ~isempty(unusable) +0445 error('RAVEN:badInput',['%d gene ids of "%s" cannot be written into a grRule, '... +0446 'because they contain whitespace, parentheses, "|" or "&": %s. Map them '... +0447 'to gene ids first, or use the accession alone for UniProt headers such '... +0448 'as "sp|P33284|HXK_KLULA".'],numel(unusable),getModelFor,... +0449 strjoin(unusable(1:min(3,end))',', ')); +0450 end 0451 -0452 for i=1:numel(models) -0453 a=ismember(models{useOrderIndexes(i)}.genes,allGenes{i+1}); -0454 -0455 %Remove reactions that are not associated to any of the genes in -0456 %allGenes, thereby also keeping complexes where only for one of the -0457 %genes was matched -0458 [rxnsToKeep,~] = find(models{useOrderIndexes(i)}.rxnGeneMat(:,a)); -0459 rxnsToRemove = repmat(1,numel(models{useOrderIndexes(i)}.rxns),1); -0460 rxnsToRemove(rxnsToKeep) = 0; -0461 rxnsToRemove(ismember(models{useOrderIndexes(i)}.rxns,geneFreeRxns{useOrderIndexes(i)})) = 0; -0462 rxnsToRemove = find(rxnsToRemove); -0463 models{useOrderIndexes(i)}=removeReactions(models{useOrderIndexes(i)},rxnsToRemove,true,true,true); -0464 end -0465 -0466 %Since mergeModels function will be used in the end, the models are -0467 %simplified further by deleting genes/reactions in the order specified by -0468 %preferredOrder. This means that the last model will only contain reactions -0469 %for genes that mapped only to that model -0470 -0471 allUsedGenes=false(numel(allGenes{1}),1); -0472 -0473 if ~isempty(preferredOrder) && numel(models)>1 -0474 [usedGenes, ~]=find(finalMappings{1}); %All that are used in the first model in preferredOrder -0475 allUsedGenes(usedGenes)=true; -0476 for i=2:numel(finalMappings) -0477 [usedGenes, ~]=find(finalMappings{i}); -0478 usedGenes=unique(usedGenes); -0479 a=ismember(usedGenes,find(allUsedGenes)); -0480 -0481 [~, genesToDelete]=find(finalMappings{i}(usedGenes(a),:)); %IMPORTANT! IS it really correct to remove all genes that map? -0482 genesToDelete=unique(genesToDelete); %Maybe not needed, but for clarity if nothing else -0483 -0484 %Remove all the genes that were already found and add the other -0485 %ones to allUsedGenes -0486 models{useOrderIndexes(i)}=removeGenes(models{useOrderIndexes(i)},allGenes{i+1}(genesToDelete),true,true,false); -0487 allUsedGenes(usedGenes)=true; -0488 -0489 %Remove the deleted genes from finalMappings and allGenes. -0490 finalMappings{i}(:,genesToDelete)=[]; -0491 allGenes{i+1}(genesToDelete)=[]; -0492 end -0493 end -0494 -0495 %Now loop through the models and update the gene associations. Genes not -0496 %belonging to the new organism will be renamed as "OLD_MODELID_gene" -0497 for i=1:numel(models) -0498 %Find all the new genes that should be used for this model -0499 [newGenes, oldGenes]=find(finalMappings{i}); -0500 -0501 %Create a new gene list with the genes from the new organism and those -0502 %genes that could not be removed -0503 replaceableGenes=allGenes{i+1}(unique(oldGenes)); -0504 nonReplaceableGenes=setdiff(models{useOrderIndexes(i)}.genes,replaceableGenes); -0505 fullGeneList=[allGenes{1}(unique(newGenes));nonReplaceableGenes]; -0506 -0507 %Just to save some indexing later. This is the LAST index of -0508 %replaceable ones -0509 nonRepStartIndex=numel(unique(newGenes)); +0452 %Now is it time to choose which reactions should be included from which +0453 %models. If there is a preferred order specified then each gene can only +0454 %result in reactions from one model, otherwise they should all be included +0455 +0456 %Start by simplifying the models by removing genes/reactions that are not +0457 %used. This is where it gets weird with complexes, especially "or" +0458 %complexes. In this step only reactions which are encoded by one single +0459 %gene, or where all genes should be deleted, are deleted. The info on the +0460 %full complex is still present in the grRules +0461 +0462 for i=1:numel(models) +0463 a=ismember(models{useOrderIndexes(i)}.genes,allGenes{i+1}); +0464 +0465 %Remove reactions that are not associated to any of the genes in +0466 %allGenes, thereby also keeping complexes where only for one of the +0467 %genes was matched +0468 [rxnsToKeep,~] = find(models{useOrderIndexes(i)}.rxnGeneMat(:,a)); +0469 rxnsToRemove = repmat(1,numel(models{useOrderIndexes(i)}.rxns),1); +0470 rxnsToRemove(rxnsToKeep) = 0; +0471 rxnsToRemove(ismember(models{useOrderIndexes(i)}.rxns,geneFreeRxns{useOrderIndexes(i)})) = 0; +0472 rxnsToRemove = find(rxnsToRemove); +0473 models{useOrderIndexes(i)}=removeReactions(models{useOrderIndexes(i)},rxnsToRemove,true,true,true); +0474 end +0475 +0476 %Since mergeModels function will be used in the end, the models are +0477 %simplified further by deleting genes/reactions in the order specified by +0478 %preferredOrder. This means that the last model will only contain reactions +0479 %for genes that mapped only to that model +0480 +0481 allUsedGenes=false(numel(allGenes{1}),1); +0482 +0483 if ~isempty(preferredOrder) && numel(models)>1 +0484 [usedGenes, ~]=find(finalMappings{1}); %All that are used in the first model in preferredOrder +0485 allUsedGenes(usedGenes)=true; +0486 for i=2:numel(finalMappings) +0487 [usedGenes, ~]=find(finalMappings{i}); +0488 usedGenes=unique(usedGenes); +0489 a=ismember(usedGenes,find(allUsedGenes)); +0490 +0491 [~, genesToDelete]=find(finalMappings{i}(usedGenes(a),:)); %IMPORTANT! IS it really correct to remove all genes that map? +0492 genesToDelete=unique(genesToDelete); %Maybe not needed, but for clarity if nothing else +0493 +0494 %Remove all the genes that were already found and add the other +0495 %ones to allUsedGenes +0496 models{useOrderIndexes(i)}=removeGenes(models{useOrderIndexes(i)},allGenes{i+1}(genesToDelete),true,true,false); +0497 allUsedGenes(usedGenes)=true; +0498 +0499 %Remove the deleted genes from finalMappings and allGenes. +0500 finalMappings{i}(:,genesToDelete)=[]; +0501 allGenes{i+1}(genesToDelete)=[]; +0502 end +0503 end +0504 +0505 %Now loop through the models and update the gene associations. Genes not +0506 %belonging to the new organism will be renamed as "OLD_MODELID_gene" +0507 for i=1:numel(models) +0508 %Find all the new genes that should be used for this model +0509 [newGenes, oldGenes]=find(finalMappings{i}); 0510 -0511 %Construct a new rxnGeneMat -0512 newRxnGeneMat=sparse(numel(models{useOrderIndexes(i)}.rxns),numel(fullGeneList)); -0513 -0514 %Now update the rxnGeneMat. This is a little tricky and could -0515 %probably be done in a more efficient way, but I just loop through the -0516 %reactions and add them one by one -0517 for j=1:numel(models{useOrderIndexes(i)}.rxns) -0518 %Get the old genes encoding for this reaction -0519 [~, oldGeneIds]=find(models{useOrderIndexes(i)}.rxnGeneMat(j,:)); -0520 -0521 %Tokenize the grRule once; all gene substitutions operate on the -0522 %token list so that no gene ID can partially match another. -0523 tokens=gprTokenize(models{useOrderIndexes(i)}.grRules{j}); -0524 -0525 %Update the matrix for each gene. This includes replacing one gene -0526 %with several new ones if there were several matches -0527 for k=1:numel(oldGeneIds) -0528 %Match the gene to one in the gene list. This is done as a text -0529 %match. Could probably be done better, but the indexing is -0530 %complex -0531 -0532 geneName=models{useOrderIndexes(i)}.genes(oldGeneIds(k)); -0533 -0534 %First search in the mappable genes -0535 mapIndex=find(ismember(allGenes{i+1},geneName)); -0536 -0537 if ~isempty(mapIndex) -0538 % add the old genes -0539 hitGenes.oldGenes = [hitGenes.oldGenes, {geneName}]; -0540 -0541 %Get the new genes for that gene -0542 a=find(finalMappings{i}(:,mapIndex)); +0511 %Create a new gene list with the genes from the new organism and those +0512 %genes that could not be removed +0513 replaceableGenes=allGenes{i+1}(unique(oldGenes)); +0514 nonReplaceableGenes=setdiff(models{useOrderIndexes(i)}.genes,replaceableGenes); +0515 fullGeneList=[allGenes{1}(unique(newGenes));nonReplaceableGenes]; +0516 +0517 %Just to save some indexing later. This is the LAST index of +0518 %replaceable ones +0519 nonRepStartIndex=numel(unique(newGenes)); +0520 +0521 %Construct a new rxnGeneMat +0522 newRxnGeneMat=sparse(numel(models{useOrderIndexes(i)}.rxns),numel(fullGeneList)); +0523 +0524 %Now update the rxnGeneMat. This is a little tricky and could +0525 %probably be done in a more efficient way, but I just loop through the +0526 %reactions and add them one by one +0527 for j=1:numel(models{useOrderIndexes(i)}.rxns) +0528 %Get the old genes encoding for this reaction +0529 [~, oldGeneIds]=find(models{useOrderIndexes(i)}.rxnGeneMat(j,:)); +0530 +0531 %Tokenize the grRule once; all gene substitutions operate on the +0532 %token list so that no gene ID can partially match another. +0533 tokens=gprTokenize(models{useOrderIndexes(i)}.grRules{j}); +0534 +0535 %Update the matrix for each gene. This includes replacing one gene +0536 %with several new ones if there were several matches +0537 for k=1:numel(oldGeneIds) +0538 %Match the gene to one in the gene list. This is done as a text +0539 %match. Could probably be done better, but the indexing is +0540 %complex +0541 +0542 geneName=models{useOrderIndexes(i)}.genes(oldGeneIds(k)); 0543 -0544 %Find the positions of these genes in the final gene list -0545 [~, b]=ismember(allGenes{1}(a),fullGeneList); +0544 %First search in the mappable genes +0545 mapIndex=find(ismember(allGenes{i+1},geneName)); 0546 -0547 %Update the matrix -0548 newRxnGeneMat(j,b)=1; -0549 -0550 %Build repString for hitGenes (display only) -0551 repString=fullGeneList{b(1)}; -0552 if numel(b)>1 -0553 for l=2:numel(b) -0554 repString=[repString ' or ' fullGeneList{b(l)}]; %#ok<AGROW> -0555 end -0556 repString=['(' repString ')']; -0557 end -0558 hitGenes.newGenes = [hitGenes.newGenes, {repString}]; +0547 if ~isempty(mapIndex) +0548 % add the old genes +0549 hitGenes.oldGenes = [hitGenes.oldGenes, {geneName}]; +0550 +0551 %Get the new genes for that gene +0552 a=find(finalMappings{i}(:,mapIndex)); +0553 +0554 %Find the positions of these genes in the final gene list +0555 [~, b]=ismember(allGenes{1}(a),fullGeneList); +0556 +0557 %Update the matrix +0558 newRxnGeneMat(j,b)=1; 0559 -0560 %Replace the old gene token with the new gene(s) -0561 tokens=gprReplaceToken(tokens,geneName{1},fullGeneList(b)); -0562 else -0563 %Then search in the non-replaceable genes. There could only -0564 %be one match here -0565 index=find(ismember(nonReplaceableGenes,geneName)); -0566 -0567 %Update the matrix -0568 newRxnGeneMat(j,nonRepStartIndex+index)=1; +0560 %Build repString for hitGenes (display only) +0561 repString=fullGeneList{b(1)}; +0562 if numel(b)>1 +0563 for l=2:numel(b) +0564 repString=[repString ' or ' fullGeneList{b(l)}]; %#ok<AGROW> +0565 end +0566 repString=['(' repString ')']; +0567 end +0568 hitGenes.newGenes = [hitGenes.newGenes, {repString}]; 0569 -0570 oldName=strcat('OLD_',models{useOrderIndexes(i)}.id,'_',geneName{1}); -0571 tokens=gprReplaceToken(tokens,geneName{1},{oldName}); -0572 end -0573 end -0574 -0575 models{useOrderIndexes(i)}.grRules{j}=gprBuildRule(tokens); -0576 end -0577 -0578 %Add the new list of genes -0579 models{useOrderIndexes(i)}.rxnGeneMat=newRxnGeneMat; -0580 if ~isempty(nonReplaceableGenes) -0581 models{useOrderIndexes(i)}.genes=[allGenes{1}(unique(newGenes));strcat('OLD_',models{useOrderIndexes(i)}.id,'_',nonReplaceableGenes)]; -0582 else -0583 models{useOrderIndexes(i)}.genes=allGenes{1}(unique(newGenes)); -0584 end -0585 if isfield(models{useOrderIndexes(i)},'geneComps') -0586 geneComps=models{useOrderIndexes(i)}.geneComps(1); -0587 models{useOrderIndexes(i)}.geneComps=zeros(numel(models{useOrderIndexes(i)}.genes),1); -0588 %Assume that all genes are in the same compartment, and this -0589 %compartment is specified for the first gene -0590 models{useOrderIndexes(i)}.geneComps(:)=geneComps; -0591 end -0592 end -0593 -0594 %Now merge the models. All information should be correct except for "or" -0595 %complexes -0596 draftModel=mergeModels(models,'metNames'); -0597 -0598 %Remove OLD_ genes that appear only in OR branches (they were added for -0599 %AND-complex genes with no orthologs; in OR branches they are redundant). -0600 %Uses token-level removal to avoid partial-match hazards. -0601 for oldI=1:numel(draftModel.grRules) -0602 if ~contains(draftModel.grRules{oldI},'OLD_') -0603 continue -0604 end -0605 tkns=gprTokenize(draftModel.grRules{oldI}); -0606 again=true; -0607 while again -0608 again=false; -0609 for kk=1:numel(tkns) -0610 if startsWith(tkns{kk},'OLD_') -0611 if kk>1 && strcmp(tkns{kk-1},'or') -0612 tkns(kk-1:kk)=[]; -0613 again=true; break -0614 elseif kk<numel(tkns) && strcmp(tkns{kk+1},'or') -0615 tkns(kk:kk+1)=[]; -0616 again=true; break -0617 end -0618 end -0619 end -0620 end -0621 draftModel.grRules{oldI}=gprBuildRule(tkns); -0622 end -0623 -0624 %Apply the complex policy to the OLD_ genes left in "and" relations, and -0625 %write each grRule without repeated genes or redundant brackets -0626 toRemove=false(numel(draftModel.rxns),1); -0627 for j=1:numel(draftModel.grRules) -0628 rule=draftModel.grRules{j}; -0629 if isempty(rule) -0630 continue -0631 end -0632 if contains(rule,'OLD_') && strcmp(complexPolicy,'drop') -0633 toRemove(j)=true; -0634 elseif contains(rule,'OLD_') && strcmp(complexPolicy,'keep') -0635 draftModel.grRules{j}=gprSimplify(rule,'OLD_'); -0636 toRemove(j)=isempty(draftModel.grRules{j}); -0637 else -0638 draftModel.grRules{j}=gprSimplify(rule,''); -0639 end -0640 end -0641 if any(toRemove) -0642 draftModel=removeReactions(draftModel,draftModel.rxns(toRemove),true,true,true); -0643 end -0644 -0645 %Change name of the resulting model -0646 draftModel.id=getModelFor; -0647 name='Generated by getModelFromHomology using '; -0648 for i=1:numel(models) -0649 if i<numel(models) -0650 name=[name models{i}.id ', ']; -0651 else -0652 name=[name models{i}.id]; -0653 end -0654 end -0655 draftModel.name=name; -0656 %The draft is a new model: the templates' version, date and description are -0657 %not its own -0658 draftModel=rmfield(draftModel,intersect({'version','date','description'},fieldnames(draftModel))); -0659 if isfield(draftModel,'annotation') -0660 descriptive={'taxonomy','sourceUrl','note','givenName','familyName','authors','email','organization'}; -0661 draftModel.annotation=rmfield(draftModel.annotation,intersect(descriptive,fieldnames(draftModel.annotation))); -0662 end -0663 if ~preserveNotes || ~isfield(draftModel,'rxnNotes') -0664 draftModel.rxnNotes=cell(length(draftModel.rxns),1); -0665 draftModel.rxnNotes(:)={'Included by getModelFromHomology'}; -0666 end -0667 if ~preserveNotes || ~isfield(draftModel,'rxnConfidenceScores') -0668 draftModel.rxnConfidenceScores=NaN(length(draftModel.rxns),1); -0669 draftModel.rxnConfidenceScores(:)=2; -0670 end -0671 draftModel=deleteUnusedGenes(draftModel,0); -0672 %Standardize grRules and notify if problematic grRules are found -0673 [draftModel.grRules,draftModel.rxnGeneMat]=standardizeGrRules(draftModel,false); -0674 draftModel=deleteUnusedGenes(draftModel,false); -0675 end -0676 -0677 -0678 function tokens=gprTokenize(rule) -0679 % Split a GPR rule into tokens: gene IDs, "and", "or", "(", ")". -0680 % Brackets are separated from adjacent text by whitespace before splitting. -0681 if isempty(rule) -0682 tokens={}; -0683 return -0684 end -0685 r=regexprep(rule,'([\(\)])',' $1 '); -0686 r=regexprep(r,'\s+',' '); -0687 tokens=strsplit(strtrim(r),' '); -0688 tokens=tokens(~cellfun(@isempty,tokens)); -0689 end -0690 -0691 -0692 function rule=gprBuildRule(tokens) -0693 % Reconstruct a GPR rule string from a token list. -0694 if isempty(tokens) -0695 rule=''; -0696 return -0697 end -0698 rule=strjoin(tokens,' '); -0699 rule=strrep(rule,'( ','('); -0700 rule=strrep(rule,' )',')'); -0701 end -0702 -0703 -0704 function tokens=gprReplaceToken(tokens,oldGene,newGenes) -0705 % Replace each occurrence of token oldGene with the gene(s) in newGenes. -0706 % newGenes is a cell array; multiple genes are wrapped in parentheses and -0707 % joined with " or ". -0708 k=1; -0709 while k<=numel(tokens) -0710 if strcmp(tokens{k},oldGene) -0711 if numel(newGenes)==1 -0712 tokens{k}=newGenes{1}; -0713 k=k+1; -0714 else -0715 seq={'('}; -0716 for g=1:numel(newGenes) -0717 seq{end+1}=newGenes{g}; %#ok<AGROW> -0718 if g<numel(newGenes) -0719 seq{end+1}='or'; %#ok<AGROW> -0720 end -0721 end -0722 seq{end+1}=')'; -0723 tokens=[tokens(1:k-1),seq,tokens(k+1:end)]; -0724 k=k+numel(seq); -0725 end -0726 else -0727 k=k+1; -0728 end -0729 end -0730 end -0731 -0732 -0733 function rule=gprSimplify(rule,removePrefix) -0734 % Rewrite a grRule as a tree: genes starting with removePrefix (if any) are -0735 % dropped from their group, groups of the same operator are flattened, -0736 % repeated genes or groups are removed, and brackets appear only around a -0737 % group of the other operator. A rule with no gene left becomes ''. -0738 tokens=gprTokenize(rule); -0739 if isempty(tokens) -0740 rule=''; -0741 return -0742 end -0743 [node,~]=gprParseOr(tokens,1); -0744 node=gprPrune(node,removePrefix); -0745 if isempty(node) -0746 rule=''; -0747 else -0748 rule=gprRender(node,''); -0749 end -0750 end -0751 -0752 -0753 function [node,pos]=gprParseOr(tokens,pos) -0754 [node,pos]=gprParseAnd(tokens,pos); -0755 kids={node}; -0756 while pos<=numel(tokens) && strcmpi(tokens{pos},'or') -0757 [node,pos]=gprParseAnd(tokens,pos+1); -0758 kids{end+1}=node; %#ok<AGROW> +0570 %Replace the old gene token with the new gene(s) +0571 tokens=gprReplaceToken(tokens,geneName{1},fullGeneList(b)); +0572 else +0573 %Then search in the non-replaceable genes. There could only +0574 %be one match here +0575 index=find(ismember(nonReplaceableGenes,geneName)); +0576 +0577 %Update the matrix +0578 newRxnGeneMat(j,nonRepStartIndex+index)=1; +0579 +0580 oldName=strcat('OLD_',models{useOrderIndexes(i)}.id,'_',geneName{1}); +0581 tokens=gprReplaceToken(tokens,geneName{1},{oldName}); +0582 end +0583 end +0584 +0585 models{useOrderIndexes(i)}.grRules{j}=gprBuildRule(tokens); +0586 end +0587 +0588 %Add the new list of genes +0589 models{useOrderIndexes(i)}.rxnGeneMat=newRxnGeneMat; +0590 if ~isempty(nonReplaceableGenes) +0591 models{useOrderIndexes(i)}.genes=[allGenes{1}(unique(newGenes));strcat('OLD_',models{useOrderIndexes(i)}.id,'_',nonReplaceableGenes)]; +0592 else +0593 models{useOrderIndexes(i)}.genes=allGenes{1}(unique(newGenes)); +0594 end +0595 if isfield(models{useOrderIndexes(i)},'geneComps') +0596 geneComps=models{useOrderIndexes(i)}.geneComps(1); +0597 models{useOrderIndexes(i)}.geneComps=zeros(numel(models{useOrderIndexes(i)}.genes),1); +0598 %Assume that all genes are in the same compartment, and this +0599 %compartment is specified for the first gene +0600 models{useOrderIndexes(i)}.geneComps(:)=geneComps; +0601 end +0602 end +0603 +0604 %Now merge the models. All information should be correct except for "or" +0605 %complexes +0606 draftModel=mergeModels(models,'metNames'); +0607 +0608 %Remove OLD_ genes that appear only in OR branches (they were added for +0609 %AND-complex genes with no orthologs; in OR branches they are redundant). +0610 %Uses token-level removal to avoid partial-match hazards. +0611 for oldI=1:numel(draftModel.grRules) +0612 if ~contains(draftModel.grRules{oldI},'OLD_') +0613 continue +0614 end +0615 tkns=gprTokenize(draftModel.grRules{oldI}); +0616 again=true; +0617 while again +0618 again=false; +0619 for kk=1:numel(tkns) +0620 if startsWith(tkns{kk},'OLD_') +0621 if kk>1 && strcmp(tkns{kk-1},'or') +0622 tkns(kk-1:kk)=[]; +0623 again=true; break +0624 elseif kk<numel(tkns) && strcmp(tkns{kk+1},'or') +0625 tkns(kk:kk+1)=[]; +0626 again=true; break +0627 end +0628 end +0629 end +0630 end +0631 draftModel.grRules{oldI}=gprBuildRule(tkns); +0632 end +0633 +0634 %Apply the complex policy to the OLD_ genes left in "and" relations, and +0635 %write each grRule without repeated genes or redundant brackets +0636 toRemove=false(numel(draftModel.rxns),1); +0637 for j=1:numel(draftModel.grRules) +0638 rule=draftModel.grRules{j}; +0639 if isempty(rule) +0640 continue +0641 end +0642 if contains(rule,'OLD_') && strcmp(complexPolicy,'drop') +0643 toRemove(j)=true; +0644 elseif contains(rule,'OLD_') && strcmp(complexPolicy,'keep') +0645 draftModel.grRules{j}=gprSimplify(rule,'OLD_'); +0646 toRemove(j)=isempty(draftModel.grRules{j}); +0647 else +0648 draftModel.grRules{j}=gprSimplify(rule,''); +0649 end +0650 end +0651 if any(toRemove) +0652 draftModel=removeReactions(draftModel,draftModel.rxns(toRemove),true,true,true); +0653 end +0654 +0655 %Change name of the resulting model +0656 draftModel.id=getModelFor; +0657 name='Generated by getModelFromHomology using '; +0658 for i=1:numel(models) +0659 if i<numel(models) +0660 name=[name models{i}.id ', ']; +0661 else +0662 name=[name models{i}.id]; +0663 end +0664 end +0665 draftModel.name=name; +0666 %The draft is a new model: the templates' version, date and description are +0667 %not its own +0668 draftModel=rmfield(draftModel,intersect({'version','date','description'},fieldnames(draftModel))); +0669 if isfield(draftModel,'annotation') +0670 descriptive={'taxonomy','sourceUrl','note','givenName','familyName','authors','email','organization'}; +0671 draftModel.annotation=rmfield(draftModel.annotation,intersect(descriptive,fieldnames(draftModel.annotation))); +0672 end +0673 if ~preserveNotes || ~isfield(draftModel,'rxnNotes') +0674 draftModel.rxnNotes=cell(length(draftModel.rxns),1); +0675 draftModel.rxnNotes(:)={'Included by getModelFromHomology'}; +0676 end +0677 if ~preserveNotes || ~isfield(draftModel,'rxnConfidenceScores') +0678 draftModel.rxnConfidenceScores=NaN(length(draftModel.rxns),1); +0679 draftModel.rxnConfidenceScores(:)=2; +0680 end +0681 draftModel=deleteUnusedGenes(draftModel,0); +0682 %Standardize grRules and notify if problematic grRules are found +0683 [draftModel.grRules,draftModel.rxnGeneMat]=standardizeGrRules(draftModel,false); +0684 draftModel=deleteUnusedGenes(draftModel,false); +0685 end +0686 +0687 +0688 function tokens=gprTokenize(rule) +0689 % Split a GPR rule into tokens: gene IDs, "and", "or", "(", ")". +0690 % Brackets are separated from adjacent text by whitespace before splitting. +0691 if isempty(rule) +0692 tokens={}; +0693 return +0694 end +0695 r=regexprep(rule,'([\(\)])',' $1 '); +0696 r=regexprep(r,'\s+',' '); +0697 tokens=strsplit(strtrim(r),' '); +0698 tokens=tokens(~cellfun(@isempty,tokens)); +0699 end +0700 +0701 +0702 function rule=gprBuildRule(tokens) +0703 % Reconstruct a GPR rule string from a token list. +0704 if isempty(tokens) +0705 rule=''; +0706 return +0707 end +0708 rule=strjoin(tokens,' '); +0709 rule=strrep(rule,'( ','('); +0710 rule=strrep(rule,' )',')'); +0711 end +0712 +0713 +0714 function tokens=gprReplaceToken(tokens,oldGene,newGenes) +0715 % Replace each occurrence of token oldGene with the gene(s) in newGenes. +0716 % newGenes is a cell array; multiple genes are wrapped in parentheses and +0717 % joined with " or ". +0718 k=1; +0719 while k<=numel(tokens) +0720 if strcmp(tokens{k},oldGene) +0721 if numel(newGenes)==1 +0722 tokens{k}=newGenes{1}; +0723 k=k+1; +0724 else +0725 seq={'('}; +0726 for g=1:numel(newGenes) +0727 seq{end+1}=newGenes{g}; %#ok<AGROW> +0728 if g<numel(newGenes) +0729 seq{end+1}='or'; %#ok<AGROW> +0730 end +0731 end +0732 seq{end+1}=')'; +0733 tokens=[tokens(1:k-1),seq,tokens(k+1:end)]; +0734 k=k+numel(seq); +0735 end +0736 else +0737 k=k+1; +0738 end +0739 end +0740 end +0741 +0742 +0743 function rule=gprSimplify(rule,removePrefix) +0744 % Rewrite a grRule as a tree: genes starting with removePrefix (if any) are +0745 % dropped from their group, groups of the same operator are flattened, +0746 % repeated genes or groups are removed, and brackets appear only around a +0747 % group of the other operator. A rule with no gene left becomes ''. +0748 tokens=gprTokenize(rule); +0749 if isempty(tokens) +0750 rule=''; +0751 return +0752 end +0753 [node,~]=gprParseOr(tokens,1); +0754 node=gprPrune(node,removePrefix); +0755 if isempty(node) +0756 rule=''; +0757 else +0758 rule=gprRender(node,''); 0759 end -0760 if numel(kids)>1 -0761 node=struct('op','or','gene','','kids',{kids}); -0762 end -0763 end -0764 -0765 -0766 function [node,pos]=gprParseAnd(tokens,pos) -0767 [node,pos]=gprParseAtom(tokens,pos); -0768 kids={node}; -0769 while pos<=numel(tokens) && strcmpi(tokens{pos},'and') -0770 [node,pos]=gprParseAtom(tokens,pos+1); -0771 kids{end+1}=node; %#ok<AGROW> +0760 end +0761 +0762 +0763 function [node,pos]=gprParseOr(tokens,pos) +0764 [node,pos]=gprParseAnd(tokens,pos); +0765 kids={node}; +0766 while pos<=numel(tokens) && strcmpi(tokens{pos},'or') +0767 [node,pos]=gprParseAnd(tokens,pos+1); +0768 kids{end+1}=node; %#ok<AGROW> +0769 end +0770 if numel(kids)>1 +0771 node=struct('op','or','gene','','kids',{kids}); 0772 end -0773 if numel(kids)>1 -0774 node=struct('op','and','gene','','kids',{kids}); -0775 end -0776 end -0777 -0778 -0779 function [node,pos]=gprParseAtom(tokens,pos) -0780 if pos>numel(tokens) -0781 error('Malformed grRule: it ends with an operator'); +0773 end +0774 +0775 +0776 function [node,pos]=gprParseAnd(tokens,pos) +0777 [node,pos]=gprParseAtom(tokens,pos); +0778 kids={node}; +0779 while pos<=numel(tokens) && strcmpi(tokens{pos},'and') +0780 [node,pos]=gprParseAtom(tokens,pos+1); +0781 kids{end+1}=node; %#ok<AGROW> 0782 end -0783 if strcmp(tokens{pos},'(') -0784 [node,pos]=gprParseOr(tokens,pos+1); -0785 if pos>numel(tokens) || ~strcmp(tokens{pos},')') -0786 error('Malformed grRule: unbalanced brackets'); -0787 end -0788 pos=pos+1; -0789 else -0790 node=struct('op','gene','gene',tokens{pos},'kids',{{}}); -0791 pos=pos+1; +0783 if numel(kids)>1 +0784 node=struct('op','and','gene','','kids',{kids}); +0785 end +0786 end +0787 +0788 +0789 function [node,pos]=gprParseAtom(tokens,pos) +0790 if pos>numel(tokens) +0791 error('Malformed grRule: it ends with an operator'); 0792 end -0793 end -0794 -0795 -0796 function node=gprPrune(node,removePrefix) -0797 if strcmp(node.op,'gene') -0798 if ~isempty(removePrefix) && startsWith(node.gene,removePrefix) -0799 node=[]; -0800 end -0801 return +0793 if strcmp(tokens{pos},'(') +0794 [node,pos]=gprParseOr(tokens,pos+1); +0795 if pos>numel(tokens) || ~strcmp(tokens{pos},')') +0796 error('Malformed grRule: unbalanced brackets'); +0797 end +0798 pos=pos+1; +0799 else +0800 node=struct('op','gene','gene',tokens{pos},'kids',{{}}); +0801 pos=pos+1; 0802 end -0803 kids={}; -0804 seen={}; -0805 for k=1:numel(node.kids) -0806 kid=gprPrune(node.kids{k},removePrefix); -0807 if isempty(kid) -0808 continue -0809 end -0810 if strcmp(kid.op,node.op) -0811 parts=kid.kids; -0812 else -0813 parts={kid}; -0814 end -0815 for m=1:numel(parts) -0816 key=gprRender(parts{m},node.op); -0817 if ~ismember(key,seen) -0818 seen{end+1}=key; %#ok<AGROW> -0819 kids{end+1}=parts{m}; %#ok<AGROW> -0820 end -0821 end -0822 end -0823 if isempty(kids) -0824 node=[]; -0825 elseif numel(kids)==1 -0826 node=kids{1}; -0827 else -0828 node.kids=kids; -0829 end -0830 end -0831 -0832 -0833 function rule=gprRender(node,parentOp) -0834 if strcmp(node.op,'gene') -0835 rule=node.gene; -0836 return -0837 end -0838 parts=cellfun(@(k) gprRender(k,node.op),node.kids,'UniformOutput',false); -0839 rule=strjoin(parts,[' ' node.op ' ']); -0840 if ~isempty(parentOp) && ~strcmp(parentOp,node.op) -0841 rule=['(' rule ')']; -0842 end -0843 end +0803 end +0804 +0805 +0806 function node=gprPrune(node,removePrefix) +0807 if strcmp(node.op,'gene') +0808 if ~isempty(removePrefix) && startsWith(node.gene,removePrefix) +0809 node=[]; +0810 end +0811 return +0812 end +0813 kids={}; +0814 seen={}; +0815 for k=1:numel(node.kids) +0816 kid=gprPrune(node.kids{k},removePrefix); +0817 if isempty(kid) +0818 continue +0819 end +0820 if strcmp(kid.op,node.op) +0821 parts=kid.kids; +0822 else +0823 parts={kid}; +0824 end +0825 for m=1:numel(parts) +0826 key=gprRender(parts{m},node.op); +0827 if ~ismember(key,seen) +0828 seen{end+1}=key; %#ok<AGROW> +0829 kids{end+1}=parts{m}; %#ok<AGROW> +0830 end +0831 end +0832 end +0833 if isempty(kids) +0834 node=[]; +0835 elseif numel(kids)==1 +0836 node=kids{1}; +0837 else +0838 node.kids=kids; +0839 end +0840 end +0841 +0842 +0843 function rule=gprRender(node,parentOp) +0844 if strcmp(node.op,'gene') +0845 rule=node.gene; +0846 return +0847 end +0848 parts=cellfun(@(k) gprRender(k,node.op),node.kids,'UniformOutput',false); +0849 rule=strjoin(parts,[' ' node.op ' ']); +0850 if ~isempty(parentOp) && ~strcmp(parentOp,node.op) +0851 rule=['(' rule ')']; +0852 end +0853 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/reconstruction/homology/hitsFromSuppOrth.html b/docs/html/reconstruction/homology/hitsFromSuppOrth.html new file mode 100644 index 00000000..ba1e3830 --- /dev/null +++ b/docs/html/reconstruction/homology/hitsFromSuppOrth.html @@ -0,0 +1,466 @@ + + + + Description of hitsFromSuppOrth + + + + + + + + + +
    Home > reconstruction > homology > hitsFromSuppOrth.m
    + + + +

    hitsFromSuppOrth +

    + +

    PURPOSE ^

    +
    hitsFromSuppOrth Read SuppOrth consensus orthologs into a blastStructure.
    + +

    SYNOPSIS ^

    +
    function [blastStructure,suppOrthInfo]=hitsFromSuppOrth(source,organismID,varargin)
    + +

    DESCRIPTION ^

    +
     hitsFromSuppOrth  Read SuppOrth consensus orthologs into a blastStructure.
    +
    + SuppOrth (https://github.com/fma91/SuppOrth) runs several ortholog
    + predictors (OrthoFinder, SonicParanoid2, Broccoli, and optionally an
    + imported OMA run) on a set of proteomes and writes suppOrth.tsv: one row
    + per cross-species protein pair, with the number of predictors that
    + support it. This function turns that table into a blastStructure for
    + getModelFromHomology.
    +
    + Parameters
    + ----------
    + source : char or table
    +     path to suppOrth.tsv, or a MATLAB table with its columns. Protein
    +     ids are read as text, so ids such as "NA" or "0123" are kept.
    + organismID : char
    +     SuppOrth species label of the organism to build a model for: the
    +     FASTA file name without extension, or the --labels value. It becomes
    +     fromId/toId unless speciesToModel maps it to another id.
    +
    + Name-Value Arguments
    + --------------------
    + speciesToModel : cell or containers.Map
    +     SuppOrth species label to model id, as an N-by-2 cell array or a
    +     containers.Map. A label mapped to '' is left out. When given, every
    +     label in the table must be included. When empty, labels are used as
    +     model ids (default []).
    + minSupport : double
    +     minimum number of predictors that found the pair (Lv_support).
    +     SuppOrth has already filtered the table with its own --supportLv and
    +     --identity_filter; this filter is applied in addition (default 1).
    + requireTools : cell
    +     predictors that must all have found the pair, by column name
    +     ('orthofinder', 'sonicparanoid', 'broccoli', 'oma') (default {}).
    + minIdentity : double
    +     minimum SuppOrth identity. A pair without an alignment hit fails this
    +     filter (default [], no filter).
    + maxEvalue : double
    +     maximum SuppOrth e-value. A pair without an alignment hit fails this
    +     filter (default [], no filter).
    + proteinToGene : cell, containers.Map or char
    +     protein id to model gene id, as an N-by-2 cell array, a
    +     containers.Map, a JSON file with one object of strings, or a
    +     two-column tab-separated file (lines starting with # are skipped).
    +     Proteins missing from the map keep their id, and their number is
    +     reported in a warning. A gene pair takes the highest support over its
    +     protein pairs, the union of their predictors, the highest identity
    +     and bitscore, and the lowest e-value; these values can come from
    +     different protein pairs (default [], no mapping).
    +
    + Returns
    + -------
    + blastStructure : struct
    +     one element per search direction between organismID and each other
    +     species, as from getBlast or getDiamond. Every pair is written in
    +     both directions, so strictness 1 and 2 of getModelFromHomology give
    +     the same result. evalue, identity, aligLen and ppos are placeholders
    +     (0, 100, 1000, 100) that pass the filters of getModelFromHomology;
    +     bitscore is SuppOrth's value (0 when it has none), which strictness 3
    +     ranks by.
    + suppOrthInfo : table
    +     one row per gene pair, with modelId, newGene, templateGene, support,
    +     supportedBy, identity, evalue and bitscore (SuppOrth's values).
    +
    + Notes
    + -----
    + Only pairs between organismID and another species are kept. Species that
    + are not templates in getModelFromHomology have to be left out with
    + speciesToModel, because getModelFromHomology requires every id in the
    + blastStructure to be the new organism or a template.
    +
    + Examples
    + --------
    +     blastStructure = hitsFromSuppOrth('out/suppOrth.tsv','klac',...
    +        'minSupport',2,'proteinToGene','klac_genes.tsv');
    +
    + See also
    + --------
    + runSuppOrth, getModelFromHomology, getDiamond
    + + +

    CROSS-REFERENCE INFORMATION ^

    +This function calls: +
      +
    +This function is called by: +
      +
    • runSuppOrth runSuppOrth Run SuppOrth on an organism and template proteomes.
    + + +

    SUBFUNCTIONS ^

    + + +

    SOURCE CODE ^

    +
    0001 function [blastStructure,suppOrthInfo]=hitsFromSuppOrth(source,organismID,varargin)
    +0002 % hitsFromSuppOrth  Read SuppOrth consensus orthologs into a blastStructure.
    +0003 %
    +0004 % SuppOrth (https://github.com/fma91/SuppOrth) runs several ortholog
    +0005 % predictors (OrthoFinder, SonicParanoid2, Broccoli, and optionally an
    +0006 % imported OMA run) on a set of proteomes and writes suppOrth.tsv: one row
    +0007 % per cross-species protein pair, with the number of predictors that
    +0008 % support it. This function turns that table into a blastStructure for
    +0009 % getModelFromHomology.
    +0010 %
    +0011 % Parameters
    +0012 % ----------
    +0013 % source : char or table
    +0014 %     path to suppOrth.tsv, or a MATLAB table with its columns. Protein
    +0015 %     ids are read as text, so ids such as "NA" or "0123" are kept.
    +0016 % organismID : char
    +0017 %     SuppOrth species label of the organism to build a model for: the
    +0018 %     FASTA file name without extension, or the --labels value. It becomes
    +0019 %     fromId/toId unless speciesToModel maps it to another id.
    +0020 %
    +0021 % Name-Value Arguments
    +0022 % --------------------
    +0023 % speciesToModel : cell or containers.Map
    +0024 %     SuppOrth species label to model id, as an N-by-2 cell array or a
    +0025 %     containers.Map. A label mapped to '' is left out. When given, every
    +0026 %     label in the table must be included. When empty, labels are used as
    +0027 %     model ids (default []).
    +0028 % minSupport : double
    +0029 %     minimum number of predictors that found the pair (Lv_support).
    +0030 %     SuppOrth has already filtered the table with its own --supportLv and
    +0031 %     --identity_filter; this filter is applied in addition (default 1).
    +0032 % requireTools : cell
    +0033 %     predictors that must all have found the pair, by column name
    +0034 %     ('orthofinder', 'sonicparanoid', 'broccoli', 'oma') (default {}).
    +0035 % minIdentity : double
    +0036 %     minimum SuppOrth identity. A pair without an alignment hit fails this
    +0037 %     filter (default [], no filter).
    +0038 % maxEvalue : double
    +0039 %     maximum SuppOrth e-value. A pair without an alignment hit fails this
    +0040 %     filter (default [], no filter).
    +0041 % proteinToGene : cell, containers.Map or char
    +0042 %     protein id to model gene id, as an N-by-2 cell array, a
    +0043 %     containers.Map, a JSON file with one object of strings, or a
    +0044 %     two-column tab-separated file (lines starting with # are skipped).
    +0045 %     Proteins missing from the map keep their id, and their number is
    +0046 %     reported in a warning. A gene pair takes the highest support over its
    +0047 %     protein pairs, the union of their predictors, the highest identity
    +0048 %     and bitscore, and the lowest e-value; these values can come from
    +0049 %     different protein pairs (default [], no mapping).
    +0050 %
    +0051 % Returns
    +0052 % -------
    +0053 % blastStructure : struct
    +0054 %     one element per search direction between organismID and each other
    +0055 %     species, as from getBlast or getDiamond. Every pair is written in
    +0056 %     both directions, so strictness 1 and 2 of getModelFromHomology give
    +0057 %     the same result. evalue, identity, aligLen and ppos are placeholders
    +0058 %     (0, 100, 1000, 100) that pass the filters of getModelFromHomology;
    +0059 %     bitscore is SuppOrth's value (0 when it has none), which strictness 3
    +0060 %     ranks by.
    +0061 % suppOrthInfo : table
    +0062 %     one row per gene pair, with modelId, newGene, templateGene, support,
    +0063 %     supportedBy, identity, evalue and bitscore (SuppOrth's values).
    +0064 %
    +0065 % Notes
    +0066 % -----
    +0067 % Only pairs between organismID and another species are kept. Species that
    +0068 % are not templates in getModelFromHomology have to be left out with
    +0069 % speciesToModel, because getModelFromHomology requires every id in the
    +0070 % blastStructure to be the new organism or a template.
    +0071 %
    +0072 % Examples
    +0073 % --------
    +0074 %     blastStructure = hitsFromSuppOrth('out/suppOrth.tsv','klac',...
    +0075 %        'minSupport',2,'proteinToGene','klac_genes.tsv');
    +0076 %
    +0077 % See also
    +0078 % --------
    +0079 % runSuppOrth, getModelFromHomology, getDiamond
    +0080 
    +0081 p=parseRAVENargs(varargin, {'speciesToModel',[]; 'minSupport',1; ...
    +0082     'requireTools',{}; 'minIdentity',[]; 'maxEvalue',[]; 'proteinToGene',[]});
    +0083 organismID=char(organismID);
    +0084 requireTools=cellstr(p.requireTools);
    +0085 if isempty(p.requireTools)
    +0086     requireTools={};
    +0087 end
    +0088 
    +0089 T=readTable(source);
    +0090 columns=T.Properties.VariableNames;
    +0091 
    +0092 labels=unique([T.Species1;T.Species2]);
    +0093 if ~ismember(organismID,labels)
    +0094     error('RAVEN:badInput','organismID "%s" is not a species in the table: %s',...
    +0095         organismID,strjoin(labels,', '));
    +0096 end
    +0097 if isempty(p.speciesToModel)
    +0098     idOf=containers.Map(labels,labels);
    +0099 else
    +0100     idOf=toMap(p.speciesToModel);
    +0101     unmapped=labels(~isKey(idOf,labels));
    +0102     if ~isempty(unmapped)
    +0103         error('RAVEN:badInput',['speciesToModel has no entry for %s; map each '...
    +0104             'label to a model id, or to '''' to leave it out.'],strjoin(unmapped,', '));
    +0105     end
    +0106     if isempty(idOf(organismID))
    +0107         error('RAVEN:badInput','speciesToModel maps organismID "%s" to ''''.',organismID);
    +0108     end
    +0109 end
    +0110 
    +0111 %The per-predictor 0/1 columns are those between SupportedBy and Identity
    +0112 alignment={'Identity','e_val','Bitscore'};
    +0113 start=find(strcmp(columns,'SupportedBy'))+1;
    +0114 stop=find(strcmp(columns,'Identity'))-1;
    +0115 if isempty(stop)
    +0116     stop=numel(columns);
    +0117 end
    +0118 toolColumns=setdiff(columns(start:stop),alignment,'stable');
    +0119 unknown=setdiff(requireTools,toolColumns,'stable');
    +0120 if ~isempty(unknown)
    +0121     error('RAVEN:badInput','requireTools %s not in the table; its predictors are %s',...
    +0122         strjoin(unknown,', '),strjoin(toolColumns,', '));
    +0123 end
    +0124 
    +0125 keep=T.Lv_support>=p.minSupport;
    +0126 for i=1:numel(requireTools)
    +0127     keep=keep & toNumber(T.(requireTools{i}))==1;
    +0128 end
    +0129 if ~isempty(p.minIdentity)
    +0130     keep=keep & T.Identity>=p.minIdentity; %NaN fails
    +0131 end
    +0132 if ~isempty(p.maxEvalue)
    +0133     keep=keep & T.e_val<=p.maxEvalue; %NaN fails
    +0134 end
    +0135 T=T(keep,:);
    +0136 
    +0137 %Orient every pair as (organism protein, template species, template protein)
    +0138 first=strcmp(T.Species1,organismID) & ~strcmp(T.Species2,organismID);
    +0139 second=strcmp(T.Species2,organismID) & ~strcmp(T.Species1,organismID);
    +0140 newProt=[T.Protein1(first);T.Protein2(second)];
    +0141 label=[T.Species2(first);T.Species1(second)];
    +0142 template=[T.Protein2(first);T.Protein1(second)];
    +0143 support=[T.Lv_support(first);T.Lv_support(second)];
    +0144 supportedBy=[T.SupportedBy(first);T.SupportedBy(second)];
    +0145 identity=[T.Identity(first);T.Identity(second)];
    +0146 evalue=[T.e_val(first);T.e_val(second)];
    +0147 bitscore=[T.Bitscore(first);T.Bitscore(second)];
    +0148 modelId=values(idOf,label);
    +0149 modelId=modelId(:);
    +0150 I=~cellfun(@isempty,modelId);
    +0151 [newProt,template,modelId,support,supportedBy,identity,evalue,bitscore]=...
    +0152     deal(newProt(I),template(I),modelId(I),support(I),supportedBy(I),...
    +0153     identity(I),evalue(I),bitscore(I));
    +0154 
    +0155 if ~isempty(p.proteinToGene)
    +0156     geneOf=toMap(p.proteinToGene);
    +0157     proteins=unique([newProt;template]);
    +0158     nMissing=sum(~isKey(geneOf,proteins));
    +0159     if nMissing>0
    +0160         warning('RAVEN:hitsFromSuppOrth:unmappedProteins',['%d of %d proteins are not '...
    +0161             'in proteinToGene and keep their protein id.'],nMissing,numel(proteins));
    +0162     end
    +0163     newProt=mapIds(geneOf,newProt);
    +0164     template=mapIds(geneOf,template);
    +0165 end
    +0166 
    +0167 if isempty(newProt)
    +0168     error('RAVEN:badInput',['No SuppOrth pairs between "%s" and a template species '...
    +0169         'remain after filtering.'],organismID);
    +0170 end
    +0171 
    +0172 %One row per gene pair, sorted by model, new gene and template gene
    +0173 key=strcat(modelId,char(31),newProt,char(31),template);
    +0174 [key,firstRow,group]=unique(key);
    +0175 info=table();
    +0176 info.modelId=modelId(firstRow);
    +0177 info.newGene=newProt(firstRow);
    +0178 info.templateGene=template(firstRow);
    +0179 info.support=accumarray(group,support,[],@max);
    +0180 rows=accumarray(group,(1:numel(group))',[],@(r) {r});
    +0181 info.supportedBy=cellfun(@(r) joinLabels(supportedBy(r)),rows,'UniformOutput',false);
    +0182 info.identity=accumarray(group,identity,[],@max);
    +0183 info.evalue=accumarray(group,evalue,[],@min);
    +0184 info.bitscore=accumarray(group,bitscore,[],@max);
    +0185 suppOrthInfo=info;
    +0186 
    +0187 newId=idOf(organismID);
    +0188 score=info.bitscore;
    +0189 score(isnan(score))=0;
    +0190 models=unique(info.modelId);
    +0191 blastStructure=[];
    +0192 for direction=1:2
    +0193     for i=1:numel(models)
    +0194         r=strcmp(info.modelId,models{i});
    +0195         element=[];
    +0196         if direction==1
    +0197             element.fromId=models{i};
    +0198             element.toId=newId;
    +0199             element.fromGenes=info.templateGene(r);
    +0200             element.toGenes=info.newGene(r);
    +0201         else
    +0202             element.fromId=newId;
    +0203             element.toId=models{i};
    +0204             element.fromGenes=info.newGene(r);
    +0205             element.toGenes=info.templateGene(r);
    +0206         end
    +0207         n=sum(r);
    +0208         element.evalue=zeros(n,1);
    +0209         element.identity=100*ones(n,1);
    +0210         element.aligLen=1000*ones(n,1);
    +0211         element.bitscore=score(r);
    +0212         element.ppos=100*ones(n,1);
    +0213         blastStructure=[blastStructure element]; %#ok<AGROW>
    +0214     end
    +0215 end
    +0216 end
    +0217 
    +0218 function T=readTable(source)
    +0219 required={'Species1','Species2','Protein1','Protein2','Lv_support','SupportedBy'};
    +0220 text=[required(1:4),{'SupportedBy'}];
    +0221 if istable(source)
    +0222     T=source;
    +0223 else
    +0224     source=char(source);
    +0225     if ~isfile(source)
    +0226         error('RAVEN:badInput','SuppOrth table "%s" does not exist.',source);
    +0227     end
    +0228     lines=splitlines(fileread(source));
    +0229     lines=lines(~cellfun(@isempty,lines));
    +0230     header=strsplit(lines{1},'\t','CollapseDelimiters',false);
    +0231     fields=regexp(lines(2:end),'\t','split');
    +0232     width=cellfun(@numel,fields);
    +0233     bad=find(width~=numel(header),1);
    +0234     if ~isempty(bad)
    +0235         error('RAVEN:badInput','%s: line %d has %d fields, the header has %d.',...
    +0236             source,bad+1,width(bad),numel(header));
    +0237     end
    +0238     fields=vertcat(fields{:});
    +0239     if isempty(fields)
    +0240         fields=cell(0,numel(header));
    +0241     end
    +0242     T=cell2table(fields,'VariableNames',header);
    +0243 end
    +0244 missing=setdiff(required,T.Properties.VariableNames,'stable');
    +0245 if ~isempty(missing)
    +0246     error('RAVEN:badInput',['Not a suppOrth.tsv table (SuppOrth 0.4): missing columns %s. '...
    +0247         'suppOrth.genes.tsv is not supported; use proteinToGene instead.'],strjoin(missing,', '));
    +0248 end
    +0249 %Protein ids such as "NA" or "0123" stay text
    +0250 for i=1:numel(text)
    +0251     T.(text{i})=cellstr(string(T.(text{i})));
    +0252 end
    +0253 for c={'Identity','e_val','Bitscore'}
    +0254     if ismember(c{1},T.Properties.VariableNames)
    +0255         T.(c{1})=toNumber(T.(c{1}));
    +0256     else
    +0257         T.(c{1})=nan(height(T),1);
    +0258     end
    +0259 end
    +0260 T.Lv_support=toNumber(T.Lv_support);
    +0261 end
    +0262 
    +0263 function x=toNumber(x)
    +0264 %Read numbers exactly; empty and non-numeric fields become NaN
    +0265 if ~isnumeric(x)
    +0266     x=str2double(x);
    +0267 end
    +0268 x=double(x(:));
    +0269 end
    +0270 
    +0271 function m=toMap(x)
    +0272 if isa(x,'containers.Map')
    +0273     m=x;
    +0274 elseif iscell(x)
    +0275     if size(x,2)~=2
    +0276         error('RAVEN:badInput','A cell array map needs two columns.');
    +0277     end
    +0278     m=containers.Map(cellstr(string(x(:,1))),cellstr(string(x(:,2))));
    +0279 elseif ischar(x) || isstring(x)
    +0280     m=readMapFile(char(x));
    +0281 else
    +0282     error('RAVEN:badInput','A map must be a two-column cell array, a containers.Map or a file.');
    +0283 end
    +0284 end
    +0285 
    +0286 function m=readMapFile(file)
    +0287 [~,~,ext]=fileparts(file);
    +0288 if any(strcmpi(ext,{'.pkl','.pickle'}))
    +0289     error('RAVEN:badInput',['%s: pickle maps are not read; use a cell array, '...
    +0290         'a JSON file or a two-column tab-separated file.'],file);
    +0291 end
    +0292 if ~isfile(file)
    +0293     error('RAVEN:badInput','Map file "%s" does not exist.',file);
    +0294 end
    +0295 text=fileread(file);
    +0296 if strcmpi(ext,'.json')
    +0297     %Keys are protein ids, which jsondecode would turn into field names
    +0298     pairs=regexp(text,'"((?:[^"\\]|\\.)*)"\s*:\s*"((?:[^"\\]|\\.)*)"','tokens');
    +0299     pairs=vertcat(pairs{:});
    +0300     if isempty(pairs)
    +0301         m=containers.Map('KeyType','char','ValueType','char');
    +0302         return
    +0303     end
    +0304     pairs=regexprep(pairs,'\\(["\\/])','$1');
    +0305     m=containers.Map(pairs(:,1),pairs(:,2));
    +0306     return
    +0307 end
    +0308 lines=splitlines(text);
    +0309 lines=lines(~cellfun(@isempty,strtrim(lines)) & ~startsWith(lines,'#'));
    +0310 fields=regexp(lines,'\t','split');
    +0311 short=find(cellfun(@numel,fields)<2,1);
    +0312 if ~isempty(short)
    +0313     error('RAVEN:badInput','%s: expected two tab-separated columns, got "%s"',file,lines{short});
    +0314 end
    +0315 keys=strtrim(cellfun(@(f) f{1},fields,'UniformOutput',false));
    +0316 vals=strtrim(cellfun(@(f) f{2},fields,'UniformOutput',false));
    +0317 if isempty(keys)
    +0318     m=containers.Map('KeyType','char','ValueType','char');
    +0319 else
    +0320     m=containers.Map(keys,vals);
    +0321 end
    +0322 end
    +0323 
    +0324 function ids=mapIds(m,ids)
    +0325 known=isKey(m,ids);
    +0326 if any(known)
    +0327     ids(known)=values(m,ids(known));
    +0328 end
    +0329 end
    +0330 
    +0331 function joined=joinLabels(labels)
    +0332 %Union of comma-separated predictor labels, in SuppOrth's F, S, B, O order
    +0333 found=strtrim(split(strjoin(labels,','),','));
    +0334 found=unique(found(~cellfun(@isempty,found)));
    +0335 order={'F','S','B','O'};
    +0336 [isKnown,rank]=ismember(found,order);
    +0337 known=found(isKnown);
    +0338 [~,o]=sort(rank(isKnown));
    +0339 joined=strjoin([known(o);sort(found(~isKnown))],',');
    +0340 end
    +
    Generated by m2html © 2005
    + + \ No newline at end of file diff --git a/docs/html/reconstruction/homology/index.html b/docs/html/reconstruction/homology/index.html index c8786ec7..5c4a5b2b 100644 --- a/docs/html/reconstruction/homology/index.html +++ b/docs/html/reconstruction/homology/index.html @@ -19,7 +19,7 @@

    Index for reconstruction/homology

    Matlab files in this directory:

    -
     getBlastgetBlast Bidirectional BLAST between an organism and template organisms.
     getDiamondgetDiamond Bidirectional BLAST with DIAMOND against template organisms.
     getModelFromHomologygetModelFromHomology Construct a new model from existing models and homology.
     makeFakeBlastStructuremakeFakeBlastStructure Make a fake blastStructure from an ortholog list.
    + getBlastgetBlast Bidirectional BLAST between an organism and template organisms.  getDiamondgetDiamond Bidirectional BLAST with DIAMOND against template organisms.  getModelFromHomologygetModelFromHomology Construct a new model from existing models and homology.  hitsFromSuppOrthhitsFromSuppOrth Read SuppOrth consensus orthologs into a blastStructure.  makeFakeBlastStructuremakeFakeBlastStructure Make a fake blastStructure from an ortholog list.  runSuppOrthrunSuppOrth Run SuppOrth on an organism and template proteomes. diff --git a/docs/html/reconstruction/homology/makeFakeBlastStructure.html b/docs/html/reconstruction/homology/makeFakeBlastStructure.html index de9f71a7..287b2148 100644 --- a/docs/html/reconstruction/homology/makeFakeBlastStructure.html +++ b/docs/html/reconstruction/homology/makeFakeBlastStructure.html @@ -62,7 +62,7 @@

    DESCRIPTION ^getModelFromHomology, getBlast + getModelFromHomology, getBlast, hitsFromSuppOrth

    CROSS-REFERENCE INFORMATION ^

    @@ -113,7 +113,7 @@

    SOURCE CODE ^% 0035 % See also 0036 % -------- -0037 % getModelFromHomology, getBlast +0037 % getModelFromHomology, getBlast, hitsFromSuppOrth 0038 0039 if nargin<3 0040 error('All three parameters should be set'); diff --git a/docs/html/reconstruction/homology/runSuppOrth.html b/docs/html/reconstruction/homology/runSuppOrth.html new file mode 100644 index 00000000..63c57d32 --- /dev/null +++ b/docs/html/reconstruction/homology/runSuppOrth.html @@ -0,0 +1,401 @@ + + + + Description of runSuppOrth + + + + + + + + + +
    Home > reconstruction > homology > runSuppOrth.m
    + + + +

    runSuppOrth +

    + +

    PURPOSE ^

    +
    runSuppOrth Run SuppOrth on an organism and template proteomes.
    + +

    SYNOPSIS ^

    +
    function [blastStructure,suppOrthInfo]=runSuppOrth(organismID,fastaFile,modelIDs,refFastaFiles,outDir,varargin)
    + +

    DESCRIPTION ^

    +
     runSuppOrth  Run SuppOrth on an organism and template proteomes.
    +
    + Runs the suppOrth command (https://github.com/fma91/SuppOrth) and reads
    + its consensus orthologs with hitsFromSuppOrth.
    +
    + Parameters
    + ----------
    + organismID : char
    +     the id of the organism of interest, as in getModelFromHomology.
    + fastaFile : char
    +     a FASTA file (optionally gzipped) with the proteins of the organism.
    + modelIDs : cell
    +     the template model ids, as "model.id" in getModelFromHomology.
    + refFastaFiles : cell
    +     the proteome of each template, in the order of modelIDs, with FASTA
    +     ids equal to the gene ids of that model (or mapped to them with
    +     proteinToGene).
    + outDir : char
    +     folder for SuppOrth's output.
    +
    + Name-Value Arguments
    + --------------------
    + tools : char
    +     comma-separated predictors, or 'all' for orthofinder, sonicparanoid
    +     and broccoli (default 'all').
    + supportLv : double
    +     SuppOrth's --supportLv: a pair is kept if at least this many
    +     predictors found it, or if its identity is above identityFilter
    +     (default 3, or the number of tools when fewer are selected).
    + identityFilter : double
    +     SuppOrth's --identity_filter (default 80).
    + alignment : char
    +     SuppOrth's --alignment (default 'diamond').
    + threads : double
    +     number of threads (default: the cores assigned to MATLAB).
    + extraArgs : cell
    +     further arguments for "suppOrth run", for example {'--resume'} or
    +     {'--oma','path/to/Output'} (default {}).
    + suppOrth : char
    +     the suppOrth command. If empty, the RAVEN_SUPPORTH environment
    +     variable is used, and then suppOrth on the PATH (default '').
    +
    + The name-value arguments of hitsFromSuppOrth (minSupport, requireTools,
    + minIdentity, maxEvalue, proteinToGene, speciesToModel) are passed on.
    +
    + Returns
    + -------
    + blastStructure : struct
    +     as from hitsFromSuppOrth, for getModelFromHomology.
    + suppOrthInfo : table
    +     SuppOrth's values per gene pair, as from hitsFromSuppOrth.
    +
    + Notes
    + -----
    + SuppOrth requires protein ids to be unique over all proteomes. When two
    + FASTA files share an id (for example two templates of the same species),
    + every id is given a "<label>__" prefix in copies written to
    + outDir/prefixed_inputs, and the prefix is removed from the returned
    + hits. outDir/suppOrth.tsv then contains the prefixed ids. SuppOrth's
    + console output is written to outDir/run_supporth.log.
    +
    + SuppOrth's predictors run on Linux and macOS only. On Windows, run
    + SuppOrth under WSL or on a Linux host, and read suppOrth.tsv with
    + hitsFromSuppOrth.
    +
    + Examples
    + --------
    +     blastStructure = runSuppOrth('klac','klac.fasta',{'yeastGEM'},...
    +        {'yeast.fasta'},'supporth_out','proteinToGene','klac_genes.tsv');
    +     draftModel = getModelFromHomology(yeastGEM,blastStructure,'klac');
    +
    + See also
    + --------
    + hitsFromSuppOrth, getModelFromHomology, getDiamond
    + + +

    CROSS-REFERENCE INFORMATION ^

    +This function calls: +
      +
    • hitsFromSuppOrth hitsFromSuppOrth Read SuppOrth consensus orthologs into a blastStructure.
    +This function is called by: +
      +
    + + +

    SUBFUNCTIONS ^

    + + +

    SOURCE CODE ^

    +
    0001 function [blastStructure,suppOrthInfo]=runSuppOrth(organismID,fastaFile,modelIDs,refFastaFiles,outDir,varargin)
    +0002 % runSuppOrth  Run SuppOrth on an organism and template proteomes.
    +0003 %
    +0004 % Runs the suppOrth command (https://github.com/fma91/SuppOrth) and reads
    +0005 % its consensus orthologs with hitsFromSuppOrth.
    +0006 %
    +0007 % Parameters
    +0008 % ----------
    +0009 % organismID : char
    +0010 %     the id of the organism of interest, as in getModelFromHomology.
    +0011 % fastaFile : char
    +0012 %     a FASTA file (optionally gzipped) with the proteins of the organism.
    +0013 % modelIDs : cell
    +0014 %     the template model ids, as "model.id" in getModelFromHomology.
    +0015 % refFastaFiles : cell
    +0016 %     the proteome of each template, in the order of modelIDs, with FASTA
    +0017 %     ids equal to the gene ids of that model (or mapped to them with
    +0018 %     proteinToGene).
    +0019 % outDir : char
    +0020 %     folder for SuppOrth's output.
    +0021 %
    +0022 % Name-Value Arguments
    +0023 % --------------------
    +0024 % tools : char
    +0025 %     comma-separated predictors, or 'all' for orthofinder, sonicparanoid
    +0026 %     and broccoli (default 'all').
    +0027 % supportLv : double
    +0028 %     SuppOrth's --supportLv: a pair is kept if at least this many
    +0029 %     predictors found it, or if its identity is above identityFilter
    +0030 %     (default 3, or the number of tools when fewer are selected).
    +0031 % identityFilter : double
    +0032 %     SuppOrth's --identity_filter (default 80).
    +0033 % alignment : char
    +0034 %     SuppOrth's --alignment (default 'diamond').
    +0035 % threads : double
    +0036 %     number of threads (default: the cores assigned to MATLAB).
    +0037 % extraArgs : cell
    +0038 %     further arguments for "suppOrth run", for example {'--resume'} or
    +0039 %     {'--oma','path/to/Output'} (default {}).
    +0040 % suppOrth : char
    +0041 %     the suppOrth command. If empty, the RAVEN_SUPPORTH environment
    +0042 %     variable is used, and then suppOrth on the PATH (default '').
    +0043 %
    +0044 % The name-value arguments of hitsFromSuppOrth (minSupport, requireTools,
    +0045 % minIdentity, maxEvalue, proteinToGene, speciesToModel) are passed on.
    +0046 %
    +0047 % Returns
    +0048 % -------
    +0049 % blastStructure : struct
    +0050 %     as from hitsFromSuppOrth, for getModelFromHomology.
    +0051 % suppOrthInfo : table
    +0052 %     SuppOrth's values per gene pair, as from hitsFromSuppOrth.
    +0053 %
    +0054 % Notes
    +0055 % -----
    +0056 % SuppOrth requires protein ids to be unique over all proteomes. When two
    +0057 % FASTA files share an id (for example two templates of the same species),
    +0058 % every id is given a "<label>__" prefix in copies written to
    +0059 % outDir/prefixed_inputs, and the prefix is removed from the returned
    +0060 % hits. outDir/suppOrth.tsv then contains the prefixed ids. SuppOrth's
    +0061 % console output is written to outDir/run_supporth.log.
    +0062 %
    +0063 % SuppOrth's predictors run on Linux and macOS only. On Windows, run
    +0064 % SuppOrth under WSL or on a Linux host, and read suppOrth.tsv with
    +0065 % hitsFromSuppOrth.
    +0066 %
    +0067 % Examples
    +0068 % --------
    +0069 %     blastStructure = runSuppOrth('klac','klac.fasta',{'yeastGEM'},...
    +0070 %        {'yeast.fasta'},'supporth_out','proteinToGene','klac_genes.tsv');
    +0071 %     draftModel = getModelFromHomology(yeastGEM,blastStructure,'klac');
    +0072 %
    +0073 % See also
    +0074 % --------
    +0075 % hitsFromSuppOrth, getModelFromHomology, getDiamond
    +0076 
    +0077 readerArgs={'speciesToModel',[]; 'minSupport',1; 'requireTools',{}; ...
    +0078     'minIdentity',[]; 'maxEvalue',[]; 'proteinToGene',[]};
    +0079 p=parseRAVENargs(varargin, [{'tools','all'; 'supportLv',[]; 'identityFilter',80; ...
    +0080     'alignment','diamond'; 'threads',[]; 'extraArgs',{}; 'suppOrth',''}; readerArgs]);
    +0081 
    +0082 if ispc
    +0083     error('RAVEN:badInput',['SuppOrth''s predictors run on Linux and macOS only. Run '...
    +0084         'SuppOrth under WSL or on a Linux host, and read suppOrth.tsv with hitsFromSuppOrth.']);
    +0085 end
    +0086 organismID=char(organismID);
    +0087 modelIDs=cellstr(modelIDs);
    +0088 refFastaFiles=cellstr(refFastaFiles);
    +0089 if numel(modelIDs)~=numel(refFastaFiles)
    +0090     error('RAVEN:badInput','modelIDs and refFastaFiles must have the same length.');
    +0091 end
    +0092 if isempty(modelIDs)
    +0093     error('RAVEN:badInput','At least one template is needed.');
    +0094 end
    +0095 labels=[{organismID};modelIDs(:)];
    +0096 for i=1:numel(labels)
    +0097     l=labels{i};
    +0098     if isempty(l) || any(strcmp(l,{'.','..'})) || ~strcmp(l,strtrim(l)) || any(ismember(l,',/\'))
    +0099         error('RAVEN:badInput',['"%s" cannot be a SuppOrth species label: it must be '...
    +0100             'non-empty, not "." or "..", have no leading or trailing space, and contain '...
    +0101             'no ",", "/" or "\\".'],l);
    +0102     end
    +0103 end
    +0104 if numel(unique(labels))~=numel(labels)
    +0105     error('RAVEN:badInput','organismID and modelIDs must be distinct, got %s',strjoin(labels,', '));
    +0106 end
    +0107 
    +0108 tools=strtrim(strsplit(char(p.tools),','));
    +0109 tools=tools(~cellfun(@isempty,tools));
    +0110 if isequal(tools,{'all'})
    +0111     tools={'orthofinder','sonicparanoid','broccoli'};
    +0112 end
    +0113 supportLv=p.supportLv;
    +0114 if isempty(supportLv)
    +0115     supportLv=min(3,numel(tools));
    +0116 end
    +0117 if supportLv<1 || supportLv>4
    +0118     error('RAVEN:badInput','supportLv must be between 1 and 4, got %d',supportLv);
    +0119 end
    +0120 extraArgs=cellstr(p.extraArgs);
    +0121 if isempty(p.extraArgs)
    +0122     extraArgs={};
    +0123 end
    +0124 if supportLv>numel(tools) && ~any(strcmp(extraArgs,'--oma'))
    +0125     warning('RAVEN:runSuppOrth:supportLv',['supportLv=%d is more than the %d predictor(s) '...
    +0126         'selected; SuppOrth then keeps only pairs above identityFilter (%g %%).'],...
    +0127         supportLv,numel(tools),p.identityFilter);
    +0128 end
    +0129 threads=p.threads;
    +0130 if isempty(threads)
    +0131     cores=evalc('feature(''numcores'')');
    +0132     cores=strsplit(cores,'MATLAB was assigned: ');
    +0133     threads=str2double(regexp(cores{2},'^\d*','match','once'));
    +0134 end
    +0135 
    +0136 exe=resolveSuppOrth(char(p.suppOrth));
    +0137 fastas=[{char(fastaFile)};refFastaFiles(:)];
    +0138 for i=1:numel(fastas)
    +0139     if ~isfile(fastas{i})
    +0140         error('RAVEN:badInput','FASTA file "%s" does not exist.',fastas{i});
    +0141     end
    +0142 end
    +0143 if ~isfolder(outDir)
    +0144     mkdir(outDir);
    +0145 end
    +0146 
    +0147 %SuppOrth requires protein ids to be unique over all proteomes. Templates of
    +0148 %the same species often share gene ids, so in that case every id gets a
    +0149 %"<label>__" prefix in a copy of its FASTA, and the prefix is removed below.
    +0150 ids=cell(numel(fastas),1);
    +0151 for i=1:numel(fastas)
    +0152     ids{i}=fastaIds(fastas{i});
    +0153     ids{i}=unique(ids{i});
    +0154 end
    +0155 allIds=vertcat(ids{:});
    +0156 shared=numel(unique(allIds))<numel(allIds);
    +0157 if shared
    +0158     inputs=fullfile(outDir,'prefixed_inputs');
    +0159     if ~isfolder(inputs)
    +0160         mkdir(inputs);
    +0161     end
    +0162     for i=1:numel(fastas)
    +0163         dest=fullfile(inputs,[labels{i} '.faa']);
    +0164         writePrefixed(fastas{i},dest,[labels{i} '__']);
    +0165         fastas{i}=dest;
    +0166     end
    +0167 end
    +0168 
    +0169 args=[{exe,'run'},fastas',{'--labels',strjoin(labels,','),'-o',char(outDir),...
    +0170     '--tools',strjoin(tools,','),'--supportLv',num2str(supportLv),...
    +0171     '--identity_filter',num2str(p.identityFilter),'--alignment',char(p.alignment),...
    +0172     '-t',num2str(threads)},extraArgs(:)'];
    +0173 cmd=strjoin(cellfun(@shellQuote,args,'UniformOutput',false),' ');
    +0174 %MATLAB's library path can break the predictors' compiled dependencies
    +0175 [status,output]=system(['env -u LD_LIBRARY_PATH -u DYLD_LIBRARY_PATH ' cmd ' 2>&1']);
    +0176 logFile=fullfile(outDir,'run_supporth.log');
    +0177 fid=fopen(logFile,'w');
    +0178 fprintf(fid,'%s\n\n%s\n',strjoin(args,' '),strtrim(output));
    +0179 fclose(fid);
    +0180 if status~=0
    +0181     lines=splitlines(strtrim(output));
    +0182     %SuppOrth reports a failed stage before the consensus summary, which can
    +0183     %push it out of the last lines
    +0184     head=lines(1:max(0,end-20));
    +0185     failed=head(contains(head,'FAILED') | startsWith(head,'error'));
    +0186     tail=[failed;lines(max(1,end-19):end)];
    +0187     error('RAVEN:runSuppOrth:failed',['suppOrth exited with code %d; its output in %s '...
    +0188         'is incomplete and was not read. Full output in %s.\n%s'],status,char(outDir),...
    +0189         logFile,strjoin(tail,newline));
    +0190 end
    +0191 
    +0192 source=fullfile(outDir,'suppOrth.tsv');
    +0193 if shared
    +0194     source=stripPrefixes(source);
    +0195 end
    +0196 readerValues=cellfun(@(n) p.(n),readerArgs(:,1),'UniformOutput',false);
    +0197 reader=[readerArgs(:,1),readerValues]';
    +0198 [blastStructure,suppOrthInfo]=hitsFromSuppOrth(source,organismID,reader{:});
    +0199 end
    +0200 
    +0201 function exe=resolveSuppOrth(exe)
    +0202 if ~isempty(exe)
    +0203     return
    +0204 end
    +0205 exe=getenv('RAVEN_SUPPORTH');
    +0206 if ~isempty(exe)
    +0207     return
    +0208 end
    +0209 for name={'suppOrth','supporth'}
    +0210     [status,found]=system(['command -v ' name{1}]);
    +0211     if status==0 && ~isempty(strtrim(found))
    +0212         exe=strtrim(found);
    +0213         return
    +0214     end
    +0215 end
    +0216 error('RAVEN:runSuppOrth:notFound',['Could not find the SuppOrth command suppOrth. '...
    +0217     'Install it with pip install ''git+https://github.com/fma91/SuppOrth.git@stand_alone'' followed '...
    +0218     'by suppOrth install, then put suppOrth on the PATH, set RAVEN_SUPPORTH, or pass '...
    +0219     'the suppOrth argument.']);
    +0220 end
    +0221 
    +0222 function lines=readFastaLines(file)
    +0223 [~,~,ext]=fileparts(file);
    +0224 if strcmpi(ext,'.gz')
    +0225     tmp=tempname;
    +0226     unzipped=gunzip(file,tmp);
    +0227     text=fileread(unzipped{1});
    +0228     rmdir(tmp,'s');
    +0229 else
    +0230     text=fileread(file);
    +0231 end
    +0232 lines=splitlines(text);
    +0233 end
    +0234 
    +0235 function ids=fastaIds(file)
    +0236 %Sequence ids as SuppOrth reads them: the first word of each header line
    +0237 lines=readFastaLines(file);
    +0238 headers=lines(startsWith(lines,'>'));
    +0239 words=regexp(headers,'^>\s*(\S+)','tokens','once');
    +0240 words=words(~cellfun(@isempty,words));
    +0241 ids=cellfun(@(w) w{1},words,'UniformOutput',false);
    +0242 end
    +0243 
    +0244 function writePrefixed(src,dest,prefix)
    +0245 lines=readFastaLines(src);
    +0246 isHeader=startsWith(lines,'>');
    +0247 lines(isHeader)=strcat('>',prefix,extractAfter(lines(isHeader),1));
    +0248 if ~isempty(lines) && isempty(lines{end})
    +0249     lines(end)=[];
    +0250 end
    +0251 fid=fopen(dest,'w');
    +0252 fprintf(fid,'%s\n',lines{:});
    +0253 fclose(fid);
    +0254 end
    +0255 
    +0256 function T=stripPrefixes(file)
    +0257 %Remove the "<label>__" prefix that runSuppOrth added to protein ids
    +0258 lines=splitlines(fileread(file));
    +0259 lines=lines(~cellfun(@isempty,lines));
    +0260 header=strsplit(lines{1},'\t','CollapseDelimiters',false);
    +0261 fields=regexp(lines(2:end),'\t','split');
    +0262 fields=vertcat(fields{:});
    +0263 if isempty(fields)
    +0264     fields=cell(0,numel(header));
    +0265 end
    +0266 T=cell2table(fields,'VariableNames',header);
    +0267 for pair={{'Species1','Protein1'},{'Species2','Protein2'}}
    +0268     s=T.(pair{1}{1});
    +0269     prot=T.(pair{1}{2});
    +0270     prefix=strcat(s,'__');
    +0271     has=cellfun(@startsWith,prot,prefix);
    +0272     prot(has)=cellfun(@(x,pre) x(numel(pre)+1:end),prot(has),prefix(has),'UniformOutput',false);
    +0273     T.(pair{1}{2})=prot;
    +0274 end
    +0275 end
    +0276 
    +0277 function q=shellQuote(s)
    +0278 q=['''' strrep(char(s),'''','''\''''') ''''];
    +0279 end
    +
    Generated by m2html © 2005
    + + \ No newline at end of file From e44fcf08536854859ec6fdfbc21df98a5be746f2 Mon Sep 17 00:00:00 2001 From: Eduard Kerkhoven Date: Sat, 17 Oct 2026 08:25:49 +0200 Subject: [PATCH 21/48] Download assets from CompMetEng/toolbox-assets with fetchToolboxAsset (#5) raven-data moved to CompMetEng/toolbox-assets, which holds the downloadable assets of RAVEN, raven-toolbox, GECKO and geckopy. fetchRavenDataAsset is renamed fetchToolboxAsset and downloads from the new repository; every caller and reference is updated. Original-commit: https://github.com/SysBioChalmers/RAVEN/commit/f50baf2ab2079d4338010dcd6d4094cf40d373bd --- .github/scripts/assemble_offline_software.py | 14 +++++++------- .github/workflows/tests.yml | 2 +- checkRaven.m | 6 +++--- docs/v3_review.md | 6 +++--- src/installation/downloadRavenBinaries.m | 10 +++++----- .../{fetchRavenDataAsset.m => fetchToolboxAsset.m} | 13 +++++++------ src/reconstruction/kegg/KEGG_VERSION.md | 4 ++-- src/reconstruction/kegg/buildGlobalKEGGModel.m | 4 ++-- src/reconstruction/kegg/getKEGGModelForOrganism.m | 8 ++++---- src/reconstruction/kegg/getModelFromKEGG.m | 2 +- src/reconstruction/kegg/getPhylDist.m | 3 ++- src/reconstruction/kegg/keggDataVersion.m | 6 +++--- tests/function_tests/tBinaries.m | 4 ++-- tests/function_tests/tReconstruction.m | 4 ++-- 14 files changed, 44 insertions(+), 42 deletions(-) rename src/installation/{fetchRavenDataAsset.m => fetchToolboxAsset.m} (68%) diff --git a/.github/scripts/assemble_offline_software.py b/.github/scripts/assemble_offline_software.py index 4193fcf2..52be9a27 100644 --- a/.github/scripts/assemble_offline_software.py +++ b/.github/scripts/assemble_offline_software.py @@ -1,14 +1,14 @@ #!/usr/bin/env python """Populate RAVEN's src/external/ with every platform's external binaries. -Downloads the per-platform binary ZIPs from raven-data and lays them out under +Downloads the per-platform binary ZIPs from toolbox-assets and lays them out under ``/src/external//`` using RAVEN's suffix scheme — bare name = Linux, ``.mac`` = macOS, ``.exe`` = Windows. With all platforms present in one tree, the whole RAVEN checkout can be zipped into a single **OS-independent** offline distribution (see the release-bundle workflow). Run from the RAVEN root, or pass ``--raven ``. Requires network access to -https://github.com/SysBioChalmers/raven-data . +https://github.com/CompMetEng/toolbox-assets . """ from __future__ import annotations @@ -18,11 +18,11 @@ import zipfile from pathlib import Path -RAVEN_DATA = "https://github.com/SysBioChalmers/raven-data/releases/download" -# raven-data platform key -> RAVEN binary suffix. +TOOLBOX_ASSETS = "https://github.com/CompMetEng/toolbox-assets/releases/download" +# toolbox-assets platform key -> RAVEN binary suffix. PLATFORMS = {"linux-x86_64": "", "macos-arm64": ".mac", "windows-x86_64": ".exe"} -# (software subdir, raven-data bundle, {platform|"_": version}, {bare executable names}) +# (software subdir, toolbox-assets bundle, {platform|"_": version}, {bare executable names}) CLI_TOOLS = [ ("blast+", "blast", {"_": "2.17.0"}, {"blastp", "makeblastdb"}), ("diamond", "diamond", {"_": "2.1.17"}, {"diamond"}), @@ -31,7 +31,7 @@ def _fetch(url: str) -> bytes: - with urllib.request.urlopen(url, timeout=120) as resp: # noqa: S310 (pinned raven-data URLs) + with urllib.request.urlopen(url, timeout=120) as resp: # noqa: S310 (pinned toolbox-assets URLs) return resp.read() @@ -60,7 +60,7 @@ def assemble(raven: Path) -> None: # macOS ships bare names; rename them to .mac. Linux keeps bare names, # Windows already carries .exe — so only the macOS members need renaming. rename = {e: e + ".mac" for e in exes} if suffix == ".mac" else {} - _extract(_fetch(f"{RAVEN_DATA}/{bundle}-{version}/{asset}"), dest, rename) + _extract(_fetch(f"{TOOLBOX_ASSETS}/{bundle}-{version}/{asset}"), dest, rename) def main(argv: list[str] | None = None) -> None: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8d4001f7..e895a36a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -72,7 +72,7 @@ jobs: binary-tests: name: Binary download (${{ matrix.os }}) - # Validate the on-demand fetch of the external binaries from raven-data on + # Validate the on-demand fetch of the external binaries from toolbox-assets on # Linux and macOS. Running the downloaded binaries also confirms the macOS # Gatekeeper quarantine is cleared so they can execute. strategy: diff --git a/checkRaven.m b/checkRaven.m index 3b471652..a0963f89 100755 --- a/checkRaven.m +++ b/checkRaven.m @@ -251,13 +251,13 @@ printOrange(' Skipping check of binary executables\n') else % The BLAST+/DIAMOND/HMMER binaries are downloaded on demand from - % raven-data; fetch any that are missing for this platform before + % toolbox-assets; fetch any that are missing for this platform before % checking. This doubles as the up-front "prefetch" for users who want % everything ready after installation. try downloadRavenBinaries({'blast+','diamond','hmmer'}); catch ME - printOrange([' Could not download binaries from raven-data: ' ME.message '\n' ... + printOrange([' Could not download binaries from toolbox-assets: ' ME.message '\n' ... ' (offline? fetch the "*-binaries" RAVEN release for an offline install)\n']); end % Run each bundled binary with a version/help flag to confirm it @@ -294,7 +294,7 @@ function testSolver(solver, model) function testBinary(ravenDir, tool, binName, versionArg) % Run a bundled binary with versionArg to confirm it executes; error if not. if ispc - binEnd = '.exe'; % native Windows builds (raven-data windows-x86_64 ZIPs) + binEnd = '.exe'; % native Windows builds (toolbox-assets windows-x86_64 ZIPs) elseif ismac binEnd = '.mac'; else diff --git a/docs/v3_review.md b/docs/v3_review.md index 82d9a4e0..03663bb2 100644 --- a/docs/v3_review.md +++ b/docs/v3_review.md @@ -30,7 +30,7 @@ So the review does not repeat completed work. v3 already: and `combnk`. - Dropped the Statistics, Bioinformatics and Text Analytics toolbox dependencies. - Moved KEGG HMM libraries and external binaries out of git to on-demand fetch from the - `raven-data` release. + `toolbox-assets` release. - Reorganized the repo into functional folders; reformatted docstrings to NumPy style. - Added `progressReport`, `parseRAVENargs` (positional-or-named arguments), and a `matlab.unittest` suite. @@ -147,7 +147,7 @@ Plus a standalone `mapIDsViaMNXref(type, ids, fromDB, toDB)` ID translator. 1. Relocate to the develop3 layout (e.g. `reconstruction/metanetx/` or `annotation/metanetx/`), drop the duplicated `struct_conversion/*`, rebase on develop3's `convertMiriams`. 2. **Delete the committed data**; host MNXref 4.5 (gzipped TSVs, and ideally a prebuilt `.mat`) - as a `raven-data` release asset and add a `downloadMNXref.m` modeled exactly on the + as a `toolbox-assets` release asset and add a `downloadMNXref.m` modeled exactly on the develop3 KEGG-HMM / `downloadRavenBinaries` on-demand pattern (sentinel-file check, cache under a gitignored dir). 3. Update parsing to the documented **4.5** columns (header-driven `readtable`, handle the @@ -379,6 +379,6 @@ tracked as a low-priority item in the Tier-3 table. 2. **Where should new strain-design / community code that needs metaheuristics live**, given the dependency question — lean on the Global Optimization Toolbox (`gamultiobj`), or keep v3's "minimize toolbox dependencies" stance and ship a self-contained EA? -4. **MNXref data hosting** — confirm the `raven-data` release is the right home for the MNXref +4. **MNXref data hosting** — confirm the `toolbox-assets` release is the right home for the MNXref 4.5 assets (mirroring KEGG HMMs/binaries), and whether to ship a prebuilt `.mat` (7 MB, fast) or only the gzipped TSVs (slower first build, always current). diff --git a/src/installation/downloadRavenBinaries.m b/src/installation/downloadRavenBinaries.m index 0aea8760..8df2e4a7 100644 --- a/src/installation/downloadRavenBinaries.m +++ b/src/installation/downloadRavenBinaries.m @@ -1,7 +1,7 @@ function downloadRavenBinaries(tools) % downloadRavenBinaries % Download RAVEN's external command-line binaries on demand from the -% raven-data repository into RAVEN's software/ directory. Only the binary +% toolbox-assets repository into RAVEN's src/external/ directory. Only the binary % matching the current platform is fetched, and only when needed. % % Input: @@ -14,7 +14,7 @@ function downloadRavenBinaries(tools) % unzip it into the RAVEN software/ directory instead. % % The binaries are hosted as release assets in -% https://github.com/SysBioChalmers/raven-data. +% https://github.com/CompMetEng/toolbox-assets. % % Usage: downloadRavenBinaries({'blast+','diamond'}) % @@ -29,7 +29,7 @@ function downloadRavenBinaries(tools) ravenDir = findRAVENroot(); -% raven-data platform key for the per-platform binary ZIPs. +% toolbox-assets platform key for the per-platform binary ZIPs. if ispc plat = 'windows-x86_64'; elseif ismac @@ -78,11 +78,11 @@ function downloadRavenBinaries(tools) continue; % already provisioned end - zipPath = fetchRavenDataAsset(fullfile(ravenDir,'src','external'),tag,asset); + zipPath = fetchToolboxAsset(fullfile(ravenDir,'src','external'),tag,asset); unzip(zipPath,destDir); delete(zipPath); - % macOS uses the .mac suffix throughout RAVEN, but the raven-data macOS ZIP + % macOS uses the .mac suffix throughout RAVEN, but the toolbox-assets macOS ZIP % ships bare names, so rename the extracted executables to match. if ismac for k = 1:numel(execs) diff --git a/src/installation/fetchRavenDataAsset.m b/src/installation/fetchToolboxAsset.m similarity index 68% rename from src/installation/fetchRavenDataAsset.m rename to src/installation/fetchToolboxAsset.m index 5e5266eb..1942a794 100644 --- a/src/installation/fetchRavenDataAsset.m +++ b/src/installation/fetchToolboxAsset.m @@ -1,9 +1,10 @@ -function localPath=fetchRavenDataAsset(destDir,releaseTag,assetName) -% fetchRavenDataAsset Download (if needed) a raven-data release asset. +function localPath=fetchToolboxAsset(destDir,releaseTag,assetName) +% fetchToolboxAsset Download (if needed) a toolbox-assets release asset. % -% Downloads assetName from the given raven-data release tag into destDir +% Downloads assetName from the given release tag of +% https://github.com/CompMetEng/toolbox-assets into destDir % and returns its local path. An already-downloaded copy is reused as-is. -% Generic across raven-data's release families (KEGG artefacts, HMM +% Generic across toolbox-assets' release families (KEGG artefacts, HMM % libraries, binaries, ...): nothing here is specific to any one of % them, so callers supply the release tag and asset name rather than % this function hardcoding either. @@ -13,7 +14,7 @@ % destDir : char % directory to download into (created if it does not exist yet). % releaseTag : char -% the raven-data release tag, e.g. 'kegg118'. +% the toolbox-assets release tag, e.g. 'kegg118'. % assetName : char % the release asset file name, e.g. 'kegg118_core.tar.gz'. % @@ -31,7 +32,7 @@ end fprintf(['Downloading ' assetName '... ']); try - websave(localPath,['https://github.com/SysBioChalmers/raven-data/releases/download/' releaseTag '/' assetName]); + websave(localPath,['https://github.com/CompMetEng/toolbox-assets/releases/download/' releaseTag '/' assetName]); catch ME if strcmp(ME.identifier,'MATLAB:webservices:HTTP404StatusCodeError') error('Failed to download %s, the server returned a 404 error, try again later. If the problem persists please report it on the RAVEN GitHub Issues page: https://github.com/SysBioChalmers/RAVEN/issues',assetName) diff --git a/src/reconstruction/kegg/KEGG_VERSION.md b/src/reconstruction/kegg/KEGG_VERSION.md index 5095c18c..e43f3b80 100644 --- a/src/reconstruction/kegg/KEGG_VERSION.md +++ b/src/reconstruction/kegg/KEGG_VERSION.md @@ -1,12 +1,12 @@ kegg118 -This is the raven-data release tag that every KEGG-artefact download in +This is the toolbox-assets release tag that every KEGG-artefact download in this folder reads from --- getModelFromKEGG's core reference-model/table bundle, and getKEGGModelForOrganism's HMM libraries. It is the single place this version is recorded; every function that downloads a KEGG artefact reads it from here via keggDataVersion.m, instead of hardcoding a "kegg###" string of its own. Bump this file, not the -calling code, when raven-data publishes a new KEGG release. +calling code, when toolbox-assets publishes a new KEGG release. The version is read as the file's first line; everything below this point is only for a human reader. diff --git a/src/reconstruction/kegg/buildGlobalKEGGModel.m b/src/reconstruction/kegg/buildGlobalKEGGModel.m index e25dd916..f31b248a 100644 --- a/src/reconstruction/kegg/buildGlobalKEGGModel.m +++ b/src/reconstruction/kegg/buildGlobalKEGGModel.m @@ -1,7 +1,7 @@ function [model,KOModel,isSpontaneous,isUndefinedStoich,isIncomplete,isGeneral]=buildGlobalKEGGModel(ravenPath) % buildGlobalKEGGModel Download and assemble the global KEGG model. % -% Fetches the raven-data _core.tar.gz bundle (the +% Fetches the toolbox-assets _core.tar.gz bundle (the % gene-free reference model plus the ko_reaction / organism_gene_ko / % rxn_flags relational tables), then joins every organism's % genes onto the reference reactions through their shared KO ids. The @@ -27,7 +27,7 @@ kver=keggDataVersion(); keggDir=fullfile(ravenPath,'src','reconstruction','kegg'); -archive=fetchRavenDataAsset(keggDir,kver,[kver '_core.tar.gz']); +archive=fetchToolboxAsset(keggDir,kver,[kver '_core.tar.gz']); coreDir=fullfile(keggDir,[kver '_core']); if ~isfolder(coreDir) diff --git a/src/reconstruction/kegg/getKEGGModelForOrganism.m b/src/reconstruction/kegg/getKEGGModelForOrganism.m index 8f6dfa5e..de8ce653 100755 --- a/src/reconstruction/kegg/getKEGGModelForOrganism.m +++ b/src/reconstruction/kegg/getKEGGModelForOrganism.m @@ -30,7 +30,7 @@ % and whether the HMMs were trained on pro- or eukaryotic sequences. The % directory name matches the published HMM library it is paired with. The % prebuilt concatenated KO HMM library (dataDir.hmm) is downloaded here -% from the corresponding raven-data release if not already present. This +% from the corresponding toolbox-assets release if not already present. This % parameter should ALWAYS be provided. % outDir : char % directory to save the results from the quering of the hidden Markov @@ -197,8 +197,8 @@ %pre-trained HMM set, compatible with the current RAVEN version. The %reconstruction uses the concatenated KO HMM library (a single %gzip-compressed flatfile, queried in one hmmsearch); if it is not already -%present it is downloaded from the corresponding raven-data release -%(https://github.com/SysBioChalmers/raven-data). +%present it is downloaded from the corresponding toolbox-assets release +%(https://github.com/CompMetEng/toolbox-assets). %Only needed for the protein-homology path (fastaFile supplied): the %annotation-only path never touches libraryFile, so skip the (100+ MB) %download/extraction entirely when there is no FASTA file to search. @@ -219,7 +219,7 @@ gunzip([libraryFile '.gz']); fprintf('COMPLETE\n'); else - fetchRavenDataAsset(fileparts(libraryFile),kver,[hmmName '.hmm.gz']); + fetchToolboxAsset(fileparts(libraryFile),kver,[hmmName '.hmm.gz']); fprintf('Extracting the HMM library file... '); gunzip([libraryFile '.gz']); fprintf('COMPLETE\n'); diff --git a/src/reconstruction/kegg/getModelFromKEGG.m b/src/reconstruction/kegg/getModelFromKEGG.m index f61ba301..f619df61 100755 --- a/src/reconstruction/kegg/getModelFromKEGG.m +++ b/src/reconstruction/kegg/getModelFromKEGG.m @@ -4,7 +4,7 @@ % Loads the global KEGG reaction/gene model from keggModel.mat. On first % use --- when no keggModel.mat is present yet --- the underlying % artefacts (the gene-free reference model plus the KO/reaction/organism- -% gene relational tables, published as a raven-data release) are +% gene relational tables, published as a toolbox-assets release) are % downloaded and assembled instead, and the result is cached to % keggModel.mat so later calls load instantly. The first build can take % a while (the organism-gene table covers every KEGG organism) and needs diff --git a/src/reconstruction/kegg/getPhylDist.m b/src/reconstruction/kegg/getPhylDist.m index f4bbbc43..57922753 100755 --- a/src/reconstruction/kegg/getPhylDist.m +++ b/src/reconstruction/kegg/getPhylDist.m @@ -2,7 +2,8 @@ % getPhylDist Load the pre-built KEGG phylogenetic distance matrix. % % Loads the pre-built phylogenetic distance matrix from keggPhylDist.mat. -% The artefact is distributed as a raven-data release asset. +% The matrix ships with RAVEN; it is derived from the KEGG taxonomy, which +% is a toolbox-assets release asset. % % Name-Value Arguments % -------------------- diff --git a/src/reconstruction/kegg/keggDataVersion.m b/src/reconstruction/kegg/keggDataVersion.m index 7fec3802..36477332 100644 --- a/src/reconstruction/kegg/keggDataVersion.m +++ b/src/reconstruction/kegg/keggDataVersion.m @@ -1,16 +1,16 @@ function version=keggDataVersion() -% keggDataVersion The raven-data release tag for KEGG artefacts. +% keggDataVersion The toolbox-assets release tag for KEGG artefacts. % % Reads reconstruction/kegg/KEGG_VERSION.md --- the single place this % version is recorded --- so every function that downloads a KEGG % artefact (getModelFromKEGG, getKEGGModelForOrganism) stays in sync -% when raven-data publishes a new KEGG release. The version is the +% when toolbox-assets publishes a new KEGG release. The version is the % file's first line. % % Returns % ------- % version : char -% the raven-data release tag, e.g. 'kegg118'. +% the toolbox-assets release tag, e.g. 'kegg118'. ravenPath=findRAVENroot(); versionFile=fullfile(ravenPath,'src','reconstruction','kegg','KEGG_VERSION.md'); diff --git a/tests/function_tests/tBinaries.m b/tests/function_tests/tBinaries.m index 23e5f29f..1a68d30f 100644 --- a/tests/function_tests/tBinaries.m +++ b/tests/function_tests/tBinaries.m @@ -1,5 +1,5 @@ classdef tBinaries < matlab.unittest.TestCase -% tBinaries On-demand download of RAVEN's external binaries from raven-data. +% tBinaries On-demand download of RAVEN's external binaries from toolbox-assets. % % Validates downloadRavenBinaries on the current platform: each tool is % fetched into software//, the executable is present, and (for the CLI @@ -7,7 +7,7 @@ % confirms the Gatekeeper quarantine attribute was cleared. % % Exercised on Linux and macOS by the "binary-tests" CI job. Requires network -% access to https://github.com/SysBioChalmers/raven-data . +% access to https://github.com/CompMetEng/toolbox-assets . methods (Test) function blast(testCase) diff --git a/tests/function_tests/tReconstruction.m b/tests/function_tests/tReconstruction.m index 1127741e..52573b22 100644 --- a/tests/function_tests/tReconstruction.m +++ b/tests/function_tests/tReconstruction.m @@ -230,14 +230,14 @@ function getDiamondRunsWhenAvailable(testCase) end function getKEGGModelForOrganismNeedsData(testCase) - testCase.assumeFail('Requires keggModel.mat and an HMM library from raven-data.'); + testCase.assumeFail('Requires keggModel.mat and an HMM library from toolbox-assets.'); end function getModelFromKEGGNeedsData(testCase) matFile = fullfile(testCase.ravenRoot,'src','reconstruction', 'kegg', 'keggModel.mat'); testCase.assumeFalse(exist(matFile, 'file') == 2, ... 'keggModel.mat is present; skipping the build-from-artefacts path.'); - testCase.assumeFail('Downloads and assembles the full KEGG artefact set from raven-data; not run automatically.'); + testCase.assumeFail('Downloads and assembles the full KEGG artefact set from toolbox-assets; not run automatically.'); end function buildGlobalGPRJoinsGenesThroughSharedKO(testCase) From bcdc2b4130817afb6e9c71fd06ef1af829ada4a1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 17 Oct 2026 06:27:38 +0000 Subject: [PATCH 22/48] doc: regenerate HTML documentation for e44fcf0 [skip ci] --- docs/html/index.html | 2 +- .../installation/downloadRavenBinaries.html | 16 +-- .../installation/fetchRavenDataAsset.html | 111 ----------------- docs/html/installation/fetchToolboxAsset.html | 113 ++++++++++++++++++ docs/html/installation/index.html | 2 +- .../kegg/buildGlobalKEGGModel.html | 8 +- .../kegg/getKEGGModelForOrganism.html | 12 +- .../reconstruction/kegg/getModelFromKEGG.html | 4 +- .../html/reconstruction/kegg/getPhylDist.html | 86 ++++++------- docs/html/reconstruction/kegg/index.html | 2 +- .../reconstruction/kegg/keggDataVersion.html | 16 +-- 11 files changed, 188 insertions(+), 184 deletions(-) delete mode 100644 docs/html/installation/fetchRavenDataAsset.html create mode 100644 docs/html/installation/fetchToolboxAsset.html diff --git a/docs/html/index.html b/docs/html/index.html index d0fdb240..270669f8 100644 --- a/docs/html/index.html +++ b/docs/html/index.html @@ -22,7 +22,7 @@

    Matlab Files found in these Directories

    FSEOF exportModel grRuleToDNF removeMets INITStepDesc exportToExcelFormat grRuleToString removeRavenFromPath addExchangeRxns extractMiriam groupRxnScores removeReactions - addGenesRaven fetchRavenDataAsset guessComposition renameModelGenes + addGenesRaven fetchToolboxAsset guessComposition renameModelGenes addIdentifierPrefix fillGaps haveFlux replaceMets addMets findDuplicateRxns hitsFromSuppOrth reporterMetabolites addRavenToUserPath findGeneDeletions importModel rescaleModelForINIT diff --git a/docs/html/installation/downloadRavenBinaries.html b/docs/html/installation/downloadRavenBinaries.html index fe226a92..64e6e4fe 100644 --- a/docs/html/installation/downloadRavenBinaries.html +++ b/docs/html/installation/downloadRavenBinaries.html @@ -29,7 +29,7 @@

    SYNOPSIS ^DESCRIPTION ^

     downloadRavenBinaries
        Download RAVEN's external command-line binaries on demand from the
    -   raven-data repository into RAVEN's software/ directory. Only the binary
    +   toolbox-assets repository into RAVEN's src/external/ directory. Only the binary
        matching the current platform is fetched, and only when needed.
     
        Input:
    @@ -42,7 +42,7 @@ 

    DESCRIPTION ^DESCRIPTION ^CROSS-REFERENCE INFORMATION ^

    This function calls: +
  • fetchToolboxAsset fetchToolboxAsset Download (if needed) a toolbox-assets release asset.
  • findRAVENroot findRAVENroot
  • This function is called by:
    @@ -65,7 +65,7 @@

    SOURCE CODE ^
    0001 function downloadRavenBinaries(tools)
     0002 % downloadRavenBinaries
     0003 %   Download RAVEN's external command-line binaries on demand from the
    -0004 %   raven-data repository into RAVEN's software/ directory. Only the binary
    +0004 %   toolbox-assets repository into RAVEN's src/external/ directory. Only the binary
     0005 %   matching the current platform is fetched, and only when needed.
     0006 %
     0007 %   Input:
    @@ -78,7 +78,7 @@ 

    SOURCE CODE ^% unzip it into the RAVEN software/ directory instead. 0015 % 0016 % The binaries are hosted as release assets in -0017 % https://github.com/SysBioChalmers/raven-data. +0017 % https://github.com/CompMetEng/toolbox-assets. 0018 % 0019 % Usage: downloadRavenBinaries({'blast+','diamond'}) 0020 % @@ -93,7 +93,7 @@

    SOURCE CODE ^findRAVENroot(); 0031 -0032 % raven-data platform key for the per-platform binary ZIPs. +0032 % toolbox-assets platform key for the per-platform binary ZIPs. 0033 if ispc 0034 plat = 'windows-x86_64'; 0035 elseif ismac @@ -142,11 +142,11 @@

    SOURCE CODE ^continue; % already provisioned 0079 end 0080 -0081 zipPath = fetchRavenDataAsset(fullfile(ravenDir,'src','external'),tag,asset); +0081 zipPath = fetchToolboxAsset(fullfile(ravenDir,'src','external'),tag,asset); 0082 unzip(zipPath,destDir); 0083 delete(zipPath); 0084 -0085 % macOS uses the .mac suffix throughout RAVEN, but the raven-data macOS ZIP +0085 % macOS uses the .mac suffix throughout RAVEN, but the toolbox-assets macOS ZIP 0086 % ships bare names, so rename the extracted executables to match. 0087 if ismac 0088 for k = 1:numel(execs) diff --git a/docs/html/installation/fetchRavenDataAsset.html b/docs/html/installation/fetchRavenDataAsset.html deleted file mode 100644 index 69c9c767..00000000 --- a/docs/html/installation/fetchRavenDataAsset.html +++ /dev/null @@ -1,111 +0,0 @@ - - - - Description of fetchRavenDataAsset - - - - - - - - - -
    Home > installation > fetchRavenDataAsset.m
    - - - -

    fetchRavenDataAsset -

    - -

    PURPOSE ^

    -
    fetchRavenDataAsset Download (if needed) a raven-data release asset.
    - -

    SYNOPSIS ^

    -
    function localPath=fetchRavenDataAsset(destDir,releaseTag,assetName)
    - -

    DESCRIPTION ^

    -
     fetchRavenDataAsset  Download (if needed) a raven-data release asset.
    -
    - Downloads assetName from the given raven-data release tag into destDir
    - and returns its local path. An already-downloaded copy is reused as-is.
    - Generic across raven-data's release families (KEGG artefacts, HMM
    - libraries, binaries, ...): nothing here is specific to any one of
    - them, so callers supply the release tag and asset name rather than
    - this function hardcoding either.
    -
    - Parameters
    - ----------
    - destDir : char
    -     directory to download into (created if it does not exist yet).
    - releaseTag : char
    -     the raven-data release tag, e.g. 'kegg118'.
    - assetName : char
    -     the release asset file name, e.g. 'kegg118_core.tar.gz'.
    -
    - Returns
    - -------
    - localPath : char
    -     path to the downloaded file.
    - - -

    CROSS-REFERENCE INFORMATION ^

    -This function calls: -
      -
    -This function is called by: - - - - - -

    SOURCE CODE ^

    -
    0001 function localPath=fetchRavenDataAsset(destDir,releaseTag,assetName)
    -0002 % fetchRavenDataAsset  Download (if needed) a raven-data release asset.
    -0003 %
    -0004 % Downloads assetName from the given raven-data release tag into destDir
    -0005 % and returns its local path. An already-downloaded copy is reused as-is.
    -0006 % Generic across raven-data's release families (KEGG artefacts, HMM
    -0007 % libraries, binaries, ...): nothing here is specific to any one of
    -0008 % them, so callers supply the release tag and asset name rather than
    -0009 % this function hardcoding either.
    -0010 %
    -0011 % Parameters
    -0012 % ----------
    -0013 % destDir : char
    -0014 %     directory to download into (created if it does not exist yet).
    -0015 % releaseTag : char
    -0016 %     the raven-data release tag, e.g. 'kegg118'.
    -0017 % assetName : char
    -0018 %     the release asset file name, e.g. 'kegg118_core.tar.gz'.
    -0019 %
    -0020 % Returns
    -0021 % -------
    -0022 % localPath : char
    -0023 %     path to the downloaded file.
    -0024 
    -0025 if ~isfolder(destDir)
    -0026     mkdir(destDir);
    -0027 end
    -0028 localPath=fullfile(destDir,assetName);
    -0029 if isfile(localPath)
    -0030     return;
    -0031 end
    -0032 fprintf(['Downloading ' assetName '... ']);
    -0033 try
    -0034     websave(localPath,['https://github.com/SysBioChalmers/raven-data/releases/download/' releaseTag '/' assetName]);
    -0035 catch ME
    -0036     if strcmp(ME.identifier,'MATLAB:webservices:HTTP404StatusCodeError')
    -0037         error('Failed to download %s, the server returned a 404 error, try again later. If the problem persists please report it on the RAVEN GitHub Issues page: https://github.com/SysBioChalmers/RAVEN/issues',assetName)
    -0038     else
    -0039         rethrow(ME);
    -0040     end
    -0041 end
    -0042 fprintf('COMPLETE\n');
    -0043 end
    -
    Generated by m2html © 2005
    - - \ No newline at end of file diff --git a/docs/html/installation/fetchToolboxAsset.html b/docs/html/installation/fetchToolboxAsset.html new file mode 100644 index 00000000..4ee3d0d4 --- /dev/null +++ b/docs/html/installation/fetchToolboxAsset.html @@ -0,0 +1,113 @@ + + + + Description of fetchToolboxAsset + + + + + + + + + +
    Home > installation > fetchToolboxAsset.m
    + + + +

    fetchToolboxAsset +

    + +

    PURPOSE ^

    +
    fetchToolboxAsset Download (if needed) a toolbox-assets release asset.
    + +

    SYNOPSIS ^

    +
    function localPath=fetchToolboxAsset(destDir,releaseTag,assetName)
    + +

    DESCRIPTION ^

    +
     fetchToolboxAsset  Download (if needed) a toolbox-assets release asset.
    +
    + Downloads assetName from the given release tag of
    + https://github.com/CompMetEng/toolbox-assets into destDir
    + and returns its local path. An already-downloaded copy is reused as-is.
    + Generic across toolbox-assets' release families (KEGG artefacts, HMM
    + libraries, binaries, ...): nothing here is specific to any one of
    + them, so callers supply the release tag and asset name rather than
    + this function hardcoding either.
    +
    + Parameters
    + ----------
    + destDir : char
    +     directory to download into (created if it does not exist yet).
    + releaseTag : char
    +     the toolbox-assets release tag, e.g. 'kegg118'.
    + assetName : char
    +     the release asset file name, e.g. 'kegg118_core.tar.gz'.
    +
    + Returns
    + -------
    + localPath : char
    +     path to the downloaded file.
    + + +

    CROSS-REFERENCE INFORMATION ^

    +This function calls: +
      +
    +This function is called by: + + + + + +

    SOURCE CODE ^

    +
    0001 function localPath=fetchToolboxAsset(destDir,releaseTag,assetName)
    +0002 % fetchToolboxAsset  Download (if needed) a toolbox-assets release asset.
    +0003 %
    +0004 % Downloads assetName from the given release tag of
    +0005 % https://github.com/CompMetEng/toolbox-assets into destDir
    +0006 % and returns its local path. An already-downloaded copy is reused as-is.
    +0007 % Generic across toolbox-assets' release families (KEGG artefacts, HMM
    +0008 % libraries, binaries, ...): nothing here is specific to any one of
    +0009 % them, so callers supply the release tag and asset name rather than
    +0010 % this function hardcoding either.
    +0011 %
    +0012 % Parameters
    +0013 % ----------
    +0014 % destDir : char
    +0015 %     directory to download into (created if it does not exist yet).
    +0016 % releaseTag : char
    +0017 %     the toolbox-assets release tag, e.g. 'kegg118'.
    +0018 % assetName : char
    +0019 %     the release asset file name, e.g. 'kegg118_core.tar.gz'.
    +0020 %
    +0021 % Returns
    +0022 % -------
    +0023 % localPath : char
    +0024 %     path to the downloaded file.
    +0025 
    +0026 if ~isfolder(destDir)
    +0027     mkdir(destDir);
    +0028 end
    +0029 localPath=fullfile(destDir,assetName);
    +0030 if isfile(localPath)
    +0031     return;
    +0032 end
    +0033 fprintf(['Downloading ' assetName '... ']);
    +0034 try
    +0035     websave(localPath,['https://github.com/CompMetEng/toolbox-assets/releases/download/' releaseTag '/' assetName]);
    +0036 catch ME
    +0037     if strcmp(ME.identifier,'MATLAB:webservices:HTTP404StatusCodeError')
    +0038         error('Failed to download %s, the server returned a 404 error, try again later. If the problem persists please report it on the RAVEN GitHub Issues page: https://github.com/SysBioChalmers/RAVEN/issues',assetName)
    +0039     else
    +0040         rethrow(ME);
    +0041     end
    +0042 end
    +0043 fprintf('COMPLETE\n');
    +0044 end
    +
    Generated by m2html © 2005
    + + \ No newline at end of file diff --git a/docs/html/installation/index.html b/docs/html/installation/index.html index c1c42ff6..1d1275fa 100644 --- a/docs/html/installation/index.html +++ b/docs/html/installation/index.html @@ -19,7 +19,7 @@

    Index for installation

    Matlab files in this directory:

    -
     addRavenToUserPathThis function writes a startup.m file in the userpath, adding RAVEN (and
     checkFunctionUniquenesscheckFunctionUniqueness
     downloadRavenBinariesdownloadRavenBinaries
     fetchRavenDataAssetfetchRavenDataAsset Download (if needed) a raven-data release asset.
     findRAVENrootfindRAVENroot
     removeRavenFromPathremoveRavenFromPath
     updateDocumentationupdateDocumentation
    + addRavenToUserPathThis function writes a startup.m file in the userpath, adding RAVEN (and  checkFunctionUniquenesscheckFunctionUniqueness  downloadRavenBinariesdownloadRavenBinaries  fetchToolboxAssetfetchToolboxAsset Download (if needed) a toolbox-assets release asset.  findRAVENrootfindRAVENroot  removeRavenFromPathremoveRavenFromPath  updateDocumentationupdateDocumentation diff --git a/docs/html/reconstruction/kegg/buildGlobalKEGGModel.html b/docs/html/reconstruction/kegg/buildGlobalKEGGModel.html index c571402f..09133068 100644 --- a/docs/html/reconstruction/kegg/buildGlobalKEGGModel.html +++ b/docs/html/reconstruction/kegg/buildGlobalKEGGModel.html @@ -29,7 +29,7 @@

    SYNOPSIS ^DESCRIPTION ^

     buildGlobalKEGGModel  Download and assemble the global KEGG model.
     
    - Fetches the raven-data <kegg version>_core.tar.gz bundle (the
    + Fetches the toolbox-assets <kegg version>_core.tar.gz bundle (the
      gene-free reference model plus the ko_reaction / organism_gene_ko /
      rxn_flags relational tables), then joins every organism's
      genes onto the reference reactions through their shared KO ids. The
    @@ -57,7 +57,7 @@ 

    DESCRIPTION ^CROSS-REFERENCE INFORMATION ^

    This function calls:
      -
    • buildGlobalGPR buildGlobalGPR Join every KEGG organism's genes onto the reference
    • keggDataVersion keggDataVersion The raven-data release tag for KEGG artefacts.
    • readKEGGTable readKEGGTable Read a gzipped KEGG relational table into a table with
    +
  • buildGlobalGPR buildGlobalGPR Join every KEGG organism's genes onto the reference
  • keggDataVersion keggDataVersion The toolbox-assets release tag for KEGG artefacts.
  • readKEGGTable readKEGGTable Read a gzipped KEGG relational table into a table with
  • This function is called by: @@ -71,7 +71,7 @@

    SOURCE CODE ^
    0001 function [model,KOModel,isSpontaneous,isUndefinedStoich,isIncomplete,isGeneral]=buildGlobalKEGGModel(ravenPath)
     0002 % buildGlobalKEGGModel  Download and assemble the global KEGG model.
     0003 %
    -0004 % Fetches the raven-data <kegg version>_core.tar.gz bundle (the
    +0004 % Fetches the toolbox-assets <kegg version>_core.tar.gz bundle (the
     0005 % gene-free reference model plus the ko_reaction / organism_gene_ko /
     0006 % rxn_flags relational tables), then joins every organism's
     0007 % genes onto the reference reactions through their shared KO ids. The
    @@ -97,7 +97,7 @@ 

    SOURCE CODE ^keggDataVersion(); 0029 keggDir=fullfile(ravenPath,'src','reconstruction','kegg'); -0030 archive=fetchRavenDataAsset(keggDir,kver,[kver '_core.tar.gz']); +0030 archive=fetchToolboxAsset(keggDir,kver,[kver '_core.tar.gz']); 0031 0032 coreDir=fullfile(keggDir,[kver '_core']); 0033 if ~isfolder(coreDir) diff --git a/docs/html/reconstruction/kegg/getKEGGModelForOrganism.html b/docs/html/reconstruction/kegg/getKEGGModelForOrganism.html index 9abcfc97..e208c085 100644 --- a/docs/html/reconstruction/kegg/getKEGGModelForOrganism.html +++ b/docs/html/reconstruction/kegg/getKEGGModelForOrganism.html @@ -58,7 +58,7 @@

    DESCRIPTION ^DESCRIPTION ^CROSS-REFERENCE INFORMATION ^

    This function calls:
      -
    • getModelFromKEGG getModelFromKEGG Load the global KEGG model.
    • getPhylDist getPhylDist Load the pre-built KEGG phylogenetic distance matrix.
    • keggDataVersion keggDataVersion The raven-data release tag for KEGG artefacts.
    +
  • getModelFromKEGG getModelFromKEGG Load the global KEGG model.
  • getPhylDist getPhylDist Load the pre-built KEGG phylogenetic distance matrix.
  • keggDataVersion keggDataVersion The toolbox-assets release tag for KEGG artefacts.
  • This function is called by:
    @@ -186,7 +186,7 @@

    SOURCE CODE ^% and whether the HMMs were trained on pro- or eukaryotic sequences. The 0031 % directory name matches the published HMM library it is paired with. The 0032 % prebuilt concatenated KO HMM library (dataDir.hmm) is downloaded here -0033 % from the corresponding raven-data release if not already present. This +0033 % from the corresponding toolbox-assets release if not already present. This 0034 % parameter should ALWAYS be provided. 0035 % outDir : char 0036 % directory to save the results from the quering of the hidden Markov @@ -353,8 +353,8 @@

    SOURCE CODE ^%pre-trained HMM set, compatible with the current RAVEN version. The 0198 %reconstruction uses the concatenated KO HMM library (a single 0199 %gzip-compressed flatfile, queried in one hmmsearch); if it is not already -0200 %present it is downloaded from the corresponding raven-data release -0201 %(https://github.com/SysBioChalmers/raven-data). +0200 %present it is downloaded from the corresponding toolbox-assets release +0201 %(https://github.com/CompMetEng/toolbox-assets). 0202 %Only needed for the protein-homology path (fastaFile supplied): the 0203 %annotation-only path never touches libraryFile, so skip the (100+ MB) 0204 %download/extraction entirely when there is no FASTA file to search. @@ -375,7 +375,7 @@

    SOURCE CODE ^'.gz']); 0220 fprintf('COMPLETE\n'); 0221 else -0222 fetchRavenDataAsset(fileparts(libraryFile),kver,[hmmName '.hmm.gz']); +0222 fetchToolboxAsset(fileparts(libraryFile),kver,[hmmName '.hmm.gz']); 0223 fprintf('Extracting the HMM library file... '); 0224 gunzip([libraryFile '.gz']); 0225 fprintf('COMPLETE\n'); diff --git a/docs/html/reconstruction/kegg/getModelFromKEGG.html b/docs/html/reconstruction/kegg/getModelFromKEGG.html index 0bf74839..df3b67f6 100644 --- a/docs/html/reconstruction/kegg/getModelFromKEGG.html +++ b/docs/html/reconstruction/kegg/getModelFromKEGG.html @@ -32,7 +32,7 @@

    DESCRIPTION ^SOURCE CODE ^% Loads the global KEGG reaction/gene model from keggModel.mat. On first 0005 % use --- when no keggModel.mat is present yet --- the underlying 0006 % artefacts (the gene-free reference model plus the KO/reaction/organism- -0007 % gene relational tables, published as a raven-data release) are +0007 % gene relational tables, published as a toolbox-assets release) are 0008 % downloaded and assembled instead, and the result is cached to 0009 % keggModel.mat so later calls load instantly. The first build can take 0010 % a while (the organism-gene table covers every KEGG organism) and needs diff --git a/docs/html/reconstruction/kegg/getPhylDist.html b/docs/html/reconstruction/kegg/getPhylDist.html index 654bbdb3..bd4553c2 100644 --- a/docs/html/reconstruction/kegg/getPhylDist.html +++ b/docs/html/reconstruction/kegg/getPhylDist.html @@ -30,7 +30,8 @@

    DESCRIPTION ^
     getPhylDist  Load the pre-built KEGG phylogenetic distance matrix.
     
      Loads the pre-built phylogenetic distance matrix from keggPhylDist.mat.
    - The artefact is distributed as a raven-data release asset.
    + The matrix ships with RAVEN; it is derived from the KEGG taxonomy, which
    + is a toolbox-assets release asset.
     
      Name-Value Arguments
      --------------------
    @@ -68,47 +69,48 @@ 

    SOURCE CODE ^% getPhylDist Load the pre-built KEGG phylogenetic distance matrix. 0003 % 0004 % Loads the pre-built phylogenetic distance matrix from keggPhylDist.mat. -0005 % The artefact is distributed as a raven-data release asset. -0006 % -0007 % Name-Value Arguments -0008 % -------------------- -0009 % onlyInKingdom : logical -0010 % if true, returns a distance matrix with Inf for cross-domain pairs -0011 % (Prokaryota vs Eukaryota) (default false). -0012 % -0013 % Returns -0014 % ------- -0015 % phylDistStruct : struct -0016 % structure with fields: -0017 % -0018 % - ids : cell array of KEGG organism abbreviations. -0019 % - distMat : pairwise distance matrix (number of tree-nodes apart). -0020 % -0021 % Notes -0022 % ----- -0023 % Distance is based on the number of nodes two organisms are apart in the -0024 % KEGG taxonomy tree. -0025 -0026 p=parseRAVENargs(varargin, {'onlyInKingdom',false}); -0027 onlyInKingdom=p.onlyInKingdom; -0028 -0029 ravenPath=findRAVENroot(); -0030 distFile=fullfile(ravenPath,'src','reconstruction','kegg','keggPhylDist.mat'); -0031 if ~exist(distFile,'file') -0032 error('getPhylDist:noData', ... -0033 ['keggPhylDist.mat not found at ' strrep(distFile,'\','/') '.\n' ... -0034 'It ships with RAVEN, so a missing copy means an incomplete checkout. ' ... -0035 'Restore it from the RAVEN repository, or generate it with the ' ... -0036 'raven-toolbox Python package.']); -0037 end -0038 fprintf(['Importing the KEGG phylogenetic distance matrix from ' strrep(distFile,'\','/') '... ']); -0039 load(distFile); -0040 fprintf('COMPLETE\n'); -0041 -0042 if onlyInKingdom==true -0043 phylDistStruct=phylDistStructOnlyInKingdom; -0044 end -0045 end

    +0005 % The matrix ships with RAVEN; it is derived from the KEGG taxonomy, which +0006 % is a toolbox-assets release asset. +0007 % +0008 % Name-Value Arguments +0009 % -------------------- +0010 % onlyInKingdom : logical +0011 % if true, returns a distance matrix with Inf for cross-domain pairs +0012 % (Prokaryota vs Eukaryota) (default false). +0013 % +0014 % Returns +0015 % ------- +0016 % phylDistStruct : struct +0017 % structure with fields: +0018 % +0019 % - ids : cell array of KEGG organism abbreviations. +0020 % - distMat : pairwise distance matrix (number of tree-nodes apart). +0021 % +0022 % Notes +0023 % ----- +0024 % Distance is based on the number of nodes two organisms are apart in the +0025 % KEGG taxonomy tree. +0026 +0027 p=parseRAVENargs(varargin, {'onlyInKingdom',false}); +0028 onlyInKingdom=p.onlyInKingdom; +0029 +0030 ravenPath=findRAVENroot(); +0031 distFile=fullfile(ravenPath,'src','reconstruction','kegg','keggPhylDist.mat'); +0032 if ~exist(distFile,'file') +0033 error('getPhylDist:noData', ... +0034 ['keggPhylDist.mat not found at ' strrep(distFile,'\','/') '.\n' ... +0035 'It ships with RAVEN, so a missing copy means an incomplete checkout. ' ... +0036 'Restore it from the RAVEN repository, or generate it with the ' ... +0037 'raven-toolbox Python package.']); +0038 end +0039 fprintf(['Importing the KEGG phylogenetic distance matrix from ' strrep(distFile,'\','/') '... ']); +0040 load(distFile); +0041 fprintf('COMPLETE\n'); +0042 +0043 if onlyInKingdom==true +0044 phylDistStruct=phylDistStructOnlyInKingdom; +0045 end +0046 end


    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/reconstruction/kegg/index.html b/docs/html/reconstruction/kegg/index.html index 86fec3d3..270bba12 100644 --- a/docs/html/reconstruction/kegg/index.html +++ b/docs/html/reconstruction/kegg/index.html @@ -19,7 +19,7 @@

    Index for reconstruction/kegg

    Matlab files in this directory:

    -
     buildGlobalGPRbuildGlobalGPR Join every KEGG organism's genes onto the reference
     buildGlobalKEGGModelbuildGlobalKEGGModel Download and assemble the global KEGG model.
     getKEGGModelForOrganismgetKEGGModelForOrganism Reconstruct a model from KEGG protein homology.
     getModelFromKEGGgetModelFromKEGG Load the global KEGG model.
     getPhylDistgetPhylDist Load the pre-built KEGG phylogenetic distance matrix.
     keggDataVersionkeggDataVersion The raven-data release tag for KEGG artefacts.
     readKEGGTablereadKEGGTable Read a gzipped KEGG relational table into a table with
    + buildGlobalGPRbuildGlobalGPR Join every KEGG organism's genes onto the reference  buildGlobalKEGGModelbuildGlobalKEGGModel Download and assemble the global KEGG model.  getKEGGModelForOrganismgetKEGGModelForOrganism Reconstruct a model from KEGG protein homology.  getModelFromKEGGgetModelFromKEGG Load the global KEGG model.  getPhylDistgetPhylDist Load the pre-built KEGG phylogenetic distance matrix.  keggDataVersionkeggDataVersion The toolbox-assets release tag for KEGG artefacts.  readKEGGTablereadKEGGTable Read a gzipped KEGG relational table into a table with

    Other Matlab-specific files in this directory:

      diff --git a/docs/html/reconstruction/kegg/keggDataVersion.html b/docs/html/reconstruction/kegg/keggDataVersion.html index 2c505c3e..12472b63 100644 --- a/docs/html/reconstruction/kegg/keggDataVersion.html +++ b/docs/html/reconstruction/kegg/keggDataVersion.html @@ -4,7 +4,7 @@ Description of keggDataVersion - + @@ -21,24 +21,24 @@

      keggDataVersion

      PURPOSE ^

      -
      keggDataVersion The raven-data release tag for KEGG artefacts.
      +
      keggDataVersion The toolbox-assets release tag for KEGG artefacts.

      SYNOPSIS ^

      function version=keggDataVersion()

      DESCRIPTION ^

      -
       keggDataVersion  The raven-data release tag for KEGG artefacts.
      +
       keggDataVersion  The toolbox-assets release tag for KEGG artefacts.
       
        Reads reconstruction/kegg/KEGG_VERSION.md --- the single place this
        version is recorded --- so every function that downloads a KEGG
        artefact (getModelFromKEGG, getKEGGModelForOrganism) stays in sync
      - when raven-data publishes a new KEGG release. The version is the
      + when toolbox-assets publishes a new KEGG release. The version is the
        file's first line.
       
        Returns
        -------
        version : char
      -     the raven-data release tag, e.g. 'kegg118'.
      + the toolbox-assets release tag, e.g. 'kegg118'.

      CROSS-REFERENCE INFORMATION ^

      @@ -54,18 +54,18 @@

      CROSS-REFERENCE INFORMATION ^
 
 <h2><a name=SOURCE CODE ^

      0001 function version=keggDataVersion()
      -0002 % keggDataVersion  The raven-data release tag for KEGG artefacts.
      +0002 % keggDataVersion  The toolbox-assets release tag for KEGG artefacts.
       0003 %
       0004 % Reads reconstruction/kegg/KEGG_VERSION.md --- the single place this
       0005 % version is recorded --- so every function that downloads a KEGG
       0006 % artefact (getModelFromKEGG, getKEGGModelForOrganism) stays in sync
      -0007 % when raven-data publishes a new KEGG release. The version is the
      +0007 % when toolbox-assets publishes a new KEGG release. The version is the
       0008 % file's first line.
       0009 %
       0010 % Returns
       0011 % -------
       0012 % version : char
      -0013 %     the raven-data release tag, e.g. 'kegg118'.
      +0013 %     the toolbox-assets release tag, e.g. 'kegg118'.
       0014 
       0015 ravenPath=findRAVENroot();
       0016 versionFile=fullfile(ravenPath,'src','reconstruction','kegg','KEGG_VERSION.md');
      
      From 2c6f9b8aa15a9c8142ce6a0015b7840770f4e92f Mon Sep 17 00:00:00 2001
      From: Eduard Kerkhoven 
      Date: Sat, 17 Oct 2026 22:22:13 +0200
      Subject: [PATCH 23/48] Fixes from the RAVEN 3 Guide review (#7)
      
      * fix: use the population std for the reporterMetabolites background
      
      The closed-form background correction assumes genes drawn with
      replacement from the scored-gene pool, which needs the population
      standard deviation. Scores now match raven-toolbox. The help states that
      results are sorted by Z-score and that P-values are one-sided.
      
      * fix: pass a sparse constraint matrix from sampleChebyshevCenter
      
      Gurobi rejects a dense model.A, so randomSampling with method 'chrr'
      failed whenever Gurobi was the RAVEN solver.
      
      * fix: return all-blocked from haveFlux when no reaction survives simplification
      
      When every reaction is a dead end, simplifyModel removes them all and the
      LP has an empty constraint matrix, which GLPK rejects.
      
      * fix: refuse a biomass balance that would make the balancing component negative
      
      scaleBiomassFraction rescales the balancing component so the biomass
      total becomes 1 g/gDW. A required fraction of zero or less inverted the
      balancing pseudoreaction without notice; it now raises an error, as does
      a balancing component with no current fraction. The help states that the
      total becomes 1 g/gDW.
      
      * feat: solve MILPs with the bundled GLPK
      
      optimizeProb no longer refuses a MILP when 'glpk' is the RAVEN solver.
      The bundled GLPK MEX supports integer variables and gives the same
      answers as Gurobi on the gap-filling and minimal-medium problems of the
      guide models. A RAVEN:glpkMILP warning recommends Gurobi for larger
      problems. The GLPK branch uses the default time limit for MILPs and
      reports a mipgap, as the Gurobi and SCIP branches do. MILPs through the
      COBRA Toolbox's GLPK interface are still refused.
      
      The help of setRavenSolver, gapFillMILP, fillGaps and getMinimalMedium
      states the solver behaviour; getMinimalMedium also states that the
      returned medium need not be unique.
      
      * fix: raise a clear error for an ambiguous name[comp] in getIndexes
      
      A metaboliteName[comp] that matches more than one metabolite ended in an
      unexplained assignment error. The error now names the matching
      metabolites.
      
      * docs: correct the help of six functions
      
      - randomSampling: the random-objective description applies to
        'randomObjective' only, not to the default ACHR.
      - checkTasks: the model's own exchange reactions are not closed.
      - findGeneDeletions: details 2 means the solve failed, not that the
        deletion is lethal.
      - checkSolution: it reads an optimizeProb result, not a Mosek one.
      - assignSBOterms: where the terms are stored and how to read them.
      - haveFlux: the test uses the current bounds.
      
      * fix: read folded YAML scalars whose continuation starts with a dash
      
      readYAMLmodel treated any indented line starting with '-' as a list
      item, so a wrapped name continuing with '-epoxidizing)' was reported as
      an unknown entry. Only a dash followed by a space, or a lone dash,
      starts a list item. This made the kegg118 reference model unreadable and
      with it getKEGGModelForOrganism.
      
      * fix: read curation tables by header and curate existing entities fully
      
      curateModelFromTables read its tables by column position, so a
      metabolite table without an inchi column wrote its notes over the
      InChIs. Columns are now found by header name, in any order, and optional
      columns can be left out. Every supplied field is written to existing
      metabolites, genes and reactions as well as to new ones, including
      metNotes and rxnReferences, which were never written to existing
      entities; a field the model lacks is created. MIRIAM columns of the
      reaction table are taken from the columns after the core fields rather
      than from a fixed position, and grRules are matched to the right
      existing reaction when existing and new reactions are mixed.
      
      * docs: state how haveFlux treats a dead-end reaction with a forced flux
      
      * fix: keep the lower bound of unreversed reaction candidates in gapFillMILP
      
      Every draft reaction with lb >= 0 is a reversal candidate, and its lower
      bound was relaxed to -bigM with a coupling that only kept the flux
      non-negative while the reaction was not reversed. A forced flux such as
      a maintenance requirement (lb > 0) was therefore dropped, and the MILP
      could report a repair, or no repair, that does not reach the growth
      floor in the returned model. The coupling now keeps the original lower
      bound unless the reaction is reversed.
      
      * fix: create rxnNotes in getKEGGModelForOrganism when the reference model has none
      
      The kegg118 reference model has no reaction notes, so readYAMLmodel
      returns no rxnNotes field and adding the provenance note failed.
      
      * fix: correct a typo in the curateModelFromTables overwrite warning
      
      * gapFillMILP adds database reactions only; gapReport stops on an infeasible model
      
      gapFillMILP no longer reverses draft reactions: the draft keeps its bounds and
      the MILP minimises the number of added reactions. The reversedRxns output and
      the weights parameter are removed.
      
      gapReport raises RAVEN:infeasibleModel when the model is infeasible under its
      own bounds or with its boundary closed, naming the forced-flux reactions.
      ---
       src/analysis/findGeneDeletions.m              |   6 +-
       src/analysis/getMinimalMedium.m               |  10 +
       src/analysis/haveFlux.m                       |  22 +-
       src/analysis/randomSampling.m                 |   9 +-
       src/analysis/reporterMetabolites.m            |  16 +-
       src/analysis/sampleChebyshevCenter.m          |   2 +-
       src/annotation/assignSBOterms.m               |   5 +-
       src/biomass/scaleBiomassFraction.m            |  26 +-
       src/curation/curateModelFromTables.m          | 385 ++++++++++--------
       src/gapfilling/fillGaps.m                     |  11 +-
       src/gapfilling/gapFillMILP.m                  | 121 ++----
       src/gapfilling/gapReport.m                    |  30 ++
       src/io/readYAMLmodel.m                        |   8 +-
       src/queries/getIndexes.m                      |  10 +-
       .../kegg/getKEGGModelForOrganism.m            |  12 +-
       src/solver/checkSolution.m                    |   9 +-
       src/solver/optimizeProb.m                     |  36 +-
       src/solver/setRavenSolver.m                   |   9 +-
       src/tasks/checkTasks.m                        |   6 +
       tests/function_tests/RavenTestCase.m          |   4 +-
       tests/function_tests/tAnalysis.m              |  37 ++
       tests/function_tests/tBiomass.m               |  41 ++
       tests/function_tests/tCuration.m              |  86 ++++
       tests/function_tests/tGapfilling.m            | 123 +++++-
       tests/function_tests/tIO.m                    |  19 +
       tests/function_tests/tQueries.m               |  23 ++
       tests/function_tests/tSampling.m              |  11 +
       tests/function_tests/tSolver.m                |  36 ++
       28 files changed, 806 insertions(+), 307 deletions(-)
      
      diff --git a/src/analysis/findGeneDeletions.m b/src/analysis/findGeneDeletions.m
      index dd0e6602..14f4c0fa 100755
      --- a/src/analysis/findGeneDeletions.m
      +++ b/src/analysis/findGeneDeletions.m
      @@ -36,8 +36,10 @@
       %     with details about each gene in originalGenes and why or why not it
       %     was deleted:
       %
      -%     - 1 : was deleted
      -%     - 2 : proved lethal in sgd (single gene deletion)
      +%     - 1 : was deleted, and the problem was solved (a lethal deletion
      +%       with zero growth also gets 1)
      +%     - 2 : the single-deletion problem had no optimal solution
      +%       (infeasible or failed); fluxes and grRatioMuts are 0
       %     - 3 : redundant, no longer used
       %     - 4 : involved in dead-end reaction
       % grRatioMuts : double
      diff --git a/src/analysis/getMinimalMedium.m b/src/analysis/getMinimalMedium.m
      index 754c8c30..186f044b 100644
      --- a/src/analysis/getMinimalMedium.m
      +++ b/src/analysis/getMinimalMedium.m
      @@ -50,6 +50,16 @@
       %
       % A y_i = 0 forces reaction i to carry non-negative flux (no net uptake);
       % y_i = 1 allows it to carry flux as low as lb_i.
      +%
      +% The minimal number of uptakes is guaranteed, but the medium itself need
      +% not be unique: when several media of the same size support the growth
      +% rate, the solver returns one of them, and another solver or solver
      +% version can return a different one.
      +%
      +% The MILP is solved with the solver set by setRavenSolver. RAVEN's bundled
      +% GLPK solves it for small models (with a RAVEN:glpkMILP warning); for
      +% larger models Gurobi is much faster and more reliable
      +% (setRavenSolver('gurobi')).
       
       p = parseRAVENargs(varargin, {'minGrowth',[]; 'verbose',true});
       minGrowth = p.minGrowth;
      diff --git a/src/analysis/haveFlux.m b/src/analysis/haveFlux.m
      index 8c005dca..fc04d630 100755
      --- a/src/analysis/haveFlux.m
      +++ b/src/analysis/haveFlux.m
      @@ -1,9 +1,10 @@
       function I=haveFlux(model,varargin)
       % haveFlux  Check which reactions can carry a flux.
       %
      -% Checks which reactions can carry a (positive or negative) flux. Is used as
      -% a faster version of getAllowedBounds if it is only interesting whether the
      -% reactions can carry a flux or not.
      +% Checks which reactions can carry a (positive or negative) flux under the
      +% model's current bounds; other media or bounds are not explored. Is used
      +% as a faster version of getAllowedBounds if it is only interesting whether
      +% the reactions can carry a flux or not.
       %
       % Parameters
       % ----------
      @@ -28,7 +29,7 @@
       % -------
       % I : logical
       %     logical array with true if the corresponding reaction can carry a
      -%     flux.
      +%     flux under the tested bounds.
       %
       % Examples
       % --------
      @@ -38,6 +39,12 @@
       % -----
       % If a model has +/- Inf bounds then those are replaced with an arbitary
       % large value of +/- 10000 prior to solving.
      +%
      +% Dead-end reactions are removed (simplifyModel) before solving. A dead-end
      +% reaction with a forced flux (lb > 0 or ub < 0) makes the model infeasible,
      +% but it is removed like any other dead end, so the remaining reactions are
      +% tested as if it were absent. A forced flux elsewhere in an infeasible
      +% model gives false for all reactions.
       
       p=parseRAVENargs(varargin, {'cutOff',10^-6; 'rxns',[]; 'seed',[]});
       if ~isempty(p.seed)
      @@ -66,6 +73,13 @@
       %First remove all dead-end reactions
       smallModel=simplifyModel(model,false,false,true,true);
       
      +%Nothing is left to solve when every reaction is a dead end, and reactions
      +%without metabolites are limited by their bounds only
      +if isempty(smallModel.rxns) || isempty(smallModel.mets)
      +    I=ismember(rxns,smallModel.rxns(smallModel.ub>cutOff | smallModel.lb<-cutOff));
      +    return
      +end
      +
       %Get the indexes of the reactions in the connected model
       indexes=getIndexes(smallModel,intersect(smallModel.rxns,rxns),'rxns');
       J=false(numel(indexes),1);
      diff --git a/src/analysis/randomSampling.m b/src/analysis/randomSampling.m
      index 95f674e6..8d3347fe 100755
      --- a/src/analysis/randomSampling.m
      +++ b/src/analysis/randomSampling.m
      @@ -81,9 +81,12 @@
       %
       % Notes
       % -----
      -% The solutions are generated by maximizing (with random weights) for a
      -% random set of nObjectives reactions (default 2). For reversible reactions
      -% it randomly chooses between maximizing and minimizing.
      +% With 'randomObjective', the solutions are generated by maximizing (with
      +% random weights) for a random set of nObjectives reactions (default 2).
      +% For reversible reactions it randomly chooses between maximizing and
      +% minimizing. The default 'achr' and the 'chrr' methods use no random
      +% objectives; they take hit-and-run steps through the interior of the
      +% solution space.
       %
       % If the model is a GECKO v3+ ecModel, then usage_prot reactions are not
       % selected for sampling, instead focusing on sampling from the metabolic
      diff --git a/src/analysis/reporterMetabolites.m b/src/analysis/reporterMetabolites.m
      index 46ee4eb3..d2a2ae83 100755
      --- a/src/analysis/reporterMetabolites.m
      +++ b/src/analysis/reporterMetabolites.m
      @@ -36,10 +36,10 @@
       %       supplied.
       %     - mets : a cell array of metabolite IDs for the metabolites for which
       %       a score could be calculated.
      -%     - metZScores : Z-scores for differential expression around each
      -%       metabolite in "mets".
      -%     - metPValues : P-values for differential expression around each
      -%       metabolite in "mets".
      +%     - metZScores : background-corrected Z-scores for differential
      +%       expression around each metabolite in "mets". The entries of all
      +%       fields are sorted by this score, highest first.
      +%     - metPValues : one-sided (upper-tail) P-values of "metZScores".
       %     - metNGenes : number of neighbouring genes for each metabolite in
       %       "mets".
       %     - meanZ : average Z-scores for the genes around each metabolite in
      @@ -57,6 +57,10 @@
       % For details about the algorithm, see Patil KR, Nielsen J, Uncovering
       % transcriptional regulation of metabolism by using metabolic network
       % topology. Proc. Natl Acad. Sci. USA 2005;102:2685-2689.
      +%
      +% The background correction uses the closed-form mean and standard
      +% deviation of the aggregate Z-score of n genes drawn at random from all
      +% scored genes, so the results are deterministic.
       
       genes=convertCharArray(genes);
       p=parseRAVENargs(varargin, {'printResults',false; 'outputFile',[]; 'geneFoldChanges',[]});
      @@ -136,11 +140,11 @@
       
       %Correct for background using the closed-form moments of the sampling
       %distribution. For n genes drawn with replacement from the scored-gene pool
      -%(mean mu, std sigma), the aggregate Z = sum(z)/sqrt(n) has:
      +%(mean mu, population std sigma), the aggregate Z = sum(z)/sqrt(n) has:
       %  E[Z] = sqrt(n)*mu  and  Std[Z] = sigma  (independent of n)
       %so the corrected score is (metZ - sqrt(n)*mu) / sigma.
       mu = mean(geneZScores);
      -sigma = std(geneZScores);
      +sigma = std(geneZScores,1);
       metZScores = (metZScores - sqrt(metNGenes) .* mu) ./ sigma;
       
       %Calculate the P-values (upper-tail of the standard normal). Uses the base
      diff --git a/src/analysis/sampleChebyshevCenter.m b/src/analysis/sampleChebyshevCenter.m
      index 7204a639..8c8198cd 100644
      --- a/src/analysis/sampleChebyshevCenter.m
      +++ b/src/analysis/sampleChebyshevCenter.m
      @@ -26,7 +26,7 @@
       norms = sqrt(sum(A.^2, 2));
       
       % Variables [x (n, free); r (1, >=0)]; minimise -r (maximises the ball radius).
      -prob.A      = [A, norms];
      +prob.A      = sparse([A, norms]);      % Gurobi and SCIP require a sparse A
       prob.a      = prob.A;
       prob.b      = b;
       prob.csense = repmat('L', 1, m);
      diff --git a/src/annotation/assignSBOterms.m b/src/annotation/assignSBOterms.m
      index 6aa0a983..d2a42190 100644
      --- a/src/annotation/assignSBOterms.m
      +++ b/src/annotation/assignSBOterms.m
      @@ -6,7 +6,10 @@
       % organism-agnostic, parameterised entirely by opts.
       %
       % SBO is written via editMiriam(..., 'fill') so pre-existing SBO
      -% annotations are preserved.
      +% annotations are preserved. The terms are stored in model.metMiriams and
      +% model.rxnMiriams under the namespace 'sbo' (there is no separate SBO
      +% field); read them with extractMiriam(model.rxnMiriams, 'sbo').
      +% exportModel writes them as the SBML sboTerm attribute.
       %
       % Parameters
       % ----------
      diff --git a/src/biomass/scaleBiomassFraction.m b/src/biomass/scaleBiomassFraction.m
      index ba360243..6c66f864 100644
      --- a/src/biomass/scaleBiomassFraction.m
      +++ b/src/biomass/scaleBiomassFraction.m
      @@ -2,7 +2,8 @@
       % scaleBiomassFraction  Rescale a biomass component to a target value.
       %
       % Rescale a biomass component to a target g/gDW value, optionally
      -% balancing a second component so the total biomass mass stays at 1 g/gDW.
      +% rescaling a second component so that the biomass total becomes 1 g/gDW,
      +% whatever the total was before.
       % The MATLAB counterpart of yeast-GEM's legacy scaleBioMass.
       %
       % Parameters
      @@ -19,8 +20,10 @@
       % Name-Value Arguments
       % --------------------
       % balanceOut : char
      -%     Second component name to adjust so the biomass total remains 1
      -%     g/gDW. Empty / omit to skip balancing.
      +%     Second component name to adjust so the biomass total becomes 1
      +%     g/gDW. It must be large enough to absorb the change: an error is
      +%     raised if its fraction would become zero or negative, or if it is
      +%     0 to begin with. Empty / omit to skip balancing.
       %
       % Returns
       % -------
      @@ -55,11 +58,24 @@
       if ~isempty(balanceOut)
           fractions = getBiomassFractions(model, biomassConfig);
           total = fractions.total;
      +    if ~isfield(fractions, balanceOut)
      +        error('scaleBiomassFraction:unknownComponent', ...
      +            'biomassConfig has no component named %s', balanceOut);
      +    end
           balanceCurrent = fractions.(balanceOut);
           if balanceCurrent == 0
      -        return;
      +        error('scaleBiomassFraction:zeroCurrent', ...
      +            ['Cannot balance with %s: current fraction is 0 ' ...
      +             '(pseudoreaction missing or empty).'], balanceOut);
      +    end
      +    balanceNew = balanceCurrent + (1 - total);
      +    if balanceNew <= 0
      +        error('scaleBiomassFraction:negativeBalance', ...
      +            ['Cannot balance with %s: its fraction would become %g g/gDW. ' ...
      +             'It is %g g/gDW, but the other components total %g g/gDW.'], ...
      +            balanceOut, balanceNew, balanceCurrent, total - balanceCurrent);
           end
      -    balanceFactor = (balanceCurrent + (1 - total)) / balanceCurrent;
      +    balanceFactor = balanceNew / balanceCurrent;
           model = scaleBiomassPseudoreaction(model, biomassConfig, balanceOut, balanceFactor);
       end
       end
      diff --git a/src/curation/curateModelFromTables.m b/src/curation/curateModelFromTables.m
      index 2cda31fe..2b0e2d0d 100644
      --- a/src/curation/curateModelFromTables.m
      +++ b/src/curation/curateModelFromTables.m
      @@ -12,28 +12,39 @@
       % This function can therefore be used to add new entities in the model, or
       % curate those already existing in the model.
       %
      +% Columns are identified by their header (case-insensitive), so their order
      +% does not matter and optional columns can be left out. Every field that a
      +% table supplies is written to new and existing entities alike; a field the
      +% model does not have yet is created, with its default value for the other
      +% entities, unless all supplied values for it are empty. An empty grRules
      +% cell leaves the rule of an existing reaction unchanged.
      +%
       % Parameters
       % ----------
       % model : struct
       %     RAVEN model structure to be curated.
       % metsInfo : char
       %     Path to a *.tsv file with metabolite information, or 'none' to skip
      -%     metabolite curation. Columns: metNames, comps, formula, charge,
      -%     inchi, metNotes, then any number of MIRIAM-namespace columns.
      +%     metabolite curation. Columns: metNames, comps (both required),
      +%     formula, charge, inchi, metNotes, and any number of MIRIAM-namespace
      +%     columns. Singular forms and the RAVEN field names (metFormulas,
      +%     metCharges, inchis) are accepted as headers too.
       %
       % Name-Value Arguments
       % --------------------
       % genesInfo : char
       %     Path to a *.tsv file with gene information, or 'none'. Columns:
      -%     genes, geneShortNames, then MIRIAM.
      +%     genes (required), geneShortNames (or geneShortName), and MIRIAM.
       % rxnsCoeffs : char
       %     Path to a *.tsv file with reaction stoichiometric coefficients, or
      -%     'none'. Columns: rxnIdx, rxnNames, metNames, comps, coefficient. One
      -%     row per (reaction, metabolite) pair.
      +%     'none'. Columns (all required): rxnIdx (or index), rxnNames,
      +%     metNames, comps, coefficient. One row per (reaction, metabolite)
      +%     pair.
       % rxnsInfo : char
       %     Path to a *.tsv file with reaction information, or 'none'. Columns:
      -%     rxnIdx, rxnNames, grRules, lb, ub, rev, subSystems, eccodes,
      -%     rxnNotes, rxnReferences, rxnConfidenceScores, then MIRIAM.
      +%     rxnIdx (or index), rxnNames (both required), grRules, lb, ub, rev,
      +%     subSystems, eccodes, rxnNotes, rxnReferences, rxnConfidenceScores,
      +%     and MIRIAM.
       % metPrefix : char
       %     Prefix used to mint fresh metabolite ids (e.g. 's_' for yeast-GEM,
       %     'M_' for the BiGG default) (default 'M_').
      @@ -52,10 +63,9 @@
       %
       % Notes
       % -----
      -% The 'everything after the core columns is MIRIAM' convention applies to
      -% all three info tables: any column whose header is not one of the listed
      -% core fields is treated as a MIRIAM annotation namespace and stored on
      -% the matching entity.
      +% In all three info tables, any column whose header is not one of the
      +% listed core fields is treated as a MIRIAM annotation namespace and
      +% stored on the matching entity.
       
       p=parseRAVENargs(varargin, {'genesInfo','none'; 'rxnsCoeffs','none'; 'rxnsInfo','none'; 'metPrefix',[]; 'rxnPrefix',[]});
       genesInfo=p.genesInfo;
      @@ -76,25 +86,32 @@
       
       %% Metabolites
       if ~strcmp(metsInfo,'none')
      -    fid = fopen(metsInfo);
      -    raw = textscan(fid,['%q' repmat(' %q',1,17)],'Delimiter','\t');
      -    fclose(fid);
      -    metsToAdd.metNames      = raw{1}(2:end);
      -    metsToAdd.compartments  = raw{2}(2:end);
      -    metsToAdd.metFormulas   = raw{3}(2:end);
      -    metsToAdd.metCharges    = cellfun(@str2num,raw{4}(2:end),'UniformOutput',false);
      -    emptyEntry = cellfun(@isempty,metsToAdd.metCharges);
      -    if all(emptyEntry)
      -        metsToAdd           = rmfield(metsToAdd,'metCharges');
      -    elseif any(emptyEntry) % If some charges are given, assume 0 for those without charges specified
      -        metsToAdd.metCharges(emptyEntry) = {0};
      -        metsToAdd.metCharges = cell2mat(metsToAdd.metCharges);
      -    else
      -        metsToAdd.metCharges = cell2mat(metsToAdd.metCharges);
      +    [header,data] = readCurationTable(metsInfo);
      +    [col,miriamCols] = matchColumns(header,metsInfo,{'metNames','comps'},{ ...
      +        'metNames',    {'metNames','metName'}; ...
      +        'comps',       {'comps','comp'}; ...
      +        'metFormulas', {'formula','formulas','metFormula','metFormulas'}; ...
      +        'metCharges',  {'charge','charges','metCharge','metCharges'}; ...
      +        'inchis',      {'inchi','inchis'}; ...
      +        'metNotes',    {'metNotes','metNote'}});
      +    metsToAdd.metNames      = data(:,col.metNames);
      +    metsToAdd.compartments  = data(:,col.comps);
      +    textFields = {'metFormulas','inchis','metNotes'};
      +    for f = textFields(isfield(col,textFields))
      +        metsToAdd.(f{1}) = data(:,col.(f{1}));
      +    end
      +    if isfield(col,'metCharges')
      +        metsToAdd.metCharges = cellfun(@str2num,data(:,col.metCharges),'UniformOutput',false);
      +        emptyEntry = cellfun(@isempty,metsToAdd.metCharges);
      +        if all(emptyEntry)
      +            metsToAdd       = rmfield(metsToAdd,'metCharges');
      +        else % If some charges are given, assume 0 for those without charges specified
      +            metsToAdd.metCharges(emptyEntry) = {0};
      +            metsToAdd.metCharges = cell2mat(metsToAdd.metCharges);
      +        end
           end
      -    metsToAdd.inchis        = raw{5}(2:end);
      -    metsToAdd.metNotes      = raw{6}(2:end);
      -    
      +    optFields = intersect({'metFormulas','metCharges','inchis','metNotes'},fieldnames(metsToAdd)','stable');
      +
           % Check if metabolite already exists (check by metName[comp])
           existingMets            = [];
           existingMetsIdx         = [];
      @@ -109,133 +126,135 @@
                   newMets	= [newMets, i];
               end
           end
      -    
      +
           % Overwrite annotation of existing entries
           if any(existingMets)
      -        if isfield(newModel,'metFormulas')
      -            newModel.metFormulas(existingMetsIdx)  = metsToAdd.metFormulas(existingMets);
      -        end
      -        if isfield(newModel,'metCharges') && isfield(metsToAdd,'metCharges')
      -            newModel.metCharges(existingMetsIdx)   = metsToAdd.metCharges(existingMets);
      +        for f = optFields
      +            newModel = writeExistingField(newModel,f{1},existingMetsIdx,metsToAdd.(f{1})(existingMets));
               end
      -        if isfield(newModel,'inchis')
      -            newModel.inchis(existingMetsIdx)       = metsToAdd.inchis(existingMets);
      -        end
      -        newModel = extractAndAddMiriam(newModel,raw(7:end),existingMets,existingMetsIdx,'met');
      +        newModel = extractAndAddMiriam(newModel,header(miriamCols),data(:,miriamCols),existingMets,existingMetsIdx,'met');
               warning(['The following metabolites are already present in the model, '...
                   'their annotation will be overwritten to match the metsInfo file. '...
                   'If you do not particularly want to curate their annotations, it '...
      -            'would be better to removes these metabolites from metsInfo:\n\t\t%s'],...
      +            'would be better to remove these metabolites from metsInfo:\n\t\t%s'],...
                   strjoin(strcat(metsToAdd.metNames(existingMets),'[',metsToAdd.compartments(existingMets),']'),'\n\t\t'));
           end
      -    
      +
           % Continue with new metabolites
           if numel(newMets)>0
      -        metsToAdd.metNames(existingMets)        = [];
      -        metsToAdd.compartments(existingMets)    = [];
      -        metsToAdd.metFormulas(existingMets)     = [];
      -        if all(cellfun(@isempty,metsToAdd.metFormulas))
      -            metsToAdd = rmfield(metsToAdd,'metFormulas');
      -        end
      -        if isfield(metsToAdd,'metCharges')
      -            metsToAdd.metCharges(existingMets)	= [];
      -        end
      -        metsToAdd.inchis(existingMets)          = [];
      -        if all(cellfun(@isempty,metsToAdd.inchis))
      -            metsToAdd = rmfield(metsToAdd,'inchis');
      -        end
      -        metsToAdd.metNotes(existingMets)        = [];
      -        if all(cellfun(@isempty,metsToAdd.metNotes))
      -            metsToAdd = rmfield(metsToAdd,'metNotes');
      +        metsToAdd.metNames      = metsToAdd.metNames(newMets);
      +        metsToAdd.compartments  = metsToAdd.compartments(newMets);
      +        for f = optFields
      +            metsToAdd.(f{1}) = metsToAdd.(f{1})(newMets);
      +            if iscell(metsToAdd.(f{1})) && all(cellfun(@isempty,metsToAdd.(f{1})))
      +                metsToAdd = rmfield(metsToAdd,f{1});
      +            end
               end
               % Add metabolites
               newModel        = addMets(newModel,metsToAdd,true,metPrefix);
               addedIdx        = numel(newModel.mets)-numel(newMets)+1:numel(newModel.mets);
      -        newModel        = extractAndAddMiriam(newModel,raw(7:end),newMets,addedIdx,'met');
      +        newModel        = extractAndAddMiriam(newModel,header(miriamCols),data(:,miriamCols),newMets,addedIdx,'met');
           end
       end
       %% Genes
       if ~strcmp(genesInfo,'none')
      -    % Gather all data, first about reaction stoichiometries...
      -    fid = fopen(genesInfo);
      -    raw = textscan(fid,['%q' repmat(' %q',1,10)],'Delimiter','\t');
      -    fclose(fid);
      -    genesToAdd.genes            = raw{1}(2:end);
      -    genesToAdd.geneShortNames   = raw{2}(2:end);
      +    [header,data] = readCurationTable(genesInfo);
      +    [col,miriamCols] = matchColumns(header,genesInfo,{'genes'},{ ...
      +        'genes',          {'genes','gene'}; ...
      +        'geneShortNames', {'geneShortNames','geneShortName'}});
      +    genesToAdd.genes            = data(:,col.genes);
      +    if isfield(col,'geneShortNames')
      +        genesToAdd.geneShortNames   = data(:,col.geneShortNames);
      +    end
       
           [~,notNewGene,existingGene] = intersect(genesToAdd.genes,newModel.genes);
           if ~isempty(notNewGene)
               warning(['The following genes are already present in the model, their annotation '...
                   'will be overwritten to match the genesInfo file: \n\t\t%s'],...
                   strjoin(genesToAdd.genes(notNewGene),'\n\t\t'));
      -        if isfield(newModel,'geneShortNames')
      -            newModel.geneShortNames(existingGene) = genesToAdd.geneShortNames(notNewGene);
      +        if isfield(genesToAdd,'geneShortNames')
      +            newModel = writeExistingField(newModel,'geneShortNames',existingGene,genesToAdd.geneShortNames(notNewGene));
               end
      -        newModel = extractAndAddMiriam(newModel,raw(3:end),notNewGene,existingGene,'gene');
      +        newModel = extractAndAddMiriam(newModel,header(miriamCols),data(:,miriamCols),notNewGene,existingGene,'gene');
           end
      -    
      +
           % Continue with new genes
           toAdd               = 1:numel(genesToAdd.genes);
           toAdd(notNewGene)   = [];
           if ~isempty(toAdd)
               genesToAdd.genes            = genesToAdd.genes(toAdd);
      -        genesToAdd.geneShortNames   = genesToAdd.geneShortNames(toAdd);
      -        if all(cellfun(@isempty,genesToAdd.geneShortNames))
      -            genesToAdd = rmfield(genesToAdd,'geneShortNames');
      +        if isfield(genesToAdd,'geneShortNames')
      +            genesToAdd.geneShortNames   = genesToAdd.geneShortNames(toAdd);
      +            if all(cellfun(@isempty,genesToAdd.geneShortNames))
      +                genesToAdd = rmfield(genesToAdd,'geneShortNames');
      +            end
               end
               % Add genes
               newModel = addGenesRaven(newModel,genesToAdd);
               addedIdx = numel(newModel.genes)-numel(toAdd)+1:numel(newModel.genes);
      -        newModel = extractAndAddMiriam(newModel,raw(3:end),toAdd,addedIdx,'gene');
      +        newModel = extractAndAddMiriam(newModel,header(miriamCols),data(:,miriamCols),toAdd,addedIdx,'gene');
           end
       end
       %% Reactions
       if ~any(strcmp({rxnsCoeffs,rxnsInfo},'none'))
           % Gather all data, first about reaction stoichiometries...
      -    fid = fopen(rxnsCoeffs);
      -    raw = textscan(fid,'%q %q %q %q %f','Delimiter','\t','HeaderLines',1);
      -    fclose(fid);
      -    rxnCheck.coeffsIdx = str2double(raw{1});
      -    rxns.rxnNames   = raw{2};
      -    rxns.metNames   = raw{3};
      -    rxns.comps      = raw{4};
      -    rxns.coeffs     = raw{5};
      -    rxnCheck.coeffs = strcat(raw{1},'***',rxns.rxnNames);
      +    [header,data] = readCurationTable(rxnsCoeffs);
      +    col = matchColumns(header,rxnsCoeffs,{'rxnIdx','rxnNames','metNames','comps','coefficient'},{ ...
      +        'rxnIdx',      {'rxnIdx','index'}; ...
      +        'rxnNames',    {'rxnNames','rxnName'}; ...
      +        'metNames',    {'metNames','metName'}; ...
      +        'comps',       {'comps','comp'}; ...
      +        'coefficient', {'coefficient','coefficients','coeffs','stoichCoeffs'}});
      +    rxnCheck.coeffsIdx = str2double(data(:,col.rxnIdx));
      +    rxns.rxnNames   = data(:,col.rxnNames);
      +    rxns.metNames   = data(:,col.metNames);
      +    rxns.comps      = data(:,col.comps);
      +    rxns.coeffs     = str2double(data(:,col.coefficient));
      +    rxnCheck.coeffs = strcat(data(:,col.rxnIdx),'***',rxns.rxnNames);
       
           % ... and then additional reaction-specific data.
      -    fid = fopen(rxnsInfo);
      -    raw = textscan(fid,['%q' repmat(' %q',1,20)],'Delimiter','\t');
      -    fclose(fid);
      -    rxnCheck.rxnsIdx = str2double(raw{1}(2:end));
      -    rxnCheck.rxns = strcat(raw{1}(2:end),'***',raw{2}(2:end));
      -    
      +    [header,data] = readCurationTable(rxnsInfo);
      +    [col,miriamCols] = matchColumns(header,rxnsInfo,{'rxnIdx','rxnNames'},{ ...
      +        'rxnIdx',              {'rxnIdx','index'}; ...
      +        'rxnNames',            {'rxnNames','rxnName'}; ...
      +        'grRules',             {'grRules'}; ...
      +        'lb',                  {'lb'}; ...
      +        'ub',                  {'ub'}; ...
      +        'rev',                 {'rev'}; ...
      +        'subSystems',          {'subSystems'}; ...
      +        'eccodes',             {'eccodes'}; ...
      +        'rxnNotes',            {'rxnNotes'}; ...
      +        'rxnReferences',       {'rxnReferences'}; ...
      +        'rxnConfidenceScores', {'rxnConfidenceScores'}});
      +    rxnCheck.rxnsIdx = str2double(data(:,col.rxnIdx));
      +    rxnCheck.rxns = strcat(data(:,col.rxnIdx),'***',data(:,col.rxnNames));
      +
           notMatching = setxor(rxnCheck.coeffs,rxnCheck.rxns);
           if numel(notMatching)>1
               error(['The following reactions ánd/or their indices are not matched '...
                   'between the rxnsInfo and rxnsCoeffs files:\n\t\t%s'],...
                   strjoin(regexprep(notMatching,'^\d+***',''),'\n\t\t'))
           end
      -    
      -    rxnsToAdd.rxnNames              = raw{2}(2:end);
      -    rxnsToAdd.grRules               = raw{3}(2:end);
      -    rxnsToAdd.lb                    = cellfun(@str2num,raw{4}(2:end));
      -    rxnsToAdd.ub                    = cellfun(@str2num,raw{5}(2:end));
      -    rxnsToAdd.rev                   = cellfun(@str2num,raw{6}(2:end));
      -    rxnsToAdd.subSystems            = raw{7}(2:end);
      -    rxnsToAdd.eccodes               = raw{8}(2:end);
      -    rxnsToAdd.rxnNotes              = raw{9}(2:end);
      -    rxnsToAdd.rxnReferences         = raw{10}(2:end);
      -    rxnsToAdd.rxnConfidenceScores   = cellfun(@str2num,raw{11}(2:end),'UniformOutput',false);
      -    emptyEntry = cellfun(@isempty,rxnsToAdd.rxnConfidenceScores);
      -    if all(emptyEntry)
      -        rxnsToAdd           = rmfield(rxnsToAdd,'rxnConfidenceScores');
      -    elseif any(emptyEntry) % If some rxnConfidenceScores are given, assume 0 for those without rxnConfidenceScores specified
      -        rxnsToAdd.rxnConfidenceScores(emptyEntry) = {0};
      -        rxnsToAdd.rxnConfidenceScores = cell2mat(rxnsToAdd.rxnConfidenceScores);
      -    else
      -        rxnsToAdd.rxnConfidenceScores = cell2mat(rxnsToAdd.rxnConfidenceScores);
      +
      +    rxnsToAdd.rxnNames              = data(:,col.rxnNames);
      +    for f = intersect({'grRules','subSystems','eccodes','rxnNotes','rxnReferences'},fieldnames(col)','stable')
      +        rxnsToAdd.(f{1}) = data(:,col.(f{1}));
           end
      -    
      +    for f = intersect({'lb','ub','rev'},fieldnames(col)','stable')
      +        rxnsToAdd.(f{1}) = cellfun(@str2num,data(:,col.(f{1})));
      +    end
      +    if isfield(col,'rxnConfidenceScores')
      +        rxnsToAdd.rxnConfidenceScores   = cellfun(@str2num,data(:,col.rxnConfidenceScores),'UniformOutput',false);
      +        emptyEntry = cellfun(@isempty,rxnsToAdd.rxnConfidenceScores);
      +        if all(emptyEntry)
      +            rxnsToAdd           = rmfield(rxnsToAdd,'rxnConfidenceScores');
      +        else % If some rxnConfidenceScores are given, assume 0 for those without rxnConfidenceScores specified
      +            rxnsToAdd.rxnConfidenceScores(emptyEntry) = {0};
      +            rxnsToAdd.rxnConfidenceScores = cell2mat(rxnsToAdd.rxnConfidenceScores);
      +        end
      +    end
      +    optFields = intersect({'subSystems','eccodes','rxnNotes','rxnReferences','rxnConfidenceScores'},fieldnames(rxnsToAdd)','stable');
      +
           existingRxn=[];
           notNewRxn=[];
           for i=1:numel(rxnCheck.rxnsIdx)
      @@ -272,7 +291,7 @@
                   notNewRxn       = [notNewRxn,i];
               end
           end
      -    
      +
           % If some reactions already existed, then replace its information
           if ~isempty(existingRxn)
               warning(['The following reactions are the same as existing model '...
      @@ -280,84 +299,126 @@
                   'rxnCoeffs file: \n\t\t(Existing reaction ID): New reaction name\n\t\t%s'],...
                   strjoin(strcat('(',newModel.rxns(existingRxn),{'): '},rxnsToAdd.rxnNames(notNewRxn)),'\n\t\t'));
               newModel.rxnNames(existingRxn) = rxnsToAdd.rxnNames(notNewRxn);
      -        newModel.lb(existingRxn)       = rxnsToAdd.lb(notNewRxn);
      -        newModel.ub(existingRxn)       = rxnsToAdd.ub(notNewRxn);
      -        newModel.rev(existingRxn)      = rxnsToAdd.rev(notNewRxn);
      -        if isfield(newModel,'subSystems')
      -            newModel.subSystems(existingRxn)    = rxnsToAdd.subSystems(notNewRxn);
      +        for f = intersect({'lb','ub','rev'},fieldnames(rxnsToAdd)','stable')
      +            newModel.(f{1})(existingRxn) = rxnsToAdd.(f{1})(notNewRxn);
               end
      -        if isfield(newModel,'eccodes')
      -            newModel.eccodes(existingRxn)       = rxnsToAdd.eccodes(notNewRxn);
      -        end
      -        if isfield(newModel,'rxnNotes')
      -            newModel.rxnNotes(existingRxn)      = rxnsToAdd.rxnNotes(notNewRxn);
      +        for f = optFields
      +            values = rxnsToAdd.(f{1})(notNewRxn);
      +            if strcmp(f{1},'subSystems') && isfield(newModel,'subSystems') ...
      +                    && any(cellfun(@iscell,newModel.subSystems))
      +                values = cellfun(@(x) {x}, values, 'UniformOutput', false);
      +            end
      +            newModel = writeExistingField(newModel,f{1},existingRxn,values);
               end
      -        if isfield(newModel,'rxnConfidenceScores') && isfield(rxnsToAdd,'rxnConfidenceScores')
      -            newModel.rxnConfidenceScores(existingRxn) = rxnsToAdd.rxnConfidenceScores(notNewRxn);
      +        if isfield(rxnsToAdd,'grRules')
      +            emptyEntries = cellfun(@isempty,rxnsToAdd.grRules(notNewRxn));
      +            if ~all(emptyEntries)
      +                newModel    = changeGrRules(newModel,newModel.rxns(existingRxn(~emptyEntries)),rxnsToAdd.grRules(notNewRxn(~emptyEntries)),true);
      +            end
               end
      -        emptyEntries = cellfun(@isempty,rxnsToAdd.grRules);
      -        if ~all(emptyEntries)
      -            newModel    = changeGrRules(newModel,newModel.rxns(existingRxn(~emptyEntries)),rxnsToAdd.grRules(notNewRxn(~emptyEntries)),true);
      -        end        
      -        newModel    = extractAndAddMiriam(newModel,raw(11:end),notNewRxn,existingRxn,'rxn');
      +        newModel    = extractAndAddMiriam(newModel,header(miriamCols),data(:,miriamCols),notNewRxn,existingRxn,'rxn');
           end
      -    
      +
           % Continue with new reactions
           toAdd               = 1:numel(rxnsToAdd.rxnNames);
           toAdd(notNewRxn)    = [];
           if ~isempty(toAdd)
               rxnsToAdd.rxnNames      = rxnsToAdd.rxnNames(toAdd);
      -        rxnsToAdd.lb            = rxnsToAdd.lb(toAdd);
      -        rxnsToAdd.ub            = rxnsToAdd.ub(toAdd);
      -        rxnsToAdd.rev           = rxnsToAdd.rev(toAdd);
      -        rxnsToAdd.subSystems    = rxnsToAdd.subSystems(toAdd);
      -        if all(cellfun(@isempty,rxnsToAdd.subSystems))
      -            rxnsToAdd = rmfield(rxnsToAdd,'subSystems');
      -        end
      -        rxnsToAdd.eccodes       = rxnsToAdd.eccodes(toAdd);
      -        if all(cellfun(@isempty,rxnsToAdd.eccodes))
      -            rxnsToAdd = rmfield(rxnsToAdd,'eccodes');
      -        end
      -        rxnsToAdd.rxnNotes      = rxnsToAdd.rxnNotes(toAdd);
      -        if all(cellfun(@isempty,rxnsToAdd.rxnNotes))
      -            rxnsToAdd = rmfield(rxnsToAdd,'rxnNotes');
      -        end
      -        if isfield(rxnsToAdd,'rxnConfidenceScores')
      -            rxnsToAdd.rxnConfidenceScores = rxnsToAdd.rxnConfidenceScores(toAdd);
      +        for f = intersect({'lb','ub','rev','grRules'},fieldnames(rxnsToAdd)','stable')
      +            rxnsToAdd.(f{1}) = rxnsToAdd.(f{1})(toAdd);
               end
      -        rxnsToAdd.rxnReferences = rxnsToAdd.rxnReferences(toAdd);
      -        if all(cellfun(@isempty,rxnsToAdd.rxnReferences))
      -            rxnsToAdd = rmfield(rxnsToAdd,'rxnReferences');
      +        for f = optFields
      +            rxnsToAdd.(f{1}) = rxnsToAdd.(f{1})(toAdd);
      +            if iscell(rxnsToAdd.(f{1})) && all(cellfun(@isempty,rxnsToAdd.(f{1})))
      +                rxnsToAdd = rmfield(rxnsToAdd,f{1});
      +            end
               end
      -        rxnsToAdd.grRules       = rxnsToAdd.grRules(toAdd);
               rxnsToAdd.mets          = rxnsToAdd.mets(toAdd);
               rxnsToAdd.stoichCoeffs  = rxnsToAdd.stoichCoeffs(toAdd);
               rxnsToAdd.rxns          = generateNewIds(model,'rxns',rxnPrefix,numel(rxnsToAdd.rxnNames));
      -        
      +
               newModel        = addRxns(newModel,rxnsToAdd,1,[],false,false);
               rxnsModelIdx    = numel(newModel.rxns)-numel(toAdd)+1:numel(newModel.rxns);
      -        newModel        = extractAndAddMiriam(newModel,raw(11:end),toAdd,rxnsModelIdx,'rxn');
      +        newModel        = extractAndAddMiriam(newModel,header(miriamCols),data(:,miriamCols),toAdd,rxnsModelIdx,'rxn');
           end
       end
       end
       
      -function newModel = extractAndAddMiriam(model,raw,inputIndex,modelIndex,type)
      -newModel=model;
      -miriamName              = cell(numel(inputIndex),1);
      -miriamValues            = cell(1,numel(inputIndex));
      -for i=1:numel(raw)
      -    miriamName{i}                       = raw{i}{1};
      -    miriamValues(1:numel(inputIndex),i) = raw{i}(inputIndex+1);
      +function [header,data] = readCurationTable(fileName)
      +% Read a tab-delimited curation table into its header row and a cell
      +% array of text with one column per header field.
      +fid = fopen(fileName);
      +if fid==-1
      +    error('Could not open the file "%s"',fileName);
      +end
      +headerLine = fgetl(fid);
      +if ~ischar(headerLine)
      +    fclose(fid);
      +    error('The file "%s" is empty',fileName);
      +end
      +headerLine = strrep(headerLine,char(65279),''); % byte order mark
      +header = strtrim(strsplit(headerLine,'\t'));
      +header = regexprep(header,'^"(.*)"$','$1');
      +nCols  = numel(header);
      +raw    = textscan(fid,repmat('%q',1,nCols),'Delimiter','\t');
      +fclose(fid);
      +nRows  = max(cellfun(@numel,raw));
      +data   = repmat({''},nRows,nCols);
      +for i=1:nCols
      +    data(1:numel(raw{i}),i) = raw{i};
      +end
       end
      -emptyMiriam     =   all(cellfun(@isempty,miriamValues),1);
      +
      +function [col,miriamCols] = matchColumns(header,fileName,required,spec)
      +% Locate the core columns of a curation table by header name (case
      +% insensitive). spec has one row per core field: {field, accepted headers}.
      +% Every other column is a MIRIAM namespace.
      +col        = struct();
      +isCore     = false(1,numel(header));
      +for i=1:size(spec,1)
      +    match = find(ismember(lower(header),lower(spec{i,2})));
      +    if ~isempty(match)
      +        col.(spec{i,1}) = match(1);
      +        isCore(match)   = true;
      +    end
      +end
      +missing = setdiff(required,fieldnames(col)','stable');
      +if ~isempty(missing)
      +    error('The file "%s" has no column with header: %s',fileName,strjoin(missing,', '));
      +end
      +miriamCols = find(~isCore & ~cellfun(@isempty,header));
      +end
      +
      +function model = writeExistingField(model,field,modelIdx,values)
      +% Write the values of an optional field onto existing entities. A field
      +% the model does not have is created from its ravenModelFields default,
      +% unless all supplied values are empty.
      +if ~isfield(model,field)
      +    if iscell(values) && all(cellfun(@isempty,values))
      +        return
      +    end
      +    reg   = ravenModelFields();
      +    entry = reg(strcmp({reg.name},field));
      +    n     = numel(model.([entry.type 's']));
      +    if iscell(values)
      +        model.(field) = repmat({entry.default},n,1);
      +    else
      +        model.(field) = repmat(entry.default,n,1);
      +    end
      +end
      +model.(field)(modelIdx) = values;
      +end
      +
      +function newModel = extractAndAddMiriam(model,miriamName,miriamData,inputIndex,modelIndex,type)
      +newModel=model;
      +miriamValues    = miriamData(inputIndex,:);
      +emptyMiriam     = all(cellfun(@isempty,miriamValues),1);
       miriamName(emptyMiriam)     = [];
       miriamValues(:,emptyMiriam) = [];
      -if ~isfield(newModel,[type 'Miriams']);
      +if ~isfield(newModel,[type 'Miriams'])
           newModel.([type 'Miriams'])=cell(numel(newModel.([type 's'])),1);
       end
      -if ~isempty(miriamName)
      -    for i=1:numel(miriamName)
      -        newModel = editMiriam(newModel,type,modelIndex,miriamName{i},miriamValues(:,i),'replace');
      -    end
      +for i=1:numel(miriamName)
      +    newModel = editMiriam(newModel,type,modelIndex,miriamName{i},miriamValues(:,i),'replace');
       end
       end
      diff --git a/src/gapfilling/fillGaps.m b/src/gapfilling/fillGaps.m
      index 56480cf1..0ebe1605 100755
      --- a/src/gapfilling/fillGaps.m
      +++ b/src/gapfilling/fillGaps.m
      @@ -55,7 +55,7 @@
       %     - 'reference'   : MILP-based connectivity maximisation
       %     - 'fastLP'      : L1-norm LP relaxation via FASTCORE (gapFillFastLP)
       %     - 'swiftLP'     : SWIFTCORE single-LP variant (gapFillFastLP)
      -%     - 'gapfillMILP' : growth-floor MILP with directionality repair
      +%     - 'gapfillMILP' : growth-floor MILP adding database reactions
       %                       (gapFillMILP). Uses only models{1} as the database.
       %     - 'topological' : BFS metabolite producibility pre-screening
       %                       (gapFillTopological); no model modification.
      @@ -93,6 +93,13 @@
       %         fillGaps(model,models,allowNetProduction,useModelConstraints,...
       %         supressWarnings,rxnScores,params);
       %
      +% Notes
      +% -----
      +% The 'reference' and 'gapfillMILP' algorithms solve a MILP with the
      +% solver set by setRavenSolver. RAVEN's bundled GLPK solves it for small
      +% models (with a RAVEN:glpkMILP warning); for larger models Gurobi is much
      +% faster and more reliable (setRavenSolver('gurobi')).
      +%
       % See Also
       % --------
       % ftINITFillGaps : the ftINIT-side counterpart, used when the reference
      @@ -135,7 +142,7 @@
               if numel(models) < 1
                   error('RAVEN:badInput', 'fillGaps: gapfillMILP requires at least one reference model.');
               end
      -        [addedRxns, ~, newModel, exitFlag] = gapFillMILP(model, models{1}, ...
      +        [addedRxns, newModel, exitFlag] = gapFillMILP(model, models{1}, ...
                   'minGrowth', p.minGrowth, 'verbose', p.verbose, 'params', params);
               newConnected = {};
               cannotConnect = {};
      diff --git a/src/gapfilling/gapFillMILP.m b/src/gapfilling/gapFillMILP.m
      index 0127fe1a..34da6e53 100644
      --- a/src/gapfilling/gapFillMILP.m
      +++ b/src/gapfilling/gapFillMILP.m
      @@ -1,18 +1,18 @@
      -function [addedRxns, reversedRxns, newModel, exitFlag] = gapFillMILP(model, universalModel, varargin)
      +function [addedRxns, newModel, exitFlag] = gapFillMILP(model, universalModel, varargin)
       % gapFillMILP  Objective-based gap-filling via a global growth-floor MILP.
       %
      -% Finds the minimum-cost set of modifications to make the model's objective
      -% (biomass) >= minGrowth. Supports two repair mechanisms from Kumar et al.
      -% 2007 (Bioinformatics 23:1626-1635):
      -%   1. Directionality reversal of existing draft reactions (binary y_rev)
      -%   2. Addition of reactions from a universal database (binary y_db)
      +% Finds the smallest set of reactions from a universal database that makes
      +% the model's objective (biomass) >= minGrowth, following the database
      +% repair of Kumar et al. 2007 (BMC Bioinformatics 8:212,
      +% doi:10.1186/1471-2105-8-212). Reactions already in the draft model keep
      +% their bounds: a reaction with the wrong direction is a curation issue and
      +% is not changed here.
       %
       % The universal model should contain all candidate reactions, including
       % exchange and transport reactions if those repair classes are desired.
       %
      -% This is a SINGLE-level MILP (not bilevel). It is computationally more
      -% expensive than gapFillFastLP but supports the directionality-reversal
      -% repair mechanism and directly optimises for objective feasibility.
      +% This is a single-level MILP. It is computationally more expensive than
      +% gapFillFastLP but directly optimises for objective feasibility.
       %
       % Parameters
       % ----------
      @@ -26,11 +26,6 @@
       % 'minGrowth' (default [])
       %     minimum objective value to achieve. If empty, runs FBA on the merged
       %     model and uses 10% of the maximum as the floor.
      -% 'weights' (default [1 2 3 4])
      -%     cost weights [w_rev w_db w_exch w_trans]. w_rev is the cost per
      -%     reversed draft reaction; w_db is the cost per added universal
      -%     reaction. w_exch and w_trans are reserved for future use when
      -%     exchange and transport reactions are tracked separately.
       % 'bigM' (default 1000)
       %     Big-M constant for coupling constraints. Must be larger than any
       %     expected flux magnitude.
      @@ -44,14 +39,17 @@
       % -------
       % addedRxns : cell
       %     reaction IDs from universalModel that are selected by the MILP.
      -% reversedRxns : cell
      -%     draft reaction IDs whose directionality is reversed by the MILP.
       % newModel : struct
      -%     model with addedRxns incorporated and reversedRxns bounds updated
      -%     (lb set to -bigM for reversed reactions).
      +%     model with addedRxns incorporated.
       % exitFlag : double
       %     1 = optimal, -1 = infeasible, -2 = timeout or other failure.
       %
      +% Notes
      +% -----
      +% The MILP is solved with the solver set by setRavenSolver. RAVEN's bundled GLPK solves it for small
      +% models (with a RAVEN:glpkMILP warning); for larger models Gurobi is much
      +% faster and more reliable (setRavenSolver('gurobi')).
      +%
       % See also
       % --------
       % gapFillFastLP, gapFillTopological, fillGaps
      @@ -59,25 +57,19 @@
       % ---- Parse optional arguments ----
       p = inputParser();
       addParameter(p, 'minGrowth', [],  @(x) isempty(x) || isnumeric(x));
      -addParameter(p, 'weights',   [1 2 3 4], @isnumeric);
       addParameter(p, 'bigM',      1000,      @isnumeric);
       addParameter(p, 'params',    [],        @(x) isempty(x) || isstruct(x));
       addParameter(p, 'verbose',   true,      @islogical);
       parse(p, varargin{:});
       minGrowth = p.Results.minGrowth;
      -weights   = p.Results.weights;
       M         = p.Results.bigM;
       params    = p.Results.params;
       verbose   = p.Results.verbose;
       
      -w_rev = weights(1);
      -w_db  = weights(2);
      -
       % ---- Initialise outputs ----
      -addedRxns    = {};
      -reversedRxns = {};
      -newModel     = model;
      -exitFlag     = -1;
      +addedRxns = {};
      +newModel  = model;
      +exitFlag  = -1;
       
       % Check that the model has an objective
       if all(model.c == 0)
      @@ -99,16 +91,10 @@
       univIdx  = find(isUniv);     % positions in merged model
       nUniv    = numel(univIdx);
       
      -% Reversal candidates: draft reactions that are currently irreversible
      -% (lb >= 0). Reversing their directionality means allowing negative flux.
      -revCandMask = isDraft & mergedModel.lb >= 0;
      -revCandIdx  = find(revCandMask);   % positions in merged model
      -nRev        = numel(revCandIdx);
      -
       if verbose
           fprintf('gapFillMILP: merged model has %d mets, %d rxns (%d draft, %d universal).\n', ...
               nMergedMets, nMergedRxns, sum(isDraft), nUniv);
      -    fprintf('gapFillMILP: %d reversal candidates, %d database candidates.\n', nRev, nUniv);
      +    fprintf('gapFillMILP: %d database candidates.\n', nUniv);
       end
       
       % ---- Determine minGrowth ----
      @@ -147,35 +133,30 @@
       % ---- Build MILP ----
       % Variable ordering:
       %   cols 1 : nMergedRxns          → v (all fluxes, continuous)
      -%   cols nMergedRxns+1 : +nRev    → y_rev (binary, directionality reversal)
      -%   cols nMergedRxns+nRev+1 : end → y_db  (binary, universal reactions)
      -nVar = nMergedRxns + nRev + nUniv;
      +%   cols nMergedRxns+1 : end      → y_db (binary, universal reactions)
      +nVar = nMergedRxns + nUniv;
       
       % --- Variable bounds ---
       lb = mergedModel.lb;
       ub = mergedModel.ub;
       
      -% Relax reversal candidates to allow negative flux (Big-M on lower bound)
      -lb(revCandIdx) = -M;
      -
       % Universal reactions: allow them to be forced to 0 (extend lb toward 0)
       lb(univIdx) = min(mergedModel.lb(univIdx), 0);
       
       % Binary variable bounds
      -lb = [lb; zeros(nRev + nUniv, 1)];
      -ub = [ub; ones(nRev + nUniv, 1)];
      +lb = [lb; zeros(nUniv, 1)];
      +ub = [ub; ones(nUniv, 1)];
       
      -% --- Objective ---
      +% --- Objective: number of added universal reactions ---
       c = zeros(nVar, 1);
      -c(nMergedRxns + 1 : nMergedRxns + nRev)  = w_rev;  % y_rev cost
      -c(nMergedRxns + nRev + 1 : end)           = w_db;   % y_db cost
      +c(nMergedRxns + 1 : end) = 1;
       
       % --- Constraint blocks ---
       % (1) Steady state: S * v = 0   [nMergedMets rows, 'E']
      -A_SS = [mergedModel.S, sparse(nMergedMets, nRev + nUniv)];
      +A_SS = [mergedModel.S, sparse(nMergedMets, nUniv)];
       
       % (2) Growth floor: c' * v >= minGrowth   [1 row, 'G']
      -A_growth = [sparse(mergedModel.c'), sparse(1, nRev + nUniv)];
      +A_growth = [sparse(mergedModel.c'), sparse(1, nUniv)];
       
       % (3) Universal upper coupling: v_k - ub_k * y_db_k <= 0   [nUniv rows, 'L']
       %     (forces v_k <= 0 when y_db_k = 0)
      @@ -183,7 +164,7 @@
       % universal reaction (ub = Inf, RAVEN's own default when no upper-bound
       % annotation is set) would otherwise put an Inf coefficient into the
       % constraint matrix, and Inf*0 (y_db_k = 0) is NaN, not 0.
      -yDbCols = nMergedRxns + nRev + (1:nUniv);
      +yDbCols = nMergedRxns + (1:nUniv);
       uniUb = min(mergedModel.ub(univIdx), M);
       uniLb = max(mergedModel.lb(univIdx), -M);
       A_uniUp = sparse(...
      @@ -200,29 +181,19 @@
           [ones(1, nUniv), -uniLb'], ...
           nUniv, nVar);
       
      -% (5) Reversal coupling: v_j + M * y_rev_j >= 0   [nRev rows, 'G']
      -%     (forces v_j >= 0 when y_rev_j = 0, i.e., preserves original irreversibility)
      -yRevCols = nMergedRxns + (1:nRev);
      -A_rev = sparse(...
      -    [1:nRev, 1:nRev], ...
      -    [revCandIdx(:)', yRevCols], ...
      -    [ones(1, nRev), M * ones(1, nRev)], ...
      -    nRev, nVar);
      -
       % --- Assemble ---
      -prob.A = [A_SS; A_growth; A_uniUp; A_uniLo; A_rev];
      +prob.A = [A_SS; A_growth; A_uniUp; A_uniLo];
       prob.a = prob.A;
      -prob.b = [zeros(nMergedMets, 1); minGrowth; ...
      -          zeros(nUniv, 1); zeros(nUniv, 1); zeros(nRev, 1)];
      +prob.b = [zeros(nMergedMets, 1); minGrowth; zeros(2 * nUniv, 1)];
       prob.csense = [repmat('E', 1, nMergedMets), 'G', ...
                      repmat('L', 1, nUniv), ...
      -               repmat('G', 1, nUniv + nRev)];
      +               repmat('G', 1, nUniv)];
       prob.c      = c;
      -prob.osense = 1;   % minimise weighted sum of binary variables
      +prob.osense = 1;   % minimise the number of added reactions
       prob.lb     = lb;
       prob.ub     = ub;
       prob.vartype = repmat('C', 1, nVar);
      -prob.vartype(nMergedRxns + 1 : end) = 'B';   % y_rev and y_db are binary
      +prob.vartype(nMergedRxns + 1 : end) = 'B';   % y_db is binary
       
       if isempty(params)
           params.intTol   = 1e-9;
      @@ -232,7 +203,7 @@
       % ---- Solve ----
       if verbose
           fprintf('gapFillMILP: solving MILP (%d vars, %d binary, %d constraints)...\n', ...
      -        nVar, nRev + nUniv, size(prob.A, 1));
      +        nVar, nUniv, size(prob.A, 1));
       end
       sol = optimizeProb(prob, params, verbose);
       
      @@ -246,11 +217,8 @@
       
       % ---- Extract solution ----
       binThreshold = 1e-3;
      -yRev = sol.full(nMergedRxns + 1 : nMergedRxns + nRev);
      -yDb  = sol.full(nMergedRxns + nRev + 1 : end);
      -
      -revSelected = revCandIdx(yRev > binThreshold);
      -dbSelected  = univIdx(yDb > binThreshold);
      +yDb = sol.full(nMergedRxns + 1 : end);
      +dbSelected = univIdx(yDb > binThreshold);
       
       % Map back to original IDs. dbSelected is already restricted to universal
       % reactions via univIdx/isDraft above (an id-membership test against
      @@ -258,26 +226,15 @@
       % further filtering against universalModel.rxns is needed here -- doing so
       % would silently drop a universal reaction that mergeModels renamed
       % because its id collided with one already in the draft model.
      -reversedRxns = mergedModel.rxns(revSelected);
      -addedRxns    = mergedModel.rxns(dbSelected);
      +addedRxns = mergedModel.rxns(dbSelected);
       
       if verbose
      -    fprintf('gapFillMILP: reversed %d draft reaction(s), added %d universal reaction(s).\n', ...
      -        numel(reversedRxns), numel(addedRxns));
      +    fprintf('gapFillMILP: added %d universal reaction(s).\n', numel(addedRxns));
       end
       
       % ---- Build new model ----
       newModel = model;
       
      -% Apply directionality reversals: allow negative flux
      -for k = 1:numel(reversedRxns)
      -    rxnPos = find(getIndexes(newModel, reversedRxns(k), 'rxns', true));
      -    if ~isempty(rxnPos)
      -        newModel.lb(rxnPos) = -M;
      -        newModel.rev(rxnPos) = 1;
      -    end
      -end
      -
       % Add universal reactions. Extracted from mergedModel by the dbSelected
       % indices directly, not by looking addedRxns' names back up in
       % universalModel: that lookup fails whenever mergeModels renamed one of
      diff --git a/src/gapfilling/gapReport.m b/src/gapfilling/gapReport.m
      index b1816068..b28bdc38 100755
      --- a/src/gapfilling/gapReport.m
      +++ b/src/gapfilling/gapReport.m
      @@ -54,6 +54,14 @@
       %     the reactions that were added from the template models and which
       %     model they were added from.
       %
      +% Notes
      +% -----
      +% The model must be feasible under its own bounds, and, when the mass
      +% balancing section runs, also with its boundary closed. Otherwise
      +% gapReport stops with a RAVEN:infeasibleModel error that names the
      +% reactions with a forced flux; set the bounds of ATP maintenance and any
      +% other forced flux to zero first.
      +%
       % Examples
       % --------
       %     [noFluxRxns, noFluxRxnsRelaxed, subGraphs, notProducedMets, ...
      @@ -85,6 +93,11 @@
           calculateINOUT=false;
       end
       
      +requireFeasible(model,'under its own bounds');
      +if calculateINOUT
      +    requireFeasible(closedModel,'once its boundary reactions are closed');
      +end
      +
       model2=model;
       model2.b=[model2.b inf(numel(model2.mets),1)];
       I=haveFlux(model);
      @@ -133,3 +146,20 @@
           fprintf([num2str(numel(connectedFromTemplates)) ' unconnected reactions can be connected by including ' num2str(numel(addedFromTemplates)) ' reactions from\n' t '\n']);
       end
       end
      +
      +function requireFeasible(model,condition)
      +%Errors, naming the forced-flux reactions, if the model is infeasible
      +model.c(:)=0;
      +sol=solveLP(model);
      +if sol.stat~=-1
      +    return
      +end
      +forced=model.rxns(model.lb>0 | model.ub<0);
      +hint='';
      +if ~isempty(forced)
      +    hint=[' Reactions with a forced flux: ' strjoin(forced,', ') '.'];
      +end
      +error('RAVEN:infeasibleModel',['The model is infeasible ' condition ...
      +    ', so gapReport cannot analyse it.' strrep(hint,'%','%%') ...
      +    ' Set the bounds of ATP maintenance and any other forced flux to zero first.']);
      +end
      diff --git a/src/io/readYAMLmodel.m b/src/io/readYAMLmodel.m
      index 9d1dcb43..90a3333c 100755
      --- a/src/io/readYAMLmodel.m
      +++ b/src/io/readYAMLmodel.m
      @@ -58,9 +58,11 @@
       % A continuation line is recognised by NOT starting with '- ': every real
       % entry in this format, at every nesting depth (including a nested list
       % under a key, e.g. an annotation sub-entry indented 6+ spaces), is itself
      -% introduced with a literal leading dash, while a folded scalar's
      -% continuation is raw text with no list-item marker of its own.
      -newLine=regexp(line_raw,'^ {6,}([^-\s].*)','tokenExtents');
      +% introduced with a list-item marker (a dash followed by a space, or a dash
      +% ending the line), while a folded scalar's continuation is raw text with no
      +% list-item marker of its own. Such text can itself start with a dash, as in
      +% a KEGG reaction name wrapped before "-epoxidizing)".
      +newLine=regexp(line_raw,'^ {6,}((?!-(\s|$))\S.*)','tokenExtents');
       brokenLine=find(~cellfun('isempty',newLine));
       for i=flip(1:numel(brokenLine))
           extraLine = char(line_raw(brokenLine(i)));
      diff --git a/src/queries/getIndexes.m b/src/queries/getIndexes.m
      index 87770bbc..1c64efa5 100755
      --- a/src/queries/getIndexes.m
      +++ b/src/queries/getIndexes.m
      @@ -12,6 +12,8 @@
       %     'rxns', 'mets', or 'genes' depending on what to retrieve. 'metnames'
       %     queries metabolite names, while 'metcomps' allows providing specific
       %     metabolites and their compartments in the format metaboliteName[comp].
      +%     An error is raised if such a name matches no metabolite, or more than
      +%     one.
       %     If a model.ec structure exists (GECKO 3), then also 'ecenzymes',
       %     'ecrxns' and 'ecgenes' are allowed.
       %
      @@ -65,10 +67,14 @@
               for i=1:numel(objects)
                   index=find(strcmp(mets(i),model.metNames));
                   index=index(strcmp(comps(i),model.comps(model.metComps(index))));
      -            if ~isempty(index)
      +            if isscalar(index)
                       indexes(i)=index;
      +            elseif isempty(index)
      +                error(['Could not find object ''' objects{i} ''' in the model']);
                   else
      -                error(['Could not find object ' objects{i} ' in the model']);
      +                error(['''' objects{i} ''' is ambiguous: it matches the ' ...
      +                    'metabolites ' strjoin(model.mets(index),', ') ...
      +                    '. Use metabolite ids (type ''mets'') instead.']);
                   end
               end
               indexes=indexes(:);
      diff --git a/src/reconstruction/kegg/getKEGGModelForOrganism.m b/src/reconstruction/kegg/getKEGGModelForOrganism.m
      index de8ce653..18330504 100755
      --- a/src/reconstruction/kegg/getKEGGModelForOrganism.m
      +++ b/src/reconstruction/kegg/getKEGGModelForOrganism.m
      @@ -340,7 +340,11 @@
               model.geneMiriams{i,1}.name{1,1}='kegg.genes';
               model.geneMiriams{i,1}.value{1,1}=strcat(lower(organismID),model.genes{i,1});
           end
      -    %Add the description to the reactions
      +    %Add the description to the reactions. The reference model has no
      +    %reaction notes, so readYAMLmodel leaves the field out
      +    if ~isfield(model,'rxnNotes')
      +        model.rxnNotes=repmat({''},numel(model.rxns),1);
      +    end
           for i=1:numel(model.rxns)
               if ~isempty(model.rxnNotes{i})
                   model.rxnNotes(i)=strcat('Included by getKEGGModelForOrganism (without HMMs).',model.rxnNotes(i));
      @@ -532,7 +536,11 @@
       model.grRules = grRules;
       model.rxnGeneMat = rxnGeneMat;
       
      -%Add the description to the reactions
      +%Add the description to the reactions. The reference model has no reaction
      +%notes, so readYAMLmodel leaves the field out
      +if ~isfield(model,'rxnNotes')
      +    model.rxnNotes=repmat({''},numel(model.rxns),1);
      +end
       for i=1:numel(model.rxns)
           if ~isempty(model.rxnNotes{i})
               model.rxnNotes(i)=strcat('Included by getKEGGModelForOrganism (using HMMs).',model.rxnNotes(i));
      diff --git a/src/solver/checkSolution.m b/src/solver/checkSolution.m
      index 3fb59b49..e6b3478b 100755
      --- a/src/solver/checkSolution.m
      +++ b/src/solver/checkSolution.m
      @@ -1,10 +1,10 @@
       function [isFeasible, isOptimal]=checkSolution(res)
      -% checkSolution  Check if a Mosek solution is feasible and optimal.
      +% checkSolution  Check if a solver result is feasible and optimal.
       %
       % Parameters
       % ----------
       % res : struct
      -%     the output structure from mosekopt.
      +%     the output structure from optimizeProb.
       %
       % Returns
       % -------
      @@ -16,10 +16,7 @@
       % Examples
       % --------
       %     [isFeasible, isOptimal] = checkSolution(res);
      -%
      -% Notes
      -% -----
      -% This function also throws an error if the license has expired.
      +
       
       isFeasible=false;
       isOptimal=false;
      diff --git a/src/solver/optimizeProb.m b/src/solver/optimizeProb.m
      index de278ad1..31991dad 100755
      --- a/src/solver/optimizeProb.m
      +++ b/src/solver/optimizeProb.m
      @@ -27,6 +27,15 @@
       %     time limit. Whatever solution is in res is then either absent or not
       %     proven optimal.
       %
      +% Notes
      +% -----
      +% With 'glpk' selected, MILPs are solved by RAVEN's bundled GLPK, and a
      +% RAVEN:glpkMILP warning recommends Gurobi, which is much faster and more
      +% reliable on larger MILPs (such as ftINIT or getMinNrFluxes on
      +% genome-scale models).
      +% The GLPK interface of the COBRA Toolbox is not used for MILPs: with
      +% 'cobra' selected, set a different MILP solver with changeCobraSolver.
      +%
       % See also
       % --------
       % splitProbForConditioning
      @@ -53,13 +62,16 @@
       solver=RAVENSOLVER;
       if ~all(lower(prob.vartype) == 'c')
           milp=true;
      -    errorText = 'glpk is not suitable for solving MILPs, ';
           switch solver
               case 'glpk'
      -            error([errorText 'select a different solver with setRavenSolver().'])
      +            warning('RAVEN:glpkMILP', ['Solving a MILP with GLPK. This works for small ' ...
      +                'problems, but for larger problems Gurobi is much faster and more reliable; ' ...
      +                'select it with setRavenSolver(''gurobi''). Silence this warning with ' ...
      +                'warning(''off'',''RAVEN:glpkMILP'').'])
               case 'cobra'
                   if strcmp(CBT_MILP_SOLVER,'glpk')
      -                error([errorText 'select a different solver with changeCobraSolver() or setRavenSolver().'])
      +                error(['The GLPK interface of the COBRA Toolbox is not suitable for solving MILPs, ' ...
      +                    'select a different solver with changeCobraSolver() or setRavenSolver().'])
                   end
           end
       else
      @@ -211,16 +223,13 @@
               solverparams.tolint  = defaultparams.intTol;
               solverparams.tolobj  = defaultparams.objTol;
               solverparams.msglev  = 0; % Level of verbosity
      +        if milp
      +            solverparams.tmlim = defaultparams.timeLimit; % seconds
      +        end
               solverparams = structUpdate(solverparams,params);
       
               prob.csense = renameparams(prob.csense, {'L','G','E'}, {'U','L','S'});
       
      -        if milp
      -            solverparams.tmlim   = solverparams.tmlim*10;
      -            solverparams.msglev  = 1; % Level of verbosity
      -            disp('Issues have been observed when using GLPK for MILP solving. Be advised to carefully observe the results, or us another solver.')
      -        end
      -
               % Ensure that RAVEN glpk binary is used, return to original
               % directory afterwards
               [ravenDir,currDir]=findRAVENroot();
      @@ -241,6 +250,15 @@
               res.obj      = fmin;
               res.dual     = -extra.lambda*prob.osense;
               res.rcost    = -extra.redcosts*prob.osense;
      +        if milp
      +            % glpk reports no gap; a proven optimum has none, anything else
      +            % is not accepted by callers that test the gap
      +            if res.stat == 1
      +                res.mipgap = 0;
      +            else
      +                res.mipgap = inf;
      +            end
      +        end
               %% Use scip
           case {'soplex','scip'} % Old 'soplex' option also allowed
               % scip takes a row range [rl,ru] rather than a right-hand side plus
      diff --git a/src/solver/setRavenSolver.m b/src/solver/setRavenSolver.m
      index 0adf3663..dc534e91 100755
      --- a/src/solver/setRavenSolver.m
      +++ b/src/solver/setRavenSolver.m
      @@ -9,11 +9,16 @@ function setRavenSolver(solver)
       % solver : char
       %     string with solver name. The following options are available:
       %
      -%     - glpk : uses RAVEN-provided binaries.
      +%     - glpk : uses RAVEN-provided binaries. Solves LPs and MILPs; for
      +%       MILPs a RAVEN:glpkMILP warning recommends Gurobi, which is much
      +%       faster and more reliable on larger problems.
       %     - gurobi : requires a working Gurobi installation.
      +%     - scip : requires a SCIP MEX interface. RAVEN provides a
      +%       precompiled binary for Windows only.
       %     - cobra : requires a working COBRA Toolbox installation. RAVEN
       %       parses the problem to COBRA and uses the solver that is set by
      -%       changeCobraSolver.
      +%       changeCobraSolver. MILPs are refused when COBRA's MILP solver is
      +%       GLPK.
       %
       % Examples
       % --------
      diff --git a/src/tasks/checkTasks.m b/src/tasks/checkTasks.m
      index 3f8a3804..e9e309bc 100755
      --- a/src/tasks/checkTasks.m
      +++ b/src/tasks/checkTasks.m
      @@ -8,6 +8,12 @@
       % one new equation, and one change of reaction bounds. If more bounds are
       % needed to define the task, then several rows can be used for each task.
       %
      +% The task inputs and outputs are applied to the boundary metabolites of
      +% the model. The model's own exchange reactions are not closed: they keep
      +% their bounds, so in a model without boundary metabolites they stay open
      +% during every task. To let the task define the whole boundary, run
      +% checkTasks on closeModel(model).
      +%
       % Parameters
       % ----------
       % model : struct
      diff --git a/tests/function_tests/RavenTestCase.m b/tests/function_tests/RavenTestCase.m
      index f6af4ee7..245e896d 100644
      --- a/tests/function_tests/RavenTestCase.m
      +++ b/tests/function_tests/RavenTestCase.m
      @@ -71,8 +71,8 @@ function assumeDependency(testCase, isAvailable, label)
               end
       
               function assumeMILPSolver(testCase)
      -            % Switch to a MILP-capable solver for this test, or skip if none.
      -            % GLPK (the default) cannot solve MILPs in RAVEN.
      +            % Switch to Gurobi or SCIP for this test, or skip if neither is
      +            % available.
                   if ~isempty(which('gurobi'))
                       evalc('setRavenSolver(''gurobi'')');
                   else
      diff --git a/tests/function_tests/tAnalysis.m b/tests/function_tests/tAnalysis.m
      index d19f8000..fe54823a 100644
      --- a/tests/function_tests/tAnalysis.m
      +++ b/tests/function_tests/tAnalysis.m
      @@ -29,6 +29,25 @@ function haveFluxSeedIsReproducible(testCase)
                   testCase.verifyEqual(I1, I2);
               end
       
      +        function haveFluxAllDeadEnds(testCase)
      +            % A model in which every reaction is a dead end has nothing
      +            % left to solve after simplification; all reactions are
      +            % blocked and no solver error is raised.
      +            m = struct();
      +            m.id = 'deadEnds'; m.rxns = {}; m.S = []; m.rev = [];
      +            m.mets = {'A';'B';'C'}; m.metNames = m.mets;
      +            m.comps = {'c'}; m.compNames = m.comps; m.metComps = [1;1;1];
      +            m.genes = {}; m.grRules = {}; m.rxnGeneMat = [];
      +            r = struct();
      +            r.rxns = {'R1';'R2'};
      +            r.equations = {'A[c] => B[c]';'B[c] => C[c]'};
      +            evalc('m = addRxns(m, r, 3);');
      +            m.lb = [0;0]; m.ub = [1000;1000];
      +            m.c = [0;0]; m.b = zeros(3,1);
      +            I = haveFlux(m);
      +            testCase.verifyEqual(I, [false; false]);
      +        end
      +
               function getMinNrFluxesReturnsFlux(testCase)
                   testCase.assumeMILPSolver();
                   evalc('[x, I, exitFlag] = getMinNrFluxes(testCase.model, testCase.model.rxns);');
      @@ -274,6 +293,24 @@ function reporterMetabolitesIsDeterministic(testCase)
                   testCase.verifyEqual(rm1.metZScores, rm2.metZScores);
               end
       
      +        function reporterMetabolitesUsesPopulationStd(testCase)
      +            % The closed-form background assumes genes drawn with
      +            % replacement from the scored pool, so it divides by the
      +            % population standard deviation of the gene Z-scores.
      +            m = struct();
      +            m.mets       = {'A';'B'};
      +            m.metNames   = {'A';'B'};
      +            m.genes      = {'g1';'g2';'g3'};
      +            m.S          = sparse([1 0; 0 1]);
      +            m.rxnGeneMat = sparse([1 1 0; 0 0 1]);
      +            pvals = [0.01; 0.2; 0.5];
      +            z = sqrt(2)*erfcinv(2*pvals);
      +            expectedA = ((z(1)+z(2))/sqrt(2) - sqrt(2)*mean(z)) / std(z,1);
      +            rm = reporterMetabolites(m, m.genes, pvals);
      +            testCase.verifyEqual(rm.metZScores(strcmp(rm.mets,'A')), expectedA, 'AbsTol', 1e-12);
      +            testCase.verifyEqual(rm.metPValues(strcmp(rm.mets,'A')), 0.5*erfc(expectedA/sqrt(2)), 'AbsTol', 1e-12);
      +        end
      +
               function FSEOFRuns(testCase)
                   biomassRxn = testCase.model.rxns{find(testCase.model.c == 1, 1)};
                   [~, exchIdx] = getExchangeRxns(testCase.model);
      diff --git a/tests/function_tests/tBiomass.m b/tests/function_tests/tBiomass.m
      index dd9c7276..7b7c0faa 100644
      --- a/tests/function_tests/tBiomass.m
      +++ b/tests/function_tests/tBiomass.m
      @@ -26,6 +26,22 @@ function scaleBiomassFractionErrorsOnMissingComponent(testCase)
                       'protein', 0.5), ?MException);
               end
       
      +        function scaleBiomassFractionBalancesToOne(testCase)
      +            [m, cfg] = tBiomass.twoComponentToyModel();
      +            out = scaleBiomassFraction(m, cfg, 'protein', 0.7, 'balanceOut', 'DNA');
      +            f = getBiomassFractions(out, cfg);
      +            testCase.verifyEqual(f.protein, 0.7, 'AbsTol', 1e-9);
      +            testCase.verifyEqual(f.DNA, 0.3, 'AbsTol', 1e-9);
      +        end
      +
      +        function scaleBiomassFractionRefusesNegativeBalance(testCase)
      +            % Balancing protein 1.2 against DNA would need DNA at -0.2,
      +            % which would turn the DNA pseudoreaction around.
      +            [m, cfg] = tBiomass.twoComponentToyModel();
      +            testCase.verifyError(@() scaleBiomassFraction(m, cfg, 'protein', 1.2, ...
      +                'balanceOut', 'DNA'), 'scaleBiomassFraction:negativeBalance');
      +        end
      +
               function scaleBiomassPseudoreactionErrorsOnMissingComponent(testCase)
                   cfg = testCase.biomassConfig();
                   testCase.verifyError(@() scaleBiomassPseudoreaction(testCase.model, cfg, ...
      @@ -93,6 +109,31 @@ function fitParametersRuns(testCase)
           end
       
           methods (Static, Access = private)
      +        function [m, cfg] = twoComponentToyModel()
      +            % Protein and DNA pseudoreactions in grams, 0.5 g/gDW each.
      +            m = struct();
      +            m.id        = 'toy';
      +            m.rxns      = {'R1';'R2'};
      +            m.rxnNames  = {'protein pseudoreaction';'DNA pseudoreaction'};
      +            m.mets      = {'x_c';'y_c';'h_c';'prot_c';'dna_c'};
      +            m.metNames  = {'x';'y';'H+';'protein';'DNA'};
      +            m.metComps  = ones(5,1);
      +            m.metCharges= zeros(5,1);
      +            m.comps     = {'c'};
      +            m.compNames = {'cytosol'};
      +            m.S         = sparse([-0.5 0; 0 -0.5; 0 0; 1 0; 0 1]);
      +            m.lb = [0;0]; m.ub = [1000;1000]; m.rev = [0;0]; m.c = [0;0]; m.b = zeros(5,1);
      +            m.grRules = {'';''}; m.genes = {}; m.rxnGeneMat = sparse(2,0);
      +
      +            cfg = struct();
      +            cfg.biomass_rxn = 'R1';
      +            cfg.proton_met  = 'h_c';
      +            cfg.components  = {struct('name','protein', ...
      +                'pseudoreaction_name','protein pseudoreaction','mass_strategy','grams'), ...
      +                struct('name','DNA', ...
      +                'pseudoreaction_name','DNA pseudoreaction','mass_strategy','grams')};
      +        end
      +
               function [m, cfg] = protonToyModel()
                   % One pseudoreaction: x_c -> protein, with h_c available to carry
                   % the charge balance.
      diff --git a/tests/function_tests/tCuration.m b/tests/function_tests/tCuration.m
      index ae6327af..845dc6c5 100644
      --- a/tests/function_tests/tCuration.m
      +++ b/tests/function_tests/tCuration.m
      @@ -39,6 +39,80 @@ function curateModelFromTablesOverwritesExactDuplicate(testCase)
                   testCase.verifyEqual(out.rxnNames{1}, 'newRxn');
               end
       
      +        function curateModelFromTablesReadsMetColumnsByHeader(testCase)
      +            % Columns are found by header, so a table without an inchi
      +            % column and in another order must not write its notes over
      +            % the InChIs; notes and charges reach existing metabolites,
      +            % creating the fields the model lacks.
      +            model = tCuration.toyModel();
      +            model.inchis = {'InChI=A';'InChI=B';'InChI=C';'InChI=H'};
      +            metsF = tCuration.writeTsv(testCase, { ...
      +                'comps\tmetNames\tcharge\tmetNotes\tformula', ...
      +                'c\tA\t-2\tnote A\tC1'});
      +            evalc('out = curateModelFromTables(model,metsF);');
      +
      +            testCase.verifyNumElements(out.mets, 4);
      +            testCase.verifyEqual(out.inchis, model.inchis);
      +            testCase.verifyEqual(out.metFormulas{1}, 'C1');
      +            testCase.verifyEqual(out.metNotes, {'note A';'';'';''});
      +            testCase.verifyEqual(out.metCharges(1), -2);
      +            testCase.verifyTrue(all(isnan(out.metCharges(2:4))));
      +        end
      +
      +        function curateModelFromTablesWritesExistingRxnFields(testCase)
      +            % Notes, references, confidence scores and MIRIAM columns
      +            % reach an existing reaction, and grRules are matched to the
      +            % right reaction when existing and new reactions are mixed.
      +            % The yeast-GEM tables name the index column "index".
      +            model = tCuration.toyModel();
      +            model.genes      = {'g1'};
      +            model.rxnGeneMat = sparse(1,1);
      +            coeffsF = tCuration.writeTsv(testCase, { ...
      +                'index\trxnNames\tmetNames\tcomps\tcoefficient', ...
      +                '1\tdupRxn\tA\tc\t-1', '1\tdupRxn\tB\tc\t-1', ...
      +                '1\tdupRxn\tC\tc\t1',  '1\tdupRxn\tH\tc\t1', ...
      +                '2\tnewRxn\tA\tc\t-1', '2\tnewRxn\tB\tc\t-1', ...
      +                '2\tnewRxn\tC\tc\t1'});
      +            infoF = tCuration.writeTsv(testCase, { ...
      +                ['index\trxnNames\tgrRules\tlb\tub\trev\tsubSystems\t' ...
      +                 'eccodes\trxnNotes\trxnReferences\trxnConfidenceScores\tkegg.reaction'], ...
      +                '1\tdupRxn\t\t0\t1000\t0\t\t\ta note\tPMID:1\t3\tR00001', ...
      +                '2\tnewRxn\tg1\t0\t1000\t0\t\t\t\t\t\t'});
      +            evalc(['out = curateModelFromTables(model,''none'',''rxnsCoeffs'',' ...
      +                'coeffsF,''rxnsInfo'',infoF);']);
      +
      +            testCase.verifyNumElements(out.rxns, 2);
      +            testCase.verifyEqual(out.rxnNames{1}, 'dupRxn');
      +            testCase.verifyEqual(out.rxnNotes{1}, 'a note');
      +            testCase.verifyEqual(out.rxnReferences{1}, 'PMID:1');
      +            testCase.verifyEqual(out.rxnConfidenceScores(1), 3);
      +            testCase.verifyEqual(out.rxnMiriams{1}.name, {'kegg.reaction'});
      +            testCase.verifyEqual(out.rxnMiriams{1}.value, {'R00001'});
      +            testCase.verifyEqual(out.grRules{1}, '');
      +            testCase.verifyEqual(out.grRules{2}, 'g1');
      +        end
      +
      +        function curateModelFromTablesWritesExistingGeneFields(testCase)
      +            % yeast-GEM gene tables use the header geneShortName; it must
      +            % be read as the short name, not as a MIRIAM namespace.
      +            model = tCuration.toyModel();
      +            model.genes      = {'g1'};
      +            model.rxnGeneMat = sparse(1,1);
      +            genesF = tCuration.writeTsv(testCase, { ...
      +                'genes\tgeneShortName\tuniprot', 'g1\tGENE1\tP12345'});
      +            evalc('out = curateModelFromTables(model,''none'',''genesInfo'',genesF);');
      +
      +            testCase.verifyEqual(out.geneShortNames, {'GENE1'});
      +            testCase.verifyEqual(out.geneMiriams{1}.name, {'uniprot'});
      +            testCase.verifyEqual(out.geneMiriams{1}.value, {'P12345'});
      +        end
      +
      +        function curateModelFromTablesRequiresKeyColumns(testCase)
      +            model = tCuration.toyModel();
      +            metsF = tCuration.writeTsv(testCase, {'metNames\tformula', 'A\tC1'});
      +            testCase.verifyError(@() curateModelFromTables(model,metsF), ?MException);
      +        end
      +
           end
       
           methods (Static, Access = private)
      @@ -92,5 +166,17 @@ function curateModelFromTablesOverwritesExactDuplicate(testCase)
                   fclose(fid);
               end
       
      +        function f = writeTsv(testCase, lines)
      +            % Write tab-delimited lines (with \t escapes) to a temporary file.
      +            d = tempname; mkdir(d);
      +            testCase.addTeardown(@() rmdir(d,'s'));
      +            f = fullfile(d,'table.tsv');
      +            fid = fopen(f,'w');
      +            for i=1:numel(lines)
      +                fprintf(fid,[lines{i} '\n']);
      +            end
      +            fclose(fid);
      +        end
      +
           end
       end
      diff --git a/tests/function_tests/tGapfilling.m b/tests/function_tests/tGapfilling.m
      index 7e6e8de2..d08bb14c 100644
      --- a/tests/function_tests/tGapfilling.m
      +++ b/tests/function_tests/tGapfilling.m
      @@ -80,6 +80,26 @@ function gapReportRuns(testCase)
                   testCase.verifyClass(noFluxRxns, 'cell');
               end
       
      +        function gapReportInfeasibleModelErrors(testCase)
      +            % Maint needs 20 units of a but uptake allows 10.
      +            m = testCase.tinyModel({'ExA';'Maint'}, sparse([1 -1]), [0;20], [10;1000]);
      +            err = testCase.gapReportError(m);
      +            testCase.verifySubstring(err.message, 'under its own bounds');
      +            testCase.verifySubstring(err.message, 'Maint');
      +            testCase.verifySubstring(err.message, 'ATP maintenance');
      +        end
      +
      +        function gapReportInfeasibleOnceClosedErrors(testCase)
      +            % Uptake of a comes from the unconstrained a_b: feasible with
      +            % the boundary open, but Maint cannot run once it is closed.
      +            m = testCase.tinyModel({'ExA';'Maint'}, sparse([1 -1; -1 0]), [0;1], [10;1000]);
      +            m.mets = {'a';'a_b'}; m.metNames = m.mets; m.metComps = [1;1];
      +            m.b = zeros(2,1); m.unconstrained = [0;1];
      +            err = testCase.gapReportError(m);
      +            testCase.verifySubstring(err.message, 'once its boundary reactions are closed');
      +            testCase.verifySubstring(err.message, 'Maint');
      +        end
      +
               function gapReportKeepsPercentInModelName(testCase)
                   % A model.name containing "%" must survive intact in the
                   % report header, not be truncated by fprintf misreading it as
      @@ -314,9 +334,8 @@ function gapFillMILPRepairsGrowth(testCase)
                   modelDB = testCase.model; modelDB.id = 'DB';
                   gapModel = removeReactions(modelDB, modelDB.rxns(1:5));
                   gapModel.id = 'gapModel';
      -            evalc('[addedRxns, reversedRxns, newModel, exitFlag] = gapFillMILP(gapModel, modelDB, ''verbose'', false);');
      +            evalc('[addedRxns, newModel, exitFlag] = gapFillMILP(gapModel, modelDB, ''verbose'', false);');
                   testCase.verifyClass(addedRxns, 'cell');
      -            testCase.verifyClass(reversedRxns, 'cell');
                   testCase.verifyClass(newModel, 'struct');
                   testCase.verifyEqual(exitFlag, 1);
                   % The repaired model should be able to produce objective flux.
      @@ -349,7 +368,7 @@ function gapFillMILPHandlesUnboundedUniversalReaction(testCase)
                   modelDB.lb=0; modelDB.ub=Inf; modelDB.rev=0; modelDB.c=0; modelDB.b=0;
                   modelDB.genes={}; modelDB.grRules={''}; modelDB.rxnGeneMat=sparse(1,0);
       
      -            evalc(['[addedRxns,~,newModel,exitFlag] = gapFillMILP(gapModel, modelDB, ' ...
      +            evalc(['[addedRxns,newModel,exitFlag] = gapFillMILP(gapModel, modelDB, ' ...
                       '''verbose'', false);']);
                   testCase.verifyEqual(exitFlag, 1);
                   testCase.verifyEqual(addedRxns, {'ExA'});
      @@ -382,7 +401,7 @@ function gapFillMILPKeepsRenamedUniversalReaction(testCase)
                   modelDB.lb=0; modelDB.ub=1000; modelDB.rev=0; modelDB.c=0; modelDB.b=0;
                   modelDB.genes={}; modelDB.grRules={''}; modelDB.rxnGeneMat=sparse(1,0);
       
      -            evalc(['[addedRxns,~,newModel,exitFlag] = gapFillMILP(gapModel, modelDB, ' ...
      +            evalc(['[addedRxns,newModel,exitFlag] = gapFillMILP(gapModel, modelDB, ' ...
                       '''verbose'', false);']);
                   testCase.verifyEqual(exitFlag, 1);
                   testCase.verifyNotEmpty(addedRxns);
      @@ -390,15 +409,69 @@ function gapFillMILPKeepsRenamedUniversalReaction(testCase)
                   testCase.verifyGreaterThan(sol.f, 0);
               end
       
      -        function gapFillMILPReversesDirectionality(testCase)
      -            % gapFillMILP should reverse a reaction whose directionality is wrong.
      -            testCase.assumeMILPSolver();
      -            model = testCase.model;
      -            modelDB = model; modelDB.id = 'DB';
      -            gapModel = removeReactions(model, model.rxns(1:3));
      -            gapModel.id = 'gapModel';
      -            evalc('[~, reversedRxns, ~, ~] = gapFillMILP(gapModel, modelDB, ''verbose'', false);');
      -            testCase.verifyClass(reversedRxns, 'cell');
      +        function gapFillMILPKeepsForcedFlux(testCase)
      +            % A draft reaction with a forced flux (lb > 0, like a
      +            % maintenance drain) keeps its lower bound. Maint takes 5 of
      +            % the 10 units of a, so growth 8 needs the extra uptake U1;
      +            % dropping the forced flux would reach 8 with no repair at all.
      +            evalc('setRavenSolver(''glpk'')');
      +            warnState = warning('off','RAVEN:glpkMILP');
      +            testCase.addTeardown(@() warning(warnState));
      +            gapModel = struct();
      +            gapModel.id='gapModel'; gapModel.rxns={'ExA';'R1';'Grow';'Maint'}; gapModel.rxnNames=gapModel.rxns;
      +            gapModel.mets={'a';'b'}; gapModel.metNames=gapModel.mets; gapModel.metComps=[1;1];
      +            gapModel.comps={'c'}; gapModel.compNames={'c'};
      +            gapModel.S=sparse([1 -1 0 -1; 0 1 -1 0]); % ExA: =>a  R1: a=>b  Grow: b=>  Maint: a=>
      +            gapModel.lb=[0;0;0;5]; gapModel.ub=[10;1000;1000;1000]; gapModel.rev=[0;0;0;0];
      +            gapModel.c=[0;0;1;0]; gapModel.b=zeros(2,1);
      +            gapModel.genes={}; gapModel.grRules={'';'';'';''}; gapModel.rxnGeneMat=sparse(4,0);
      +
      +            modelDB = struct();
      +            modelDB.id='DB'; modelDB.rxns={'U1'}; modelDB.rxnNames=modelDB.rxns;
      +            modelDB.mets={'a'}; modelDB.metNames=modelDB.mets; modelDB.metComps=1;
      +            modelDB.comps={'c'}; modelDB.compNames={'c'};
      +            modelDB.S=sparse(1,1); modelDB.S(1,1)=1; % =>a
      +            modelDB.lb=0; modelDB.ub=10; modelDB.rev=0; modelDB.c=0; modelDB.b=0;
      +            modelDB.genes={}; modelDB.grRules={''}; modelDB.rxnGeneMat=sparse(1,0);
      +
      +            evalc(['[addedRxns,newModel,exitFlag] = gapFillMILP(gapModel, modelDB, ' ...
      +                '''minGrowth'', 8, ''verbose'', false);']);
      +            testCase.verifyEqual(exitFlag, 1);
      +            testCase.verifyEqual(addedRxns, {'U1'});
      +            testCase.verifyEqual(newModel.lb(ismember(newModel.rxns, 'Maint')), 5);
      +            sol = solveLP(newModel);
      +            testCase.verifyGreaterThanOrEqual(sol.f, 8 - 1e-6);
      +        end
      +
      +        function gapFillMILPKeepsDraftDirections(testCase)
      +            % A draft reaction running in the wrong direction is not
      +            % reversed: with only that repair available the MILP has no
      +            % solution, and the draft bounds are returned unchanged.
      +            evalc('setRavenSolver(''glpk'')');
      +            warnState = warning('off','RAVEN:glpkMILP');
      +            testCase.addTeardown(@() warning(warnState));
      +            gapModel = struct();
      +            gapModel.id='gapModel'; gapModel.rxns={'ExB';'R1';'Grow'}; gapModel.rxnNames=gapModel.rxns;
      +            gapModel.mets={'a';'b'}; gapModel.metNames=gapModel.mets; gapModel.metComps=[1;1];
      +            gapModel.comps={'c'}; gapModel.compNames={'c'};
      +            gapModel.S=sparse([0 -1 -1; 1 1 0]); % ExB: =>b  R1: a=>b  Grow: a=>
      +            gapModel.lb=[0;0;0]; gapModel.ub=[10;1000;1000]; gapModel.rev=[0;0;0];
      +            gapModel.c=[0;0;1]; gapModel.b=zeros(2,1);
      +            gapModel.genes={}; gapModel.grRules={'';'';''}; gapModel.rxnGeneMat=sparse(3,0);
      +
      +            modelDB = struct();
      +            modelDB.id='DB'; modelDB.rxns={'Other'}; modelDB.rxnNames=modelDB.rxns;
      +            modelDB.mets={'b'}; modelDB.metNames=modelDB.mets; modelDB.metComps=1;
      +            modelDB.comps={'c'}; modelDB.compNames={'c'};
      +            modelDB.S=sparse(1,1); modelDB.S(1,1)=-1; % b=>
      +            modelDB.lb=0; modelDB.ub=1000; modelDB.rev=0; modelDB.c=0; modelDB.b=0;
      +            modelDB.genes={}; modelDB.grRules={''}; modelDB.rxnGeneMat=sparse(1,0);
      +
      +            evalc(['[addedRxns,newModel,exitFlag] = gapFillMILP(gapModel, modelDB, ' ...
      +                '''minGrowth'', 1, ''verbose'', false);']);
      +            testCase.verifyEqual(exitFlag, -1);
      +            testCase.verifyEmpty(addedRxns);
      +            testCase.verifyEqual(newModel.lb, gapModel.lb);
               end
       
               function fillGapsDispatchesFastLP(testCase)
      @@ -435,5 +508,29 @@ function fillGapsDispatchesGapfillMILP(testCase)
                   m.b = [0;0;0];
                   m.genes = {}; m.grRules = {'';''}; m.rxnGeneMat = sparse(2,0);
               end
      +
      +        function m = tinyModel(~, rxns, S, lb, ub)
      +            % One-compartment model with metabolite a and no objective.
      +            n = numel(rxns);
      +            m = struct();
      +            m.id = 'tiny'; m.name = 'tiny';
      +            m.rxns = rxns; m.rxnNames = rxns;
      +            m.mets = {'a'}; m.metNames = {'a'}; m.metComps = 1;
      +            m.comps = {'c'}; m.compNames = {'c'};
      +            m.S = S; m.lb = lb; m.ub = ub; m.rev = zeros(n,1); m.c = zeros(n,1);
      +            m.b = 0;
      +            m.genes = {}; m.grRules = repmat({''},n,1); m.rxnGeneMat = sparse(n,0);
      +        end
      +
      +        function err = gapReportError(testCase, m)
      +            % The error gapReport raises on m, checked to be RAVEN:infeasibleModel.
      +            err = [];
      +            try
      +                evalc('gapReport(m);');
      +            catch err
      +            end
      +            testCase.assertNotEmpty(err);
      +            testCase.verifyEqual(err.identifier, 'RAVEN:infeasibleModel');
      +        end
           end
       end
      diff --git a/tests/function_tests/tIO.m b/tests/function_tests/tIO.m
      index f26ace24..86a06ae6 100644
      --- a/tests/function_tests/tIO.m
      +++ b/tests/function_tests/tIO.m
      @@ -207,6 +207,25 @@ function writeReadYAMLRoundTrip(testCase)
                   testCase.verifyEqual(numel(m2.rxns), numel(testCase.model.rxns));
               end
       
      +        function readYAMLmodelJoinsContinuationStartingWithDash(testCase)
      +            % A writer that wraps long scalars can break a name right
      +            % before a dash, so the continuation line starts with "-"
      +            % without being a list item (as in the KEGG reference model).
      +            m = testCase.model;
      +            m.rxnNames{1} = 'PLACEHOLDER';
      +            f = [tempname '.yml'];
      +            testCase.addTeardown(@() delete(f));
      +            evalc('writeYAMLmodel(m, f);');
      +            txt = fileread(f);
      +            txt = strrep(txt, 'name: PLACEHOLDER', sprintf(['name: naphthalene,NADPH:oxygen ' ...
      +                'oxidoreductase (RH-hydroxylating or \n        -epoxidizing)']));
      +            fid = fopen(f, 'w'); fwrite(fid, txt); fclose(fid);
      +            evalc('m2 = readYAMLmodel(f);');
      +            testCase.verifyEqual(m2.rxnNames{1}, ...
      +                'naphthalene,NADPH:oxygen oxidoreductase (RH-hydroxylating or -epoxidizing)');
      +            testCase.verifyEqual(numel(m2.rxns), numel(m.rxns));
      +        end
      +
               function writeReadYAMLNestsEcRxnEnzymes(testCase)
                   % Each ec-rxns entry lists its enzymes as a mapping under the
                   % "enzymes" key, so the enzyme lines are indented deeper than
      diff --git a/tests/function_tests/tQueries.m b/tests/function_tests/tQueries.m
      index 1fc2c5a6..7e918050 100644
      --- a/tests/function_tests/tQueries.m
      +++ b/tests/function_tests/tQueries.m
      @@ -293,6 +293,29 @@ function getIndexesByName(testCase)
                   testCase.verifyEqual(idx, [1;2;3]);
               end
       
      +        function getIndexesMetcompsFindsName(testCase)
      +            m = testCase.model;
      +            name = [m.metNames{1} '[' m.comps{m.metComps(1)} ']'];
      +            testCase.verifyEqual(getIndexes(m, name, 'metcomps'), 1);
      +        end
      +
      +        function getIndexesMetcompsAmbiguousNameErrors(testCase)
      +            % Two metabolites with the same name in the same compartment
      +            % cannot be told apart by name[comp]; the error names both.
      +            m = testCase.model;
      +            m.metNames{2} = m.metNames{1};
      +            m.metComps(2) = m.metComps(1);
      +            name = [m.metNames{1} '[' m.comps{m.metComps(1)} ']'];
      +            try
      +                getIndexes(m, name, 'metcomps');
      +                testCase.verifyFail('No error for an ambiguous name[comp]');
      +            catch e
      +                testCase.verifySubstring(e.message, 'ambiguous');
      +                testCase.verifySubstring(e.message, m.mets{1});
      +                testCase.verifySubstring(e.message, m.mets{2});
      +            end
      +        end
      +
               function getIndexesLogical(testCase)
                   lg = getIndexes(testCase.model, testCase.model.rxns(1), 'rxns', true);
                   testCase.verifyClass(lg, 'logical');
      diff --git a/tests/function_tests/tSampling.m b/tests/function_tests/tSampling.m
      index c365addb..797ee052 100644
      --- a/tests/function_tests/tSampling.m
      +++ b/tests/function_tests/tSampling.m
      @@ -109,6 +109,17 @@ function randomSamplingCHRRDispatch(testCase)
                   testCase.verifyLessThan(max(abs(resid(:))), 1e-6);
               end
       
      +        function randomSamplingCHRRWithGurobi(testCase)
      +            % sampleChebyshevCenter must hand Gurobi a sparse constraint
      +            % matrix, which Gurobi requires.
      +            testCase.assumeSolver('gurobi');
      +            evalc('setRavenSolver(''gurobi'')');
      +            testCase.addTeardown(@() evalc('setRavenSolver(''glpk'')'));
      +            evalc(['sols = randomSampling(testCase.model, 10, ''method'', ''chrr'', ' ...
      +                '''thinning'', 5, ''nBurnin'', 20, ''seed'', 1);']);
      +            testCase.verifySize(sols, [numel(testCase.model.rxns), 10]);
      +        end
      +
               function randomSamplingCHRRExposesInfo(testCase)
                   % The CHRR convergence diagnostics must be reachable through the
                   % documented randomSampling entry point, not only sampleCHRR.
      diff --git a/tests/function_tests/tSolver.m b/tests/function_tests/tSolver.m
      index b86eb529..dbe99a0b 100644
      --- a/tests/function_tests/tSolver.m
      +++ b/tests/function_tests/tSolver.m
      @@ -37,6 +37,42 @@ function optimizeProbReportsTimeLimit(testCase)
                   testCase.verifyFalse(res.hitTimeLimit);
               end
       
      +        function optimizeProbSolvesMILPWithGlpk(testCase)
      +            % A binary knapsack whose LP relaxation (9.33) differs from the
      +            % integer optimum (8, items 1 and 3). GLPK solves it, with a
      +            % warning that recommends Gurobi.
      +            evalc('setRavenSolver(''glpk'')');
      +            prob = struct();
      +            prob.c       = [-5; -4; -3];
      +            prob.A       = sparse([2 3 1]);
      +            prob.a       = prob.A;
      +            prob.b       = 4;
      +            prob.csense  = 'L';
      +            prob.lb      = zeros(3,1);
      +            prob.ub      = ones(3,1);
      +            prob.osense  = 1;
      +            prob.vartype = 'BBB';
      +            res = testCase.verifyWarning(@() optimizeProb(prob,[],false), 'RAVEN:glpkMILP');
      +            testCase.verifyEqual(res.stat, 1);
      +            testCase.verifyEqual(res.obj, -8, 'AbsTol', 1e-9);
      +            testCase.verifyEqual(res.full, [1; 0; 1], 'AbsTol', 1e-9);
      +            testCase.verifyEqual(res.mipgap, 0);
      +        end
      +
      +        function getMinimalMediumRunsOnGlpk(testCase)
      +            evalc('setRavenSolver(''glpk'')');
      +            warnState = warning('off','RAVEN:glpkMILP');
      +            testCase.addTeardown(@() warning(warnState));
      +            [medium, idx] = getMinimalMedium(testCase.model, 'verbose', false);
      +            testCase.verifyNotEmpty(medium);
      +            m = testCase.model;
      +            inMedium = false(numel(m.rxns),1); inMedium(idx) = true;
      +            [~, exchIdx] = getExchangeRxns(m);
      +            m.lb(intersect(exchIdx, find(~inMedium & m.lb < 0))) = 0;
      +            sol = solveLP(m);
      +            testCase.verifyGreaterThan(sol.f, 0);
      +        end
      +
               function checkSolutionOnFeasibleProblem(testCase)
                   res = optimizeProb(testCase.modelLP());
                   [isFeasible, isOptimal] = checkSolution(res);
      
      From 250b0bc4cff770f02228381c6dfd39708a828c4b Mon Sep 17 00:00:00 2001
      From: "github-actions[bot]"
       <41898282+github-actions[bot]@users.noreply.github.com>
      Date: Sat, 17 Oct 2026 20:23:49 +0000
      Subject: [PATCH 24/48] doc: regenerate HTML documentation for 2c6f9b8 [skip
       ci]
      
      ---
       docs/html/analysis/findGeneDeletions.html     |  198 +-
       docs/html/analysis/getMinimalMedium.html      |  266 +--
       docs/html/analysis/haveFlux.html              |  227 ++-
       docs/html/analysis/randomSampling.html        |  396 ++--
       docs/html/analysis/reporterMetabolites.html   |  314 ++--
       docs/html/analysis/sampleChebyshevCenter.html |    2 +-
       docs/html/annotation/assignSBOterms.html      |  328 ++--
       docs/html/biomass/scaleBiomassFraction.html   |  145 +-
       docs/html/curation/curateModelFromTables.html |  791 ++++----
       docs/html/gapfilling/fillGaps.html            |  426 +++--
       docs/html/gapfilling/gapFillFastLP.html       |    2 +-
       docs/html/gapfilling/gapFillMILP.html         |  543 +++---
       docs/html/gapfilling/gapFillTopological.html  |    2 +-
       docs/html/gapfilling/gapReport.html           |  200 +-
       docs/html/io/readYAMLmodel.html               | 1658 +++++++++--------
       docs/html/queries/getIndexes.html             |  256 +--
       .../kegg/getKEGGModelForOrganism.html         |  408 ++--
       docs/html/solver/checkSolution.html           |   49 +-
       docs/html/solver/getMinNrFluxes.html          |    2 +-
       docs/html/solver/index.html                   |    2 +-
       docs/html/solver/optimizeProb.html            |  813 ++++----
       docs/html/solver/setRavenSolver.html          |  128 +-
       docs/html/solver/solveLP.html                 |    2 +-
       docs/html/tasks/checkTasks.html               |  590 +++---
       24 files changed, 3986 insertions(+), 3762 deletions(-)
      
      diff --git a/docs/html/analysis/findGeneDeletions.html b/docs/html/analysis/findGeneDeletions.html
      index 7eb35845..9a894a24 100644
      --- a/docs/html/analysis/findGeneDeletions.html
      +++ b/docs/html/analysis/findGeneDeletions.html
      @@ -64,8 +64,10 @@ 

      DESCRIPTION ^SOURCE CODE ^% with details about each gene in originalGenes and why or why not it 0037 % was deleted: 0038 % -0039 % - 1 : was deleted -0040 % - 2 : proved lethal in sgd (single gene deletion) -0041 % - 3 : redundant, no longer used -0042 % - 4 : involved in dead-end reaction -0043 % grRatioMuts : double -0044 % growth rate ratio between mutated strain and wild type, matching the -0045 % originalGenes(genes) mutants. Note that this does not directly map -0046 % to model.genes, as is the case for COBRA getEssentialGenes. However, -0047 % this can be obtained by afterwards running: -0048 % -0049 % grRatio=zeros(1,numel(model.genes)); -0050 % grRatio(genes)=grRatioMuts; -0051 % -0052 % Examples -0053 % -------- -0054 % [genes, fluxes, originalGenes, details, grRatioMuts]=... -0055 % findGeneDeletions(model,testType); -0056 -0057 originalModel=model; -0058 p=parseRAVENargs(varargin, {'testType',[]}); -0059 testType=p.testType; -0060 if isempty(testType) -0061 testType='sgd'; -0062 else -0063 testType=char(testType); -0064 end -0065 -0066 %Check that the test type is correct -0067 if ~strcmpi(testType,'sgd') && ~strcmpi(testType,'dgd') -0068 EM='Incorrect test type'; -0069 error('RAVEN:badInput', '%s', EM); -0070 end -0071 -0072 originalGenes=model.genes; -0073 details=zeros(numel(model.genes),1); -0074 -0075 %First simplify the model to reduce the size -0076 model=simplifyModel(model,true,false,true,true); -0077 model=removeReactions(model,{},true,true); %Removes unused genes -0078 details(~ismember(originalGenes,model.genes))=4; -0079 -0080 [~, geneMapping]=ismember(model.genes,originalGenes); -0081 growthWT=solveLP(model); -0082 growthWT=growthWT.f; -0083 -0084 %Do single deletion. This is done here since the double deletion depends -0085 %on which single deletions prove lethal (to reduce the size of the system) -0086 if strcmpi(testType,'sgd') || strcmpi(testType,'dgd') -0087 fluxes=zeros(numel(model.rxns),numel(model.genes)); -0088 grRatioMuts=zeros(1,numel(model.genes)); -0089 for i=1:numel(model.genes) -0090 %Constrain all reactions involving the gene to 0 -0091 tempModel=removeGenes(model,i,false,false,false); -0092 sol=solveLP(tempModel); -0093 -0094 %If the optimization terminated successfully -0095 if sol.stat==1 -0096 fluxes(:,i)=sol.x; -0097 grRatioMuts(i)=sol.f/growthWT; -0098 details(geneMapping(i))=1; -0099 else -0100 fluxes(:,i)=0; -0101 grRatioMuts(i)=0; -0102 details(geneMapping(i))=2; -0103 end -0104 end -0105 genes=geneMapping; -0106 end -0107 -0108 %For double deletions -0109 if strcmpi(testType,'dgd') -0110 %This is a little lazy but it is fine. Check which genes have already -0111 %been deleted in "sgd" analysis. -0112 [~, I]=ismember(originalGenes(details==1),model.genes); -0113 genesToModify=nchoosek(I,2); -0114 genes=geneMapping(genesToModify); -0115 grRatioMuts=zeros(1,numel(genes)); -0116 fluxes=sparse(numel(model.rxns),size(genesToModify,1)); -0117 for i=1:size(genesToModify,1) -0118 tempModel=removeGenes(model,genesToModify(i,:),false,false,false); -0119 sol=solveLP(tempModel); -0120 -0121 if sol.stat==1 -0122 fluxes(:,i)=sol.x; -0123 grRatioMuts(i)=sol.f/growthWT; -0124 end -0125 end -0126 end -0127 -0128 %Map back to the old model -0129 [~, I]=ismember(model.rxns,originalModel.rxns); -0130 temp=fluxes; -0131 fluxes=sparse(numel(originalModel.rxns),size(temp,2)); -0132 fluxes(I,:)=temp; -0133 end

      +0039 % - 1 : was deleted, and the problem was solved (a lethal deletion +0040 % with zero growth also gets 1) +0041 % - 2 : the single-deletion problem had no optimal solution +0042 % (infeasible or failed); fluxes and grRatioMuts are 0 +0043 % - 3 : redundant, no longer used +0044 % - 4 : involved in dead-end reaction +0045 % grRatioMuts : double +0046 % growth rate ratio between mutated strain and wild type, matching the +0047 % originalGenes(genes) mutants. Note that this does not directly map +0048 % to model.genes, as is the case for COBRA getEssentialGenes. However, +0049 % this can be obtained by afterwards running: +0050 % +0051 % grRatio=zeros(1,numel(model.genes)); +0052 % grRatio(genes)=grRatioMuts; +0053 % +0054 % Examples +0055 % -------- +0056 % [genes, fluxes, originalGenes, details, grRatioMuts]=... +0057 % findGeneDeletions(model,testType); +0058 +0059 originalModel=model; +0060 p=parseRAVENargs(varargin, {'testType',[]}); +0061 testType=p.testType; +0062 if isempty(testType) +0063 testType='sgd'; +0064 else +0065 testType=char(testType); +0066 end +0067 +0068 %Check that the test type is correct +0069 if ~strcmpi(testType,'sgd') && ~strcmpi(testType,'dgd') +0070 EM='Incorrect test type'; +0071 error('RAVEN:badInput', '%s', EM); +0072 end +0073 +0074 originalGenes=model.genes; +0075 details=zeros(numel(model.genes),1); +0076 +0077 %First simplify the model to reduce the size +0078 model=simplifyModel(model,true,false,true,true); +0079 model=removeReactions(model,{},true,true); %Removes unused genes +0080 details(~ismember(originalGenes,model.genes))=4; +0081 +0082 [~, geneMapping]=ismember(model.genes,originalGenes); +0083 growthWT=solveLP(model); +0084 growthWT=growthWT.f; +0085 +0086 %Do single deletion. This is done here since the double deletion depends +0087 %on which single deletions prove lethal (to reduce the size of the system) +0088 if strcmpi(testType,'sgd') || strcmpi(testType,'dgd') +0089 fluxes=zeros(numel(model.rxns),numel(model.genes)); +0090 grRatioMuts=zeros(1,numel(model.genes)); +0091 for i=1:numel(model.genes) +0092 %Constrain all reactions involving the gene to 0 +0093 tempModel=removeGenes(model,i,false,false,false); +0094 sol=solveLP(tempModel); +0095 +0096 %If the optimization terminated successfully +0097 if sol.stat==1 +0098 fluxes(:,i)=sol.x; +0099 grRatioMuts(i)=sol.f/growthWT; +0100 details(geneMapping(i))=1; +0101 else +0102 fluxes(:,i)=0; +0103 grRatioMuts(i)=0; +0104 details(geneMapping(i))=2; +0105 end +0106 end +0107 genes=geneMapping; +0108 end +0109 +0110 %For double deletions +0111 if strcmpi(testType,'dgd') +0112 %This is a little lazy but it is fine. Check which genes have already +0113 %been deleted in "sgd" analysis. +0114 [~, I]=ismember(originalGenes(details==1),model.genes); +0115 genesToModify=nchoosek(I,2); +0116 genes=geneMapping(genesToModify); +0117 grRatioMuts=zeros(1,numel(genes)); +0118 fluxes=sparse(numel(model.rxns),size(genesToModify,1)); +0119 for i=1:size(genesToModify,1) +0120 tempModel=removeGenes(model,genesToModify(i,:),false,false,false); +0121 sol=solveLP(tempModel); +0122 +0123 if sol.stat==1 +0124 fluxes(:,i)=sol.x; +0125 grRatioMuts(i)=sol.f/growthWT; +0126 end +0127 end +0128 end +0129 +0130 %Map back to the old model +0131 [~, I]=ismember(model.rxns,originalModel.rxns); +0132 temp=fluxes; +0133 fluxes=sparse(numel(originalModel.rxns),size(temp,2)); +0134 fluxes(I,:)=temp; +0135 end
      Generated by m2html © 2005
      \ No newline at end of file diff --git a/docs/html/analysis/getMinimalMedium.html b/docs/html/analysis/getMinimalMedium.html index 90d0bbc2..87c9b731 100644 --- a/docs/html/analysis/getMinimalMedium.html +++ b/docs/html/analysis/getMinimalMedium.html @@ -77,7 +77,17 @@

      DESCRIPTION ^CROSS-REFERENCE INFORMATION ^

      @@ -146,130 +156,140 @@

      SOURCE CODE ^% 0051 % A y_i = 0 forces reaction i to carry non-negative flux (no net uptake); 0052 % y_i = 1 allows it to carry flux as low as lb_i. -0053 -0054 p = parseRAVENargs(varargin, {'minGrowth',[]; 'verbose',true}); -0055 minGrowth = p.minGrowth; -0056 verbose = p.verbose; -0057 -0058 medium = {}; -0059 mediumIdx = zeros(0,1); -0060 -0061 % ---- Resolve minGrowth ---- -0062 if isempty(minGrowth) -0063 sol0 = solveLP(model); -0064 if sol0.stat ~= 1 -0065 error('RAVEN:infeasible', ... -0066 'getMinimalMedium: model is infeasible under current bounds.'); -0067 end -0068 minGrowth = 0.1 * sol0.f; -0069 end +0053 % +0054 % The minimal number of uptakes is guaranteed, but the medium itself need +0055 % not be unique: when several media of the same size support the growth +0056 % rate, the solver returns one of them, and another solver or solver +0057 % version can return a different one. +0058 % +0059 % The MILP is solved with the solver set by setRavenSolver. RAVEN's bundled +0060 % GLPK solves it for small models (with a RAVEN:glpkMILP warning); for +0061 % larger models Gurobi is much faster and more reliable +0062 % (setRavenSolver('gurobi')). +0063 +0064 p = parseRAVENargs(varargin, {'minGrowth',[]; 'verbose',true}); +0065 minGrowth = p.minGrowth; +0066 verbose = p.verbose; +0067 +0068 medium = {}; +0069 mediumIdx = zeros(0,1); 0070 -0071 if minGrowth <= 0 -0072 warning('getMinimalMedium:zeroGrowth', ... -0073 'minGrowth <= 0; the MILP will accept a zero-growth solution.'); -0074 end -0075 -0076 % ---- Identify candidate uptake reactions ---- -0077 [~, exchIdx] = getExchangeRxns(model); -0078 % Only consider exchange reactions with lb < 0 (able to take up) -0079 lb_cap = max(model.lb(exchIdx), -1000); % cap at -1000 for numerical safety -0080 isUptake = lb_cap < 0; -0081 uptakeIdx = exchIdx(isUptake); % indices into model.rxns -0082 uptakeLb = lb_cap(isUptake); % effective lower bounds (negative) -0083 n_uptake = numel(uptakeIdx); -0084 -0085 if n_uptake == 0 -0086 if verbose -0087 fprintf('getMinimalMedium: no exchange reactions with lb < 0 found.\n'); -0088 end -0089 return -0090 end -0091 -0092 % ---- Build MILP ---- -0093 n_mets = numel(model.mets); -0094 n_rxns = numel(model.rxns); -0095 n_vars = n_rxns + n_uptake; % [flux vars | binary uptake indicators] -0096 -0097 % Objective: minimise sum(y) -0098 obj = [zeros(n_rxns,1); ones(n_uptake,1)]; -0099 -0100 % 1. Steady-state: S*v = 0 (n_mets rows) -0101 A_ss = [model.S, sparse(n_mets, n_uptake)]; -0102 b_ss = zeros(n_mets, 1); -0103 -0104 % 2. Growth lower bound: c'*v >= minGrowth (1 row) -0105 A_grw = [model.c', sparse(1, n_uptake)]; -0106 b_grw = minGrowth; -0107 -0108 % 3. Uptake coupling: v_i - lb_i*y_k >= 0 (n_uptake rows) -0109 % coefficient of v_i (in flux block) = 1 -0110 % coefficient of y_k (in binary block, k-th uptake var) = -lb_i (> 0) -0111 rows = (1:n_uptake)'; -0112 colsV = uptakeIdx; -0113 colsY = n_rxns + (1:n_uptake)'; -0114 A_upt = sparse([rows; rows], [colsV; colsY], [ones(n_uptake,1); -uptakeLb], n_uptake, n_vars); -0115 b_upt = zeros(n_uptake, 1); -0116 -0117 % Assemble — prob.a is the "original" A (without solver slack columns) and -0118 % is used by optimizeProb to know how many variables to return in res.full. -0119 prob.A = [A_ss; A_grw; A_upt]; -0120 prob.a = prob.A; -0121 prob.b = [b_ss; b_grw; b_upt]; -0122 prob.csense = [repmat('E',1,n_mets), 'G', repmat('G',1,n_uptake)]; -0123 prob.c = obj; -0124 prob.osense = 1; % minimise -0125 lb_forProb = model.lb; -0126 lb_forProb(uptakeIdx) = uptakeLb; % cap at -1000 for numerical safety -0127 prob.lb = [lb_forProb; zeros(n_uptake,1)]; -0128 prob.ub = [model.ub; ones(n_uptake,1)]; -0129 prob.vartype = [repmat('C', 1, n_rxns), repmat('I', 1, n_uptake)]; -0130 -0131 % ---- Solve ---- -0132 sol = optimizeProb(prob, [], false); -0133 isFeasible = checkSolution(sol); -0134 -0135 if ~isFeasible -0136 if verbose -0137 fprintf('getMinimalMedium: MILP returned no feasible solution.\n'); -0138 fprintf(' The model may not be able to grow at minGrowth=%.4g with any medium.\n', minGrowth); -0139 end -0140 return -0141 end -0142 -0143 % ---- Extract result ---- -0144 y_sol = sol.full(n_rxns + 1 : end); -0145 inMedium = y_sol > 0.5; -0146 mediumIdx = uptakeIdx(inMedium); -0147 medium = model.rxns(mediumIdx); -0148 -0149 if verbose -0150 printMedium_(model, medium, mediumIdx, minGrowth, n_uptake); +0071 % ---- Resolve minGrowth ---- +0072 if isempty(minGrowth) +0073 sol0 = solveLP(model); +0074 if sol0.stat ~= 1 +0075 error('RAVEN:infeasible', ... +0076 'getMinimalMedium: model is infeasible under current bounds.'); +0077 end +0078 minGrowth = 0.1 * sol0.f; +0079 end +0080 +0081 if minGrowth <= 0 +0082 warning('getMinimalMedium:zeroGrowth', ... +0083 'minGrowth <= 0; the MILP will accept a zero-growth solution.'); +0084 end +0085 +0086 % ---- Identify candidate uptake reactions ---- +0087 [~, exchIdx] = getExchangeRxns(model); +0088 % Only consider exchange reactions with lb < 0 (able to take up) +0089 lb_cap = max(model.lb(exchIdx), -1000); % cap at -1000 for numerical safety +0090 isUptake = lb_cap < 0; +0091 uptakeIdx = exchIdx(isUptake); % indices into model.rxns +0092 uptakeLb = lb_cap(isUptake); % effective lower bounds (negative) +0093 n_uptake = numel(uptakeIdx); +0094 +0095 if n_uptake == 0 +0096 if verbose +0097 fprintf('getMinimalMedium: no exchange reactions with lb < 0 found.\n'); +0098 end +0099 return +0100 end +0101 +0102 % ---- Build MILP ---- +0103 n_mets = numel(model.mets); +0104 n_rxns = numel(model.rxns); +0105 n_vars = n_rxns + n_uptake; % [flux vars | binary uptake indicators] +0106 +0107 % Objective: minimise sum(y) +0108 obj = [zeros(n_rxns,1); ones(n_uptake,1)]; +0109 +0110 % 1. Steady-state: S*v = 0 (n_mets rows) +0111 A_ss = [model.S, sparse(n_mets, n_uptake)]; +0112 b_ss = zeros(n_mets, 1); +0113 +0114 % 2. Growth lower bound: c'*v >= minGrowth (1 row) +0115 A_grw = [model.c', sparse(1, n_uptake)]; +0116 b_grw = minGrowth; +0117 +0118 % 3. Uptake coupling: v_i - lb_i*y_k >= 0 (n_uptake rows) +0119 % coefficient of v_i (in flux block) = 1 +0120 % coefficient of y_k (in binary block, k-th uptake var) = -lb_i (> 0) +0121 rows = (1:n_uptake)'; +0122 colsV = uptakeIdx; +0123 colsY = n_rxns + (1:n_uptake)'; +0124 A_upt = sparse([rows; rows], [colsV; colsY], [ones(n_uptake,1); -uptakeLb], n_uptake, n_vars); +0125 b_upt = zeros(n_uptake, 1); +0126 +0127 % Assemble — prob.a is the "original" A (without solver slack columns) and +0128 % is used by optimizeProb to know how many variables to return in res.full. +0129 prob.A = [A_ss; A_grw; A_upt]; +0130 prob.a = prob.A; +0131 prob.b = [b_ss; b_grw; b_upt]; +0132 prob.csense = [repmat('E',1,n_mets), 'G', repmat('G',1,n_uptake)]; +0133 prob.c = obj; +0134 prob.osense = 1; % minimise +0135 lb_forProb = model.lb; +0136 lb_forProb(uptakeIdx) = uptakeLb; % cap at -1000 for numerical safety +0137 prob.lb = [lb_forProb; zeros(n_uptake,1)]; +0138 prob.ub = [model.ub; ones(n_uptake,1)]; +0139 prob.vartype = [repmat('C', 1, n_rxns), repmat('I', 1, n_uptake)]; +0140 +0141 % ---- Solve ---- +0142 sol = optimizeProb(prob, [], false); +0143 isFeasible = checkSolution(sol); +0144 +0145 if ~isFeasible +0146 if verbose +0147 fprintf('getMinimalMedium: MILP returned no feasible solution.\n'); +0148 fprintf(' The model may not be able to grow at minGrowth=%.4g with any medium.\n', minGrowth); +0149 end +0150 return 0151 end -0152 end -0153 -0154 %-------------------------------------------------------------------------- -0155 function printMedium_(model, medium, mediumIdx, minGrowth, nCandidates) -0156 W = 62; -0157 fprintf('\n%s\n', repmat('=', 1, W)); -0158 fprintf(' Minimal medium (%d of %d candidate uptake reactions)\n', ... -0159 numel(medium), nCandidates); -0160 fprintf(' Target growth: %.4g\n', minGrowth); -0161 fprintf('%s\n', repmat('-', 1, W)); -0162 if isempty(medium) -0163 fprintf(' (none — model can grow without any uptake)\n'); -0164 else -0165 for i = 1:numel(medium) -0166 ri = mediumIdx(i); -0167 nm = ''; -0168 if isfield(model,'rxnNames') && ri <= numel(model.rxnNames) -0169 nm = char(model.rxnNames{ri}); -0170 end -0171 if numel(nm) > 35, nm = [nm(1:32) '...']; end -0172 fprintf(' %-20s %s\n', medium{i}, nm); -0173 end -0174 end -0175 fprintf('%s\n\n', repmat('=', 1, W)); -0176 end +0152 +0153 % ---- Extract result ---- +0154 y_sol = sol.full(n_rxns + 1 : end); +0155 inMedium = y_sol > 0.5; +0156 mediumIdx = uptakeIdx(inMedium); +0157 medium = model.rxns(mediumIdx); +0158 +0159 if verbose +0160 printMedium_(model, medium, mediumIdx, minGrowth, n_uptake); +0161 end +0162 end +0163 +0164 %-------------------------------------------------------------------------- +0165 function printMedium_(model, medium, mediumIdx, minGrowth, nCandidates) +0166 W = 62; +0167 fprintf('\n%s\n', repmat('=', 1, W)); +0168 fprintf(' Minimal medium (%d of %d candidate uptake reactions)\n', ... +0169 numel(medium), nCandidates); +0170 fprintf(' Target growth: %.4g\n', minGrowth); +0171 fprintf('%s\n', repmat('-', 1, W)); +0172 if isempty(medium) +0173 fprintf(' (none — model can grow without any uptake)\n'); +0174 else +0175 for i = 1:numel(medium) +0176 ri = mediumIdx(i); +0177 nm = ''; +0178 if isfield(model,'rxnNames') && ri <= numel(model.rxnNames) +0179 nm = char(model.rxnNames{ri}); +0180 end +0181 if numel(nm) > 35, nm = [nm(1:32) '...']; end +0182 fprintf(' %-20s %s\n', medium{i}, nm); +0183 end +0184 end +0185 fprintf('%s\n\n', repmat('=', 1, W)); +0186 end
      Generated by m2html © 2005
      \ No newline at end of file diff --git a/docs/html/analysis/haveFlux.html b/docs/html/analysis/haveFlux.html index 9968ae6f..0f20c00c 100644 --- a/docs/html/analysis/haveFlux.html +++ b/docs/html/analysis/haveFlux.html @@ -29,9 +29,10 @@

      SYNOPSIS ^DESCRIPTION ^

       haveFlux  Check which reactions can carry a flux.
       
      - Checks which reactions can carry a (positive or negative) flux. Is used as
      - a faster version of getAllowedBounds if it is only interesting whether the
      - reactions can carry a flux or not.
      + Checks which reactions can carry a (positive or negative) flux under the
      + model's current bounds; other media or bounds are not explored. Is used
      + as a faster version of getAllowedBounds if it is only interesting whether
      + the reactions can carry a flux or not.
       
        Parameters
        ----------
      @@ -56,7 +57,7 @@ 

      DESCRIPTION ^DESCRIPTION ^CROSS-REFERENCE INFORMATION ^

      @@ -83,105 +90,119 @@

      SOURCE CODE ^
      0001 function I=haveFlux(model,varargin)
       0002 % haveFlux  Check which reactions can carry a flux.
       0003 %
      -0004 % Checks which reactions can carry a (positive or negative) flux. Is used as
      -0005 % a faster version of getAllowedBounds if it is only interesting whether the
      -0006 % reactions can carry a flux or not.
      -0007 %
      -0008 % Parameters
      -0009 % ----------
      -0010 % model : struct
      -0011 %     a model structure.
      -0012 %
      -0013 % Name-Value Arguments
      -0014 % --------------------
      -0015 % cutOff : double
      -0016 %     the flux value that a reaction has to carry to be identified as
      -0017 %     positive (default 10^-6).
      -0018 % rxns : cell or logical or double
      -0019 %     either a cell array of IDs, a logical vector with the same number of
      -0020 %     elements as metabolites in the model, or a vector of indexes (default
      -0021 %     model.rxns).
      -0022 % seed : double
      -0023 %     seed for the random number generator used to shuffle the order in
      -0024 %     which reactions are tested, so that order is reproducible across
      -0025 %     runs (default: current rng state is left untouched).
      -0026 %
      -0027 % Returns
      -0028 % -------
      -0029 % I : logical
      -0030 %     logical array with true if the corresponding reaction can carry a
      -0031 %     flux.
      -0032 %
      -0033 % Examples
      -0034 % --------
      -0035 %     I = haveFlux(model, cutOff, rxns);
      -0036 %
      -0037 % Notes
      -0038 % -----
      -0039 % If a model has +/- Inf bounds then those are replaced with an arbitary
      -0040 % large value of +/- 10000 prior to solving.
      -0041 
      -0042 p=parseRAVENargs(varargin, {'cutOff',10^-6; 'rxns',[]; 'seed',[]});
      -0043 if ~isempty(p.seed)
      -0044     rng(p.seed);
      -0045 end
      -0046 cutOff=p.cutOff;
      -0047 rxns=p.rxns;
      -0048 if isempty(cutOff)
      -0049     cutOff=10^-6;
      -0050 end
      -0051 if isempty(rxns)
      -0052     rxns=model.rxns;
      -0053 elseif ~islogical(rxns) && ~isnumeric(rxns)
      -0054     rxns=convertCharArray(rxns);
      -0055 end
      -0056 
      -0057 %This is since we're maximizing for the sum of fluxes, which isn't possible
      -0058 %when there are infinite bounds
      -0059 model.lb(model.lb==-inf)=-10000;
      -0060 model.ub(model.ub==inf)=10000;
      -0061 
      -0062 %Get the reaction IDs. A bit of an awkward way, but fine.
      -0063 indexes=getIndexes(model,rxns,'rxns');
      -0064 rxns=model.rxns(indexes);
      -0065 
      -0066 %First remove all dead-end reactions
      -0067 smallModel=simplifyModel(model,false,false,true,true);
      +0004 % Checks which reactions can carry a (positive or negative) flux under the
      +0005 % model's current bounds; other media or bounds are not explored. Is used
      +0006 % as a faster version of getAllowedBounds if it is only interesting whether
      +0007 % the reactions can carry a flux or not.
      +0008 %
      +0009 % Parameters
      +0010 % ----------
      +0011 % model : struct
      +0012 %     a model structure.
      +0013 %
      +0014 % Name-Value Arguments
      +0015 % --------------------
      +0016 % cutOff : double
      +0017 %     the flux value that a reaction has to carry to be identified as
      +0018 %     positive (default 10^-6).
      +0019 % rxns : cell or logical or double
      +0020 %     either a cell array of IDs, a logical vector with the same number of
      +0021 %     elements as metabolites in the model, or a vector of indexes (default
      +0022 %     model.rxns).
      +0023 % seed : double
      +0024 %     seed for the random number generator used to shuffle the order in
      +0025 %     which reactions are tested, so that order is reproducible across
      +0026 %     runs (default: current rng state is left untouched).
      +0027 %
      +0028 % Returns
      +0029 % -------
      +0030 % I : logical
      +0031 %     logical array with true if the corresponding reaction can carry a
      +0032 %     flux under the tested bounds.
      +0033 %
      +0034 % Examples
      +0035 % --------
      +0036 %     I = haveFlux(model, cutOff, rxns);
      +0037 %
      +0038 % Notes
      +0039 % -----
      +0040 % If a model has +/- Inf bounds then those are replaced with an arbitary
      +0041 % large value of +/- 10000 prior to solving.
      +0042 %
      +0043 % Dead-end reactions are removed (simplifyModel) before solving. A dead-end
      +0044 % reaction with a forced flux (lb > 0 or ub < 0) makes the model infeasible,
      +0045 % but it is removed like any other dead end, so the remaining reactions are
      +0046 % tested as if it were absent. A forced flux elsewhere in an infeasible
      +0047 % model gives false for all reactions.
      +0048 
      +0049 p=parseRAVENargs(varargin, {'cutOff',10^-6; 'rxns',[]; 'seed',[]});
      +0050 if ~isempty(p.seed)
      +0051     rng(p.seed);
      +0052 end
      +0053 cutOff=p.cutOff;
      +0054 rxns=p.rxns;
      +0055 if isempty(cutOff)
      +0056     cutOff=10^-6;
      +0057 end
      +0058 if isempty(rxns)
      +0059     rxns=model.rxns;
      +0060 elseif ~islogical(rxns) && ~isnumeric(rxns)
      +0061     rxns=convertCharArray(rxns);
      +0062 end
      +0063 
      +0064 %This is since we're maximizing for the sum of fluxes, which isn't possible
      +0065 %when there are infinite bounds
      +0066 model.lb(model.lb==-inf)=-10000;
      +0067 model.ub(model.ub==inf)=10000;
       0068 
      -0069 %Get the indexes of the reactions in the connected model
      -0070 indexes=getIndexes(smallModel,intersect(smallModel.rxns,rxns),'rxns');
      -0071 J=false(numel(indexes),1);
      -0072 mixIndexes=indexes(randperm(numel(indexes)));
      -0073 
      -0074 %Maximize for all fluxes first to get fewer rxns to test
      -0075 smallModel.c=ones(numel(smallModel.c),1);
      -0076 sol=solveLP(smallModel);
      -0077 if ~isempty(sol.x)
      -0078     J(abs(sol.x(mixIndexes))>cutOff)=true;
      -0079 end
      -0080 
      -0081 %Loop through and maximize then minimize each rxn if it does not already
      -0082 %have a flux
      -0083 Z=zeros(numel(smallModel.c),1);
      -0084 hsSolOut=[];
      -0085 for i=[1 -1]
      -0086     for j=1:numel(J)
      -0087         if J(j)==false
      -0088             %Only minimize for reversible reactions
      -0089             if i==1 || smallModel.rev(mixIndexes(j))~=0
      -0090                 smallModel.c=Z;
      -0091                 smallModel.c(mixIndexes(j))=i;
      -0092                 [sol, hsSolOut]=solveLP(smallModel,0,[],hsSolOut);
      -0093                 if any(sol.x)
      -0094                     J(abs(sol.x(mixIndexes))>cutOff)=true;
      -0095                 end
      -0096             end
      -0097         end
      -0098     end
      -0099 end
      -0100 
      -0101 I=ismember(rxns,smallModel.rxns(mixIndexes(J)));
      -0102 end

      +0069 %Get the reaction IDs. A bit of an awkward way, but fine. +0070 indexes=getIndexes(model,rxns,'rxns'); +0071 rxns=model.rxns(indexes); +0072 +0073 %First remove all dead-end reactions +0074 smallModel=simplifyModel(model,false,false,true,true); +0075 +0076 %Nothing is left to solve when every reaction is a dead end, and reactions +0077 %without metabolites are limited by their bounds only +0078 if isempty(smallModel.rxns) || isempty(smallModel.mets) +0079 I=ismember(rxns,smallModel.rxns(smallModel.ub>cutOff | smallModel.lb<-cutOff)); +0080 return +0081 end +0082 +0083 %Get the indexes of the reactions in the connected model +0084 indexes=getIndexes(smallModel,intersect(smallModel.rxns,rxns),'rxns'); +0085 J=false(numel(indexes),1); +0086 mixIndexes=indexes(randperm(numel(indexes))); +0087 +0088 %Maximize for all fluxes first to get fewer rxns to test +0089 smallModel.c=ones(numel(smallModel.c),1); +0090 sol=solveLP(smallModel); +0091 if ~isempty(sol.x) +0092 J(abs(sol.x(mixIndexes))>cutOff)=true; +0093 end +0094 +0095 %Loop through and maximize then minimize each rxn if it does not already +0096 %have a flux +0097 Z=zeros(numel(smallModel.c),1); +0098 hsSolOut=[]; +0099 for i=[1 -1] +0100 for j=1:numel(J) +0101 if J(j)==false +0102 %Only minimize for reversible reactions +0103 if i==1 || smallModel.rev(mixIndexes(j))~=0 +0104 smallModel.c=Z; +0105 smallModel.c(mixIndexes(j))=i; +0106 [sol, hsSolOut]=solveLP(smallModel,0,[],hsSolOut); +0107 if any(sol.x) +0108 J(abs(sol.x(mixIndexes))>cutOff)=true; +0109 end +0110 end +0111 end +0112 end +0113 end +0114 +0115 I=ismember(rxns,smallModel.rxns(mixIndexes(J))); +0116 end
      Generated by m2html © 2005
      \ No newline at end of file diff --git a/docs/html/analysis/randomSampling.html b/docs/html/analysis/randomSampling.html index 037b52c1..d26543d6 100644 --- a/docs/html/analysis/randomSampling.html +++ b/docs/html/analysis/randomSampling.html @@ -109,9 +109,12 @@

      DESCRIPTION ^SOURCE CODE ^% 0082 % Notes 0083 % ----- -0084 % The solutions are generated by maximizing (with random weights) for a -0085 % random set of nObjectives reactions (default 2). For reversible reactions -0086 % it randomly chooses between maximizing and minimizing. -0087 % -0088 % If the model is a GECKO v3+ ecModel, then usage_prot reactions are not -0089 % selected for sampling, instead focusing on sampling from the metabolic -0090 % aspects that form the solution space. -0091 % -0092 % Examples -0093 % -------- -0094 % % Near-uniform interior sampling (default, ACHR): -0095 % solutions = randomSampling(model, 1000); -0096 % % Coordinate hit-and-run with rounding: -0097 % solutions = randomSampling(model, 1000, 'method', 'chrr', 'seed', 1); -0098 % % Historical random-objective vertex method: -0099 % solutions = randomSampling(model, 1000, 'method', 'randomObjective'); -0100 % -0101 % See also -0102 % -------- -0103 % sampleACHR, sampleCHRR, sampleMaxVolEllipse -0104 -0105 p=parseRAVENargs(varargin, {'nSamples',[]; 'replaceBoundsWithInf',[]; 'supressErrors',[]; 'runParallel',[]; 'goodRxns',[]; 'minFlux',[]; 'nObjectives',[]; 'seed',[]; 'method',[]; 'thinning',[]; 'nBurnin',[]}); -0106 nSamples=p.nSamples; -0107 replaceBoundsWithInf=p.replaceBoundsWithInf; -0108 supressErrors=p.supressErrors; -0109 runParallel=p.runParallel; -0110 goodRxns=p.goodRxns; -0111 minFlux=p.minFlux; -0112 nObjectives=p.nObjectives; -0113 seed=p.seed; -0114 method=p.method; -0115 thinning=p.thinning; -0116 nBurnin=p.nBurnin; -0117 info=[]; %only populated by the 'chrr' method -0118 if isempty(nSamples) -0119 nSamples=1000; -0120 end -0121 if isempty(method) -0122 method='achr'; +0084 % With 'randomObjective', the solutions are generated by maximizing (with +0085 % random weights) for a random set of nObjectives reactions (default 2). +0086 % For reversible reactions it randomly chooses between maximizing and +0087 % minimizing. The default 'achr' and the 'chrr' methods use no random +0088 % objectives; they take hit-and-run steps through the interior of the +0089 % solution space. +0090 % +0091 % If the model is a GECKO v3+ ecModel, then usage_prot reactions are not +0092 % selected for sampling, instead focusing on sampling from the metabolic +0093 % aspects that form the solution space. +0094 % +0095 % Examples +0096 % -------- +0097 % % Near-uniform interior sampling (default, ACHR): +0098 % solutions = randomSampling(model, 1000); +0099 % % Coordinate hit-and-run with rounding: +0100 % solutions = randomSampling(model, 1000, 'method', 'chrr', 'seed', 1); +0101 % % Historical random-objective vertex method: +0102 % solutions = randomSampling(model, 1000, 'method', 'randomObjective'); +0103 % +0104 % See also +0105 % -------- +0106 % sampleACHR, sampleCHRR, sampleMaxVolEllipse +0107 +0108 p=parseRAVENargs(varargin, {'nSamples',[]; 'replaceBoundsWithInf',[]; 'supressErrors',[]; 'runParallel',[]; 'goodRxns',[]; 'minFlux',[]; 'nObjectives',[]; 'seed',[]; 'method',[]; 'thinning',[]; 'nBurnin',[]}); +0109 nSamples=p.nSamples; +0110 replaceBoundsWithInf=p.replaceBoundsWithInf; +0111 supressErrors=p.supressErrors; +0112 runParallel=p.runParallel; +0113 goodRxns=p.goodRxns; +0114 minFlux=p.minFlux; +0115 nObjectives=p.nObjectives; +0116 seed=p.seed; +0117 method=p.method; +0118 thinning=p.thinning; +0119 nBurnin=p.nBurnin; +0120 info=[]; %only populated by the 'chrr' method +0121 if isempty(nSamples) +0122 nSamples=1000; 0123 end -0124 -0125 % ---- Dispatch to the requested sampler ---- -0126 % 'achr'/'chrr' are near-uniform MCMC samplers of the polytope interior, -0127 % implemented in sampleACHR / sampleCHRR. 'randomObjective' is the -0128 % random-objective vertex method (Bordel 2010), implemented below; goodRxns is -0129 % meaningful only for that method (empty otherwise). -0130 switch lower(method) -0131 case 'achr' -0132 solutions = sampleACHR(model, nSamples, thinning, [], seed); -0133 goodRxns = []; -0134 return; -0135 case 'chrr' -0136 [solutions, info] = sampleCHRR(model, nSamples, thinning, nBurnin, seed); -0137 goodRxns = []; -0138 return; -0139 case {'randomobjective', 'objective'} -0140 % fall through to the random-objective implementation below -0141 otherwise -0142 error('RAVEN:badInput', ... -0143 ['randomSampling: unknown method ''%s''; expected ''achr'', ' ... -0144 '''chrr'', or ''randomObjective''.'], method); -0145 end -0146 -0147 if isempty(replaceBoundsWithInf) -0148 replaceBoundsWithInf=true; -0149 end -0150 if isempty(supressErrors) -0151 supressErrors=false; +0124 if isempty(method) +0125 method='achr'; +0126 end +0127 +0128 % ---- Dispatch to the requested sampler ---- +0129 % 'achr'/'chrr' are near-uniform MCMC samplers of the polytope interior, +0130 % implemented in sampleACHR / sampleCHRR. 'randomObjective' is the +0131 % random-objective vertex method (Bordel 2010), implemented below; goodRxns is +0132 % meaningful only for that method (empty otherwise). +0133 switch lower(method) +0134 case 'achr' +0135 solutions = sampleACHR(model, nSamples, thinning, [], seed); +0136 goodRxns = []; +0137 return; +0138 case 'chrr' +0139 [solutions, info] = sampleCHRR(model, nSamples, thinning, nBurnin, seed); +0140 goodRxns = []; +0141 return; +0142 case {'randomobjective', 'objective'} +0143 % fall through to the random-objective implementation below +0144 otherwise +0145 error('RAVEN:badInput', ... +0146 ['randomSampling: unknown method ''%s''; expected ''achr'', ' ... +0147 '''chrr'', or ''randomObjective''.'], method); +0148 end +0149 +0150 if isempty(replaceBoundsWithInf) +0151 replaceBoundsWithInf=true; 0152 end -0153 if isempty(runParallel) -0154 runParallel=true; +0153 if isempty(supressErrors) +0154 supressErrors=false; 0155 end -0156 if isempty(minFlux) -0157 minFlux=false; +0156 if isempty(runParallel) +0157 runParallel=true; 0158 end -0159 if isempty(nObjectives) -0160 nObjectives=2; +0159 if isempty(minFlux) +0160 minFlux=false; 0161 end -0162 -0163 nW = parallelWorkersRAVEN(runParallel); -0164 -0165 nObjRxns=nObjectives; -0166 -0167 %Simplify the model to speed stuff up a little. Keep original mapping -0168 originalRxns=model.rxns; -0169 model=simplifyModel(model,false,false,true,true); -0170 [~, simpToOrig] = ismember(model.rxns, originalRxns); -0171 -0172 %goodRxns is part of the public API and stored as original-model indices. -0173 %Convert to simplified-model indices for use in the detection and sampling -0174 %loops below, dropping any reactions that were removed by simplification. -0175 if ~isempty(goodRxns) -0176 [tf, simpIdx] = ismember(originalRxns(goodRxns), model.rxns); -0177 goodRxns = double(simpIdx(tf)); -0178 end -0179 -0180 %Check that the model is feasible given the constraints -0181 [sol,~]=solveLP(model); -0182 if isempty(sol.x) -0183 EM='The model has no feasible solution, likely due to incompatible constraints'; -0184 error('RAVEN:badInput', '%s', EM); -0185 elseif sol.f==0 -0186 warning('The model objective function cannot reach a non-zero value. This might be intended, so randomSampling will continue, but this could indicate problems with your model') -0187 end -0188 -0189 nRxns = numel(model.rxns); -0190 %Reactions which can be involved in loops should not be optimized for. -0191 %Check which reactions reach an arbitrary high upper bound. This is done -0192 %on the original bounded model so that loop detection is meaningful -0193 %(Inf-bounded reactions would always reach the threshold). -0194 if isempty(goodRxns) -0195 [minF, maxF] = getAllowedBounds(model, 'runParallel', runParallel); -0196 goodRxns = true(nRxns, 1); -0197 % Reactions that reach the model's own highest bound are involved in -0198 % loops. The threshold has to be derived from the model, as the bound -0199 % replacement below does: hardcoding ±1000 matches nothing on a model -0200 % whose bounds are on another scale (ecModels, 100-scale bounds), so no -0201 % reaction would be excluded and loops would be sampled while the -0202 % function reports success. -0203 loopUb = max(model.ub); -0204 loopLb = min(model.lb); -0205 goodRxns(maxF == Inf | maxF > loopUb*0.999) = false; -0206 if loopLb < 0 -0207 goodRxns(minF == -Inf | minF < loopLb*0.999) = false; -0208 end -0209 % Reactions that cannot carry any non-zero flux -0210 goodRxns(~(maxF > 0 | minF < 0)) = false; -0211 % In ecModels do not sample from usage_prot reactions -0212 if isfield(model,'ec') -0213 goodRxns(startsWith(model.rxns,'usage_prot_')) = false; -0214 end -0215 goodRxns = find(goodRxns); -0216 end -0217 -0218 %Change the bounds to +/- Inf to prevent loops in the sampled solutions. -0219 %Applied after goodRxns detection so that loop-detection uses original bounds. -0220 if replaceBoundsWithInf==true -0221 model.ub(model.ub==max(model.ub))=Inf; -0222 if min(model.lb)<0 % Only negative lower bounds should be set to -Inf -0223 model.lb(model.lb==min(model.lb))=-Inf; -0224 end -0225 end -0226 -0227 %Seed the RNG here so that only the sampling loop is affected, not the -0228 %model simplification or goodRxns detection above. -0229 if ~isempty(seed) -0230 rng(seed); -0231 end -0232 -0233 %Reserve space for a solution matrix -0234 sols = zeros(numel(model.rxns),nSamples); -0235 sols = num2cell(sols,1); -0236 -0237 %Main loop -0238 if nSamples > 0 +0162 if isempty(nObjectives) +0163 nObjectives=2; +0164 end +0165 +0166 nW = parallelWorkersRAVEN(runParallel); +0167 +0168 nObjRxns=nObjectives; +0169 +0170 %Simplify the model to speed stuff up a little. Keep original mapping +0171 originalRxns=model.rxns; +0172 model=simplifyModel(model,false,false,true,true); +0173 [~, simpToOrig] = ismember(model.rxns, originalRxns); +0174 +0175 %goodRxns is part of the public API and stored as original-model indices. +0176 %Convert to simplified-model indices for use in the detection and sampling +0177 %loops below, dropping any reactions that were removed by simplification. +0178 if ~isempty(goodRxns) +0179 [tf, simpIdx] = ismember(originalRxns(goodRxns), model.rxns); +0180 goodRxns = double(simpIdx(tf)); +0181 end +0182 +0183 %Check that the model is feasible given the constraints +0184 [sol,~]=solveLP(model); +0185 if isempty(sol.x) +0186 EM='The model has no feasible solution, likely due to incompatible constraints'; +0187 error('RAVEN:badInput', '%s', EM); +0188 elseif sol.f==0 +0189 warning('The model objective function cannot reach a non-zero value. This might be intended, so randomSampling will continue, but this could indicate problems with your model') +0190 end +0191 +0192 nRxns = numel(model.rxns); +0193 %Reactions which can be involved in loops should not be optimized for. +0194 %Check which reactions reach an arbitrary high upper bound. This is done +0195 %on the original bounded model so that loop detection is meaningful +0196 %(Inf-bounded reactions would always reach the threshold). +0197 if isempty(goodRxns) +0198 [minF, maxF] = getAllowedBounds(model, 'runParallel', runParallel); +0199 goodRxns = true(nRxns, 1); +0200 % Reactions that reach the model's own highest bound are involved in +0201 % loops. The threshold has to be derived from the model, as the bound +0202 % replacement below does: hardcoding ±1000 matches nothing on a model +0203 % whose bounds are on another scale (ecModels, 100-scale bounds), so no +0204 % reaction would be excluded and loops would be sampled while the +0205 % function reports success. +0206 loopUb = max(model.ub); +0207 loopLb = min(model.lb); +0208 goodRxns(maxF == Inf | maxF > loopUb*0.999) = false; +0209 if loopLb < 0 +0210 goodRxns(minF == -Inf | minF < loopLb*0.999) = false; +0211 end +0212 % Reactions that cannot carry any non-zero flux +0213 goodRxns(~(maxF > 0 | minF < 0)) = false; +0214 % In ecModels do not sample from usage_prot reactions +0215 if isfield(model,'ec') +0216 goodRxns(startsWith(model.rxns,'usage_prot_')) = false; +0217 end +0218 goodRxns = find(goodRxns); +0219 end +0220 +0221 %Change the bounds to +/- Inf to prevent loops in the sampled solutions. +0222 %Applied after goodRxns detection so that loop-detection uses original bounds. +0223 if replaceBoundsWithInf==true +0224 model.ub(model.ub==max(model.ub))=Inf; +0225 if min(model.lb)<0 % Only negative lower bounds should be set to -Inf +0226 model.lb(model.lb==min(model.lb))=-Inf; +0227 end +0228 end +0229 +0230 %Seed the RNG here so that only the sampling loop is affected, not the +0231 %model simplification or goodRxns detection above. +0232 if ~isempty(seed) +0233 rng(seed); +0234 end +0235 +0236 %Reserve space for a solution matrix +0237 sols = zeros(numel(model.rxns),nSamples); +0238 sols = num2cell(sols,1); 0239 -0240 PB = progressReport(nSamples,'Performing random sampling'); -0241 parfor (i = 1:nSamples, nW) -0242 badSolutions = 1; -0243 tmpModel = model; -0244 while lt(0,badSolutions) -0245 rxns = goodRxns(randperm(numel(goodRxns), nObjRxns)); -0246 tmpModel.c = zeros(numel(tmpModel.rxns),1); -0247 -0248 multipliers = 2*randi(2,nObjRxns,1)-3; % random draws from {-1, 1} -0249 multipliers(tmpModel.ub(rxns)<0) = 1; +0240 %Main loop +0241 if nSamples > 0 +0242 +0243 PB = progressReport(nSamples,'Performing random sampling'); +0244 parfor (i = 1:nSamples, nW) +0245 badSolutions = 1; +0246 tmpModel = model; +0247 while lt(0,badSolutions) +0248 rxns = goodRxns(randperm(numel(goodRxns), nObjRxns)); +0249 tmpModel.c = zeros(numel(tmpModel.rxns),1); 0250 -0251 tmpModel.c(rxns) = rand(nObjRxns,1).*multipliers; -0252 -0253 if true(minFlux) -0254 sol=solveLP(tmpModel,1); -0255 else -0256 sol=solveLP(tmpModel,0); -0257 end -0258 if any(sol.x) && abs(sol.f)>10^-8 -0259 sols{i} = sol.x; -0260 badSolutions = 0; -0261 else -0262 badSolutions = badSolutions+1; -0263 %If it only finds bad solutions then throw an error. -0264 if badSolutions == 100 && supressErrors==false -0265 error('The program is having problems finding non-zero solutions (ignoring reactions that might be involved in loops). Review the constraints on your model. Set supressErrors to true to ignore this error'); -0266 end -0267 end -0268 end -0269 count(PB); -0270 end -0271 -0272 %Map to original model -0273 sols = cell2mat(sols); -0274 solutions=zeros(numel(originalRxns),nSamples); -0275 solutions(simpToOrig,:)=sols; -0276 else -0277 solutions=[]; -0278 end -0279 %Return goodRxns as original-model indices so callers can pass them back -0280 %safely across separate calls (which each re-simplify the model internally). -0281 goodRxns = double(simpToOrig(goodRxns)); -0282 end +0251 multipliers = 2*randi(2,nObjRxns,1)-3; % random draws from {-1, 1} +0252 multipliers(tmpModel.ub(rxns)<0) = 1; +0253 +0254 tmpModel.c(rxns) = rand(nObjRxns,1).*multipliers; +0255 +0256 if true(minFlux) +0257 sol=solveLP(tmpModel,1); +0258 else +0259 sol=solveLP(tmpModel,0); +0260 end +0261 if any(sol.x) && abs(sol.f)>10^-8 +0262 sols{i} = sol.x; +0263 badSolutions = 0; +0264 else +0265 badSolutions = badSolutions+1; +0266 %If it only finds bad solutions then throw an error. +0267 if badSolutions == 100 && supressErrors==false +0268 error('The program is having problems finding non-zero solutions (ignoring reactions that might be involved in loops). Review the constraints on your model. Set supressErrors to true to ignore this error'); +0269 end +0270 end +0271 end +0272 count(PB); +0273 end +0274 +0275 %Map to original model +0276 sols = cell2mat(sols); +0277 solutions=zeros(numel(originalRxns),nSamples); +0278 solutions(simpToOrig,:)=sols; +0279 else +0280 solutions=[]; +0281 end +0282 %Return goodRxns as original-model indices so callers can pass them back +0283 %safely across separate calls (which each re-simplify the model internally). +0284 goodRxns = double(simpToOrig(goodRxns)); +0285 end
      Generated by m2html © 2005
      \ No newline at end of file diff --git a/docs/html/analysis/reporterMetabolites.html b/docs/html/analysis/reporterMetabolites.html index 232585cc..4f11bc25 100644 --- a/docs/html/analysis/reporterMetabolites.html +++ b/docs/html/analysis/reporterMetabolites.html @@ -64,10 +64,10 @@

      DESCRIPTION ^DESCRIPTION ^CROSS-REFERENCE INFORMATION ^

      @@ -137,10 +141,10 @@

      SOURCE CODE ^% supplied. 0037 % - mets : a cell array of metabolite IDs for the metabolites for which 0038 % a score could be calculated. -0039 % - metZScores : Z-scores for differential expression around each -0040 % metabolite in "mets". -0041 % - metPValues : P-values for differential expression around each -0042 % metabolite in "mets". +0039 % - metZScores : background-corrected Z-scores for differential +0040 % expression around each metabolite in "mets". The entries of all +0041 % fields are sorted by this score, highest first. +0042 % - metPValues : one-sided (upper-tail) P-values of "metZScores". 0043 % - metNGenes : number of neighbouring genes for each metabolite in 0044 % "mets". 0045 % - meanZ : average Z-scores for the genes around each metabolite in @@ -158,154 +162,158 @@

      SOURCE CODE ^% For details about the algorithm, see Patil KR, Nielsen J, Uncovering 0058 % transcriptional regulation of metabolism by using metabolic network 0059 % topology. Proc. Natl Acad. Sci. USA 2005;102:2685-2689. -0060 -0061 genes=convertCharArray(genes); -0062 p=parseRAVENargs(varargin, {'printResults',false; 'outputFile',[]; 'geneFoldChanges',[]}); -0063 printResults=p.printResults; -0064 outputFile=p.outputFile; -0065 geneFoldChanges=p.geneFoldChanges; -0066 -0067 %Check some stuff -0068 if numel(genes)~=numel(genePValues) -0069 EM='The number of genes and the number of Z-scores must be the same'; -0070 error('RAVEN:badInput', '%s', EM); -0071 end -0072 if ~isfield(model,'rxnGeneMat') -0073 EM='The model structure must have a rxnGeneMat field'; +0060 % +0061 % The background correction uses the closed-form mean and standard +0062 % deviation of the aggregate Z-score of n genes drawn at random from all +0063 % scored genes, so the results are deterministic. +0064 +0065 genes=convertCharArray(genes); +0066 p=parseRAVENargs(varargin, {'printResults',false; 'outputFile',[]; 'geneFoldChanges',[]}); +0067 printResults=p.printResults; +0068 outputFile=p.outputFile; +0069 geneFoldChanges=p.geneFoldChanges; +0070 +0071 %Check some stuff +0072 if numel(genes)~=numel(genePValues) +0073 EM='The number of genes and the number of Z-scores must be the same'; 0074 error('RAVEN:badInput', '%s', EM); 0075 end -0076 -0077 %Remove the genes which are not in the model -0078 genePValues(~ismember(genes,model.genes))=[]; -0079 if any(geneFoldChanges) -0080 geneFoldChanges(~ismember(genes,model.genes))=[]; -0081 end -0082 genes(~ismember(genes,model.genes))=[]; -0083 -0084 %Remove the genes that has NA P-value -0085 genes(isnan(genePValues))=[]; -0086 if any(geneFoldChanges) -0087 geneFoldChanges(isnan(genePValues))=[]; -0088 end -0089 genePValues(isnan(genePValues))=[]; -0090 -0091 %Convert p-values to Z-scores. Uses base MATLAB erfcinv instead of the -0092 %Statistics Toolbox norminv: -norminv(p) == sqrt(2)*erfcinv(2*p). -0093 geneZScores=sqrt(2)*erfcinv(2*genePValues); +0076 if ~isfield(model,'rxnGeneMat') +0077 EM='The model structure must have a rxnGeneMat field'; +0078 error('RAVEN:badInput', '%s', EM); +0079 end +0080 +0081 %Remove the genes which are not in the model +0082 genePValues(~ismember(genes,model.genes))=[]; +0083 if any(geneFoldChanges) +0084 geneFoldChanges(~ismember(genes,model.genes))=[]; +0085 end +0086 genes(~ismember(genes,model.genes))=[]; +0087 +0088 %Remove the genes that has NA P-value +0089 genes(isnan(genePValues))=[]; +0090 if any(geneFoldChanges) +0091 geneFoldChanges(isnan(genePValues))=[]; +0092 end +0093 genePValues(isnan(genePValues))=[]; 0094 -0095 %This is to prevent errors if the p-values are exactly 0 or 1 -0096 geneZScores(geneZScores==-inf)=-15; -0097 geneZScores(geneZScores==inf)=15; +0095 %Convert p-values to Z-scores. Uses base MATLAB erfcinv instead of the +0096 %Statistics Toolbox norminv: -norminv(p) == sqrt(2)*erfcinv(2*p). +0097 geneZScores=sqrt(2)*erfcinv(2*genePValues); 0098 -0099 %For each metabolite, calculate the aggregate Z-score and keep track of the -0100 %number of neighbouring genes -0101 metZScores=nan(numel(model.mets),1); -0102 metNGenes=nan(numel(model.mets),1); -0103 meanZ=nan(numel(model.mets),1); -0104 stdZ=nan(numel(model.mets),1); -0105 %model.genes -> genes(filtered list) index, computed once rather than -0106 %re-scanning the whole "genes" list (via ismember) for every metabolite, -0107 %which is quadratic in a genome-scale model's mets x genes. -0108 [~,geneIdxInList]=ismember(model.genes,genes); -0109 for i=1:numel(model.mets) -0110 %Get the involved rxns -0111 I=model.S(i,:); -0112 -0113 %Get the involved genes -0114 [~, J]=find(model.rxnGeneMat(I~=0,:)); -0115 -0116 %Find the genes in the gene list -0117 K=unique(geneIdxInList(J)); -0118 K=K(K>0); -0119 -0120 %Calculate the aggregated Z-score for the metabolite -0121 if any(K) -0122 metZScores(i)=sum(geneZScores(K))/sqrt(numel(K)); -0123 meanZ(i)=mean(geneZScores(K)); -0124 stdZ(i)=std(geneZScores(K)); -0125 metNGenes(i)=numel(K); -0126 end -0127 end -0128 -0129 %Remove the metabolites which have no Z-scores -0130 mets=model.mets(~isnan(metZScores)); -0131 metNames=model.metNames(~isnan(metZScores)); -0132 metZScores(isnan(metZScores))=[]; -0133 metNGenes(isnan(metNGenes))=[]; -0134 meanZ(isnan(meanZ))=[]; -0135 stdZ(isnan(stdZ))=[]; -0136 -0137 %Correct for background using the closed-form moments of the sampling -0138 %distribution. For n genes drawn with replacement from the scored-gene pool -0139 %(mean mu, std sigma), the aggregate Z = sum(z)/sqrt(n) has: -0140 % E[Z] = sqrt(n)*mu and Std[Z] = sigma (independent of n) -0141 %so the corrected score is (metZ - sqrt(n)*mu) / sigma. -0142 mu = mean(geneZScores); -0143 sigma = std(geneZScores); -0144 metZScores = (metZScores - sqrt(metNGenes) .* mu) ./ sigma; -0145 -0146 %Calculate the P-values (upper-tail of the standard normal). Uses the base -0147 %MATLAB erfc so this does not depend on the Statistics Toolbox normcdf: -0148 %1 - normcdf(z) == 0.5*erfc(z/sqrt(2)). -0149 metPValues=0.5*erfc(metZScores/sqrt(2)); -0150 -0151 %Sort the results -0152 [metZScores, I]=sort(metZScores,'descend'); -0153 mets=mets(I); -0154 metNames=metNames(I); -0155 metPValues=metPValues(I); -0156 metNGenes=metNGenes(I); -0157 meanZ=meanZ(I); -0158 stdZ=stdZ(I); -0159 -0160 %Prepare the output -0161 repMets.mets=mets; -0162 repMets.metNames=metNames; -0163 repMets.metZScores=metZScores; -0164 repMets.metPValues=metPValues; -0165 repMets.metNGenes=metNGenes; -0166 repMets.meanZ=meanZ; -0167 repMets.stdZ=stdZ; -0168 -0169 %Call this function recursively if geneFoldChanges has been specified -0170 repMets(1).test='all'; -0171 if any(geneFoldChanges) -0172 repMets=[repMets;reporterMetabolites(model,genes(geneFoldChanges>=0),genePValues(geneFoldChanges>=0))]; -0173 repMets=[repMets;reporterMetabolites(model,genes(geneFoldChanges<0),genePValues(geneFoldChanges<0))]; -0174 repMets(2).test='only up'; -0175 repMets(3).test='only down'; -0176 end -0177 -0178 %This is for printing results to the screen. For printing a full list of -0179 %all scores, specify a output file -0180 %Escapes metabolite text before it is spliced into an fprintf template, -0181 %since a literal "%" in e.g. a metabolite name would otherwise be misread -0182 %as a format directive and swallow the remainder of the line. -0183 esc=@(s) strrep(s,'%','%%'); -0184 -0185 if printResults==true -0186 for i=1:numel(repMets) -0187 fprintf(['TOP 20 REPORTER METABOLITES\nTEST TYPE: ' repMets(i).test '\nID\tNAME\tP-VALUE\n']); -0188 for j=1:min(20,numel(repMets(i).mets)) -0189 fprintf([esc(repMets(i).mets{j}) '\t' esc(repMets(i).metNames{j}) '\t' num2str(repMets(i).metPValues(j)) '\n']); -0190 end -0191 fprintf('\n'); -0192 end -0193 end -0194 -0195 %Print results to file -0196 if any(outputFile) -0197 fid=fopen(outputFile,'w'); -0198 for i=1:numel(repMets) -0199 fprintf(fid,['REPORTER METABOLITES USING TEST TYPE: ' repMets(i).test '\n']); -0200 fprintf(fid,'ID\tNAME\tZ-SCORE\tP-VALUE\tNUMBER OF NEIGHBOURS\tAVERAGE Z-SCORE\tSTD Z-SCORE\n'); -0201 for j=1:numel(repMets(i).mets) -0202 fprintf(fid,[esc(repMets(i).mets{j}) '\t' esc(repMets(i).metNames{j}) '\t' num2str(repMets(i).metZScores(j)) '\t' num2str(repMets(i).metPValues(j)) '\t' num2str(repMets(i).metNGenes(j)) '\t' num2str(repMets(i).meanZ(j)) '\t' num2str(repMets(i).stdZ(j)) '\n']); -0203 end -0204 end -0205 fclose(fid); -0206 end -0207 end +0099 %This is to prevent errors if the p-values are exactly 0 or 1 +0100 geneZScores(geneZScores==-inf)=-15; +0101 geneZScores(geneZScores==inf)=15; +0102 +0103 %For each metabolite, calculate the aggregate Z-score and keep track of the +0104 %number of neighbouring genes +0105 metZScores=nan(numel(model.mets),1); +0106 metNGenes=nan(numel(model.mets),1); +0107 meanZ=nan(numel(model.mets),1); +0108 stdZ=nan(numel(model.mets),1); +0109 %model.genes -> genes(filtered list) index, computed once rather than +0110 %re-scanning the whole "genes" list (via ismember) for every metabolite, +0111 %which is quadratic in a genome-scale model's mets x genes. +0112 [~,geneIdxInList]=ismember(model.genes,genes); +0113 for i=1:numel(model.mets) +0114 %Get the involved rxns +0115 I=model.S(i,:); +0116 +0117 %Get the involved genes +0118 [~, J]=find(model.rxnGeneMat(I~=0,:)); +0119 +0120 %Find the genes in the gene list +0121 K=unique(geneIdxInList(J)); +0122 K=K(K>0); +0123 +0124 %Calculate the aggregated Z-score for the metabolite +0125 if any(K) +0126 metZScores(i)=sum(geneZScores(K))/sqrt(numel(K)); +0127 meanZ(i)=mean(geneZScores(K)); +0128 stdZ(i)=std(geneZScores(K)); +0129 metNGenes(i)=numel(K); +0130 end +0131 end +0132 +0133 %Remove the metabolites which have no Z-scores +0134 mets=model.mets(~isnan(metZScores)); +0135 metNames=model.metNames(~isnan(metZScores)); +0136 metZScores(isnan(metZScores))=[]; +0137 metNGenes(isnan(metNGenes))=[]; +0138 meanZ(isnan(meanZ))=[]; +0139 stdZ(isnan(stdZ))=[]; +0140 +0141 %Correct for background using the closed-form moments of the sampling +0142 %distribution. For n genes drawn with replacement from the scored-gene pool +0143 %(mean mu, population std sigma), the aggregate Z = sum(z)/sqrt(n) has: +0144 % E[Z] = sqrt(n)*mu and Std[Z] = sigma (independent of n) +0145 %so the corrected score is (metZ - sqrt(n)*mu) / sigma. +0146 mu = mean(geneZScores); +0147 sigma = std(geneZScores,1); +0148 metZScores = (metZScores - sqrt(metNGenes) .* mu) ./ sigma; +0149 +0150 %Calculate the P-values (upper-tail of the standard normal). Uses the base +0151 %MATLAB erfc so this does not depend on the Statistics Toolbox normcdf: +0152 %1 - normcdf(z) == 0.5*erfc(z/sqrt(2)). +0153 metPValues=0.5*erfc(metZScores/sqrt(2)); +0154 +0155 %Sort the results +0156 [metZScores, I]=sort(metZScores,'descend'); +0157 mets=mets(I); +0158 metNames=metNames(I); +0159 metPValues=metPValues(I); +0160 metNGenes=metNGenes(I); +0161 meanZ=meanZ(I); +0162 stdZ=stdZ(I); +0163 +0164 %Prepare the output +0165 repMets.mets=mets; +0166 repMets.metNames=metNames; +0167 repMets.metZScores=metZScores; +0168 repMets.metPValues=metPValues; +0169 repMets.metNGenes=metNGenes; +0170 repMets.meanZ=meanZ; +0171 repMets.stdZ=stdZ; +0172 +0173 %Call this function recursively if geneFoldChanges has been specified +0174 repMets(1).test='all'; +0175 if any(geneFoldChanges) +0176 repMets=[repMets;reporterMetabolites(model,genes(geneFoldChanges>=0),genePValues(geneFoldChanges>=0))]; +0177 repMets=[repMets;reporterMetabolites(model,genes(geneFoldChanges<0),genePValues(geneFoldChanges<0))]; +0178 repMets(2).test='only up'; +0179 repMets(3).test='only down'; +0180 end +0181 +0182 %This is for printing results to the screen. For printing a full list of +0183 %all scores, specify a output file +0184 %Escapes metabolite text before it is spliced into an fprintf template, +0185 %since a literal "%" in e.g. a metabolite name would otherwise be misread +0186 %as a format directive and swallow the remainder of the line. +0187 esc=@(s) strrep(s,'%','%%'); +0188 +0189 if printResults==true +0190 for i=1:numel(repMets) +0191 fprintf(['TOP 20 REPORTER METABOLITES\nTEST TYPE: ' repMets(i).test '\nID\tNAME\tP-VALUE\n']); +0192 for j=1:min(20,numel(repMets(i).mets)) +0193 fprintf([esc(repMets(i).mets{j}) '\t' esc(repMets(i).metNames{j}) '\t' num2str(repMets(i).metPValues(j)) '\n']); +0194 end +0195 fprintf('\n'); +0196 end +0197 end +0198 +0199 %Print results to file +0200 if any(outputFile) +0201 fid=fopen(outputFile,'w'); +0202 for i=1:numel(repMets) +0203 fprintf(fid,['REPORTER METABOLITES USING TEST TYPE: ' repMets(i).test '\n']); +0204 fprintf(fid,'ID\tNAME\tZ-SCORE\tP-VALUE\tNUMBER OF NEIGHBOURS\tAVERAGE Z-SCORE\tSTD Z-SCORE\n'); +0205 for j=1:numel(repMets(i).mets) +0206 fprintf(fid,[esc(repMets(i).mets{j}) '\t' esc(repMets(i).metNames{j}) '\t' num2str(repMets(i).metZScores(j)) '\t' num2str(repMets(i).metPValues(j)) '\t' num2str(repMets(i).metNGenes(j)) '\t' num2str(repMets(i).meanZ(j)) '\t' num2str(repMets(i).stdZ(j)) '\n']); +0207 end +0208 end +0209 fclose(fid); +0210 end +0211 end
      Generated by m2html © 2005
      \ No newline at end of file diff --git a/docs/html/analysis/sampleChebyshevCenter.html b/docs/html/analysis/sampleChebyshevCenter.html index f37ba7f7..3b69e197 100644 --- a/docs/html/analysis/sampleChebyshevCenter.html +++ b/docs/html/analysis/sampleChebyshevCenter.html @@ -90,7 +90,7 @@

      SOURCE CODE ^% Variables [x (n, free); r (1, >=0)]; minimise -r (maximises the ball radius). -0029 prob.A = [A, norms]; +0029 prob.A = sparse([A, norms]); % Gurobi and SCIP require a sparse A 0030 prob.a = prob.A; 0031 prob.b = b; 0032 prob.csense = repmat('L', 1, m); diff --git a/docs/html/annotation/assignSBOterms.html b/docs/html/annotation/assignSBOterms.html index bac884a1..f15081a2 100644 --- a/docs/html/annotation/assignSBOterms.html +++ b/docs/html/annotation/assignSBOterms.html @@ -34,7 +34,10 @@

      DESCRIPTION ^SOURCE CODE ^% organism-agnostic, parameterised entirely by opts. 0007 % 0008 % SBO is written via editMiriam(..., 'fill') so pre-existing SBO -0009 % annotations are preserved. -0010 % -0011 % Parameters -0012 % ---------- -0013 % model : struct -0014 % RAVEN model struct. -0015 % -0016 % Name-Value Arguments -0017 % -------------------- -0018 % opts : struct -0019 % Struct with any of the following fields. Missing fields take the -0020 % defaults shown: -0021 % -0022 % - biomassMetNames : {'biomass','DNA','RNA','protein','carbohydrate', -0023 % 'lipid','cofactor','ion'} -0024 % - biomassMetSuffixes : {' backbone',' chain'} -0025 % - biomassRxnName : 'biomass pseudoreaction' -0026 % - ngamRxnName : 'non-growth associated maintenance reaction' -0027 % - pseudoreactionSubstrings : {'pseudoreaction','SLIME rxn'} -0028 % - onlyLastReactionForPseudo : false. yeast-GEM bug-compat flag that -0029 % replicates the legacy `for i=numel(...)` typo (pseudoreaction SBOs -0030 % applied only to the last reaction). Off by default; turn ON for -0031 % byte-equivalent yeast-GEM output. -0032 % -0033 % Returns -0034 % ------- -0035 % model : struct -0036 % Modified model. -0037 % -0038 % Examples -0039 % -------- -0040 % model = assignSBOterms(model); -0041 % model = assignSBOterms(model, struct('onlyLastReactionForPseudo', true)); -0042 % -0043 % Notes -0044 % ----- -0045 % Metabolites: -0046 % -0047 % SBO:0000649 (Biomass) when met.name is in opts.biomassMetNames, or -0048 % ends with any of opts.biomassMetSuffixes. Otherwise SBO:0000247 -0049 % (Simple chemical). -0050 % -0051 % Reactions (default → override → pseudoreaction override): -0052 % -0053 % SBO:0000176 (Metabolic reaction) default. -0054 % Single-reactant reactions become: -0055 % SBO:0000627 (exchange) if the lone metabolite is extracellular -0056 % (compartment 'e' or compartment name containing -0057 % 'extracellular'), -0058 % SBO:0000632 (sink) if coef < 0, -0059 % SBO:0000628 (demand) otherwise. -0060 % Transport reactions (detected by opts.transportDetector or the -0061 % default heuristic: same metName in ≥ 2 compartments in a single -0062 % reaction) → SBO:0000655. -0063 % Reactions whose name matches opts.biomassRxnName → SBO:0000629. -0064 % Reactions whose name matches opts.ngamRxnName → SBO:0000630. -0065 % Reactions whose name contains any of opts.pseudoreactionSubstrings -0066 % → SBO:0000395. -0067 -0068 p=parseRAVENargs(varargin, {'opts',[]}); -0069 opts=p.opts; -0070 if isempty(opts) -0071 opts = struct(); -0072 end -0073 opts = applyDefaults(opts); -0074 -0075 % Metabolite SBO ------------------------------------------------------ -0076 metsSBO = cell(size(model.mets)); -0077 for i = 1:length(model.mets) -0078 metName = model.metNames{i}; -0079 if any(strcmp(opts.biomassMetNames, metName)) || endsWithAny(metName, opts.biomassMetSuffixes) -0080 metsSBO{i} = 'SBO:0000649'; -0081 else -0082 metsSBO{i} = 'SBO:0000247'; -0083 end -0084 end -0085 -0086 % Reaction SBO -------------------------------------------------------- -0087 rxnSBO = cell(size(model.rxns)); -0088 rxnSBO(:) = {'SBO:0000176'}; -0089 -0090 % Single-reactant reactions -0091 reactantNumber = sum(model.S ~= 0, 1); -0092 singleRxns = find(reactantNumber == 1); -0093 for k = 1:numel(singleRxns) -0094 idx = singleRxns(k); -0095 metRow = find(model.S(:, idx)); -0096 compName = model.compNames{model.metComps(metRow)}; -0097 compShort = model.comps{model.metComps(metRow)}; -0098 if strcmp(compShort, 'e') || strcmp(compName, 'extracellular') -0099 rxnSBO{idx} = 'SBO:0000627'; -0100 elseif sum(model.S(:, idx)) < 0 -0101 rxnSBO{idx} = 'SBO:0000632'; -0102 else -0103 rxnSBO{idx} = 'SBO:0000628'; -0104 end -0105 end -0106 -0107 % Transport reactions -0108 if isfield(opts, 'transportRxnIdxs') && ~isempty(opts.transportRxnIdxs) -0109 transportIdxs = opts.transportRxnIdxs; -0110 else -0111 transportIdxs = getTransportRxns(model); -0112 end -0113 rxnSBO(transportIdxs) = {'SBO:0000655'}; -0114 -0115 % Pseudoreaction overrides -0116 if opts.onlyLastReactionForPseudo -0117 pseudoTargets = numel(model.rxns); -0118 else -0119 pseudoTargets = 1:numel(model.rxns); -0120 end -0121 for ii = pseudoTargets -0122 name = model.rxnNames{ii}; -0123 if strcmp(name, opts.biomassRxnName) -0124 rxnSBO{ii} = 'SBO:0000629'; -0125 elseif strcmp(name, opts.ngamRxnName) -0126 rxnSBO{ii} = 'SBO:0000630'; -0127 else -0128 for k = 1:numel(opts.pseudoreactionSubstrings) -0129 if contains(name, opts.pseudoreactionSubstrings{k}) -0130 rxnSBO{ii} = 'SBO:0000395'; -0131 break; -0132 end -0133 end -0134 end -0135 end -0136 -0137 model = editMiriam(model, 'met', 'all', 'sbo', metsSBO, 'fill'); -0138 model = editMiriam(model, 'rxn', 'all', 'sbo', rxnSBO, 'fill'); -0139 end -0140 -0141 -0142 function opts = applyDefaults(opts) -0143 defaults = struct( ... -0144 'biomassMetNames', {{'biomass','DNA','RNA','protein','carbohydrate','lipid','cofactor','ion'}}, ... -0145 'biomassMetSuffixes', {{' backbone',' chain'}}, ... -0146 'biomassRxnName', 'biomass pseudoreaction', ... -0147 'ngamRxnName', 'non-growth associated maintenance reaction', ... -0148 'pseudoreactionSubstrings', {{'pseudoreaction','SLIME rxn'}}, ... -0149 'onlyLastReactionForPseudo', false); -0150 fields = fieldnames(defaults); -0151 for k = 1:numel(fields) -0152 f = fields{k}; -0153 if ~isfield(opts, f) || isempty(opts.(f)) -0154 opts.(f) = defaults.(f); -0155 end -0156 end -0157 end -0158 -0159 -0160 function tf = endsWithAny(s, suffixes) -0161 tf = false; -0162 for i = 1:numel(suffixes) -0163 if endsWith(s, suffixes{i}) -0164 tf = true; -0165 return; -0166 end -0167 end -0168 end +0009 % annotations are preserved. The terms are stored in model.metMiriams and +0010 % model.rxnMiriams under the namespace 'sbo' (there is no separate SBO +0011 % field); read them with extractMiriam(model.rxnMiriams, 'sbo'). +0012 % exportModel writes them as the SBML sboTerm attribute. +0013 % +0014 % Parameters +0015 % ---------- +0016 % model : struct +0017 % RAVEN model struct. +0018 % +0019 % Name-Value Arguments +0020 % -------------------- +0021 % opts : struct +0022 % Struct with any of the following fields. Missing fields take the +0023 % defaults shown: +0024 % +0025 % - biomassMetNames : {'biomass','DNA','RNA','protein','carbohydrate', +0026 % 'lipid','cofactor','ion'} +0027 % - biomassMetSuffixes : {' backbone',' chain'} +0028 % - biomassRxnName : 'biomass pseudoreaction' +0029 % - ngamRxnName : 'non-growth associated maintenance reaction' +0030 % - pseudoreactionSubstrings : {'pseudoreaction','SLIME rxn'} +0031 % - onlyLastReactionForPseudo : false. yeast-GEM bug-compat flag that +0032 % replicates the legacy `for i=numel(...)` typo (pseudoreaction SBOs +0033 % applied only to the last reaction). Off by default; turn ON for +0034 % byte-equivalent yeast-GEM output. +0035 % +0036 % Returns +0037 % ------- +0038 % model : struct +0039 % Modified model. +0040 % +0041 % Examples +0042 % -------- +0043 % model = assignSBOterms(model); +0044 % model = assignSBOterms(model, struct('onlyLastReactionForPseudo', true)); +0045 % +0046 % Notes +0047 % ----- +0048 % Metabolites: +0049 % +0050 % SBO:0000649 (Biomass) when met.name is in opts.biomassMetNames, or +0051 % ends with any of opts.biomassMetSuffixes. Otherwise SBO:0000247 +0052 % (Simple chemical). +0053 % +0054 % Reactions (default → override → pseudoreaction override): +0055 % +0056 % SBO:0000176 (Metabolic reaction) default. +0057 % Single-reactant reactions become: +0058 % SBO:0000627 (exchange) if the lone metabolite is extracellular +0059 % (compartment 'e' or compartment name containing +0060 % 'extracellular'), +0061 % SBO:0000632 (sink) if coef < 0, +0062 % SBO:0000628 (demand) otherwise. +0063 % Transport reactions (detected by opts.transportDetector or the +0064 % default heuristic: same metName in ≥ 2 compartments in a single +0065 % reaction) → SBO:0000655. +0066 % Reactions whose name matches opts.biomassRxnName → SBO:0000629. +0067 % Reactions whose name matches opts.ngamRxnName → SBO:0000630. +0068 % Reactions whose name contains any of opts.pseudoreactionSubstrings +0069 % → SBO:0000395. +0070 +0071 p=parseRAVENargs(varargin, {'opts',[]}); +0072 opts=p.opts; +0073 if isempty(opts) +0074 opts = struct(); +0075 end +0076 opts = applyDefaults(opts); +0077 +0078 % Metabolite SBO ------------------------------------------------------ +0079 metsSBO = cell(size(model.mets)); +0080 for i = 1:length(model.mets) +0081 metName = model.metNames{i}; +0082 if any(strcmp(opts.biomassMetNames, metName)) || endsWithAny(metName, opts.biomassMetSuffixes) +0083 metsSBO{i} = 'SBO:0000649'; +0084 else +0085 metsSBO{i} = 'SBO:0000247'; +0086 end +0087 end +0088 +0089 % Reaction SBO -------------------------------------------------------- +0090 rxnSBO = cell(size(model.rxns)); +0091 rxnSBO(:) = {'SBO:0000176'}; +0092 +0093 % Single-reactant reactions +0094 reactantNumber = sum(model.S ~= 0, 1); +0095 singleRxns = find(reactantNumber == 1); +0096 for k = 1:numel(singleRxns) +0097 idx = singleRxns(k); +0098 metRow = find(model.S(:, idx)); +0099 compName = model.compNames{model.metComps(metRow)}; +0100 compShort = model.comps{model.metComps(metRow)}; +0101 if strcmp(compShort, 'e') || strcmp(compName, 'extracellular') +0102 rxnSBO{idx} = 'SBO:0000627'; +0103 elseif sum(model.S(:, idx)) < 0 +0104 rxnSBO{idx} = 'SBO:0000632'; +0105 else +0106 rxnSBO{idx} = 'SBO:0000628'; +0107 end +0108 end +0109 +0110 % Transport reactions +0111 if isfield(opts, 'transportRxnIdxs') && ~isempty(opts.transportRxnIdxs) +0112 transportIdxs = opts.transportRxnIdxs; +0113 else +0114 transportIdxs = getTransportRxns(model); +0115 end +0116 rxnSBO(transportIdxs) = {'SBO:0000655'}; +0117 +0118 % Pseudoreaction overrides +0119 if opts.onlyLastReactionForPseudo +0120 pseudoTargets = numel(model.rxns); +0121 else +0122 pseudoTargets = 1:numel(model.rxns); +0123 end +0124 for ii = pseudoTargets +0125 name = model.rxnNames{ii}; +0126 if strcmp(name, opts.biomassRxnName) +0127 rxnSBO{ii} = 'SBO:0000629'; +0128 elseif strcmp(name, opts.ngamRxnName) +0129 rxnSBO{ii} = 'SBO:0000630'; +0130 else +0131 for k = 1:numel(opts.pseudoreactionSubstrings) +0132 if contains(name, opts.pseudoreactionSubstrings{k}) +0133 rxnSBO{ii} = 'SBO:0000395'; +0134 break; +0135 end +0136 end +0137 end +0138 end +0139 +0140 model = editMiriam(model, 'met', 'all', 'sbo', metsSBO, 'fill'); +0141 model = editMiriam(model, 'rxn', 'all', 'sbo', rxnSBO, 'fill'); +0142 end +0143 +0144 +0145 function opts = applyDefaults(opts) +0146 defaults = struct( ... +0147 'biomassMetNames', {{'biomass','DNA','RNA','protein','carbohydrate','lipid','cofactor','ion'}}, ... +0148 'biomassMetSuffixes', {{' backbone',' chain'}}, ... +0149 'biomassRxnName', 'biomass pseudoreaction', ... +0150 'ngamRxnName', 'non-growth associated maintenance reaction', ... +0151 'pseudoreactionSubstrings', {{'pseudoreaction','SLIME rxn'}}, ... +0152 'onlyLastReactionForPseudo', false); +0153 fields = fieldnames(defaults); +0154 for k = 1:numel(fields) +0155 f = fields{k}; +0156 if ~isfield(opts, f) || isempty(opts.(f)) +0157 opts.(f) = defaults.(f); +0158 end +0159 end +0160 end +0161 +0162 +0163 function tf = endsWithAny(s, suffixes) +0164 tf = false; +0165 for i = 1:numel(suffixes) +0166 if endsWith(s, suffixes{i}) +0167 tf = true; +0168 return; +0169 end +0170 end +0171 end
      Generated by m2html © 2005
      \ No newline at end of file diff --git a/docs/html/biomass/scaleBiomassFraction.html b/docs/html/biomass/scaleBiomassFraction.html index b5caf640..64b26094 100644 --- a/docs/html/biomass/scaleBiomassFraction.html +++ b/docs/html/biomass/scaleBiomassFraction.html @@ -30,7 +30,8 @@

      DESCRIPTION ^
       scaleBiomassFraction  Rescale a biomass component to a target value.
       
        Rescale a biomass component to a target g/gDW value, optionally
      - balancing a second component so the total biomass mass stays at 1 g/gDW.
      + rescaling a second component so that the biomass total becomes 1 g/gDW,
      + whatever the total was before.
        The MATLAB counterpart of yeast-GEM's legacy scaleBioMass.
       
        Parameters
      @@ -47,8 +48,10 @@ 

      DESCRIPTION ^SOURCE CODE ^% scaleBiomassFraction Rescale a biomass component to a target value. 0003 % 0004 % Rescale a biomass component to a target g/gDW value, optionally -0005 % balancing a second component so the total biomass mass stays at 1 g/gDW. -0006 % The MATLAB counterpart of yeast-GEM's legacy scaleBioMass. -0007 % -0008 % Parameters -0009 % ---------- -0010 % model : struct -0011 % RAVEN model struct. -0012 % biomassConfig : struct -0013 % Struct (see getBiomassFractions). -0014 % componentName : char -0015 % Component to rescale. -0016 % newValue : double -0017 % Target fraction in g/gDW. -0018 % -0019 % Name-Value Arguments -0020 % -------------------- -0021 % balanceOut : char -0022 % Second component name to adjust so the biomass total remains 1 -0023 % g/gDW. Empty / omit to skip balancing. -0024 % -0025 % Returns -0026 % ------- -0027 % model : struct -0028 % Modified model. -0029 % -0030 % Examples -0031 % -------- -0032 % model = scaleBiomassFraction(model, biomassConfig, 'protein', 0.5, 'carbohydrate'); -0033 % -0034 % See also -0035 % -------- -0036 % getBiomassFractions -0037 -0038 p=parseRAVENargs(varargin, {'balanceOut',''}); -0039 balanceOut=p.balanceOut; +0005 % rescaling a second component so that the biomass total becomes 1 g/gDW, +0006 % whatever the total was before. +0007 % The MATLAB counterpart of yeast-GEM's legacy scaleBioMass. +0008 % +0009 % Parameters +0010 % ---------- +0011 % model : struct +0012 % RAVEN model struct. +0013 % biomassConfig : struct +0014 % Struct (see getBiomassFractions). +0015 % componentName : char +0016 % Component to rescale. +0017 % newValue : double +0018 % Target fraction in g/gDW. +0019 % +0020 % Name-Value Arguments +0021 % -------------------- +0022 % balanceOut : char +0023 % Second component name to adjust so the biomass total becomes 1 +0024 % g/gDW. It must be large enough to absorb the change: an error is +0025 % raised if its fraction would become zero or negative, or if it is +0026 % 0 to begin with. Empty / omit to skip balancing. +0027 % +0028 % Returns +0029 % ------- +0030 % model : struct +0031 % Modified model. +0032 % +0033 % Examples +0034 % -------- +0035 % model = scaleBiomassFraction(model, biomassConfig, 'protein', 0.5, 'carbohydrate'); +0036 % +0037 % See also +0038 % -------- +0039 % getBiomassFractions 0040 -0041 fractions = getBiomassFractions(model, biomassConfig); -0042 if ~isfield(fractions, componentName) -0043 error('scaleBiomassFraction:unknownComponent', ... -0044 'biomassConfig has no component named %s', componentName); -0045 end -0046 current = fractions.(componentName); -0047 if current == 0 -0048 error('scaleBiomassFraction:zeroCurrent', ... -0049 ['Cannot scale %s to %g: current fraction is 0 ' ... -0050 '(pseudoreaction missing or empty).'], componentName, newValue); -0051 end -0052 factor = newValue / current; -0053 model = scaleBiomassPseudoreaction(model, biomassConfig, componentName, factor); -0054 -0055 if ~isempty(balanceOut) -0056 fractions = getBiomassFractions(model, biomassConfig); -0057 total = fractions.total; -0058 balanceCurrent = fractions.(balanceOut); -0059 if balanceCurrent == 0 -0060 return; -0061 end -0062 balanceFactor = (balanceCurrent + (1 - total)) / balanceCurrent; -0063 model = scaleBiomassPseudoreaction(model, biomassConfig, balanceOut, balanceFactor); -0064 end -0065 end

      +0041 p=parseRAVENargs(varargin, {'balanceOut',''}); +0042 balanceOut=p.balanceOut; +0043 +0044 fractions = getBiomassFractions(model, biomassConfig); +0045 if ~isfield(fractions, componentName) +0046 error('scaleBiomassFraction:unknownComponent', ... +0047 'biomassConfig has no component named %s', componentName); +0048 end +0049 current = fractions.(componentName); +0050 if current == 0 +0051 error('scaleBiomassFraction:zeroCurrent', ... +0052 ['Cannot scale %s to %g: current fraction is 0 ' ... +0053 '(pseudoreaction missing or empty).'], componentName, newValue); +0054 end +0055 factor = newValue / current; +0056 model = scaleBiomassPseudoreaction(model, biomassConfig, componentName, factor); +0057 +0058 if ~isempty(balanceOut) +0059 fractions = getBiomassFractions(model, biomassConfig); +0060 total = fractions.total; +0061 if ~isfield(fractions, balanceOut) +0062 error('scaleBiomassFraction:unknownComponent', ... +0063 'biomassConfig has no component named %s', balanceOut); +0064 end +0065 balanceCurrent = fractions.(balanceOut); +0066 if balanceCurrent == 0 +0067 error('scaleBiomassFraction:zeroCurrent', ... +0068 ['Cannot balance with %s: current fraction is 0 ' ... +0069 '(pseudoreaction missing or empty).'], balanceOut); +0070 end +0071 balanceNew = balanceCurrent + (1 - total); +0072 if balanceNew <= 0 +0073 error('scaleBiomassFraction:negativeBalance', ... +0074 ['Cannot balance with %s: its fraction would become %g g/gDW. ' ... +0075 'It is %g g/gDW, but the other components total %g g/gDW.'], ... +0076 balanceOut, balanceNew, balanceCurrent, total - balanceCurrent); +0077 end +0078 balanceFactor = balanceNew / balanceCurrent; +0079 model = scaleBiomassPseudoreaction(model, biomassConfig, balanceOut, balanceFactor); +0080 end +0081 end
      Generated by m2html © 2005
      \ No newline at end of file diff --git a/docs/html/curation/curateModelFromTables.html b/docs/html/curation/curateModelFromTables.html index 3e0625fb..abf33502 100644 --- a/docs/html/curation/curateModelFromTables.html +++ b/docs/html/curation/curateModelFromTables.html @@ -40,28 +40,39 @@

      DESCRIPTION ^DESCRIPTION ^CROSS-REFERENCE INFORMATION ^

      @@ -97,7 +107,7 @@

      CROSS-REFERENCE INFORMATION ^
 
 <h2><a name=SUBFUNCTIONS ^

      +
    • function [header,data] = readCurationTable(fileName)
    • function [col,miriamCols] = matchColumns(header,fileName,required,spec)
    • function model = writeExistingField(model,field,modelIdx,values)
    • function newModel = extractAndAddMiriam(model,miriamName,miriamData,inputIndex,modelIndex,type)

    SOURCE CODE ^

    0001 function newModel=curateModelFromTables(model,metsInfo,varargin)
    @@ -114,355 +124,416 @@ 

    SOURCE CODE ^% This function can therefore be used to add new entities in the model, or 0013 % curate those already existing in the model. 0014 % -0015 % Parameters -0016 % ---------- -0017 % model : struct -0018 % RAVEN model structure to be curated. -0019 % metsInfo : char -0020 % Path to a *.tsv file with metabolite information, or 'none' to skip -0021 % metabolite curation. Columns: metNames, comps, formula, charge, -0022 % inchi, metNotes, then any number of MIRIAM-namespace columns. -0023 % -0024 % Name-Value Arguments -0025 % -------------------- -0026 % genesInfo : char -0027 % Path to a *.tsv file with gene information, or 'none'. Columns: -0028 % genes, geneShortNames, then MIRIAM. -0029 % rxnsCoeffs : char -0030 % Path to a *.tsv file with reaction stoichiometric coefficients, or -0031 % 'none'. Columns: rxnIdx, rxnNames, metNames, comps, coefficient. One -0032 % row per (reaction, metabolite) pair. -0033 % rxnsInfo : char -0034 % Path to a *.tsv file with reaction information, or 'none'. Columns: -0035 % rxnIdx, rxnNames, grRules, lb, ub, rev, subSystems, eccodes, -0036 % rxnNotes, rxnReferences, rxnConfidenceScores, then MIRIAM. -0037 % metPrefix : char -0038 % Prefix used to mint fresh metabolite ids (e.g. 's_' for yeast-GEM, -0039 % 'M_' for the BiGG default) (default 'M_'). -0040 % rxnPrefix : char -0041 % Prefix used to mint fresh reaction ids (default 'R_'). -0042 % -0043 % Returns -0044 % ------- -0045 % newModel : struct -0046 % Curated RAVEN model structure. -0047 % -0048 % Examples -0049 % -------- -0050 % newModel = curateModelFromTables(model, metsInfo, genesInfo, ... -0051 % rxnsCoeffs, rxnsInfo, metPrefix, rxnPrefix); -0052 % -0053 % Notes -0054 % ----- -0055 % The 'everything after the core columns is MIRIAM' convention applies to -0056 % all three info tables: any column whose header is not one of the listed -0057 % core fields is treated as a MIRIAM annotation namespace and stored on -0058 % the matching entity. -0059 -0060 p=parseRAVENargs(varargin, {'genesInfo','none'; 'rxnsCoeffs','none'; 'rxnsInfo','none'; 'metPrefix',[]; 'rxnPrefix',[]}); -0061 genesInfo=p.genesInfo; -0062 rxnsCoeffs=p.rxnsCoeffs; -0063 rxnsInfo=p.rxnsInfo; -0064 metPrefix=p.metPrefix; -0065 if isempty(metPrefix) -0066 metPrefix = 'M_'; -0067 end -0068 rxnPrefix=p.rxnPrefix; -0069 if isempty(rxnPrefix) -0070 rxnPrefix = 'R_'; -0071 end -0072 if xor(strcmp(rxnsCoeffs,'none'), strcmp(rxnsInfo,'none')) -0073 error('Provide both a ''rxnsInfo'' and a ''rxnsCoeffs'' file') -0074 end -0075 newModel=model; -0076 -0077 %% Metabolites -0078 if ~strcmp(metsInfo,'none') -0079 fid = fopen(metsInfo); -0080 raw = textscan(fid,['%q' repmat(' %q',1,17)],'Delimiter','\t'); -0081 fclose(fid); -0082 metsToAdd.metNames = raw{1}(2:end); -0083 metsToAdd.compartments = raw{2}(2:end); -0084 metsToAdd.metFormulas = raw{3}(2:end); -0085 metsToAdd.metCharges = cellfun(@str2num,raw{4}(2:end),'UniformOutput',false); -0086 emptyEntry = cellfun(@isempty,metsToAdd.metCharges); -0087 if all(emptyEntry) -0088 metsToAdd = rmfield(metsToAdd,'metCharges'); -0089 elseif any(emptyEntry) % If some charges are given, assume 0 for those without charges specified -0090 metsToAdd.metCharges(emptyEntry) = {0}; -0091 metsToAdd.metCharges = cell2mat(metsToAdd.metCharges); -0092 else -0093 metsToAdd.metCharges = cell2mat(metsToAdd.metCharges); -0094 end -0095 metsToAdd.inchis = raw{5}(2:end); -0096 metsToAdd.metNotes = raw{6}(2:end); -0097 -0098 % Check if metabolite already exists (check by metName[comp]) -0099 existingMets = []; -0100 existingMetsIdx = []; -0101 newMets = []; -0102 for i=1:numel(metsToAdd.metNames) -0103 metIdx = find(strcmp(newModel.metNames,metsToAdd.metNames{i})); -0104 existMet = strcmp(metsToAdd.compartments{i},newModel.comps(newModel.metComps(metIdx))); -0105 if any(existMet) -0106 existingMets = [existingMets, i]; -0107 existingMetsIdx = [existingMetsIdx, metIdx(existMet)]; -0108 else -0109 newMets = [newMets, i]; -0110 end -0111 end -0112 -0113 % Overwrite annotation of existing entries -0114 if any(existingMets) -0115 if isfield(newModel,'metFormulas') -0116 newModel.metFormulas(existingMetsIdx) = metsToAdd.metFormulas(existingMets); -0117 end -0118 if isfield(newModel,'metCharges') && isfield(metsToAdd,'metCharges') -0119 newModel.metCharges(existingMetsIdx) = metsToAdd.metCharges(existingMets); -0120 end -0121 if isfield(newModel,'inchis') -0122 newModel.inchis(existingMetsIdx) = metsToAdd.inchis(existingMets); -0123 end -0124 newModel = extractAndAddMiriam(newModel,raw(7:end),existingMets,existingMetsIdx,'met'); -0125 warning(['The following metabolites are already present in the model, '... -0126 'their annotation will be overwritten to match the metsInfo file. '... -0127 'If you do not particularly want to curate their annotations, it '... -0128 'would be better to removes these metabolites from metsInfo:\n\t\t%s'],... -0129 strjoin(strcat(metsToAdd.metNames(existingMets),'[',metsToAdd.compartments(existingMets),']'),'\n\t\t')); -0130 end -0131 -0132 % Continue with new metabolites -0133 if numel(newMets)>0 -0134 metsToAdd.metNames(existingMets) = []; -0135 metsToAdd.compartments(existingMets) = []; -0136 metsToAdd.metFormulas(existingMets) = []; -0137 if all(cellfun(@isempty,metsToAdd.metFormulas)) -0138 metsToAdd = rmfield(metsToAdd,'metFormulas'); -0139 end -0140 if isfield(metsToAdd,'metCharges') -0141 metsToAdd.metCharges(existingMets) = []; -0142 end -0143 metsToAdd.inchis(existingMets) = []; -0144 if all(cellfun(@isempty,metsToAdd.inchis)) -0145 metsToAdd = rmfield(metsToAdd,'inchis'); -0146 end -0147 metsToAdd.metNotes(existingMets) = []; -0148 if all(cellfun(@isempty,metsToAdd.metNotes)) -0149 metsToAdd = rmfield(metsToAdd,'metNotes'); -0150 end -0151 % Add metabolites -0152 newModel = addMets(newModel,metsToAdd,true,metPrefix); -0153 addedIdx = numel(newModel.mets)-numel(newMets)+1:numel(newModel.mets); -0154 newModel = extractAndAddMiriam(newModel,raw(7:end),newMets,addedIdx,'met'); -0155 end -0156 end -0157 %% Genes -0158 if ~strcmp(genesInfo,'none') -0159 % Gather all data, first about reaction stoichiometries... -0160 fid = fopen(genesInfo); -0161 raw = textscan(fid,['%q' repmat(' %q',1,10)],'Delimiter','\t'); -0162 fclose(fid); -0163 genesToAdd.genes = raw{1}(2:end); -0164 genesToAdd.geneShortNames = raw{2}(2:end); -0165 -0166 [~,notNewGene,existingGene] = intersect(genesToAdd.genes,newModel.genes); -0167 if ~isempty(notNewGene) -0168 warning(['The following genes are already present in the model, their annotation '... -0169 'will be overwritten to match the genesInfo file: \n\t\t%s'],... -0170 strjoin(genesToAdd.genes(notNewGene),'\n\t\t')); -0171 if isfield(newModel,'geneShortNames') -0172 newModel.geneShortNames(existingGene) = genesToAdd.geneShortNames(notNewGene); -0173 end -0174 newModel = extractAndAddMiriam(newModel,raw(3:end),notNewGene,existingGene,'gene'); -0175 end -0176 -0177 % Continue with new genes -0178 toAdd = 1:numel(genesToAdd.genes); -0179 toAdd(notNewGene) = []; -0180 if ~isempty(toAdd) -0181 genesToAdd.genes = genesToAdd.genes(toAdd); -0182 genesToAdd.geneShortNames = genesToAdd.geneShortNames(toAdd); -0183 if all(cellfun(@isempty,genesToAdd.geneShortNames)) -0184 genesToAdd = rmfield(genesToAdd,'geneShortNames'); -0185 end -0186 % Add genes -0187 newModel = addGenesRaven(newModel,genesToAdd); -0188 addedIdx = numel(newModel.genes)-numel(toAdd)+1:numel(newModel.genes); -0189 newModel = extractAndAddMiriam(newModel,raw(3:end),toAdd,addedIdx,'gene'); -0190 end -0191 end -0192 %% Reactions -0193 if ~any(strcmp({rxnsCoeffs,rxnsInfo},'none')) -0194 % Gather all data, first about reaction stoichiometries... -0195 fid = fopen(rxnsCoeffs); -0196 raw = textscan(fid,'%q %q %q %q %f','Delimiter','\t','HeaderLines',1); -0197 fclose(fid); -0198 rxnCheck.coeffsIdx = str2double(raw{1}); -0199 rxns.rxnNames = raw{2}; -0200 rxns.metNames = raw{3}; -0201 rxns.comps = raw{4}; -0202 rxns.coeffs = raw{5}; -0203 rxnCheck.coeffs = strcat(raw{1},'***',rxns.rxnNames); -0204 -0205 % ... and then additional reaction-specific data. -0206 fid = fopen(rxnsInfo); -0207 raw = textscan(fid,['%q' repmat(' %q',1,20)],'Delimiter','\t'); -0208 fclose(fid); -0209 rxnCheck.rxnsIdx = str2double(raw{1}(2:end)); -0210 rxnCheck.rxns = strcat(raw{1}(2:end),'***',raw{2}(2:end)); -0211 -0212 notMatching = setxor(rxnCheck.coeffs,rxnCheck.rxns); -0213 if numel(notMatching)>1 -0214 error(['The following reactions ánd/or their indices are not matched '... -0215 'between the rxnsInfo and rxnsCoeffs files:\n\t\t%s'],... -0216 strjoin(regexprep(notMatching,'^\d+***',''),'\n\t\t')) -0217 end -0218 -0219 rxnsToAdd.rxnNames = raw{2}(2:end); -0220 rxnsToAdd.grRules = raw{3}(2:end); -0221 rxnsToAdd.lb = cellfun(@str2num,raw{4}(2:end)); -0222 rxnsToAdd.ub = cellfun(@str2num,raw{5}(2:end)); -0223 rxnsToAdd.rev = cellfun(@str2num,raw{6}(2:end)); -0224 rxnsToAdd.subSystems = raw{7}(2:end); -0225 rxnsToAdd.eccodes = raw{8}(2:end); -0226 rxnsToAdd.rxnNotes = raw{9}(2:end); -0227 rxnsToAdd.rxnReferences = raw{10}(2:end); -0228 rxnsToAdd.rxnConfidenceScores = cellfun(@str2num,raw{11}(2:end),'UniformOutput',false); -0229 emptyEntry = cellfun(@isempty,rxnsToAdd.rxnConfidenceScores); -0230 if all(emptyEntry) -0231 rxnsToAdd = rmfield(rxnsToAdd,'rxnConfidenceScores'); -0232 elseif any(emptyEntry) % If some rxnConfidenceScores are given, assume 0 for those without rxnConfidenceScores specified -0233 rxnsToAdd.rxnConfidenceScores(emptyEntry) = {0}; -0234 rxnsToAdd.rxnConfidenceScores = cell2mat(rxnsToAdd.rxnConfidenceScores); -0235 else -0236 rxnsToAdd.rxnConfidenceScores = cell2mat(rxnsToAdd.rxnConfidenceScores); +0015 % Columns are identified by their header (case-insensitive), so their order +0016 % does not matter and optional columns can be left out. Every field that a +0017 % table supplies is written to new and existing entities alike; a field the +0018 % model does not have yet is created, with its default value for the other +0019 % entities, unless all supplied values for it are empty. An empty grRules +0020 % cell leaves the rule of an existing reaction unchanged. +0021 % +0022 % Parameters +0023 % ---------- +0024 % model : struct +0025 % RAVEN model structure to be curated. +0026 % metsInfo : char +0027 % Path to a *.tsv file with metabolite information, or 'none' to skip +0028 % metabolite curation. Columns: metNames, comps (both required), +0029 % formula, charge, inchi, metNotes, and any number of MIRIAM-namespace +0030 % columns. Singular forms and the RAVEN field names (metFormulas, +0031 % metCharges, inchis) are accepted as headers too. +0032 % +0033 % Name-Value Arguments +0034 % -------------------- +0035 % genesInfo : char +0036 % Path to a *.tsv file with gene information, or 'none'. Columns: +0037 % genes (required), geneShortNames (or geneShortName), and MIRIAM. +0038 % rxnsCoeffs : char +0039 % Path to a *.tsv file with reaction stoichiometric coefficients, or +0040 % 'none'. Columns (all required): rxnIdx (or index), rxnNames, +0041 % metNames, comps, coefficient. One row per (reaction, metabolite) +0042 % pair. +0043 % rxnsInfo : char +0044 % Path to a *.tsv file with reaction information, or 'none'. Columns: +0045 % rxnIdx (or index), rxnNames (both required), grRules, lb, ub, rev, +0046 % subSystems, eccodes, rxnNotes, rxnReferences, rxnConfidenceScores, +0047 % and MIRIAM. +0048 % metPrefix : char +0049 % Prefix used to mint fresh metabolite ids (e.g. 's_' for yeast-GEM, +0050 % 'M_' for the BiGG default) (default 'M_'). +0051 % rxnPrefix : char +0052 % Prefix used to mint fresh reaction ids (default 'R_'). +0053 % +0054 % Returns +0055 % ------- +0056 % newModel : struct +0057 % Curated RAVEN model structure. +0058 % +0059 % Examples +0060 % -------- +0061 % newModel = curateModelFromTables(model, metsInfo, genesInfo, ... +0062 % rxnsCoeffs, rxnsInfo, metPrefix, rxnPrefix); +0063 % +0064 % Notes +0065 % ----- +0066 % In all three info tables, any column whose header is not one of the +0067 % listed core fields is treated as a MIRIAM annotation namespace and +0068 % stored on the matching entity. +0069 +0070 p=parseRAVENargs(varargin, {'genesInfo','none'; 'rxnsCoeffs','none'; 'rxnsInfo','none'; 'metPrefix',[]; 'rxnPrefix',[]}); +0071 genesInfo=p.genesInfo; +0072 rxnsCoeffs=p.rxnsCoeffs; +0073 rxnsInfo=p.rxnsInfo; +0074 metPrefix=p.metPrefix; +0075 if isempty(metPrefix) +0076 metPrefix = 'M_'; +0077 end +0078 rxnPrefix=p.rxnPrefix; +0079 if isempty(rxnPrefix) +0080 rxnPrefix = 'R_'; +0081 end +0082 if xor(strcmp(rxnsCoeffs,'none'), strcmp(rxnsInfo,'none')) +0083 error('Provide both a ''rxnsInfo'' and a ''rxnsCoeffs'' file') +0084 end +0085 newModel=model; +0086 +0087 %% Metabolites +0088 if ~strcmp(metsInfo,'none') +0089 [header,data] = readCurationTable(metsInfo); +0090 [col,miriamCols] = matchColumns(header,metsInfo,{'metNames','comps'},{ ... +0091 'metNames', {'metNames','metName'}; ... +0092 'comps', {'comps','comp'}; ... +0093 'metFormulas', {'formula','formulas','metFormula','metFormulas'}; ... +0094 'metCharges', {'charge','charges','metCharge','metCharges'}; ... +0095 'inchis', {'inchi','inchis'}; ... +0096 'metNotes', {'metNotes','metNote'}}); +0097 metsToAdd.metNames = data(:,col.metNames); +0098 metsToAdd.compartments = data(:,col.comps); +0099 textFields = {'metFormulas','inchis','metNotes'}; +0100 for f = textFields(isfield(col,textFields)) +0101 metsToAdd.(f{1}) = data(:,col.(f{1})); +0102 end +0103 if isfield(col,'metCharges') +0104 metsToAdd.metCharges = cellfun(@str2num,data(:,col.metCharges),'UniformOutput',false); +0105 emptyEntry = cellfun(@isempty,metsToAdd.metCharges); +0106 if all(emptyEntry) +0107 metsToAdd = rmfield(metsToAdd,'metCharges'); +0108 else % If some charges are given, assume 0 for those without charges specified +0109 metsToAdd.metCharges(emptyEntry) = {0}; +0110 metsToAdd.metCharges = cell2mat(metsToAdd.metCharges); +0111 end +0112 end +0113 optFields = intersect({'metFormulas','metCharges','inchis','metNotes'},fieldnames(metsToAdd)','stable'); +0114 +0115 % Check if metabolite already exists (check by metName[comp]) +0116 existingMets = []; +0117 existingMetsIdx = []; +0118 newMets = []; +0119 for i=1:numel(metsToAdd.metNames) +0120 metIdx = find(strcmp(newModel.metNames,metsToAdd.metNames{i})); +0121 existMet = strcmp(metsToAdd.compartments{i},newModel.comps(newModel.metComps(metIdx))); +0122 if any(existMet) +0123 existingMets = [existingMets, i]; +0124 existingMetsIdx = [existingMetsIdx, metIdx(existMet)]; +0125 else +0126 newMets = [newMets, i]; +0127 end +0128 end +0129 +0130 % Overwrite annotation of existing entries +0131 if any(existingMets) +0132 for f = optFields +0133 newModel = writeExistingField(newModel,f{1},existingMetsIdx,metsToAdd.(f{1})(existingMets)); +0134 end +0135 newModel = extractAndAddMiriam(newModel,header(miriamCols),data(:,miriamCols),existingMets,existingMetsIdx,'met'); +0136 warning(['The following metabolites are already present in the model, '... +0137 'their annotation will be overwritten to match the metsInfo file. '... +0138 'If you do not particularly want to curate their annotations, it '... +0139 'would be better to remove these metabolites from metsInfo:\n\t\t%s'],... +0140 strjoin(strcat(metsToAdd.metNames(existingMets),'[',metsToAdd.compartments(existingMets),']'),'\n\t\t')); +0141 end +0142 +0143 % Continue with new metabolites +0144 if numel(newMets)>0 +0145 metsToAdd.metNames = metsToAdd.metNames(newMets); +0146 metsToAdd.compartments = metsToAdd.compartments(newMets); +0147 for f = optFields +0148 metsToAdd.(f{1}) = metsToAdd.(f{1})(newMets); +0149 if iscell(metsToAdd.(f{1})) && all(cellfun(@isempty,metsToAdd.(f{1}))) +0150 metsToAdd = rmfield(metsToAdd,f{1}); +0151 end +0152 end +0153 % Add metabolites +0154 newModel = addMets(newModel,metsToAdd,true,metPrefix); +0155 addedIdx = numel(newModel.mets)-numel(newMets)+1:numel(newModel.mets); +0156 newModel = extractAndAddMiriam(newModel,header(miriamCols),data(:,miriamCols),newMets,addedIdx,'met'); +0157 end +0158 end +0159 %% Genes +0160 if ~strcmp(genesInfo,'none') +0161 [header,data] = readCurationTable(genesInfo); +0162 [col,miriamCols] = matchColumns(header,genesInfo,{'genes'},{ ... +0163 'genes', {'genes','gene'}; ... +0164 'geneShortNames', {'geneShortNames','geneShortName'}}); +0165 genesToAdd.genes = data(:,col.genes); +0166 if isfield(col,'geneShortNames') +0167 genesToAdd.geneShortNames = data(:,col.geneShortNames); +0168 end +0169 +0170 [~,notNewGene,existingGene] = intersect(genesToAdd.genes,newModel.genes); +0171 if ~isempty(notNewGene) +0172 warning(['The following genes are already present in the model, their annotation '... +0173 'will be overwritten to match the genesInfo file: \n\t\t%s'],... +0174 strjoin(genesToAdd.genes(notNewGene),'\n\t\t')); +0175 if isfield(genesToAdd,'geneShortNames') +0176 newModel = writeExistingField(newModel,'geneShortNames',existingGene,genesToAdd.geneShortNames(notNewGene)); +0177 end +0178 newModel = extractAndAddMiriam(newModel,header(miriamCols),data(:,miriamCols),notNewGene,existingGene,'gene'); +0179 end +0180 +0181 % Continue with new genes +0182 toAdd = 1:numel(genesToAdd.genes); +0183 toAdd(notNewGene) = []; +0184 if ~isempty(toAdd) +0185 genesToAdd.genes = genesToAdd.genes(toAdd); +0186 if isfield(genesToAdd,'geneShortNames') +0187 genesToAdd.geneShortNames = genesToAdd.geneShortNames(toAdd); +0188 if all(cellfun(@isempty,genesToAdd.geneShortNames)) +0189 genesToAdd = rmfield(genesToAdd,'geneShortNames'); +0190 end +0191 end +0192 % Add genes +0193 newModel = addGenesRaven(newModel,genesToAdd); +0194 addedIdx = numel(newModel.genes)-numel(toAdd)+1:numel(newModel.genes); +0195 newModel = extractAndAddMiriam(newModel,header(miriamCols),data(:,miriamCols),toAdd,addedIdx,'gene'); +0196 end +0197 end +0198 %% Reactions +0199 if ~any(strcmp({rxnsCoeffs,rxnsInfo},'none')) +0200 % Gather all data, first about reaction stoichiometries... +0201 [header,data] = readCurationTable(rxnsCoeffs); +0202 col = matchColumns(header,rxnsCoeffs,{'rxnIdx','rxnNames','metNames','comps','coefficient'},{ ... +0203 'rxnIdx', {'rxnIdx','index'}; ... +0204 'rxnNames', {'rxnNames','rxnName'}; ... +0205 'metNames', {'metNames','metName'}; ... +0206 'comps', {'comps','comp'}; ... +0207 'coefficient', {'coefficient','coefficients','coeffs','stoichCoeffs'}}); +0208 rxnCheck.coeffsIdx = str2double(data(:,col.rxnIdx)); +0209 rxns.rxnNames = data(:,col.rxnNames); +0210 rxns.metNames = data(:,col.metNames); +0211 rxns.comps = data(:,col.comps); +0212 rxns.coeffs = str2double(data(:,col.coefficient)); +0213 rxnCheck.coeffs = strcat(data(:,col.rxnIdx),'***',rxns.rxnNames); +0214 +0215 % ... and then additional reaction-specific data. +0216 [header,data] = readCurationTable(rxnsInfo); +0217 [col,miriamCols] = matchColumns(header,rxnsInfo,{'rxnIdx','rxnNames'},{ ... +0218 'rxnIdx', {'rxnIdx','index'}; ... +0219 'rxnNames', {'rxnNames','rxnName'}; ... +0220 'grRules', {'grRules'}; ... +0221 'lb', {'lb'}; ... +0222 'ub', {'ub'}; ... +0223 'rev', {'rev'}; ... +0224 'subSystems', {'subSystems'}; ... +0225 'eccodes', {'eccodes'}; ... +0226 'rxnNotes', {'rxnNotes'}; ... +0227 'rxnReferences', {'rxnReferences'}; ... +0228 'rxnConfidenceScores', {'rxnConfidenceScores'}}); +0229 rxnCheck.rxnsIdx = str2double(data(:,col.rxnIdx)); +0230 rxnCheck.rxns = strcat(data(:,col.rxnIdx),'***',data(:,col.rxnNames)); +0231 +0232 notMatching = setxor(rxnCheck.coeffs,rxnCheck.rxns); +0233 if numel(notMatching)>1 +0234 error(['The following reactions ánd/or their indices are not matched '... +0235 'between the rxnsInfo and rxnsCoeffs files:\n\t\t%s'],... +0236 strjoin(regexprep(notMatching,'^\d+***',''),'\n\t\t')) 0237 end -0238 -0239 existingRxn=[]; -0240 notNewRxn=[]; -0241 for i=1:numel(rxnCheck.rxnsIdx) -0242 rxnRows = find(rxnCheck.rxnsIdx(i)==rxnCheck.coeffsIdx); -0243 rxnsToAdd.mets{i,1} = cell(1,1); -0244 rxnsToAdd.stoichCoeffs{i,1} = cell(1,1); -0245 for j=1:numel(rxnRows) -0246 newMetComp = [rxns.metNames{rxnRows(j)}, '[', rxns.comps{rxnRows(j)}, ']']; -0247 try -0248 newMetComp = getIndexes(newModel,newMetComp,'metcomps'); -0249 catch -0250 error(['Not all metabolites in reaction "%s" are present in the '... -0251 'model or in the provided table with new metabolites.'],rxnsToAdd.rxnNames{i}) -0252 end -0253 rxnsToAdd.mets{i}(j) = newModel.mets(newMetComp); -0254 rxnsToAdd.stoichCoeffs{i}{j} = rxns.coeffs(rxnRows(j)); -0255 end -0256 rxnsToAdd.stoichCoeffs{i}=cell2mat(rxnsToAdd.stoichCoeffs{i}); -0257 %Check if the reaction not already exists, by stoichiometry of its -0258 %products and reactants -0259 metIdx = getIndexes(newModel,rxnsToAdd.mets{i},'mets'); -0260 modelCoeffs = transpose(newModel.S(metIdx,:)); -0261 modelCoeffs2 = find(~all(modelCoeffs==0,2)); -0262 % Only reactions built from exactly these metabolites can be -0263 % duplicates. Any model reaction that also involves other -0264 % metabolites projects onto the same row of coefficients, but is a -0265 % different reaction and must not be overwritten. -0266 sameSupport = full(sum(newModel.S(:,modelCoeffs2)~=0,1))' == numel(metIdx); -0267 modelCoeffs2 = modelCoeffs2(sameSupport); -0268 modelCoeffs = modelCoeffs(modelCoeffs2,:); -0269 [~, duplicateRxn] = intersect(modelCoeffs,rxnsToAdd.stoichCoeffs{i},'rows'); -0270 if ~isempty(duplicateRxn) -0271 existingRxn = [existingRxn,modelCoeffs2(duplicateRxn)]; -0272 notNewRxn = [notNewRxn,i]; -0273 end -0274 end -0275 -0276 % If some reactions already existed, then replace its information -0277 if ~isempty(existingRxn) -0278 warning(['The following reactions are the same as existing model '... -0279 'reactions, their annotation will be overwritten to match the '... -0280 'rxnCoeffs file: \n\t\t(Existing reaction ID): New reaction name\n\t\t%s'],... -0281 strjoin(strcat('(',newModel.rxns(existingRxn),{'): '},rxnsToAdd.rxnNames(notNewRxn)),'\n\t\t')); -0282 newModel.rxnNames(existingRxn) = rxnsToAdd.rxnNames(notNewRxn); -0283 newModel.lb(existingRxn) = rxnsToAdd.lb(notNewRxn); -0284 newModel.ub(existingRxn) = rxnsToAdd.ub(notNewRxn); -0285 newModel.rev(existingRxn) = rxnsToAdd.rev(notNewRxn); -0286 if isfield(newModel,'subSystems') -0287 newModel.subSystems(existingRxn) = rxnsToAdd.subSystems(notNewRxn); -0288 end -0289 if isfield(newModel,'eccodes') -0290 newModel.eccodes(existingRxn) = rxnsToAdd.eccodes(notNewRxn); -0291 end -0292 if isfield(newModel,'rxnNotes') -0293 newModel.rxnNotes(existingRxn) = rxnsToAdd.rxnNotes(notNewRxn); -0294 end -0295 if isfield(newModel,'rxnConfidenceScores') && isfield(rxnsToAdd,'rxnConfidenceScores') -0296 newModel.rxnConfidenceScores(existingRxn) = rxnsToAdd.rxnConfidenceScores(notNewRxn); -0297 end -0298 emptyEntries = cellfun(@isempty,rxnsToAdd.grRules); -0299 if ~all(emptyEntries) -0300 newModel = changeGrRules(newModel,newModel.rxns(existingRxn(~emptyEntries)),rxnsToAdd.grRules(notNewRxn(~emptyEntries)),true); -0301 end -0302 newModel = extractAndAddMiriam(newModel,raw(11:end),notNewRxn,existingRxn,'rxn'); -0303 end -0304 -0305 % Continue with new reactions -0306 toAdd = 1:numel(rxnsToAdd.rxnNames); -0307 toAdd(notNewRxn) = []; -0308 if ~isempty(toAdd) -0309 rxnsToAdd.rxnNames = rxnsToAdd.rxnNames(toAdd); -0310 rxnsToAdd.lb = rxnsToAdd.lb(toAdd); -0311 rxnsToAdd.ub = rxnsToAdd.ub(toAdd); -0312 rxnsToAdd.rev = rxnsToAdd.rev(toAdd); -0313 rxnsToAdd.subSystems = rxnsToAdd.subSystems(toAdd); -0314 if all(cellfun(@isempty,rxnsToAdd.subSystems)) -0315 rxnsToAdd = rmfield(rxnsToAdd,'subSystems'); -0316 end -0317 rxnsToAdd.eccodes = rxnsToAdd.eccodes(toAdd); -0318 if all(cellfun(@isempty,rxnsToAdd.eccodes)) -0319 rxnsToAdd = rmfield(rxnsToAdd,'eccodes'); -0320 end -0321 rxnsToAdd.rxnNotes = rxnsToAdd.rxnNotes(toAdd); -0322 if all(cellfun(@isempty,rxnsToAdd.rxnNotes)) -0323 rxnsToAdd = rmfield(rxnsToAdd,'rxnNotes'); -0324 end -0325 if isfield(rxnsToAdd,'rxnConfidenceScores') -0326 rxnsToAdd.rxnConfidenceScores = rxnsToAdd.rxnConfidenceScores(toAdd); -0327 end -0328 rxnsToAdd.rxnReferences = rxnsToAdd.rxnReferences(toAdd); -0329 if all(cellfun(@isempty,rxnsToAdd.rxnReferences)) -0330 rxnsToAdd = rmfield(rxnsToAdd,'rxnReferences'); -0331 end -0332 rxnsToAdd.grRules = rxnsToAdd.grRules(toAdd); -0333 rxnsToAdd.mets = rxnsToAdd.mets(toAdd); -0334 rxnsToAdd.stoichCoeffs = rxnsToAdd.stoichCoeffs(toAdd); -0335 rxnsToAdd.rxns = generateNewIds(model,'rxns',rxnPrefix,numel(rxnsToAdd.rxnNames)); -0336 -0337 newModel = addRxns(newModel,rxnsToAdd,1,[],false,false); -0338 rxnsModelIdx = numel(newModel.rxns)-numel(toAdd)+1:numel(newModel.rxns); -0339 newModel = extractAndAddMiriam(newModel,raw(11:end),toAdd,rxnsModelIdx,'rxn'); -0340 end -0341 end -0342 end -0343 -0344 function newModel = extractAndAddMiriam(model,raw,inputIndex,modelIndex,type) -0345 newModel=model; -0346 miriamName = cell(numel(inputIndex),1); -0347 miriamValues = cell(1,numel(inputIndex)); -0348 for i=1:numel(raw) -0349 miriamName{i} = raw{i}{1}; -0350 miriamValues(1:numel(inputIndex),i) = raw{i}(inputIndex+1); -0351 end -0352 emptyMiriam = all(cellfun(@isempty,miriamValues),1); -0353 miriamName(emptyMiriam) = []; -0354 miriamValues(:,emptyMiriam) = []; -0355 if ~isfield(newModel,[type 'Miriams']); -0356 newModel.([type 'Miriams'])=cell(numel(newModel.([type 's'])),1); -0357 end -0358 if ~isempty(miriamName) -0359 for i=1:numel(miriamName) -0360 newModel = editMiriam(newModel,type,modelIndex,miriamName{i},miriamValues(:,i),'replace'); -0361 end -0362 end -0363 end

    +0238 +0239 rxnsToAdd.rxnNames = data(:,col.rxnNames); +0240 for f = intersect({'grRules','subSystems','eccodes','rxnNotes','rxnReferences'},fieldnames(col)','stable') +0241 rxnsToAdd.(f{1}) = data(:,col.(f{1})); +0242 end +0243 for f = intersect({'lb','ub','rev'},fieldnames(col)','stable') +0244 rxnsToAdd.(f{1}) = cellfun(@str2num,data(:,col.(f{1}))); +0245 end +0246 if isfield(col,'rxnConfidenceScores') +0247 rxnsToAdd.rxnConfidenceScores = cellfun(@str2num,data(:,col.rxnConfidenceScores),'UniformOutput',false); +0248 emptyEntry = cellfun(@isempty,rxnsToAdd.rxnConfidenceScores); +0249 if all(emptyEntry) +0250 rxnsToAdd = rmfield(rxnsToAdd,'rxnConfidenceScores'); +0251 else % If some rxnConfidenceScores are given, assume 0 for those without rxnConfidenceScores specified +0252 rxnsToAdd.rxnConfidenceScores(emptyEntry) = {0}; +0253 rxnsToAdd.rxnConfidenceScores = cell2mat(rxnsToAdd.rxnConfidenceScores); +0254 end +0255 end +0256 optFields = intersect({'subSystems','eccodes','rxnNotes','rxnReferences','rxnConfidenceScores'},fieldnames(rxnsToAdd)','stable'); +0257 +0258 existingRxn=[]; +0259 notNewRxn=[]; +0260 for i=1:numel(rxnCheck.rxnsIdx) +0261 rxnRows = find(rxnCheck.rxnsIdx(i)==rxnCheck.coeffsIdx); +0262 rxnsToAdd.mets{i,1} = cell(1,1); +0263 rxnsToAdd.stoichCoeffs{i,1} = cell(1,1); +0264 for j=1:numel(rxnRows) +0265 newMetComp = [rxns.metNames{rxnRows(j)}, '[', rxns.comps{rxnRows(j)}, ']']; +0266 try +0267 newMetComp = getIndexes(newModel,newMetComp,'metcomps'); +0268 catch +0269 error(['Not all metabolites in reaction "%s" are present in the '... +0270 'model or in the provided table with new metabolites.'],rxnsToAdd.rxnNames{i}) +0271 end +0272 rxnsToAdd.mets{i}(j) = newModel.mets(newMetComp); +0273 rxnsToAdd.stoichCoeffs{i}{j} = rxns.coeffs(rxnRows(j)); +0274 end +0275 rxnsToAdd.stoichCoeffs{i}=cell2mat(rxnsToAdd.stoichCoeffs{i}); +0276 %Check if the reaction not already exists, by stoichiometry of its +0277 %products and reactants +0278 metIdx = getIndexes(newModel,rxnsToAdd.mets{i},'mets'); +0279 modelCoeffs = transpose(newModel.S(metIdx,:)); +0280 modelCoeffs2 = find(~all(modelCoeffs==0,2)); +0281 % Only reactions built from exactly these metabolites can be +0282 % duplicates. Any model reaction that also involves other +0283 % metabolites projects onto the same row of coefficients, but is a +0284 % different reaction and must not be overwritten. +0285 sameSupport = full(sum(newModel.S(:,modelCoeffs2)~=0,1))' == numel(metIdx); +0286 modelCoeffs2 = modelCoeffs2(sameSupport); +0287 modelCoeffs = modelCoeffs(modelCoeffs2,:); +0288 [~, duplicateRxn] = intersect(modelCoeffs,rxnsToAdd.stoichCoeffs{i},'rows'); +0289 if ~isempty(duplicateRxn) +0290 existingRxn = [existingRxn,modelCoeffs2(duplicateRxn)]; +0291 notNewRxn = [notNewRxn,i]; +0292 end +0293 end +0294 +0295 % If some reactions already existed, then replace its information +0296 if ~isempty(existingRxn) +0297 warning(['The following reactions are the same as existing model '... +0298 'reactions, their annotation will be overwritten to match the '... +0299 'rxnCoeffs file: \n\t\t(Existing reaction ID): New reaction name\n\t\t%s'],... +0300 strjoin(strcat('(',newModel.rxns(existingRxn),{'): '},rxnsToAdd.rxnNames(notNewRxn)),'\n\t\t')); +0301 newModel.rxnNames(existingRxn) = rxnsToAdd.rxnNames(notNewRxn); +0302 for f = intersect({'lb','ub','rev'},fieldnames(rxnsToAdd)','stable') +0303 newModel.(f{1})(existingRxn) = rxnsToAdd.(f{1})(notNewRxn); +0304 end +0305 for f = optFields +0306 values = rxnsToAdd.(f{1})(notNewRxn); +0307 if strcmp(f{1},'subSystems') && isfield(newModel,'subSystems') ... +0308 && any(cellfun(@iscell,newModel.subSystems)) +0309 values = cellfun(@(x) {x}, values, 'UniformOutput', false); +0310 end +0311 newModel = writeExistingField(newModel,f{1},existingRxn,values); +0312 end +0313 if isfield(rxnsToAdd,'grRules') +0314 emptyEntries = cellfun(@isempty,rxnsToAdd.grRules(notNewRxn)); +0315 if ~all(emptyEntries) +0316 newModel = changeGrRules(newModel,newModel.rxns(existingRxn(~emptyEntries)),rxnsToAdd.grRules(notNewRxn(~emptyEntries)),true); +0317 end +0318 end +0319 newModel = extractAndAddMiriam(newModel,header(miriamCols),data(:,miriamCols),notNewRxn,existingRxn,'rxn'); +0320 end +0321 +0322 % Continue with new reactions +0323 toAdd = 1:numel(rxnsToAdd.rxnNames); +0324 toAdd(notNewRxn) = []; +0325 if ~isempty(toAdd) +0326 rxnsToAdd.rxnNames = rxnsToAdd.rxnNames(toAdd); +0327 for f = intersect({'lb','ub','rev','grRules'},fieldnames(rxnsToAdd)','stable') +0328 rxnsToAdd.(f{1}) = rxnsToAdd.(f{1})(toAdd); +0329 end +0330 for f = optFields +0331 rxnsToAdd.(f{1}) = rxnsToAdd.(f{1})(toAdd); +0332 if iscell(rxnsToAdd.(f{1})) && all(cellfun(@isempty,rxnsToAdd.(f{1}))) +0333 rxnsToAdd = rmfield(rxnsToAdd,f{1}); +0334 end +0335 end +0336 rxnsToAdd.mets = rxnsToAdd.mets(toAdd); +0337 rxnsToAdd.stoichCoeffs = rxnsToAdd.stoichCoeffs(toAdd); +0338 rxnsToAdd.rxns = generateNewIds(model,'rxns',rxnPrefix,numel(rxnsToAdd.rxnNames)); +0339 +0340 newModel = addRxns(newModel,rxnsToAdd,1,[],false,false); +0341 rxnsModelIdx = numel(newModel.rxns)-numel(toAdd)+1:numel(newModel.rxns); +0342 newModel = extractAndAddMiriam(newModel,header(miriamCols),data(:,miriamCols),toAdd,rxnsModelIdx,'rxn'); +0343 end +0344 end +0345 end +0346 +0347 function [header,data] = readCurationTable(fileName) +0348 % Read a tab-delimited curation table into its header row and a cell +0349 % array of text with one column per header field. +0350 fid = fopen(fileName); +0351 if fid==-1 +0352 error('Could not open the file "%s"',fileName); +0353 end +0354 headerLine = fgetl(fid); +0355 if ~ischar(headerLine) +0356 fclose(fid); +0357 error('The file "%s" is empty',fileName); +0358 end +0359 headerLine = strrep(headerLine,char(65279),''); % byte order mark +0360 header = strtrim(strsplit(headerLine,'\t')); +0361 header = regexprep(header,'^"(.*)"$','$1'); +0362 nCols = numel(header); +0363 raw = textscan(fid,repmat('%q',1,nCols),'Delimiter','\t'); +0364 fclose(fid); +0365 nRows = max(cellfun(@numel,raw)); +0366 data = repmat({''},nRows,nCols); +0367 for i=1:nCols +0368 data(1:numel(raw{i}),i) = raw{i}; +0369 end +0370 end +0371 +0372 function [col,miriamCols] = matchColumns(header,fileName,required,spec) +0373 % Locate the core columns of a curation table by header name (case +0374 % insensitive). spec has one row per core field: {field, accepted headers}. +0375 % Every other column is a MIRIAM namespace. +0376 col = struct(); +0377 isCore = false(1,numel(header)); +0378 for i=1:size(spec,1) +0379 match = find(ismember(lower(header),lower(spec{i,2}))); +0380 if ~isempty(match) +0381 col.(spec{i,1}) = match(1); +0382 isCore(match) = true; +0383 end +0384 end +0385 missing = setdiff(required,fieldnames(col)','stable'); +0386 if ~isempty(missing) +0387 error('The file "%s" has no column with header: %s',fileName,strjoin(missing,', ')); +0388 end +0389 miriamCols = find(~isCore & ~cellfun(@isempty,header)); +0390 end +0391 +0392 function model = writeExistingField(model,field,modelIdx,values) +0393 % Write the values of an optional field onto existing entities. A field +0394 % the model does not have is created from its ravenModelFields default, +0395 % unless all supplied values are empty. +0396 if ~isfield(model,field) +0397 if iscell(values) && all(cellfun(@isempty,values)) +0398 return +0399 end +0400 reg = ravenModelFields(); +0401 entry = reg(strcmp({reg.name},field)); +0402 n = numel(model.([entry.type 's'])); +0403 if iscell(values) +0404 model.(field) = repmat({entry.default},n,1); +0405 else +0406 model.(field) = repmat(entry.default,n,1); +0407 end +0408 end +0409 model.(field)(modelIdx) = values; +0410 end +0411 +0412 function newModel = extractAndAddMiriam(model,miriamName,miriamData,inputIndex,modelIndex,type) +0413 newModel=model; +0414 miriamValues = miriamData(inputIndex,:); +0415 emptyMiriam = all(cellfun(@isempty,miriamValues),1); +0416 miriamName(emptyMiriam) = []; +0417 miriamValues(:,emptyMiriam) = []; +0418 if ~isfield(newModel,[type 'Miriams']) +0419 newModel.([type 'Miriams'])=cell(numel(newModel.([type 's'])),1); +0420 end +0421 for i=1:numel(miriamName) +0422 newModel = editMiriam(newModel,type,modelIndex,miriamName{i},miriamValues(:,i),'replace'); +0423 end +0424 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/gapfilling/fillGaps.html b/docs/html/gapfilling/fillGaps.html index aeb0f66e..439e4ecc 100644 --- a/docs/html/gapfilling/fillGaps.html +++ b/docs/html/gapfilling/fillGaps.html @@ -83,7 +83,7 @@

    DESCRIPTION ^DESCRIPTION ^ftINITFillGaps : the ftINIT-side counterpart, used when the reference @@ -131,7 +138,7 @@

    DESCRIPTION ^CROSS-REFERENCE INFORMATION ^

    This function calls:
      -
    • gapFillFastLP gapFillFastLP LP-based gap-filling (fastGapFill / swiftGapFill).
    • gapFillMILP gapFillMILP Objective-based gap-filling via a global growth-floor MILP.
    • gapFillTopological gapFillTopological BFS metabolite-producibility pre-screening.
    +
  • gapFillFastLP gapFillFastLP LP-based gap-filling (fastGapFill / swiftGapFill).
  • gapFillMILP gapFillMILP Objective-based gap-filling via a global growth-floor MILP.
  • gapFillTopological gapFillTopological BFS metabolite-producibility pre-screening.
  • This function is called by:
    • fitTasks fitTasks Fill gaps in a model so it can perform a list of tasks.
    • gapReport gapReport Perform a gap analysis and summarize the results.
    @@ -197,7 +204,7 @@

    SOURCE CODE ^% - 'reference' : MILP-based connectivity maximisation 0056 % - 'fastLP' : L1-norm LP relaxation via FASTCORE (gapFillFastLP) 0057 % - 'swiftLP' : SWIFTCORE single-LP variant (gapFillFastLP) -0058 % - 'gapfillMILP' : growth-floor MILP with directionality repair +0058 % - 'gapfillMILP' : growth-floor MILP adding database reactions 0059 % (gapFillMILP). Uses only models{1} as the database. 0060 % - 'topological' : BFS metabolite producibility pre-screening 0061 % (gapFillTopological); no model modification. @@ -235,213 +242,220 @@

    SOURCE CODE ^% fillGaps(model,models,allowNetProduction,useModelConstraints,... 0094 % supressWarnings,rxnScores,params); 0095 % -0096 % See Also -0097 % -------- -0098 % ftINITFillGaps : the ftINIT-side counterpart, used when the reference -0099 % model already contains the model's own reactions, so no per-task -0100 % merge is needed. -0101 -0102 %If the user only supplied a single template model -0103 if ~iscell(models) -0104 models={models}; -0105 end -0106 -0107 p=parseRAVENargs(varargin, {'allowNetProduction',false; ... -0108 'useModelConstraints',false; ... -0109 'supressWarnings',false; ... -0110 'rxnScores',[]; ... -0111 'params',[]; ... -0112 'algorithm','reference'; ... -0113 'epsilon',1e-4; ... -0114 'minGrowth',[]; ... -0115 'verbose',true}); -0116 allowNetProduction=p.allowNetProduction; -0117 useModelConstraints=p.useModelConstraints; -0118 supressWarnings=p.supressWarnings; -0119 rxnScores=p.rxnScores; -0120 params=p.params; -0121 -0122 % ---- Dispatch to alternative gap-filling algorithms ---- -0123 switch lower(p.algorithm) -0124 case {'fastlp', 'swiftlp'} -0125 if numel(models) < 1 -0126 error('RAVEN:badInput', 'fillGaps: fastLP/swiftLP requires at least one reference model.'); -0127 end -0128 variant = strrep(lower(p.algorithm), 'lp', ''); -0129 [addedRxns, newModel, cannotConnect] = gapFillFastLP(model, models{1}, ... -0130 'epsilon', p.epsilon, 'verbose', p.verbose, 'variant', variant); -0131 newConnected = {}; -0132 exitFlag = 1; -0133 return; -0134 case 'gapfillmilp' -0135 if numel(models) < 1 -0136 error('RAVEN:badInput', 'fillGaps: gapfillMILP requires at least one reference model.'); -0137 end -0138 [addedRxns, ~, newModel, exitFlag] = gapFillMILP(model, models{1}, ... -0139 'minGrowth', p.minGrowth, 'verbose', p.verbose, 'params', params); -0140 newConnected = {}; -0141 cannotConnect = {}; -0142 return; -0143 case 'topological' -0144 if numel(models) < 1 -0145 error('RAVEN:badInput', 'fillGaps: topological requires at least one reference model.'); -0146 end -0147 result = gapFillTopological(model, models{1}, 'verbose', p.verbose); -0148 addedRxns = {}; -0149 newModel = model; -0150 cannotConnect = result.blockedMets; -0151 newConnected = {}; -0152 exitFlag = 1; -0153 return; -0154 end -0155 % Falls through to the 'reference' algorithm for all other values. -0156 -0157 if isempty(rxnScores) -0158 rxnScores=cell(numel(models),1); -0159 for i=1:numel(models) -0160 rxnScores{i}=ones(numel(models{i}.rxns),1)*-1; -0161 end -0162 end -0163 if isempty(params) -0164 params=struct(); -0165 end -0166 -0167 if ~iscell(rxnScores) -0168 rxnScores={rxnScores}; +0096 % Notes +0097 % ----- +0098 % The 'reference' and 'gapfillMILP' algorithms solve a MILP with the +0099 % solver set by setRavenSolver. RAVEN's bundled GLPK solves it for small +0100 % models (with a RAVEN:glpkMILP warning); for larger models Gurobi is much +0101 % faster and more reliable (setRavenSolver('gurobi')). +0102 % +0103 % See Also +0104 % -------- +0105 % ftINITFillGaps : the ftINIT-side counterpart, used when the reference +0106 % model already contains the model's own reactions, so no per-task +0107 % merge is needed. +0108 +0109 %If the user only supplied a single template model +0110 if ~iscell(models) +0111 models={models}; +0112 end +0113 +0114 p=parseRAVENargs(varargin, {'allowNetProduction',false; ... +0115 'useModelConstraints',false; ... +0116 'supressWarnings',false; ... +0117 'rxnScores',[]; ... +0118 'params',[]; ... +0119 'algorithm','reference'; ... +0120 'epsilon',1e-4; ... +0121 'minGrowth',[]; ... +0122 'verbose',true}); +0123 allowNetProduction=p.allowNetProduction; +0124 useModelConstraints=p.useModelConstraints; +0125 supressWarnings=p.supressWarnings; +0126 rxnScores=p.rxnScores; +0127 params=p.params; +0128 +0129 % ---- Dispatch to alternative gap-filling algorithms ---- +0130 switch lower(p.algorithm) +0131 case {'fastlp', 'swiftlp'} +0132 if numel(models) < 1 +0133 error('RAVEN:badInput', 'fillGaps: fastLP/swiftLP requires at least one reference model.'); +0134 end +0135 variant = strrep(lower(p.algorithm), 'lp', ''); +0136 [addedRxns, newModel, cannotConnect] = gapFillFastLP(model, models{1}, ... +0137 'epsilon', p.epsilon, 'verbose', p.verbose, 'variant', variant); +0138 newConnected = {}; +0139 exitFlag = 1; +0140 return; +0141 case 'gapfillmilp' +0142 if numel(models) < 1 +0143 error('RAVEN:badInput', 'fillGaps: gapfillMILP requires at least one reference model.'); +0144 end +0145 [addedRxns, newModel, exitFlag] = gapFillMILP(model, models{1}, ... +0146 'minGrowth', p.minGrowth, 'verbose', p.verbose, 'params', params); +0147 newConnected = {}; +0148 cannotConnect = {}; +0149 return; +0150 case 'topological' +0151 if numel(models) < 1 +0152 error('RAVEN:badInput', 'fillGaps: topological requires at least one reference model.'); +0153 end +0154 result = gapFillTopological(model, models{1}, 'verbose', p.verbose); +0155 addedRxns = {}; +0156 newModel = model; +0157 cannotConnect = result.blockedMets; +0158 newConnected = {}; +0159 exitFlag = 1; +0160 return; +0161 end +0162 % Falls through to the 'reference' algorithm for all other values. +0163 +0164 if isempty(rxnScores) +0165 rxnScores=cell(numel(models),1); +0166 for i=1:numel(models) +0167 rxnScores{i}=ones(numel(models{i}.rxns),1)*-1; +0168 end 0169 end -0170 -0171 models=models(:); -0172 rxnScores=rxnScores(:); +0170 if isempty(params) +0171 params=struct(); +0172 end 0173 -0174 %Check if the original model has an unconstrained field. If so, give a -0175 %warning -0176 if supressWarnings==false -0177 if isfield(model,'unconstrained') -0178 EM='This algorithm is meant to function on a model with exchange reactions for uptake and excretion of metabolites. The current model still has the "unconstrained" field'; -0179 warning('RAVEN:warning', '%s', EM); -0180 else -0181 if isempty(getExchangeRxns(model,'both')) -0182 fprintf('NOTE: This algorithm is meant to function on a model with exchange reactions for uptake and excretion of metabolites. The current model does not seem to contain any such reactions.\n'); -0183 end -0184 end -0185 end -0186 -0187 %Simplify the template models to remove constrained rxns. At the same time, -0188 %check that the id of the template models is not the same as the model. That -0189 %would cause an error further down -0190 for i=1:numel(models) -0191 models{i}.rxnScores=rxnScores{i}; -0192 models{i}=simplifyModel(models{i},false,false,true); -0193 if strcmpi(models{i}.id,model.id) -0194 EM='The reference model(s) cannot have the same id as the model'; -0195 error('RAVEN:badInput', '%s', EM); -0196 end -0197 end -0198 -0199 %This is a rather ugly solution to the issue that it is a bit tricky to keep -0200 %track of which scores belong to which reactions. This requires that -0201 %removeReactions and mergeModels are modified to check for the new field. -0202 model.rxnScores=zeros(numel(model.rxns),1); -0203 -0204 %First merge all models into one big one -0205 allModels=mergeModels([{model};models],'metNames',true); -0206 -0207 %Add that net production is ok -0208 if allowNetProduction==true -0209 %A second column in model.b means that the b field is lower and upper -0210 %bound on the RHS. -0211 model.b=[model.b(:,1) inf(numel(model.mets),1)]; -0212 allModels.b=[allModels.b(:,1) inf(numel(allModels.mets),1)]; -0213 end -0214 -0215 if useModelConstraints==true -0216 newConnected={}; -0217 cannotConnect={}; -0218 -0219 %Check that the input model is not solveable without any input -0220 sol=solveLP(model); -0221 if ~isempty(sol.f) -0222 addedRxns={}; -0223 newModel=model; -0224 exitFlag=1; -0225 return; -0226 end -0227 -0228 %Then check that the merged model is solveable -0229 sol=solveLP(allModels); -0230 if isempty(sol.f) -0231 EM='There are no reactions in the template model(s) that can make the model constraints satisfied'; -0232 error('RAVEN:badInput', '%s', EM); +0174 if ~iscell(rxnScores) +0175 rxnScores={rxnScores}; +0176 end +0177 +0178 models=models(:); +0179 rxnScores=rxnScores(:); +0180 +0181 %Check if the original model has an unconstrained field. If so, give a +0182 %warning +0183 if supressWarnings==false +0184 if isfield(model,'unconstrained') +0185 EM='This algorithm is meant to function on a model with exchange reactions for uptake and excretion of metabolites. The current model still has the "unconstrained" field'; +0186 warning('RAVEN:warning', '%s', EM); +0187 else +0188 if isempty(getExchangeRxns(model,'both')) +0189 fprintf('NOTE: This algorithm is meant to function on a model with exchange reactions for uptake and excretion of metabolites. The current model does not seem to contain any such reactions.\n'); +0190 end +0191 end +0192 end +0193 +0194 %Simplify the template models to remove constrained rxns. At the same time, +0195 %check that the id of the template models is not the same as the model. That +0196 %would cause an error further down +0197 for i=1:numel(models) +0198 models{i}.rxnScores=rxnScores{i}; +0199 models{i}=simplifyModel(models{i},false,false,true); +0200 if strcmpi(models{i}.id,model.id) +0201 EM='The reference model(s) cannot have the same id as the model'; +0202 error('RAVEN:badInput', '%s', EM); +0203 end +0204 end +0205 +0206 %This is a rather ugly solution to the issue that it is a bit tricky to keep +0207 %track of which scores belong to which reactions. This requires that +0208 %removeReactions and mergeModels are modified to check for the new field. +0209 model.rxnScores=zeros(numel(model.rxns),1); +0210 +0211 %First merge all models into one big one +0212 allModels=mergeModels([{model};models],'metNames',true); +0213 +0214 %Add that net production is ok +0215 if allowNetProduction==true +0216 %A second column in model.b means that the b field is lower and upper +0217 %bound on the RHS. +0218 model.b=[model.b(:,1) inf(numel(model.mets),1)]; +0219 allModels.b=[allModels.b(:,1) inf(numel(allModels.mets),1)]; +0220 end +0221 +0222 if useModelConstraints==true +0223 newConnected={}; +0224 cannotConnect={}; +0225 +0226 %Check that the input model is not solveable without any input +0227 sol=solveLP(model); +0228 if ~isempty(sol.f) +0229 addedRxns={}; +0230 newModel=model; +0231 exitFlag=1; +0232 return; 0233 end 0234 -0235 %Remove dead ends for speed reasons. This has to be done here and -0236 %duplicate below because there is otherwise a risk that a reaction -0237 %which is constrained to something relevant is removed -0238 allModels=simplifyModel(allModels,false,false,false,true,false,false,false,[],true); -0239 allModels.c(:)=0; -0240 else -0241 %Remove dead ends for speed reasons -0242 allModels=simplifyModel(allModels,false,false,false,true,false,false,false,[],true); -0243 allModels.c(:)=0; -0244 -0245 %If model constraints should not be used, then determine which reactions -0246 %to force to have flux -0247 -0248 %Get the reactions that can carry flux in the original model -0249 originalFlux=haveFlux(model,1); -0250 -0251 %For the ones that cannot carry flux, see if they can do so in the -0252 %merged model. Reactions from model keep their id unchanged in -0253 %allModels (mergeModels renames colliding ids in the other input -0254 %models, never in model itself), so intersecting with allModels.rxns -0255 %both identifies them and drops any that simplifyModel has since -0256 %removed as dead ends. The previous strcmp(allModels.rxnFrom,model.id) -0257 %failed whenever model already carried a rxnFrom field of its own, as -0258 %getModelFromHomology output does, tagging each reaction with its -0259 %template rather than model.id, so mergeModels leaves it untouched and -0260 %it never matches model.id here. -0261 toCheck=intersect(model.rxns(~originalFlux),allModels.rxns); -0262 -0263 %Get the ones that still cannot carry flux. Guard against an empty -0264 %toCheck, since haveFlux interprets an empty reaction list as "all -0265 %reactions" rather than "none" -0266 if isempty(toCheck) -0267 K=toCheck; -0268 else -0269 I=haveFlux(allModels,1,toCheck); -0270 %Get the reactions that cannot carry flux in the original model, but can -0271 %in the merged one -0272 K=toCheck(I); -0273 end -0274 -0275 %This is a temporary thing to only look at the non-reversible rxns. -0276 %This is because all reversible rxns can have a flux in the -0277 %irreversible model format that is used by getMinNrFluxes -0278 [~, I]=ismember(K,model.rxns); -0279 K(model.rev(I)~=0)=[]; -0280 -0281 %Constrain all reactions in the original model to have a flux -0282 allModels.lb(ismember(allModels.rxns,K))=0.1; -0283 -0284 newConnected=K; -0285 cannotConnect=setdiff(model.rxns(~originalFlux),newConnected); -0286 end -0287 -0288 %Then minimize for the number of fluxes used. The fixed rxns (model's own) -0289 %do not need to participate; see the comment above on why this is -0290 %identified by rxn ID rather than allModels.rxnFrom. -0291 templateRxns=find(~ismember(allModels.rxns,model.rxns)); -0292 [~, J, exitFlag]=getMinNrFluxes(allModels,templateRxns,params,allModels.rxnScores(templateRxns)); -0293 -0294 %Remove everything except for the added ones -0295 I=true(numel(allModels.rxns),1); -0296 I(templateRxns(J))=false; -0297 addedModel=removeReactions(allModels,I,true,true,true); -0298 -0299 newModel=mergeModels({model;addedModel},'metNames',true); -0300 addedRxns=setdiff(newModel.rxns,model.rxns); -0301 newModel=rmfield(newModel,'rxnScores'); -0302 end +0235 %Then check that the merged model is solveable +0236 sol=solveLP(allModels); +0237 if isempty(sol.f) +0238 EM='There are no reactions in the template model(s) that can make the model constraints satisfied'; +0239 error('RAVEN:badInput', '%s', EM); +0240 end +0241 +0242 %Remove dead ends for speed reasons. This has to be done here and +0243 %duplicate below because there is otherwise a risk that a reaction +0244 %which is constrained to something relevant is removed +0245 allModels=simplifyModel(allModels,false,false,false,true,false,false,false,[],true); +0246 allModels.c(:)=0; +0247 else +0248 %Remove dead ends for speed reasons +0249 allModels=simplifyModel(allModels,false,false,false,true,false,false,false,[],true); +0250 allModels.c(:)=0; +0251 +0252 %If model constraints should not be used, then determine which reactions +0253 %to force to have flux +0254 +0255 %Get the reactions that can carry flux in the original model +0256 originalFlux=haveFlux(model,1); +0257 +0258 %For the ones that cannot carry flux, see if they can do so in the +0259 %merged model. Reactions from model keep their id unchanged in +0260 %allModels (mergeModels renames colliding ids in the other input +0261 %models, never in model itself), so intersecting with allModels.rxns +0262 %both identifies them and drops any that simplifyModel has since +0263 %removed as dead ends. The previous strcmp(allModels.rxnFrom,model.id) +0264 %failed whenever model already carried a rxnFrom field of its own, as +0265 %getModelFromHomology output does, tagging each reaction with its +0266 %template rather than model.id, so mergeModels leaves it untouched and +0267 %it never matches model.id here. +0268 toCheck=intersect(model.rxns(~originalFlux),allModels.rxns); +0269 +0270 %Get the ones that still cannot carry flux. Guard against an empty +0271 %toCheck, since haveFlux interprets an empty reaction list as "all +0272 %reactions" rather than "none" +0273 if isempty(toCheck) +0274 K=toCheck; +0275 else +0276 I=haveFlux(allModels,1,toCheck); +0277 %Get the reactions that cannot carry flux in the original model, but can +0278 %in the merged one +0279 K=toCheck(I); +0280 end +0281 +0282 %This is a temporary thing to only look at the non-reversible rxns. +0283 %This is because all reversible rxns can have a flux in the +0284 %irreversible model format that is used by getMinNrFluxes +0285 [~, I]=ismember(K,model.rxns); +0286 K(model.rev(I)~=0)=[]; +0287 +0288 %Constrain all reactions in the original model to have a flux +0289 allModels.lb(ismember(allModels.rxns,K))=0.1; +0290 +0291 newConnected=K; +0292 cannotConnect=setdiff(model.rxns(~originalFlux),newConnected); +0293 end +0294 +0295 %Then minimize for the number of fluxes used. The fixed rxns (model's own) +0296 %do not need to participate; see the comment above on why this is +0297 %identified by rxn ID rather than allModels.rxnFrom. +0298 templateRxns=find(~ismember(allModels.rxns,model.rxns)); +0299 [~, J, exitFlag]=getMinNrFluxes(allModels,templateRxns,params,allModels.rxnScores(templateRxns)); +0300 +0301 %Remove everything except for the added ones +0302 I=true(numel(allModels.rxns),1); +0303 I(templateRxns(J))=false; +0304 addedModel=removeReactions(allModels,I,true,true,true); +0305 +0306 newModel=mergeModels({model;addedModel},'metNames',true); +0307 addedRxns=setdiff(newModel.rxns,model.rxns); +0308 newModel=rmfield(newModel,'rxnScores'); +0309 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/gapfilling/gapFillFastLP.html b/docs/html/gapfilling/gapFillFastLP.html index 16cf6d5a..af091c6e 100644 --- a/docs/html/gapfilling/gapFillFastLP.html +++ b/docs/html/gapfilling/gapFillFastLP.html @@ -70,7 +70,7 @@

    DESCRIPTION ^gapFillFastCore, gapFillSwiftCore, gapFillMILP, fillGaps + gapFillFastCore, gapFillSwiftCore, gapFillMILP, fillGaps

    CROSS-REFERENCE INFORMATION ^

    diff --git a/docs/html/gapfilling/gapFillMILP.html b/docs/html/gapfilling/gapFillMILP.html index f6f468e4..c851a3e6 100644 --- a/docs/html/gapfilling/gapFillMILP.html +++ b/docs/html/gapfilling/gapFillMILP.html @@ -24,23 +24,23 @@

    PURPOSE ^gapFillMILP Objective-based gap-filling via a global growth-floor MILP.

    SYNOPSIS ^

    -
    function [addedRxns, reversedRxns, newModel, exitFlag] = gapFillMILP(model, universalModel, varargin)
    +
    function [addedRxns, newModel, exitFlag] = gapFillMILP(model, universalModel, varargin)

    DESCRIPTION ^

     gapFillMILP  Objective-based gap-filling via a global growth-floor MILP.
     
    - Finds the minimum-cost set of modifications to make the model's objective
    - (biomass) >= minGrowth. Supports two repair mechanisms from Kumar et al.
    - 2007 (Bioinformatics 23:1626-1635):
    -   1. Directionality reversal of existing draft reactions (binary y_rev)
    -   2. Addition of reactions from a universal database (binary y_db)
    + Finds the smallest set of reactions from a universal database that makes
    + the model's objective (biomass) >= minGrowth, following the database
    + repair of Kumar et al. 2007 (BMC Bioinformatics 8:212,
    + doi:10.1186/1471-2105-8-212). Reactions already in the draft model keep
    + their bounds: a reaction with the wrong direction is a curation issue and
    + is not changed here.
     
      The universal model should contain all candidate reactions, including
      exchange and transport reactions if those repair classes are desired.
     
    - This is a SINGLE-level MILP (not bilevel). It is computationally more
    - expensive than gapFillFastLP but supports the directionality-reversal
    - repair mechanism and directly optimises for objective feasibility.
    + This is a single-level MILP. It is computationally more expensive than
    + gapFillFastLP but directly optimises for objective feasibility.
     
      Parameters
      ----------
    @@ -54,11 +54,6 @@ 

    DESCRIPTION ^DESCRIPTION ^gapFillFastLP, gapFillTopological, fillGaps

    @@ -97,21 +95,21 @@

    CROSS-REFERENCE INFORMATION ^
 
 
 <h2><a name=SOURCE CODE ^

    -
    0001 function [addedRxns, reversedRxns, newModel, exitFlag] = gapFillMILP(model, universalModel, varargin)
    +
    0001 function [addedRxns, newModel, exitFlag] = gapFillMILP(model, universalModel, varargin)
     0002 % gapFillMILP  Objective-based gap-filling via a global growth-floor MILP.
     0003 %
    -0004 % Finds the minimum-cost set of modifications to make the model's objective
    -0005 % (biomass) >= minGrowth. Supports two repair mechanisms from Kumar et al.
    -0006 % 2007 (Bioinformatics 23:1626-1635):
    -0007 %   1. Directionality reversal of existing draft reactions (binary y_rev)
    -0008 %   2. Addition of reactions from a universal database (binary y_db)
    -0009 %
    -0010 % The universal model should contain all candidate reactions, including
    -0011 % exchange and transport reactions if those repair classes are desired.
    -0012 %
    -0013 % This is a SINGLE-level MILP (not bilevel). It is computationally more
    -0014 % expensive than gapFillFastLP but supports the directionality-reversal
    -0015 % repair mechanism and directly optimises for objective feasibility.
    +0004 % Finds the smallest set of reactions from a universal database that makes
    +0005 % the model's objective (biomass) >= minGrowth, following the database
    +0006 % repair of Kumar et al. 2007 (BMC Bioinformatics 8:212,
    +0007 % doi:10.1186/1471-2105-8-212). Reactions already in the draft model keep
    +0008 % their bounds: a reaction with the wrong direction is a curation issue and
    +0009 % is not changed here.
    +0010 %
    +0011 % The universal model should contain all candidate reactions, including
    +0012 % exchange and transport reactions if those repair classes are desired.
    +0013 %
    +0014 % This is a single-level MILP. It is computationally more expensive than
    +0015 % gapFillFastLP but directly optimises for objective feasibility.
     0016 %
     0017 % Parameters
     0018 % ----------
    @@ -125,273 +123,230 @@ 

    SOURCE CODE ^% 'minGrowth' (default []) 0027 % minimum objective value to achieve. If empty, runs FBA on the merged 0028 % model and uses 10% of the maximum as the floor. -0029 % 'weights' (default [1 2 3 4]) -0030 % cost weights [w_rev w_db w_exch w_trans]. w_rev is the cost per -0031 % reversed draft reaction; w_db is the cost per added universal -0032 % reaction. w_exch and w_trans are reserved for future use when -0033 % exchange and transport reactions are tracked separately. -0034 % 'bigM' (default 1000) -0035 % Big-M constant for coupling constraints. Must be larger than any -0036 % expected flux magnitude. -0037 % 'params' (default []) -0038 % solver parameter struct passed to optimizeProb. Fields such as -0039 % params.TimeLimit and params.intTol can be used to tune the MILP. -0040 % 'verbose' (default true) -0041 % print progress messages. -0042 % -0043 % Returns -0044 % ------- -0045 % addedRxns : cell -0046 % reaction IDs from universalModel that are selected by the MILP. -0047 % reversedRxns : cell -0048 % draft reaction IDs whose directionality is reversed by the MILP. -0049 % newModel : struct -0050 % model with addedRxns incorporated and reversedRxns bounds updated -0051 % (lb set to -bigM for reversed reactions). -0052 % exitFlag : double -0053 % 1 = optimal, -1 = infeasible, -2 = timeout or other failure. -0054 % -0055 % See also -0056 % -------- -0057 % gapFillFastLP, gapFillTopological, fillGaps -0058 -0059 % ---- Parse optional arguments ---- -0060 p = inputParser(); -0061 addParameter(p, 'minGrowth', [], @(x) isempty(x) || isnumeric(x)); -0062 addParameter(p, 'weights', [1 2 3 4], @isnumeric); -0063 addParameter(p, 'bigM', 1000, @isnumeric); -0064 addParameter(p, 'params', [], @(x) isempty(x) || isstruct(x)); -0065 addParameter(p, 'verbose', true, @islogical); -0066 parse(p, varargin{:}); -0067 minGrowth = p.Results.minGrowth; -0068 weights = p.Results.weights; -0069 M = p.Results.bigM; -0070 params = p.Results.params; -0071 verbose = p.Results.verbose; -0072 -0073 w_rev = weights(1); -0074 w_db = weights(2); -0075 -0076 % ---- Initialise outputs ---- -0077 addedRxns = {}; -0078 reversedRxns = {}; -0079 newModel = model; -0080 exitFlag = -1; -0081 -0082 % Check that the model has an objective -0083 if all(model.c == 0) -0084 error('gapFillMILP: model has no objective function. Set model.c.'); -0085 end -0086 -0087 % ---- Merge draft + universal database ---- -0088 mergedModel = mergeModels({model; universalModel}); -0089 nMergedMets = numel(mergedModel.mets); -0090 nMergedRxns = numel(mergedModel.rxns); -0091 -0092 % Identify draft reactions in the merged model -0093 isDraft = ismember(mergedModel.rxns, model.rxns); -0094 isUniv = ~isDraft; -0095 -0096 % Universal reactions that can carry flux (non-trivially blocked) -0097 canCarry = (mergedModel.ub > 0 | mergedModel.lb < 0); -0098 isUniv = isUniv & canCarry; -0099 univIdx = find(isUniv); % positions in merged model -0100 nUniv = numel(univIdx); -0101 -0102 % Reversal candidates: draft reactions that are currently irreversible -0103 % (lb >= 0). Reversing their directionality means allowing negative flux. -0104 revCandMask = isDraft & mergedModel.lb >= 0; -0105 revCandIdx = find(revCandMask); % positions in merged model -0106 nRev = numel(revCandIdx); -0107 -0108 if verbose -0109 fprintf('gapFillMILP: merged model has %d mets, %d rxns (%d draft, %d universal).\n', ... -0110 nMergedMets, nMergedRxns, sum(isDraft), nUniv); -0111 fprintf('gapFillMILP: %d reversal candidates, %d database candidates.\n', nRev, nUniv); -0112 end -0113 -0114 % ---- Determine minGrowth ---- -0115 if isempty(minGrowth) -0116 % Run FBA on merged model (unconstrained) to get max growth. -0117 probFBA.A = mergedModel.S; -0118 probFBA.a = probFBA.A; -0119 probFBA.b = zeros(nMergedMets, 1); -0120 probFBA.csense = repmat('E', 1, nMergedMets); -0121 probFBA.c = -mergedModel.c; % negate: osense=1 minimises -0122 probFBA.osense = 1; -0123 probFBA.lb = mergedModel.lb; -0124 probFBA.ub = mergedModel.ub; -0125 probFBA.vartype = repmat('C', 1, nMergedRxns); -0126 solFBA = optimizeProb(probFBA, params, false); -0127 if ~checkSolution(solFBA) -0128 if verbose -0129 fprintf('gapFillMILP: FBA on merged model is infeasible — cannot gap-fill.\n'); -0130 end -0131 return; -0132 end -0133 maxGrowth = mergedModel.c' * solFBA.full(1:nMergedRxns); -0134 if maxGrowth <= 0 -0135 if verbose -0136 fprintf('gapFillMILP: merged model objective is non-positive (%.4g). Cannot gap-fill.\n', ... -0137 maxGrowth); -0138 end -0139 return; -0140 end -0141 minGrowth = 0.1 * maxGrowth; -0142 if verbose -0143 fprintf('gapFillMILP: setting minGrowth = %.4g (10%% of max %.4g).\n', minGrowth, maxGrowth); -0144 end -0145 end -0146 -0147 % ---- Build MILP ---- -0148 % Variable ordering: -0149 % cols 1 : nMergedRxns → v (all fluxes, continuous) -0150 % cols nMergedRxns+1 : +nRev → y_rev (binary, directionality reversal) -0151 % cols nMergedRxns+nRev+1 : end → y_db (binary, universal reactions) -0152 nVar = nMergedRxns + nRev + nUniv; +0029 % 'bigM' (default 1000) +0030 % Big-M constant for coupling constraints. Must be larger than any +0031 % expected flux magnitude. +0032 % 'params' (default []) +0033 % solver parameter struct passed to optimizeProb. Fields such as +0034 % params.TimeLimit and params.intTol can be used to tune the MILP. +0035 % 'verbose' (default true) +0036 % print progress messages. +0037 % +0038 % Returns +0039 % ------- +0040 % addedRxns : cell +0041 % reaction IDs from universalModel that are selected by the MILP. +0042 % newModel : struct +0043 % model with addedRxns incorporated. +0044 % exitFlag : double +0045 % 1 = optimal, -1 = infeasible, -2 = timeout or other failure. +0046 % +0047 % Notes +0048 % ----- +0049 % The MILP is solved with the solver set by setRavenSolver. RAVEN's bundled GLPK solves it for small +0050 % models (with a RAVEN:glpkMILP warning); for larger models Gurobi is much +0051 % faster and more reliable (setRavenSolver('gurobi')). +0052 % +0053 % See also +0054 % -------- +0055 % gapFillFastLP, gapFillTopological, fillGaps +0056 +0057 % ---- Parse optional arguments ---- +0058 p = inputParser(); +0059 addParameter(p, 'minGrowth', [], @(x) isempty(x) || isnumeric(x)); +0060 addParameter(p, 'bigM', 1000, @isnumeric); +0061 addParameter(p, 'params', [], @(x) isempty(x) || isstruct(x)); +0062 addParameter(p, 'verbose', true, @islogical); +0063 parse(p, varargin{:}); +0064 minGrowth = p.Results.minGrowth; +0065 M = p.Results.bigM; +0066 params = p.Results.params; +0067 verbose = p.Results.verbose; +0068 +0069 % ---- Initialise outputs ---- +0070 addedRxns = {}; +0071 newModel = model; +0072 exitFlag = -1; +0073 +0074 % Check that the model has an objective +0075 if all(model.c == 0) +0076 error('gapFillMILP: model has no objective function. Set model.c.'); +0077 end +0078 +0079 % ---- Merge draft + universal database ---- +0080 mergedModel = mergeModels({model; universalModel}); +0081 nMergedMets = numel(mergedModel.mets); +0082 nMergedRxns = numel(mergedModel.rxns); +0083 +0084 % Identify draft reactions in the merged model +0085 isDraft = ismember(mergedModel.rxns, model.rxns); +0086 isUniv = ~isDraft; +0087 +0088 % Universal reactions that can carry flux (non-trivially blocked) +0089 canCarry = (mergedModel.ub > 0 | mergedModel.lb < 0); +0090 isUniv = isUniv & canCarry; +0091 univIdx = find(isUniv); % positions in merged model +0092 nUniv = numel(univIdx); +0093 +0094 if verbose +0095 fprintf('gapFillMILP: merged model has %d mets, %d rxns (%d draft, %d universal).\n', ... +0096 nMergedMets, nMergedRxns, sum(isDraft), nUniv); +0097 fprintf('gapFillMILP: %d database candidates.\n', nUniv); +0098 end +0099 +0100 % ---- Determine minGrowth ---- +0101 if isempty(minGrowth) +0102 % Run FBA on merged model (unconstrained) to get max growth. +0103 probFBA.A = mergedModel.S; +0104 probFBA.a = probFBA.A; +0105 probFBA.b = zeros(nMergedMets, 1); +0106 probFBA.csense = repmat('E', 1, nMergedMets); +0107 probFBA.c = -mergedModel.c; % negate: osense=1 minimises +0108 probFBA.osense = 1; +0109 probFBA.lb = mergedModel.lb; +0110 probFBA.ub = mergedModel.ub; +0111 probFBA.vartype = repmat('C', 1, nMergedRxns); +0112 solFBA = optimizeProb(probFBA, params, false); +0113 if ~checkSolution(solFBA) +0114 if verbose +0115 fprintf('gapFillMILP: FBA on merged model is infeasible — cannot gap-fill.\n'); +0116 end +0117 return; +0118 end +0119 maxGrowth = mergedModel.c' * solFBA.full(1:nMergedRxns); +0120 if maxGrowth <= 0 +0121 if verbose +0122 fprintf('gapFillMILP: merged model objective is non-positive (%.4g). Cannot gap-fill.\n', ... +0123 maxGrowth); +0124 end +0125 return; +0126 end +0127 minGrowth = 0.1 * maxGrowth; +0128 if verbose +0129 fprintf('gapFillMILP: setting minGrowth = %.4g (10%% of max %.4g).\n', minGrowth, maxGrowth); +0130 end +0131 end +0132 +0133 % ---- Build MILP ---- +0134 % Variable ordering: +0135 % cols 1 : nMergedRxns → v (all fluxes, continuous) +0136 % cols nMergedRxns+1 : end → y_db (binary, universal reactions) +0137 nVar = nMergedRxns + nUniv; +0138 +0139 % --- Variable bounds --- +0140 lb = mergedModel.lb; +0141 ub = mergedModel.ub; +0142 +0143 % Universal reactions: allow them to be forced to 0 (extend lb toward 0) +0144 lb(univIdx) = min(mergedModel.lb(univIdx), 0); +0145 +0146 % Binary variable bounds +0147 lb = [lb; zeros(nUniv, 1)]; +0148 ub = [ub; ones(nUniv, 1)]; +0149 +0150 % --- Objective: number of added universal reactions --- +0151 c = zeros(nVar, 1); +0152 c(nMergedRxns + 1 : end) = 1; 0153 -0154 % --- Variable bounds --- -0155 lb = mergedModel.lb; -0156 ub = mergedModel.ub; +0154 % --- Constraint blocks --- +0155 % (1) Steady state: S * v = 0 [nMergedMets rows, 'E'] +0156 A_SS = [mergedModel.S, sparse(nMergedMets, nUniv)]; 0157 -0158 % Relax reversal candidates to allow negative flux (Big-M on lower bound) -0159 lb(revCandIdx) = -M; +0158 % (2) Growth floor: c' * v >= minGrowth [1 row, 'G'] +0159 A_growth = [sparse(mergedModel.c'), sparse(1, nUniv)]; 0160 -0161 % Universal reactions: allow them to be forced to 0 (extend lb toward 0) -0162 lb(univIdx) = min(mergedModel.lb(univIdx), 0); -0163 -0164 % Binary variable bounds -0165 lb = [lb; zeros(nRev + nUniv, 1)]; -0166 ub = [ub; ones(nRev + nUniv, 1)]; -0167 -0168 % --- Objective --- -0169 c = zeros(nVar, 1); -0170 c(nMergedRxns + 1 : nMergedRxns + nRev) = w_rev; % y_rev cost -0171 c(nMergedRxns + nRev + 1 : end) = w_db; % y_db cost -0172 -0173 % --- Constraint blocks --- -0174 % (1) Steady state: S * v = 0 [nMergedMets rows, 'E'] -0175 A_SS = [mergedModel.S, sparse(nMergedMets, nRev + nUniv)]; -0176 -0177 % (2) Growth floor: c' * v >= minGrowth [1 row, 'G'] -0178 A_growth = [sparse(mergedModel.c'), sparse(1, nRev + nUniv)]; -0179 -0180 % (3) Universal upper coupling: v_k - ub_k * y_db_k <= 0 [nUniv rows, 'L'] -0181 % (forces v_k <= 0 when y_db_k = 0) -0182 % Coupling coefficients are capped at the Big-M constant: an unbounded -0183 % universal reaction (ub = Inf, RAVEN's own default when no upper-bound -0184 % annotation is set) would otherwise put an Inf coefficient into the -0185 % constraint matrix, and Inf*0 (y_db_k = 0) is NaN, not 0. -0186 yDbCols = nMergedRxns + nRev + (1:nUniv); -0187 uniUb = min(mergedModel.ub(univIdx), M); -0188 uniLb = max(mergedModel.lb(univIdx), -M); -0189 A_uniUp = sparse(... -0190 [1:nUniv, 1:nUniv], ... -0191 [univIdx(:)', yDbCols], ... -0192 [ones(1, nUniv), -uniUb'], ... -0193 nUniv, nVar); -0194 -0195 % (4) Universal lower coupling: v_k - lb_k * y_db_k >= 0 [nUniv rows, 'G'] -0196 % (forces v_k >= 0 when y_db_k = 0; together with (3): v_k = 0 when y_db_k = 0) -0197 A_uniLo = sparse(... -0198 [1:nUniv, 1:nUniv], ... -0199 [univIdx(:)', yDbCols], ... -0200 [ones(1, nUniv), -uniLb'], ... -0201 nUniv, nVar); +0161 % (3) Universal upper coupling: v_k - ub_k * y_db_k <= 0 [nUniv rows, 'L'] +0162 % (forces v_k <= 0 when y_db_k = 0) +0163 % Coupling coefficients are capped at the Big-M constant: an unbounded +0164 % universal reaction (ub = Inf, RAVEN's own default when no upper-bound +0165 % annotation is set) would otherwise put an Inf coefficient into the +0166 % constraint matrix, and Inf*0 (y_db_k = 0) is NaN, not 0. +0167 yDbCols = nMergedRxns + (1:nUniv); +0168 uniUb = min(mergedModel.ub(univIdx), M); +0169 uniLb = max(mergedModel.lb(univIdx), -M); +0170 A_uniUp = sparse(... +0171 [1:nUniv, 1:nUniv], ... +0172 [univIdx(:)', yDbCols], ... +0173 [ones(1, nUniv), -uniUb'], ... +0174 nUniv, nVar); +0175 +0176 % (4) Universal lower coupling: v_k - lb_k * y_db_k >= 0 [nUniv rows, 'G'] +0177 % (forces v_k >= 0 when y_db_k = 0; together with (3): v_k = 0 when y_db_k = 0) +0178 A_uniLo = sparse(... +0179 [1:nUniv, 1:nUniv], ... +0180 [univIdx(:)', yDbCols], ... +0181 [ones(1, nUniv), -uniLb'], ... +0182 nUniv, nVar); +0183 +0184 % --- Assemble --- +0185 prob.A = [A_SS; A_growth; A_uniUp; A_uniLo]; +0186 prob.a = prob.A; +0187 prob.b = [zeros(nMergedMets, 1); minGrowth; zeros(2 * nUniv, 1)]; +0188 prob.csense = [repmat('E', 1, nMergedMets), 'G', ... +0189 repmat('L', 1, nUniv), ... +0190 repmat('G', 1, nUniv)]; +0191 prob.c = c; +0192 prob.osense = 1; % minimise the number of added reactions +0193 prob.lb = lb; +0194 prob.ub = ub; +0195 prob.vartype = repmat('C', 1, nVar); +0196 prob.vartype(nMergedRxns + 1 : end) = 'B'; % y_db is binary +0197 +0198 if isempty(params) +0199 params.intTol = 1e-9; +0200 params.TimeLimit = 600; +0201 end 0202 -0203 % (5) Reversal coupling: v_j + M * y_rev_j >= 0 [nRev rows, 'G'] -0204 % (forces v_j >= 0 when y_rev_j = 0, i.e., preserves original irreversibility) -0205 yRevCols = nMergedRxns + (1:nRev); -0206 A_rev = sparse(... -0207 [1:nRev, 1:nRev], ... -0208 [revCandIdx(:)', yRevCols], ... -0209 [ones(1, nRev), M * ones(1, nRev)], ... -0210 nRev, nVar); -0211 -0212 % --- Assemble --- -0213 prob.A = [A_SS; A_growth; A_uniUp; A_uniLo; A_rev]; -0214 prob.a = prob.A; -0215 prob.b = [zeros(nMergedMets, 1); minGrowth; ... -0216 zeros(nUniv, 1); zeros(nUniv, 1); zeros(nRev, 1)]; -0217 prob.csense = [repmat('E', 1, nMergedMets), 'G', ... -0218 repmat('L', 1, nUniv), ... -0219 repmat('G', 1, nUniv + nRev)]; -0220 prob.c = c; -0221 prob.osense = 1; % minimise weighted sum of binary variables -0222 prob.lb = lb; -0223 prob.ub = ub; -0224 prob.vartype = repmat('C', 1, nVar); -0225 prob.vartype(nMergedRxns + 1 : end) = 'B'; % y_rev and y_db are binary -0226 -0227 if isempty(params) -0228 params.intTol = 1e-9; -0229 params.TimeLimit = 600; -0230 end -0231 -0232 % ---- Solve ---- -0233 if verbose -0234 fprintf('gapFillMILP: solving MILP (%d vars, %d binary, %d constraints)...\n', ... -0235 nVar, nRev + nUniv, size(prob.A, 1)); -0236 end -0237 sol = optimizeProb(prob, params, verbose); -0238 -0239 if ~checkSolution(sol) -0240 if verbose -0241 fprintf('gapFillMILP: MILP infeasible or timed out.\n'); -0242 end -0243 exitFlag = -1; -0244 return; -0245 end -0246 -0247 % ---- Extract solution ---- -0248 binThreshold = 1e-3; -0249 yRev = sol.full(nMergedRxns + 1 : nMergedRxns + nRev); -0250 yDb = sol.full(nMergedRxns + nRev + 1 : end); -0251 -0252 revSelected = revCandIdx(yRev > binThreshold); -0253 dbSelected = univIdx(yDb > binThreshold); -0254 -0255 % Map back to original IDs. dbSelected is already restricted to universal -0256 % reactions via univIdx/isDraft above (an id-membership test against -0257 % model.rxns, not a name it could have been renamed away from), so no -0258 % further filtering against universalModel.rxns is needed here -- doing so -0259 % would silently drop a universal reaction that mergeModels renamed -0260 % because its id collided with one already in the draft model. -0261 reversedRxns = mergedModel.rxns(revSelected); -0262 addedRxns = mergedModel.rxns(dbSelected); -0263 -0264 if verbose -0265 fprintf('gapFillMILP: reversed %d draft reaction(s), added %d universal reaction(s).\n', ... -0266 numel(reversedRxns), numel(addedRxns)); -0267 end -0268 -0269 % ---- Build new model ---- -0270 newModel = model; -0271 -0272 % Apply directionality reversals: allow negative flux -0273 for k = 1:numel(reversedRxns) -0274 rxnPos = find(getIndexes(newModel, reversedRxns(k), 'rxns', true)); -0275 if ~isempty(rxnPos) -0276 newModel.lb(rxnPos) = -M; -0277 newModel.rev(rxnPos) = 1; -0278 end -0279 end -0280 -0281 % Add universal reactions. Extracted from mergedModel by the dbSelected -0282 % indices directly, not by looking addedRxns' names back up in -0283 % universalModel: that lookup fails whenever mergeModels renamed one of -0284 % them because its id collided with one already in the draft model, in -0285 % which case setdiff would keep the (unrenamed) original in universalModel -0286 % marked for removal, silently dropping the very reaction just selected. -0287 if ~isempty(dbSelected) -0288 toRemove = true(nMergedRxns, 1); -0289 toRemove(dbSelected) = false; -0290 trimmedUniv = removeReactions(mergedModel, toRemove); -0291 newModel = mergeModels({newModel; trimmedUniv}); -0292 end -0293 -0294 exitFlag = 1; -0295 end

    +0203 % ---- Solve ---- +0204 if verbose +0205 fprintf('gapFillMILP: solving MILP (%d vars, %d binary, %d constraints)...\n', ... +0206 nVar, nUniv, size(prob.A, 1)); +0207 end +0208 sol = optimizeProb(prob, params, verbose); +0209 +0210 if ~checkSolution(sol) +0211 if verbose +0212 fprintf('gapFillMILP: MILP infeasible or timed out.\n'); +0213 end +0214 exitFlag = -1; +0215 return; +0216 end +0217 +0218 % ---- Extract solution ---- +0219 binThreshold = 1e-3; +0220 yDb = sol.full(nMergedRxns + 1 : end); +0221 dbSelected = univIdx(yDb > binThreshold); +0222 +0223 % Map back to original IDs. dbSelected is already restricted to universal +0224 % reactions via univIdx/isDraft above (an id-membership test against +0225 % model.rxns, not a name it could have been renamed away from), so no +0226 % further filtering against universalModel.rxns is needed here -- doing so +0227 % would silently drop a universal reaction that mergeModels renamed +0228 % because its id collided with one already in the draft model. +0229 addedRxns = mergedModel.rxns(dbSelected); +0230 +0231 if verbose +0232 fprintf('gapFillMILP: added %d universal reaction(s).\n', numel(addedRxns)); +0233 end +0234 +0235 % ---- Build new model ---- +0236 newModel = model; +0237 +0238 % Add universal reactions. Extracted from mergedModel by the dbSelected +0239 % indices directly, not by looking addedRxns' names back up in +0240 % universalModel: that lookup fails whenever mergeModels renamed one of +0241 % them because its id collided with one already in the draft model, in +0242 % which case setdiff would keep the (unrenamed) original in universalModel +0243 % marked for removal, silently dropping the very reaction just selected. +0244 if ~isempty(dbSelected) +0245 toRemove = true(nMergedRxns, 1); +0246 toRemove(dbSelected) = false; +0247 trimmedUniv = removeReactions(mergedModel, toRemove); +0248 newModel = mergeModels({newModel; trimmedUniv}); +0249 end +0250 +0251 exitFlag = 1; +0252 end

    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/gapfilling/gapFillTopological.html b/docs/html/gapfilling/gapFillTopological.html index 1efacfb2..978acda2 100644 --- a/docs/html/gapfilling/gapFillTopological.html +++ b/docs/html/gapfilling/gapFillTopological.html @@ -79,7 +79,7 @@

    DESCRIPTION ^gapFillFastLP, gapFillMILP, fillGaps + gapFillFastLP, gapFillMILP, fillGaps

    CROSS-REFERENCE INFORMATION ^

    diff --git a/docs/html/gapfilling/gapReport.html b/docs/html/gapfilling/gapReport.html index e41dec3e..f4c84ee8 100644 --- a/docs/html/gapfilling/gapReport.html +++ b/docs/html/gapfilling/gapReport.html @@ -80,6 +80,14 @@

    DESCRIPTION ^CROSS-REFERENCE INFORMATION ^
 </ul>
 <!-- crossreference -->
 
-
+<h2><a name=SUBFUNCTIONS ^

    +

    SOURCE CODE ^

    0001 function [noFluxRxns, noFluxRxnsRelaxed, subGraphs, notProducedMets, minToConnect,...
    @@ -155,85 +165,115 @@ 

    SOURCE CODE ^% the reactions that were added from the template models and which 0055 % model they were added from. 0056 % -0057 % Examples -0058 % -------- -0059 % [noFluxRxns, noFluxRxnsRelaxed, subGraphs, notProducedMets, ... -0060 % minToConnect, neededForProductionMat, connectedFromTemplates, ... -0061 % addedFromTemplates] = gapReport(model, templateModels); -0062 -0063 p=parseRAVENargs(varargin, {'templateModels',[]}); -0064 templateModels=p.templateModels; -0065 -0066 if isempty(templateModels) -0067 templateModels=[]; -0068 connectedFromTemplates=[]; -0069 addedFromTemplates=[]; -0070 end -0071 -0072 %Escapes model text before it is spliced into an fprintf template, since -0073 %a literal "%" in e.g. a model id or metabolite name would otherwise be -0074 %misread as a format directive and swallow the remainder of the line. -0075 esc=@(s) strrep(s,'%','%%'); -0076 -0077 fprintf(['Gap analysis for ' esc(model.id) ' - ' esc(model.name) '\n\n']); -0078 if isfield(model,'unconstrained') -0079 calculateINOUT=true; -0080 closedModel=model; -0081 model=simplifyModel(model); -0082 else -0083 canConsumeWithoutOutput={}; -0084 canProduceWithoutInput={}; -0085 calculateINOUT=false; -0086 end -0087 -0088 model2=model; -0089 model2.b=[model2.b inf(numel(model2.mets),1)]; -0090 I=haveFlux(model); -0091 noFluxRxns=model.rxns(~I); -0092 J=haveFlux(model2); -0093 noFluxRxnsRelaxed=model2.rxns(~J); -0094 bModel=removeReactions(model,~I,true,true); -0095 cModel=removeReactions(model2,~J,true,true); -0096 fprintf('***Overview\n'); -0097 fprintf([num2str(numel(model.rxns)-sum(I)) ' out of ' num2str(numel(model.rxns))... -0098 ' reactions cannot carry flux (' num2str(numel(model.rxns)-sum(J)) ' if net production of all metabolites is allowed)\n']); -0099 fprintf([num2str(numel(model.mets)-numel(bModel.mets)) ' out of ' num2str(numel(model.mets))... -0100 ' metabolites are unreachable (' num2str(numel(model.mets)-numel(cModel.mets)) ' if net production of all metabolites is allowed)\n']); -0101 -0102 fprintf('\n***Isolated subnetworks\n'); -0103 subGraphs=getAllSubGraphs(model); -0104 fprintf(['A total of ' num2str(size(subGraphs,2)) ' isolated sub-networks are present in the model\n']); -0105 for i=1:size(subGraphs,2) -0106 fprintf(['\t' num2str(i) '. ' num2str(sum(subGraphs(:,i))) ' metabolites\n']); -0107 end -0108 -0109 fprintf('\n***Metabolite connectivity\n'); -0110 [notProducedMets, ~, neededForProductionMat,minToConnect]=checkProduction(model,true,model.comps,false); -0111 fprintf(['To enable net production of all metabolites, a total of ' num2str(numel(minToConnect)) ' metabolites must be connected\n']); -0112 fprintf('Top 10 metabolites to connect:\n'); -0113 for i=1:min(10,numel(minToConnect)) -0114 fprintf(['\t' num2str(i) '. ' esc(minToConnect{i}) '\n']); -0115 end -0116 -0117 if calculateINOUT==true -0118 fprintf('\n***Mass balancing\n'); -0119 produced=canExchange(closedModel,'produce'); -0120 canProduceWithoutInput=closedModel.mets(produced); -0121 consumed=canExchange(closedModel,'consume'); -0122 canConsumeWithoutOutput=closedModel.mets(consumed); -0123 fprintf([num2str(numel(canConsumeWithoutOutput)) ' metabolites could be consumed without any outputs\n' num2str(numel(canProduceWithoutInput)) ' metabolites could be produced without any inputs\n']); -0124 end -0125 -0126 if ~isempty(templateModels) -0127 fprintf('\n***Automated gap-filling\n'); -0128 [connectedFromTemplates, ~, addedFromTemplates]=fillGaps(model,templateModels); -0129 t=esc(templateModels{1}.id); -0130 for i=2:numel(templateModels) -0131 t=[t ', ' esc(templateModels{i}.id)]; -0132 end -0133 fprintf([num2str(numel(connectedFromTemplates)) ' unconnected reactions can be connected by including ' num2str(numel(addedFromTemplates)) ' reactions from\n' t '\n']); -0134 end -0135 end

    +0057 % Notes +0058 % ----- +0059 % The model must be feasible under its own bounds, and, when the mass +0060 % balancing section runs, also with its boundary closed. Otherwise +0061 % gapReport stops with a RAVEN:infeasibleModel error that names the +0062 % reactions with a forced flux; set the bounds of ATP maintenance and any +0063 % other forced flux to zero first. +0064 % +0065 % Examples +0066 % -------- +0067 % [noFluxRxns, noFluxRxnsRelaxed, subGraphs, notProducedMets, ... +0068 % minToConnect, neededForProductionMat, connectedFromTemplates, ... +0069 % addedFromTemplates] = gapReport(model, templateModels); +0070 +0071 p=parseRAVENargs(varargin, {'templateModels',[]}); +0072 templateModels=p.templateModels; +0073 +0074 if isempty(templateModels) +0075 templateModels=[]; +0076 connectedFromTemplates=[]; +0077 addedFromTemplates=[]; +0078 end +0079 +0080 %Escapes model text before it is spliced into an fprintf template, since +0081 %a literal "%" in e.g. a model id or metabolite name would otherwise be +0082 %misread as a format directive and swallow the remainder of the line. +0083 esc=@(s) strrep(s,'%','%%'); +0084 +0085 fprintf(['Gap analysis for ' esc(model.id) ' - ' esc(model.name) '\n\n']); +0086 if isfield(model,'unconstrained') +0087 calculateINOUT=true; +0088 closedModel=model; +0089 model=simplifyModel(model); +0090 else +0091 canConsumeWithoutOutput={}; +0092 canProduceWithoutInput={}; +0093 calculateINOUT=false; +0094 end +0095 +0096 requireFeasible(model,'under its own bounds'); +0097 if calculateINOUT +0098 requireFeasible(closedModel,'once its boundary reactions are closed'); +0099 end +0100 +0101 model2=model; +0102 model2.b=[model2.b inf(numel(model2.mets),1)]; +0103 I=haveFlux(model); +0104 noFluxRxns=model.rxns(~I); +0105 J=haveFlux(model2); +0106 noFluxRxnsRelaxed=model2.rxns(~J); +0107 bModel=removeReactions(model,~I,true,true); +0108 cModel=removeReactions(model2,~J,true,true); +0109 fprintf('***Overview\n'); +0110 fprintf([num2str(numel(model.rxns)-sum(I)) ' out of ' num2str(numel(model.rxns))... +0111 ' reactions cannot carry flux (' num2str(numel(model.rxns)-sum(J)) ' if net production of all metabolites is allowed)\n']); +0112 fprintf([num2str(numel(model.mets)-numel(bModel.mets)) ' out of ' num2str(numel(model.mets))... +0113 ' metabolites are unreachable (' num2str(numel(model.mets)-numel(cModel.mets)) ' if net production of all metabolites is allowed)\n']); +0114 +0115 fprintf('\n***Isolated subnetworks\n'); +0116 subGraphs=getAllSubGraphs(model); +0117 fprintf(['A total of ' num2str(size(subGraphs,2)) ' isolated sub-networks are present in the model\n']); +0118 for i=1:size(subGraphs,2) +0119 fprintf(['\t' num2str(i) '. ' num2str(sum(subGraphs(:,i))) ' metabolites\n']); +0120 end +0121 +0122 fprintf('\n***Metabolite connectivity\n'); +0123 [notProducedMets, ~, neededForProductionMat,minToConnect]=checkProduction(model,true,model.comps,false); +0124 fprintf(['To enable net production of all metabolites, a total of ' num2str(numel(minToConnect)) ' metabolites must be connected\n']); +0125 fprintf('Top 10 metabolites to connect:\n'); +0126 for i=1:min(10,numel(minToConnect)) +0127 fprintf(['\t' num2str(i) '. ' esc(minToConnect{i}) '\n']); +0128 end +0129 +0130 if calculateINOUT==true +0131 fprintf('\n***Mass balancing\n'); +0132 produced=canExchange(closedModel,'produce'); +0133 canProduceWithoutInput=closedModel.mets(produced); +0134 consumed=canExchange(closedModel,'consume'); +0135 canConsumeWithoutOutput=closedModel.mets(consumed); +0136 fprintf([num2str(numel(canConsumeWithoutOutput)) ' metabolites could be consumed without any outputs\n' num2str(numel(canProduceWithoutInput)) ' metabolites could be produced without any inputs\n']); +0137 end +0138 +0139 if ~isempty(templateModels) +0140 fprintf('\n***Automated gap-filling\n'); +0141 [connectedFromTemplates, ~, addedFromTemplates]=fillGaps(model,templateModels); +0142 t=esc(templateModels{1}.id); +0143 for i=2:numel(templateModels) +0144 t=[t ', ' esc(templateModels{i}.id)]; +0145 end +0146 fprintf([num2str(numel(connectedFromTemplates)) ' unconnected reactions can be connected by including ' num2str(numel(addedFromTemplates)) ' reactions from\n' t '\n']); +0147 end +0148 end +0149 +0150 function requireFeasible(model,condition) +0151 %Errors, naming the forced-flux reactions, if the model is infeasible +0152 model.c(:)=0; +0153 sol=solveLP(model); +0154 if sol.stat~=-1 +0155 return +0156 end +0157 forced=model.rxns(model.lb>0 | model.ub<0); +0158 hint=''; +0159 if ~isempty(forced) +0160 hint=[' Reactions with a forced flux: ' strjoin(forced,', ') '.']; +0161 end +0162 error('RAVEN:infeasibleModel',['The model is infeasible ' condition ... +0163 ', so gapReport cannot analyse it.' strrep(hint,'%','%%') ... +0164 ' Set the bounds of ATP maintenance and any other forced flux to zero first.']); +0165 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/io/readYAMLmodel.html b/docs/html/io/readYAMLmodel.html index a858c2cc..631a0cb3 100644 --- a/docs/html/io/readYAMLmodel.html +++ b/docs/html/io/readYAMLmodel.html @@ -124,835 +124,837 @@

    SOURCE CODE ^% A continuation line is recognised by NOT starting with '- ': every real 0059 % entry in this format, at every nesting depth (including a nested list 0060 % under a key, e.g. an annotation sub-entry indented 6+ spaces), is itself -0061 % introduced with a literal leading dash, while a folded scalar's -0062 % continuation is raw text with no list-item marker of its own. -0063 newLine=regexp(line_raw,'^ {6,}([^-\s].*)','tokenExtents'); -0064 brokenLine=find(~cellfun('isempty',newLine)); -0065 for i=flip(1:numel(brokenLine)) -0066 extraLine = char(line_raw(brokenLine(i))); -0067 extraLine = extraLine(newLine{brokenLine(i)}{1}(1):end); -0068 % deblank, not strtrim: only the trailing whitespace is spurious (e.g. -0069 % "- inchis: " with nothing after the colon on this line); the leading -0070 % indentation must survive untouched for the key-detection regex below. -0071 line_raw{brokenLine(i)-1} = strjoin({deblank(line_raw{brokenLine(i)-1}),extraLine},' '); -0072 end -0073 line_raw(brokenLine)=[]; -0074 -0075 line_key = regexprep(line_raw,'^ *-? ([^:]+)(:)($| .*)','$1'); -0076 line_key = regexprep(line_key,'(.*!!omap)|(---)|( {4,}.*)',''); -0077 -0078 line_value = regexprep(line_raw, '.*:$',''); -0079 % Captures everything after "key: " verbatim, quotes and all; any wrapping -0080 % quotes are left in place here and removed next by the anchored-pair -0081 % strip below, which handles both quote styles. -0082 line_value = regexprep(line_value, '[^":]+: (.+)$','$1'); -0083 % The list-marker strip must run before quote detection below, not after: -0084 % a quoted *list item* (e.g. a pubmed id under an annotation block, -0085 % ` - "10714900"`) still has its leading "- " attached at this -0086 % point, so the anchored ^".*"$/^'.*'$ checks --- which require the quote -0087 % to be the very first character --- would never match it, leaving the -0088 % quotes on the value uninspected and unescaped. Stripping the marker -0089 % first means a quoted list item's value starts directly with the quote -0090 % character, the same shape as a quoted `key: "value"` scalar, so the -0091 % existing anchored logic below already handles both without further -0092 % changes. -0093 line_value = regexprep(line_value, '^ {4,}- ',''); -0094 % Strips wrapping quotes, either style, but only a matching pair anchored -0095 % at both ends of the value --- not every quote character in it. Some -0096 % writers single-quote anything YAML requires quoting (a value that would -0097 % otherwise parse as a date/number/boolean, or one containing a character -0098 % with special meaning), where RAVEN's own writer only ever double-quotes, -0099 % so both styles must be handled here. Anchoring (rather than a global -0100 % strip) also preserves a genuine apostrophe in an unquoted value, e.g. -0101 % "yeast's hexokinase reaction" --- only the wrapping pair is a delimiter, -0102 % not every quote-like character. -0103 wasSingleQuoted = ~cellfun('isempty', regexp(line_value, '^''.*''$', 'once')); -0104 wasDoubleQuoted = ~cellfun('isempty', regexp(line_value, '^".*"$', 'once')); -0105 line_value = regexprep(line_value, '^(["''])(.*)\1$','$2'); -0106 % Undo each quote style's own escaping so the reader recovers the exact -0107 % original text: single-quoted YAML doubles a literal apostrophe ('' -> '); -0108 % double-quoted YAML backslash-escapes a literal backslash and double quote -0109 % (\\ -> \, \" -> "). The backslash pass goes through a sentinel character -0110 % first so an escaped backslash immediately followed by an escaped quote -0111 % (\\\" in the file, a literal \" in the value) round-trips correctly -0112 % instead of the second pass misreading the first's output. -0113 line_value(wasSingleQuoted) = strrep(line_value(wasSingleQuoted), '''''', ''''); -0114 line_value(wasDoubleQuoted) = strrep(line_value(wasDoubleQuoted), '\\', char(1)); -0115 line_value(wasDoubleQuoted) = strrep(line_value(wasDoubleQuoted), '\"', '"'); -0116 line_value(wasDoubleQuoted) = strrep(line_value(wasDoubleQuoted), char(1), '\'); -0117 line_value(strcmp(line_value,'''''')) = {''}; -0118 line_value(strcmp(line_value,line_raw)) = {''}; -0119 -0120 modelFields = {'id',char();... -0121 'name',char();... -0122 'description',char();... -0123 'version',char();... -0124 'date',char();... -0125 'annotation',struct();... -0126 'rxns',{};... -0127 'rxnNames',{};... -0128 'mets',{};... -0129 'metNames',{};... -0130 'S',sparse([]);... -0131 'lb',{};... %Changed to double in the end. -0132 'ub',{};... %Changed to double in the end. -0133 'rev',{};... %Changed to double in the end. -0134 'c',[];... -0135 'b',cell(0,0);... %Changed to double in the end. -0136 'genes',cell(0,0);... -0137 'grRules',cell(0,0);... -0138 'rxnGeneMat',sparse([]);... -0139 'rxnComps',cell(0,0);... %Changed to double in the end. -0140 'subSystems',cell(0,0);... -0141 'eccodes',cell(0,0);... -0142 'rxnMiriams',cell(0,0);... -0143 'rxnDeltaG',{};... %Changed to double in the end. -0144 'rxnNotes',cell(0,0);... -0145 'rxnReferences',cell(0,0);... -0146 'rxnConfidenceScores',cell(0,0);... -0147 'metComps',cell(0,0);... %Changed to double in the end. -0148 'inchis',cell(0,0);... -0149 'metSmiles',cell(0,0);... -0150 'metFormulas',cell(0,0);... -0151 'metMiriams',cell(0,0);... -0152 'metDeltaG',{};... %Changed to double in the end. -0153 'metCharges',cell(0,0);... %Changed to double in the end. -0154 'metNotes',cell(0,0);... -0155 'comps',cell(0,0);... -0156 'compNames',cell(0,0);... -0157 'compOutside',cell(0,0);... -0158 'compMiriams',cell(0,0);... -0159 'geneComps',cell(0,0);... %Changed to double in the end. -0160 'geneMiriams',cell(0,0);... -0161 'geneShortNames',cell(0,0);... -0162 'proteins',cell(0,0);... -0163 'unconstrained',cell(0,0);... %Changed to double in the end. -0164 'metFrom',cell(0,0);... -0165 'rxnFrom',cell(0,0)}; -0166 for i=1:size(modelFields,1) -0167 model.(modelFields{i,1})=modelFields{i,2}; -0168 end -0169 -0170 % If GECKO model — accept both the legacy `geckoLight` (inside metaData) -0171 % and the alternate top-level `gecko_light` key. -0172 if any(contains(line_key,'geckoLight')) || any(contains(line_key,'gecko_light')) -0173 isGECKO=true; -0174 ecFields = {'geckoLight', false;... -0175 'rxns', {};... -0176 'kcat', {};... -0177 'source', cell(0,0);... -0178 'notes', cell(0,0);... -0179 'eccodes', cell(0,0);... -0180 'genes', cell(0,0);... -0181 'enzymes', cell(0,0);... -0182 'mw', cell(0,0);... -0183 'sequence', cell(0,0);... -0184 'concs', cell(0,0);... -0185 'rxnEnzMat', []}; -0186 for i=1:size(ecFields,1) -0187 model.ec.(ecFields{i,1})=ecFields{i,2}; -0188 end -0189 ecGecko=cell(25000,2); ecGeckoNo=1; -0190 enzStoich=cell(100000,3); enzStoichNo=1; -0191 else -0192 isGECKO=false; -0193 end -0194 -0195 section = 0; -0196 metMiriams=cell(100000,3); metMirNo=1; -0197 rxnMiriams=cell(100000,3); rxnMirNo=1; -0198 geneMiriams=cell(100000,3); genMirNo=1; -0199 compMiriams=cell(1000,3); compMirNo=1; -0200 subSystems=cell(100000,2); subSysNo=1; -0201 eccodes=cell(100000,2); ecCodeNo=1; -0202 equations=cell(100000,3); equatiNo=1; -0203 -0204 for i=1:numel(line_key) -0205 tline_raw = line_raw{i}; -0206 tline_key = line_key{i}; -0207 tline_value = line_value{i}; -0208 % import different sections — accept the !!omap-tagged variant as -0209 % well, so a file using that tag is recognized too. -0210 switch tline_raw -0211 case {'- metaData:', '- metaData: !!omap'} -0212 section = 1; -0213 if verbose -0214 fprintf('\t%d\n', section); -0215 end -0216 continue % Go to next line -0217 case '- metabolites:' -0218 section = 2; -0219 if verbose -0220 fprintf('\t%d\n', section); -0221 end -0222 pos=0; -0223 continue -0224 case '- reactions:' -0225 section = 3; -0226 if verbose -0227 fprintf('\t%d\n', section); -0228 end -0229 pos=0; -0230 continue -0231 case '- genes:' -0232 section = 4; -0233 if verbose -0234 fprintf('\t%d\n', section); -0235 end -0236 pos=0; -0237 continue -0238 case '- genes: []' -0239 % Flow-style empty list: a gene-less model. No entries follow, -0240 % so there is nothing to switch into --- the next recognised -0241 % header is matched fresh on the next iteration regardless of -0242 % what `section` is left as. -0243 continue -0244 case '- compartments: !!omap' -0245 section = 5; -0246 if verbose -0247 fprintf('\t%d\n', section); -0248 end -0249 pos=0; -0250 continue -0251 case '- ec-rxns:' -0252 section = 6; -0253 if verbose -0254 fprintf('\t%d\n', section); -0255 end -0256 pos=0; -0257 continue -0258 case '- ec-enzymes:' -0259 section = 7; -0260 if verbose -0261 fprintf('\t%d\n', section); -0262 end -0263 pos=0; -0264 continue -0265 end -0266 -0267 % Alternate root-level keys (id, name, version, gecko_light), written -0268 % at the top level with no metaData section. RAVEN's own writer -0269 % normally puts them inside metaData, but we accept both layouts so a -0270 % YAML file using the top-level convention can be ingested directly. -0271 if isempty(regexp(tline_raw, '^ {2,}', 'once')) -0272 switch tline_key -0273 case 'id' -0274 if isempty(model.id), model.id = tline_value; end -0275 continue -0276 case 'name' -0277 if isempty(model.name), model.name = tline_value; end -0278 continue -0279 case 'version' -0280 if ~isfield(model,'version') || isempty(model.version) -0281 model.version = tline_value; -0282 end -0283 continue -0284 case 'gecko_light' -0285 if isGECKO && strcmp(tline_value,'true') -0286 model.ec.geckoLight = true; -0287 end -0288 continue -0289 end -0290 end -0291 -0292 % skip over empty keys -0293 if isempty(tline_raw) || (isempty(tline_key) && contains(tline_raw,'!!omap')) -0294 continue; -0295 end -0296 -0297 % import metaData -0298 if section == 1 -0299 switch tline_key -0300 case {'short_name','id'} %short_name used by human-GEM -0301 model.id = tline_value; -0302 case 'name' -0303 model.name = tline_value; -0304 case 'full_name' %used by human-GEM -0305 model.description = tline_value; -0306 case 'version' -0307 model.version = tline_value; -0308 case 'date' -0309 model.date = tline_value; -0310 case 'taxonomy' -0311 model.annotation.taxonomy = tline_value; -0312 case {'description','note'} %description used by human-GEM -0313 model.annotation.note = tline_value; -0314 case 'github' -0315 model.annotation.sourceUrl = tline_value; -0316 case 'sourceUrl' -0317 model.annotation.sourceUrl = tline_value; -0318 case 'givenName' -0319 model.annotation.givenName = tline_value; -0320 case 'familyName' -0321 model.annotation.familyName = tline_value; -0322 case 'authors' -0323 model.annotation.authorList = tline_value; -0324 case 'email' -0325 model.annotation.email = tline_value; -0326 case 'organization' -0327 model.annotation.organization = tline_value; -0328 case {'geckoLight','gecko_light'} -0329 if strcmp(tline_value,'true') -0330 model.ec.geckoLight = true; -0331 end -0332 otherwise -0333 % Caller-specific provenance key with no RAVEN-defined slot -0334 % (e.g. a tool-specific `_version` key): preserve it instead -0335 % of silently dropping it, so a write/read round trip is -0336 % lossless. Mirrors writeYAMLmodel.m's symmetric fallback, -0337 % which emits any model.annotation field not in its own -0338 % fixed list. -0339 if isvarname(tline_key) -0340 model.annotation.(tline_key) = tline_value; -0341 end -0342 end; continue -0343 end -0344 -0345 % import metabolites: -0346 if section == 2 -0347 switch tline_key -0348 case 'id' -0349 pos = pos + 1; -0350 model = readFieldValue(model, 'mets', tline_value,pos); -0351 readList=''; miriamKey=''; -0352 case 'name' -0353 model = readFieldValue(model, 'metNames', tline_value, pos); -0354 readList=''; miriamKey=''; -0355 case 'compartment' -0356 model = readFieldValue(model, 'metComps', tline_value, pos); -0357 readList=''; miriamKey=''; -0358 case 'formula' -0359 model = readFieldValue(model, 'metFormulas', tline_value, pos); -0360 readList=''; miriamKey=''; -0361 case 'charge' -0362 model = readFieldValue(model, 'metCharges', tline_value, pos); -0363 readList=''; miriamKey=''; -0364 case 'notes' -0365 model = readFieldValue(model, 'metNotes', tline_value, pos); -0366 readList=''; miriamKey=''; -0367 case 'inchis' -0368 model = readFieldValue(model, 'inchis', tline_value, pos); -0369 readList=''; miriamKey=''; -0370 case 'smiles' -0371 % Top-level (legacy MATLAB) and inside-annotation (current -0372 % writer) layouts both land here. The current writer -0373 % always emits smiles as a block list, even for a single -0374 % value; a state distinct from 'annotation' -0375 % ('smilesInAnnotation') reads that list so that once it -0376 % ends this can resume annotation gathering rather than -0377 % getting stuck treating every later line in the same -0378 % annotation block as another smiles entry. A single-line -0379 % "smiles: value" (legacy, or empty tline_value absent) -0380 % doesn't reset readList — preserves the annotation -0381 % gathering state if SMILES is nested inside the -0382 % annotation block alongside other entries. -0383 if isempty(tline_value) -0384 readList = 'smilesInAnnotation'; -0385 else -0386 model = readFieldValue(model, 'metSmiles', tline_value, pos); -0387 end -0388 case 'deltaG' -0389 model = readFieldValue(model, 'metDeltaG', tline_value, pos); -0390 readList=''; miriamKey=''; -0391 case 'metFrom' -0392 model = readFieldValue(model, 'metFrom', tline_value, pos); -0393 readList=''; miriamKey=''; -0394 case 'annotation' -0395 readList = 'annotation'; -0396 otherwise -0397 switch readList -0398 case 'annotation' -0399 [metMiriams, miriamKey, metMirNo] = gatherAnnotation(pos,metMiriams,tline_key,tline_value,miriamKey,metMirNo); -0400 case 'smilesInAnnotation' -0401 % A bare list item ("- OC(=O)...") has no colon, -0402 % so tline_key is empty --- still inside the list. -0403 % Anything else is the annotation block's next -0404 % entry, not another smiles value: the list has -0405 % ended (RAVEN keeps only one smiles per -0406 % metabolite, so a second list item, if any, -0407 % simply overwrites the first). -0408 if isempty(tline_key) -0409 model = readFieldValue(model, 'metSmiles', regexprep(tline_value,'^ +- "?(.*)"?$','$1'), pos); -0410 else -0411 readList = 'annotation'; -0412 [metMiriams, miriamKey, metMirNo] = gatherAnnotation(pos,metMiriams,tline_key,tline_value,miriamKey,metMirNo); -0413 end -0414 otherwise -0415 error(['Unknown entry in yaml file: ' tline_raw]) -0416 end -0417 end; continue -0418 end -0419 -0420 % import reactions: -0421 if section == 3 -0422 switch tline_key -0423 case 'id' -0424 pos = pos + 1; -0425 model = readFieldValue(model, 'rxns', tline_value,pos); -0426 readList=''; miriamKey=''; -0427 case 'name' -0428 model = readFieldValue(model, 'rxnNames', tline_value, pos); -0429 readList=''; miriamKey=''; -0430 case 'lower_bound' -0431 model.lb(pos,1) = {tline_value}; -0432 readList=''; miriamKey=''; -0433 case 'upper_bound' -0434 model.ub(pos,1) = {tline_value}; -0435 readList=''; miriamKey=''; -0436 case 'rev' -0437 model.rev(pos,1) = {tline_value}; -0438 readList=''; miriamKey=''; -0439 case 'gene_reaction_rule' -0440 model = readFieldValue(model, 'grRules', tline_value, pos); -0441 readList=''; miriamKey=''; -0442 case {'notes','rxnNotes'} -0443 % `notes` is the canonical reaction-side key; `rxnNotes` -0444 % is the legacy RAVEN MATLAB key, kept here for -0445 % backward-compatible reads. -0446 model = readFieldValue(model, 'rxnNotes', tline_value, pos); -0447 readList=''; miriamKey=''; -0448 case 'rxnFrom' -0449 model = readFieldValue(model, 'rxnFrom', tline_value, pos); -0450 readList=''; miriamKey=''; -0451 case 'deltaG' -0452 model = readFieldValue(model, 'rxnDeltaG', tline_value, pos); -0453 readList=''; miriamKey=''; -0454 case 'objective_coefficient' -0455 model.c(pos,1) = str2double(tline_value); -0456 readList=''; miriamKey=''; -0457 case 'references' -0458 model = readFieldValue(model, 'rxnReferences', tline_value, pos); -0459 readList=''; miriamKey=''; -0460 case 'confidence_score' -0461 model = readFieldValue(model, 'rxnConfidenceScores', tline_value, pos); -0462 readList=''; miriamKey=''; -0463 case 'eccodes' -0464 if isempty(tline_value) -0465 readList = 'eccodes'; -0466 else -0467 eccodes(ecCodeNo,1:2)={pos,tline_value}; -0468 ecCodeNo=ecCodeNo+1; -0469 end -0470 case 'ec-code' -0471 % EC numbers emitted inside the `annotation` block -0472 % (current writer). Captured into model.eccodes the same -0473 % way metabolite `smiles` is pulled out of the annotation -0474 % block; the legacy top-level `eccodes` key above is still -0475 % accepted for older files. A block list of codes follows -0476 % when the value is empty, read via a state distinct from -0477 % the legacy 'eccodes' above ('eccodesInAnnotation', not -0478 % 'eccodes') specifically so that once the list ends this -0479 % can resume annotation gathering rather than getting -0480 % stuck treating every later line in the same annotation -0481 % block as another EC code. -0482 if isempty(tline_value) -0483 readList = 'eccodesInAnnotation'; -0484 else -0485 eccodes(ecCodeNo,1:2)={pos,tline_value}; -0486 ecCodeNo=ecCodeNo+1; -0487 end -0488 case 'subsystem' -0489 if isempty(tline_value) -0490 readList = 'subsystem'; -0491 else -0492 subSystems(subSysNo,1:2)={pos,tline_value}; -0493 subSysNo=subSysNo+1; -0494 end -0495 case 'metabolites' -0496 readList = 'equation'; -0497 case 'annotation' -0498 readList = 'annotation'; -0499 -0500 otherwise -0501 switch readList -0502 case 'eccodes' -0503 eccodes(ecCodeNo,1:2)={pos,regexprep(tline_value,'^ +- "?(.*)"?$','$1')}; -0504 ecCodeNo=ecCodeNo+1; -0505 case 'eccodesInAnnotation' -0506 % A bare list item ("- 1.1.2.4") has no colon, so -0507 % tline_key is empty --- still inside the list. -0508 % Anything else is the annotation block's next -0509 % entry (e.g. "kegg.reaction: ..."), not another -0510 % EC code: the list has ended, so resume normal -0511 % annotation gathering for this line and all that -0512 % follow, instead of silently absorbing them here. -0513 if isempty(tline_key) -0514 eccodes(ecCodeNo,1:2)={pos,regexprep(tline_value,'^ +- "?(.*)"?$','$1')}; -0515 ecCodeNo=ecCodeNo+1; -0516 else -0517 readList = 'annotation'; -0518 [rxnMiriams, miriamKey,rxnMirNo] = gatherAnnotation(pos,rxnMiriams,tline_key,tline_value,miriamKey,rxnMirNo); -0519 end -0520 case 'subsystem' -0521 subSystems(subSysNo,1:2)={pos,regexprep(tline_value,'^ +- "?(.*)"?$','$1')}; -0522 subSysNo=subSysNo+1; -0523 case 'annotation' -0524 [rxnMiriams, miriamKey,rxnMirNo] = gatherAnnotation(pos,rxnMiriams,tline_key,tline_value,miriamKey,rxnMirNo); -0525 case 'equation' -0526 coeff = sscanf(tline_value,'%f'); -0527 equations(equatiNo,1:3)={pos,tline_key,coeff}; -0528 equatiNo=equatiNo+1; -0529 otherwise -0530 error(['Unknown entry in yaml file: ' tline_raw]) -0531 end -0532 end; continue -0533 end -0534 -0535 % import genes: -0536 if section == 4 -0537 switch tline_key -0538 case 'id' -0539 pos = pos + 1; -0540 model = readFieldValue(model, 'genes', tline_value, pos); -0541 readList = ''; -0542 miriamKey = ''; -0543 case 'name' -0544 model = readFieldValue(model, 'geneShortNames', tline_value, pos); -0545 case 'protein' -0546 model = readFieldValue(model, 'proteins', tline_value, pos); -0547 case 'annotation' -0548 readList = 'annotation'; -0549 otherwise -0550 switch readList -0551 case 'annotation' -0552 [geneMiriams, miriamKey,genMirNo] = gatherAnnotation(pos,geneMiriams,tline_key,tline_value,miriamKey,genMirNo); -0553 otherwise -0554 error(['Unknown entry in yaml file: ' tline_raw]) -0555 end -0556 end; continue -0557 end -0558 -0559 % import compartments: -0560 if section == 5 -0561 switch tline_key -0562 case 'annotation' -0563 readList = 'annotation'; -0564 otherwise -0565 % Annotation entries are indented deeper than the -0566 % compartment they belong to, so anything at compartment -0567 % level ends the annotation and starts a new compartment. -0568 if strcmp(readList,'annotation') && ~isempty(regexp(tline_raw,'^ {6,}','once')) -0569 [compMiriams, miriamKey, compMirNo] = gatherAnnotation(pos,compMiriams,tline_key,tline_value,miriamKey,compMirNo); -0570 else -0571 pos = pos + 1; -0572 model.comps(end+1,1) = {tline_key}; -0573 model.compNames(end+1,1) = {tline_value}; -0574 readList = ''; miriamKey = ''; -0575 end -0576 end; continue -0577 end -0578 -0579 % import ec reaction info -0580 if section == 6 -0581 switch tline_key -0582 case 'id' -0583 pos = pos + 1; -0584 model.ec = readFieldValue(model.ec, 'rxns', tline_value, pos); -0585 readList=''; -0586 case 'kcat' -0587 model.ec = readFieldValue(model.ec, 'kcat', tline_value, pos); -0588 readList=''; -0589 case 'source' -0590 model.ec = readFieldValue(model.ec, 'source', tline_value, pos); -0591 readList=''; -0592 case 'notes' -0593 model.ec = readFieldValue(model.ec, 'notes', tline_value, pos); -0594 readList=''; -0595 case 'eccodes' -0596 if isempty(tline_value) -0597 readList = 'eccodes'; -0598 else -0599 ecGecko(ecGeckoNo,1:2)={pos,tline_value}; -0600 ecGeckoNo=ecGeckoNo+1; -0601 end -0602 case 'enzymes' -0603 readList = 'enzStoich'; -0604 otherwise -0605 switch readList -0606 case 'eccodes' -0607 ecGecko(ecGeckoNo,1:2)={pos,regexprep(tline_value,'^ +- "?(.*)"?$','$1')}; -0608 ecGeckoNo=ecGeckoNo+1; -0609 case 'enzStoich' -0610 coeff = sscanf(tline_value,'%f'); -0611 enzStoich(enzStoichNo,1:3)={pos,tline_key,coeff}; -0612 enzStoichNo=enzStoichNo+1; -0613 otherwise -0614 error(['Unknown entry in yaml file: ' tline_raw]) -0615 end -0616 end; continue -0617 end -0618 -0619 % import ec enzyme info -0620 if section == 7 -0621 switch tline_key -0622 case 'genes' -0623 pos = pos + 1; -0624 model.ec = readFieldValue(model.ec, 'genes', tline_value, pos); -0625 case 'enzymes' -0626 model.ec = readFieldValue(model.ec, 'enzymes', tline_value, pos); -0627 case 'mw' -0628 model.ec = readFieldValue(model.ec, 'mw', tline_value, pos); -0629 case 'sequence' -0630 model.ec = readFieldValue(model.ec, 'sequence', tline_value, pos); -0631 case 'concs' -0632 model.ec = readFieldValue(model.ec, 'concs', tline_value, pos); -0633 otherwise -0634 error(['Unknown entry in yaml file: ' tline_raw]) -0635 end; continue -0636 end -0637 end -0638 -0639 %Parse annotations -0640 if ~isempty(metMiriams) -0641 locs = cell2mat(metMiriams(:,1)); -0642 for i=unique(locs)' -0643 model.metMiriams{i,1}.name=metMiriams(locs==i,2); -0644 model.metMiriams{i,1}.value=metMiriams(locs==i,3); -0645 end -0646 end -0647 if ~isempty(rxnMiriams) -0648 locs = cell2mat(rxnMiriams(:,1)); -0649 for i=unique(locs)' -0650 model.rxnMiriams{i,1}.name=rxnMiriams(locs==i,2); -0651 model.rxnMiriams{i,1}.value=rxnMiriams(locs==i,3); -0652 end -0653 end -0654 if ~isempty(geneMiriams) -0655 locs = cell2mat(geneMiriams(:,1)); -0656 for i=unique(locs)' -0657 model.geneMiriams{i,1}.name=geneMiriams(locs==i,2); -0658 model.geneMiriams{i,1}.value=geneMiriams(locs==i,3); -0659 end -0660 end -0661 if ~isempty(compMiriams) -0662 locs = cell2mat(compMiriams(:,1)); -0663 for i=unique(locs)' -0664 model.compMiriams{i,1}.name=compMiriams(locs==i,2); -0665 model.compMiriams{i,1}.value=compMiriams(locs==i,3); -0666 end -0667 end -0668 -0669 %Parse subSystems -0670 if ~isempty(subSystems) -0671 locs = cell2mat(subSystems(:,1)); -0672 for i=unique(locs)' -0673 model.subSystems{i,1}=subSystems(locs==i,2); -0674 end -0675 end -0676 -0677 %Parse ec-codes -0678 if ~isempty(eccodes) -0679 locs = cell2mat(eccodes(:,1)); -0680 for i=unique(locs)' -0681 eccodesCat=strjoin(eccodes(locs==i,2),';'); -0682 model.eccodes{i,1}=eccodesCat; -0683 end -0684 emptyEc=cellfun('isempty',model.eccodes); -0685 model.eccodes(emptyEc)={''}; -0686 end -0687 -0688 % follow-up data processing -0689 if verbose -0690 fprintf('\nimporting completed\nfollow-up processing...'); -0691 end -0692 [~, model.metComps] = ismember(model.metComps, model.comps); -0693 [~, model.geneComps] = ismember(model.geneComps, model.comps); -0694 [~, model.rxnComps] = ismember(model.rxnComps, model.comps); -0695 % A metabolite with no explicit compartment is still ismember'd above (it -0696 % parses as ''), so it resolves to 0 -- not the "assume first compartment" -0697 % default the emptyOrFill call further down intends -- unless it happens -0698 % to be the trailing metabolite in the file, the only case emptyOrFill -0699 % actually reaches (it only extends a too-short array, and ismember has -0700 % already made this one full-length). Catch every 0 here so the default -0701 % applies regardless of position, not just at the end of the list. -0702 model.metComps(model.metComps==0) = 1; -0703 -0704 % Fill S-matrix -0705 rxnIdx = cellfun('isempty', equations(:,1)); -0706 equations(rxnIdx,:) = ''; -0707 rxnIdx = cell2mat(equations(:,1)); -0708 [~,metIdx] = ismember(equations(:,2),model.mets); -0709 coeffs = cell2mat(equations(:,3)); -0710 model.S=sparse(max(metIdx),max(rxnIdx)); -0711 linearIndices = sub2ind([max(metIdx), max(rxnIdx)],metIdx,rxnIdx); -0712 model.S(linearIndices) = coeffs; -0713 -0714 % Convert strings to numeric -0715 model.metCharges = str2double(model.metCharges); -0716 model.lb = str2double(model.lb); -0717 model.ub = str2double(model.ub); -0718 model.rxnConfidenceScores = str2double(model.rxnConfidenceScores); -0719 model.b = zeros(length(model.mets),1); -0720 model.metDeltaG = str2double(model.metDeltaG); -0721 model.rxnDeltaG = str2double(model.rxnDeltaG); -0722 -0723 % Fill some other fields -0724 model.annotation.defaultLB = min(model.lb); -0725 model.annotation.defaultUB = max(model.ub); -0726 if numel(model.lb)<numel(model.rxns) %No LB reported = min -0727 model.lb(end+1:numel(model.rxns)-numel(model.lb),1) = double(model.annotation.defaultLB); -0728 end -0729 if numel(model.ub)<numel(model.rxns) %No UB reported = max -0730 model.ub(end+1:numel(model.rxns)-numel(model.ub),1) = double(model.annotation.defaultUB); -0731 end -0732 if ~all(cellfun('isempty',model.rev)) -0733 model.rev = str2double(model.rev); -0734 else -0735 model.rev = []; -0736 end -0737 if numel(model.rev)<numel(model.rxns) %No rev reported, assume from LB and UB -0738 model.rev(end+1:numel(model.rxns)-numel(model.rev),1) = double(model.lb<0 & model.ub>0); -0739 end -0740 -0741 % Remove empty fields, otherwise fill to correct length -0742 % Reactions -0743 for i={'rxnNames','grRules','eccodes','rxnNotes','rxnReferences',... -0744 'rxnFrom','subSystems','rxnMiriams'} % Empty strings -0745 model = emptyOrFill(model,i{1},{''},'rxns'); -0746 end -0747 for i={'c'} % Zeros -0748 model = emptyOrFill(model,i{1},0,'rxns',true); -0749 end -0750 for i={'rxnConfidenceScores','rxnDeltaG'} % NaNs -0751 model = emptyOrFill(model,i{1},NaN,'rxns'); -0752 end -0753 for i={'rxnComps'} % Ones, assume first compartment -0754 model = emptyOrFill(model,i{1},1,'rxns'); -0755 end -0756 % Metabolites -0757 for i={'metNames','inchis','metFormulas','metMiriams','metFrom','metSmiles','metNotes'} % Empty strings -0758 model = emptyOrFill(model,i{1},{''},'mets'); -0759 end -0760 for i={'unconstrained'} % Zeros -0761 model = emptyOrFill(model,i{1},0,'mets'); -0762 end -0763 for i={'metCharges','metDeltaG'} % NaNs -0764 model = emptyOrFill(model,i{1},NaN,'mets'); -0765 end -0766 for i={'metComps'} % Ones, assume first compartment -0767 model = emptyOrFill(model,i{1},1,'mets'); -0768 end -0769 % Genes -0770 for i={'geneMiriams','geneShortNames','proteins'} % Empty strings -0771 model = emptyOrFill(model,i{1},{''},'genes'); -0772 end -0773 for i={'geneComps'} % Ones, assume first compartment -0774 model = emptyOrFill(model,i{1},1,'genes'); -0775 end -0776 % Comps -0777 for i={'compNames','compMiriams'} % Empty strings -0778 model = emptyOrFill(model,i{1},{''},'comps'); -0779 end -0780 for i={'compOutside'} % First comp -0781 model = emptyOrFill(model,i{1},model.comps{1},'comps'); -0782 end -0783 % Single fields are kept, even if empty -0784 % for i={'description','name','version','date','annotation'} -0785 % if isempty(model.(i{1})) -0786 % model = rmfield(model,i{1}); -0787 % end -0788 % end -0789 -0790 if isfield(model,'subSystems') -0791 % If all entries are 1x1, then flatten -0792 if all(cellfun(@(x) numel(x) <= 1, model.subSystems)) -0793 model.subSystems = transpose([model.subSystems{:}]); -0794 end -0795 end -0796 -0797 % Make rxnGeneMat fields and map to the existing model.genes field -0798 if isfield(model,'grRules') -0799 [genes, rxnGeneMat] = getGenesFromGrRules(model.grRules); -0800 model.rxnGeneMat = sparse(numel(model.rxns),numel(model.genes)); -0801 [~,geneOrder] = ismember(genes,model.genes); -0802 if any(geneOrder == 0) -0803 error(['The grRules includes the following gene(s), that are not in '... -0804 'the list of model genes: ', genes{~geneOrder}]) -0805 end -0806 model.rxnGeneMat(:,geneOrder) = rxnGeneMat; -0807 else -0808 % Gene-less model: keep rxnGeneMat dimensionally consistent with the -0809 % number of reactions and genes, rather than leaving it empty. -0810 model.rxnGeneMat = sparse(numel(model.rxns),numel(model.genes)); -0811 end -0812 -0813 % Finalize GECKO model -0814 if isGECKO -0815 % Fill in empty fields and empty entries -0816 for i={'kcat','source','notes','eccodes'} % Even keep empty -0817 model.ec = emptyOrFill(model.ec,i{1},{''},'rxns',true); -0818 end -0819 for i={'enzymes','mw','sequence'} -0820 model.ec = emptyOrFill(model.ec,i{1},{''},'genes',true); -0821 end -0822 model.ec = emptyOrFill(model.ec,'concs',{'NaN'},'genes',true); -0823 model.ec = emptyOrFill(model.ec,'kcat',{'0'},'genes',true); -0824 % Change string to double -0825 for i={'kcat','mw','concs'} -0826 if isfield(model.ec,i{1}) -0827 model.ec.(i{1}) = str2double(model.ec.(i{1})); -0828 end -0829 end -0830 % Fill rxnEnzMat -0831 rxnIdx = cellfun('isempty', enzStoich(:,1)); -0832 enzStoich(rxnIdx,:) = ''; -0833 rxnIdx = cell2mat(enzStoich(:,1)); -0834 [~,enzIdx] = ismember(enzStoich(:,2),model.ec.enzymes); -0835 coeffs = cell2mat(enzStoich(:,3)); -0836 model.ec.rxnEnzMat = zeros(numel(model.ec.rxns), numel(model.ec.genes)); -0837 linearIndices = sub2ind([numel(model.ec.rxns), numel(model.ec.genes)], rxnIdx, enzIdx); -0838 model.ec.rxnEnzMat(linearIndices) = coeffs; -0839 %Parse ec-codes -0840 if ~isempty(ecGecko) -0841 locs = cell2mat(ecGecko(:,1)); -0842 for i=unique(locs)' -0843 ecGeckoCat=strjoin(ecGecko(locs==i,2),';'); -0844 model.ec.eccodes{i,1}=ecGeckoCat; -0845 end -0846 emptyEc=cellfun('isempty',model.ec.eccodes); -0847 model.ec.eccodes(emptyEc)={''}; -0848 end -0849 end -0850 -0851 if verbose -0852 fprintf(' Done!\n'); -0853 end -0854 end -0855 -0856 function model = emptyOrFill(model,field,emptyEntry,type,keepEmpty) -0857 if nargin<5 -0858 keepEmpty=false; -0859 end -0860 if isnumeric(emptyEntry) -0861 emptyCells=isempty(model.(field)); -0862 else -0863 emptyCells=cellfun('isempty',model.(field)); -0864 end -0865 if all(emptyCells) && ~keepEmpty -0866 model = rmfield(model, field); -0867 elseif numel(model.(field))<numel(model.(type)) -0868 model.(field)(end+1:numel(model.(type)),1)=emptyEntry; -0869 end -0870 end -0871 -0872 function model = readFieldValue(model, fieldName, value, pos) -0873 if numel(model.(fieldName))<pos-1 -0874 model.(fieldName)(end+1:pos,1) = {''}; -0875 end -0876 model.(fieldName)(pos,1) = {value}; +0061 % introduced with a list-item marker (a dash followed by a space, or a dash +0062 % ending the line), while a folded scalar's continuation is raw text with no +0063 % list-item marker of its own. Such text can itself start with a dash, as in +0064 % a KEGG reaction name wrapped before "-epoxidizing)". +0065 newLine=regexp(line_raw,'^ {6,}((?!-(\s|$))\S.*)','tokenExtents'); +0066 brokenLine=find(~cellfun('isempty',newLine)); +0067 for i=flip(1:numel(brokenLine)) +0068 extraLine = char(line_raw(brokenLine(i))); +0069 extraLine = extraLine(newLine{brokenLine(i)}{1}(1):end); +0070 % deblank, not strtrim: only the trailing whitespace is spurious (e.g. +0071 % "- inchis: " with nothing after the colon on this line); the leading +0072 % indentation must survive untouched for the key-detection regex below. +0073 line_raw{brokenLine(i)-1} = strjoin({deblank(line_raw{brokenLine(i)-1}),extraLine},' '); +0074 end +0075 line_raw(brokenLine)=[]; +0076 +0077 line_key = regexprep(line_raw,'^ *-? ([^:]+)(:)($| .*)','$1'); +0078 line_key = regexprep(line_key,'(.*!!omap)|(---)|( {4,}.*)',''); +0079 +0080 line_value = regexprep(line_raw, '.*:$',''); +0081 % Captures everything after "key: " verbatim, quotes and all; any wrapping +0082 % quotes are left in place here and removed next by the anchored-pair +0083 % strip below, which handles both quote styles. +0084 line_value = regexprep(line_value, '[^":]+: (.+)$','$1'); +0085 % The list-marker strip must run before quote detection below, not after: +0086 % a quoted *list item* (e.g. a pubmed id under an annotation block, +0087 % ` - "10714900"`) still has its leading "- " attached at this +0088 % point, so the anchored ^".*"$/^'.*'$ checks --- which require the quote +0089 % to be the very first character --- would never match it, leaving the +0090 % quotes on the value uninspected and unescaped. Stripping the marker +0091 % first means a quoted list item's value starts directly with the quote +0092 % character, the same shape as a quoted `key: "value"` scalar, so the +0093 % existing anchored logic below already handles both without further +0094 % changes. +0095 line_value = regexprep(line_value, '^ {4,}- ',''); +0096 % Strips wrapping quotes, either style, but only a matching pair anchored +0097 % at both ends of the value --- not every quote character in it. Some +0098 % writers single-quote anything YAML requires quoting (a value that would +0099 % otherwise parse as a date/number/boolean, or one containing a character +0100 % with special meaning), where RAVEN's own writer only ever double-quotes, +0101 % so both styles must be handled here. Anchoring (rather than a global +0102 % strip) also preserves a genuine apostrophe in an unquoted value, e.g. +0103 % "yeast's hexokinase reaction" --- only the wrapping pair is a delimiter, +0104 % not every quote-like character. +0105 wasSingleQuoted = ~cellfun('isempty', regexp(line_value, '^''.*''$', 'once')); +0106 wasDoubleQuoted = ~cellfun('isempty', regexp(line_value, '^".*"$', 'once')); +0107 line_value = regexprep(line_value, '^(["''])(.*)\1$','$2'); +0108 % Undo each quote style's own escaping so the reader recovers the exact +0109 % original text: single-quoted YAML doubles a literal apostrophe ('' -> '); +0110 % double-quoted YAML backslash-escapes a literal backslash and double quote +0111 % (\\ -> \, \" -> "). The backslash pass goes through a sentinel character +0112 % first so an escaped backslash immediately followed by an escaped quote +0113 % (\\\" in the file, a literal \" in the value) round-trips correctly +0114 % instead of the second pass misreading the first's output. +0115 line_value(wasSingleQuoted) = strrep(line_value(wasSingleQuoted), '''''', ''''); +0116 line_value(wasDoubleQuoted) = strrep(line_value(wasDoubleQuoted), '\\', char(1)); +0117 line_value(wasDoubleQuoted) = strrep(line_value(wasDoubleQuoted), '\"', '"'); +0118 line_value(wasDoubleQuoted) = strrep(line_value(wasDoubleQuoted), char(1), '\'); +0119 line_value(strcmp(line_value,'''''')) = {''}; +0120 line_value(strcmp(line_value,line_raw)) = {''}; +0121 +0122 modelFields = {'id',char();... +0123 'name',char();... +0124 'description',char();... +0125 'version',char();... +0126 'date',char();... +0127 'annotation',struct();... +0128 'rxns',{};... +0129 'rxnNames',{};... +0130 'mets',{};... +0131 'metNames',{};... +0132 'S',sparse([]);... +0133 'lb',{};... %Changed to double in the end. +0134 'ub',{};... %Changed to double in the end. +0135 'rev',{};... %Changed to double in the end. +0136 'c',[];... +0137 'b',cell(0,0);... %Changed to double in the end. +0138 'genes',cell(0,0);... +0139 'grRules',cell(0,0);... +0140 'rxnGeneMat',sparse([]);... +0141 'rxnComps',cell(0,0);... %Changed to double in the end. +0142 'subSystems',cell(0,0);... +0143 'eccodes',cell(0,0);... +0144 'rxnMiriams',cell(0,0);... +0145 'rxnDeltaG',{};... %Changed to double in the end. +0146 'rxnNotes',cell(0,0);... +0147 'rxnReferences',cell(0,0);... +0148 'rxnConfidenceScores',cell(0,0);... +0149 'metComps',cell(0,0);... %Changed to double in the end. +0150 'inchis',cell(0,0);... +0151 'metSmiles',cell(0,0);... +0152 'metFormulas',cell(0,0);... +0153 'metMiriams',cell(0,0);... +0154 'metDeltaG',{};... %Changed to double in the end. +0155 'metCharges',cell(0,0);... %Changed to double in the end. +0156 'metNotes',cell(0,0);... +0157 'comps',cell(0,0);... +0158 'compNames',cell(0,0);... +0159 'compOutside',cell(0,0);... +0160 'compMiriams',cell(0,0);... +0161 'geneComps',cell(0,0);... %Changed to double in the end. +0162 'geneMiriams',cell(0,0);... +0163 'geneShortNames',cell(0,0);... +0164 'proteins',cell(0,0);... +0165 'unconstrained',cell(0,0);... %Changed to double in the end. +0166 'metFrom',cell(0,0);... +0167 'rxnFrom',cell(0,0)}; +0168 for i=1:size(modelFields,1) +0169 model.(modelFields{i,1})=modelFields{i,2}; +0170 end +0171 +0172 % If GECKO model — accept both the legacy `geckoLight` (inside metaData) +0173 % and the alternate top-level `gecko_light` key. +0174 if any(contains(line_key,'geckoLight')) || any(contains(line_key,'gecko_light')) +0175 isGECKO=true; +0176 ecFields = {'geckoLight', false;... +0177 'rxns', {};... +0178 'kcat', {};... +0179 'source', cell(0,0);... +0180 'notes', cell(0,0);... +0181 'eccodes', cell(0,0);... +0182 'genes', cell(0,0);... +0183 'enzymes', cell(0,0);... +0184 'mw', cell(0,0);... +0185 'sequence', cell(0,0);... +0186 'concs', cell(0,0);... +0187 'rxnEnzMat', []}; +0188 for i=1:size(ecFields,1) +0189 model.ec.(ecFields{i,1})=ecFields{i,2}; +0190 end +0191 ecGecko=cell(25000,2); ecGeckoNo=1; +0192 enzStoich=cell(100000,3); enzStoichNo=1; +0193 else +0194 isGECKO=false; +0195 end +0196 +0197 section = 0; +0198 metMiriams=cell(100000,3); metMirNo=1; +0199 rxnMiriams=cell(100000,3); rxnMirNo=1; +0200 geneMiriams=cell(100000,3); genMirNo=1; +0201 compMiriams=cell(1000,3); compMirNo=1; +0202 subSystems=cell(100000,2); subSysNo=1; +0203 eccodes=cell(100000,2); ecCodeNo=1; +0204 equations=cell(100000,3); equatiNo=1; +0205 +0206 for i=1:numel(line_key) +0207 tline_raw = line_raw{i}; +0208 tline_key = line_key{i}; +0209 tline_value = line_value{i}; +0210 % import different sections — accept the !!omap-tagged variant as +0211 % well, so a file using that tag is recognized too. +0212 switch tline_raw +0213 case {'- metaData:', '- metaData: !!omap'} +0214 section = 1; +0215 if verbose +0216 fprintf('\t%d\n', section); +0217 end +0218 continue % Go to next line +0219 case '- metabolites:' +0220 section = 2; +0221 if verbose +0222 fprintf('\t%d\n', section); +0223 end +0224 pos=0; +0225 continue +0226 case '- reactions:' +0227 section = 3; +0228 if verbose +0229 fprintf('\t%d\n', section); +0230 end +0231 pos=0; +0232 continue +0233 case '- genes:' +0234 section = 4; +0235 if verbose +0236 fprintf('\t%d\n', section); +0237 end +0238 pos=0; +0239 continue +0240 case '- genes: []' +0241 % Flow-style empty list: a gene-less model. No entries follow, +0242 % so there is nothing to switch into --- the next recognised +0243 % header is matched fresh on the next iteration regardless of +0244 % what `section` is left as. +0245 continue +0246 case '- compartments: !!omap' +0247 section = 5; +0248 if verbose +0249 fprintf('\t%d\n', section); +0250 end +0251 pos=0; +0252 continue +0253 case '- ec-rxns:' +0254 section = 6; +0255 if verbose +0256 fprintf('\t%d\n', section); +0257 end +0258 pos=0; +0259 continue +0260 case '- ec-enzymes:' +0261 section = 7; +0262 if verbose +0263 fprintf('\t%d\n', section); +0264 end +0265 pos=0; +0266 continue +0267 end +0268 +0269 % Alternate root-level keys (id, name, version, gecko_light), written +0270 % at the top level with no metaData section. RAVEN's own writer +0271 % normally puts them inside metaData, but we accept both layouts so a +0272 % YAML file using the top-level convention can be ingested directly. +0273 if isempty(regexp(tline_raw, '^ {2,}', 'once')) +0274 switch tline_key +0275 case 'id' +0276 if isempty(model.id), model.id = tline_value; end +0277 continue +0278 case 'name' +0279 if isempty(model.name), model.name = tline_value; end +0280 continue +0281 case 'version' +0282 if ~isfield(model,'version') || isempty(model.version) +0283 model.version = tline_value; +0284 end +0285 continue +0286 case 'gecko_light' +0287 if isGECKO && strcmp(tline_value,'true') +0288 model.ec.geckoLight = true; +0289 end +0290 continue +0291 end +0292 end +0293 +0294 % skip over empty keys +0295 if isempty(tline_raw) || (isempty(tline_key) && contains(tline_raw,'!!omap')) +0296 continue; +0297 end +0298 +0299 % import metaData +0300 if section == 1 +0301 switch tline_key +0302 case {'short_name','id'} %short_name used by human-GEM +0303 model.id = tline_value; +0304 case 'name' +0305 model.name = tline_value; +0306 case 'full_name' %used by human-GEM +0307 model.description = tline_value; +0308 case 'version' +0309 model.version = tline_value; +0310 case 'date' +0311 model.date = tline_value; +0312 case 'taxonomy' +0313 model.annotation.taxonomy = tline_value; +0314 case {'description','note'} %description used by human-GEM +0315 model.annotation.note = tline_value; +0316 case 'github' +0317 model.annotation.sourceUrl = tline_value; +0318 case 'sourceUrl' +0319 model.annotation.sourceUrl = tline_value; +0320 case 'givenName' +0321 model.annotation.givenName = tline_value; +0322 case 'familyName' +0323 model.annotation.familyName = tline_value; +0324 case 'authors' +0325 model.annotation.authorList = tline_value; +0326 case 'email' +0327 model.annotation.email = tline_value; +0328 case 'organization' +0329 model.annotation.organization = tline_value; +0330 case {'geckoLight','gecko_light'} +0331 if strcmp(tline_value,'true') +0332 model.ec.geckoLight = true; +0333 end +0334 otherwise +0335 % Caller-specific provenance key with no RAVEN-defined slot +0336 % (e.g. a tool-specific `_version` key): preserve it instead +0337 % of silently dropping it, so a write/read round trip is +0338 % lossless. Mirrors writeYAMLmodel.m's symmetric fallback, +0339 % which emits any model.annotation field not in its own +0340 % fixed list. +0341 if isvarname(tline_key) +0342 model.annotation.(tline_key) = tline_value; +0343 end +0344 end; continue +0345 end +0346 +0347 % import metabolites: +0348 if section == 2 +0349 switch tline_key +0350 case 'id' +0351 pos = pos + 1; +0352 model = readFieldValue(model, 'mets', tline_value,pos); +0353 readList=''; miriamKey=''; +0354 case 'name' +0355 model = readFieldValue(model, 'metNames', tline_value, pos); +0356 readList=''; miriamKey=''; +0357 case 'compartment' +0358 model = readFieldValue(model, 'metComps', tline_value, pos); +0359 readList=''; miriamKey=''; +0360 case 'formula' +0361 model = readFieldValue(model, 'metFormulas', tline_value, pos); +0362 readList=''; miriamKey=''; +0363 case 'charge' +0364 model = readFieldValue(model, 'metCharges', tline_value, pos); +0365 readList=''; miriamKey=''; +0366 case 'notes' +0367 model = readFieldValue(model, 'metNotes', tline_value, pos); +0368 readList=''; miriamKey=''; +0369 case 'inchis' +0370 model = readFieldValue(model, 'inchis', tline_value, pos); +0371 readList=''; miriamKey=''; +0372 case 'smiles' +0373 % Top-level (legacy MATLAB) and inside-annotation (current +0374 % writer) layouts both land here. The current writer +0375 % always emits smiles as a block list, even for a single +0376 % value; a state distinct from 'annotation' +0377 % ('smilesInAnnotation') reads that list so that once it +0378 % ends this can resume annotation gathering rather than +0379 % getting stuck treating every later line in the same +0380 % annotation block as another smiles entry. A single-line +0381 % "smiles: value" (legacy, or empty tline_value absent) +0382 % doesn't reset readList — preserves the annotation +0383 % gathering state if SMILES is nested inside the +0384 % annotation block alongside other entries. +0385 if isempty(tline_value) +0386 readList = 'smilesInAnnotation'; +0387 else +0388 model = readFieldValue(model, 'metSmiles', tline_value, pos); +0389 end +0390 case 'deltaG' +0391 model = readFieldValue(model, 'metDeltaG', tline_value, pos); +0392 readList=''; miriamKey=''; +0393 case 'metFrom' +0394 model = readFieldValue(model, 'metFrom', tline_value, pos); +0395 readList=''; miriamKey=''; +0396 case 'annotation' +0397 readList = 'annotation'; +0398 otherwise +0399 switch readList +0400 case 'annotation' +0401 [metMiriams, miriamKey, metMirNo] = gatherAnnotation(pos,metMiriams,tline_key,tline_value,miriamKey,metMirNo); +0402 case 'smilesInAnnotation' +0403 % A bare list item ("- OC(=O)...") has no colon, +0404 % so tline_key is empty --- still inside the list. +0405 % Anything else is the annotation block's next +0406 % entry, not another smiles value: the list has +0407 % ended (RAVEN keeps only one smiles per +0408 % metabolite, so a second list item, if any, +0409 % simply overwrites the first). +0410 if isempty(tline_key) +0411 model = readFieldValue(model, 'metSmiles', regexprep(tline_value,'^ +- "?(.*)"?$','$1'), pos); +0412 else +0413 readList = 'annotation'; +0414 [metMiriams, miriamKey, metMirNo] = gatherAnnotation(pos,metMiriams,tline_key,tline_value,miriamKey,metMirNo); +0415 end +0416 otherwise +0417 error(['Unknown entry in yaml file: ' tline_raw]) +0418 end +0419 end; continue +0420 end +0421 +0422 % import reactions: +0423 if section == 3 +0424 switch tline_key +0425 case 'id' +0426 pos = pos + 1; +0427 model = readFieldValue(model, 'rxns', tline_value,pos); +0428 readList=''; miriamKey=''; +0429 case 'name' +0430 model = readFieldValue(model, 'rxnNames', tline_value, pos); +0431 readList=''; miriamKey=''; +0432 case 'lower_bound' +0433 model.lb(pos,1) = {tline_value}; +0434 readList=''; miriamKey=''; +0435 case 'upper_bound' +0436 model.ub(pos,1) = {tline_value}; +0437 readList=''; miriamKey=''; +0438 case 'rev' +0439 model.rev(pos,1) = {tline_value}; +0440 readList=''; miriamKey=''; +0441 case 'gene_reaction_rule' +0442 model = readFieldValue(model, 'grRules', tline_value, pos); +0443 readList=''; miriamKey=''; +0444 case {'notes','rxnNotes'} +0445 % `notes` is the canonical reaction-side key; `rxnNotes` +0446 % is the legacy RAVEN MATLAB key, kept here for +0447 % backward-compatible reads. +0448 model = readFieldValue(model, 'rxnNotes', tline_value, pos); +0449 readList=''; miriamKey=''; +0450 case 'rxnFrom' +0451 model = readFieldValue(model, 'rxnFrom', tline_value, pos); +0452 readList=''; miriamKey=''; +0453 case 'deltaG' +0454 model = readFieldValue(model, 'rxnDeltaG', tline_value, pos); +0455 readList=''; miriamKey=''; +0456 case 'objective_coefficient' +0457 model.c(pos,1) = str2double(tline_value); +0458 readList=''; miriamKey=''; +0459 case 'references' +0460 model = readFieldValue(model, 'rxnReferences', tline_value, pos); +0461 readList=''; miriamKey=''; +0462 case 'confidence_score' +0463 model = readFieldValue(model, 'rxnConfidenceScores', tline_value, pos); +0464 readList=''; miriamKey=''; +0465 case 'eccodes' +0466 if isempty(tline_value) +0467 readList = 'eccodes'; +0468 else +0469 eccodes(ecCodeNo,1:2)={pos,tline_value}; +0470 ecCodeNo=ecCodeNo+1; +0471 end +0472 case 'ec-code' +0473 % EC numbers emitted inside the `annotation` block +0474 % (current writer). Captured into model.eccodes the same +0475 % way metabolite `smiles` is pulled out of the annotation +0476 % block; the legacy top-level `eccodes` key above is still +0477 % accepted for older files. A block list of codes follows +0478 % when the value is empty, read via a state distinct from +0479 % the legacy 'eccodes' above ('eccodesInAnnotation', not +0480 % 'eccodes') specifically so that once the list ends this +0481 % can resume annotation gathering rather than getting +0482 % stuck treating every later line in the same annotation +0483 % block as another EC code. +0484 if isempty(tline_value) +0485 readList = 'eccodesInAnnotation'; +0486 else +0487 eccodes(ecCodeNo,1:2)={pos,tline_value}; +0488 ecCodeNo=ecCodeNo+1; +0489 end +0490 case 'subsystem' +0491 if isempty(tline_value) +0492 readList = 'subsystem'; +0493 else +0494 subSystems(subSysNo,1:2)={pos,tline_value}; +0495 subSysNo=subSysNo+1; +0496 end +0497 case 'metabolites' +0498 readList = 'equation'; +0499 case 'annotation' +0500 readList = 'annotation'; +0501 +0502 otherwise +0503 switch readList +0504 case 'eccodes' +0505 eccodes(ecCodeNo,1:2)={pos,regexprep(tline_value,'^ +- "?(.*)"?$','$1')}; +0506 ecCodeNo=ecCodeNo+1; +0507 case 'eccodesInAnnotation' +0508 % A bare list item ("- 1.1.2.4") has no colon, so +0509 % tline_key is empty --- still inside the list. +0510 % Anything else is the annotation block's next +0511 % entry (e.g. "kegg.reaction: ..."), not another +0512 % EC code: the list has ended, so resume normal +0513 % annotation gathering for this line and all that +0514 % follow, instead of silently absorbing them here. +0515 if isempty(tline_key) +0516 eccodes(ecCodeNo,1:2)={pos,regexprep(tline_value,'^ +- "?(.*)"?$','$1')}; +0517 ecCodeNo=ecCodeNo+1; +0518 else +0519 readList = 'annotation'; +0520 [rxnMiriams, miriamKey,rxnMirNo] = gatherAnnotation(pos,rxnMiriams,tline_key,tline_value,miriamKey,rxnMirNo); +0521 end +0522 case 'subsystem' +0523 subSystems(subSysNo,1:2)={pos,regexprep(tline_value,'^ +- "?(.*)"?$','$1')}; +0524 subSysNo=subSysNo+1; +0525 case 'annotation' +0526 [rxnMiriams, miriamKey,rxnMirNo] = gatherAnnotation(pos,rxnMiriams,tline_key,tline_value,miriamKey,rxnMirNo); +0527 case 'equation' +0528 coeff = sscanf(tline_value,'%f'); +0529 equations(equatiNo,1:3)={pos,tline_key,coeff}; +0530 equatiNo=equatiNo+1; +0531 otherwise +0532 error(['Unknown entry in yaml file: ' tline_raw]) +0533 end +0534 end; continue +0535 end +0536 +0537 % import genes: +0538 if section == 4 +0539 switch tline_key +0540 case 'id' +0541 pos = pos + 1; +0542 model = readFieldValue(model, 'genes', tline_value, pos); +0543 readList = ''; +0544 miriamKey = ''; +0545 case 'name' +0546 model = readFieldValue(model, 'geneShortNames', tline_value, pos); +0547 case 'protein' +0548 model = readFieldValue(model, 'proteins', tline_value, pos); +0549 case 'annotation' +0550 readList = 'annotation'; +0551 otherwise +0552 switch readList +0553 case 'annotation' +0554 [geneMiriams, miriamKey,genMirNo] = gatherAnnotation(pos,geneMiriams,tline_key,tline_value,miriamKey,genMirNo); +0555 otherwise +0556 error(['Unknown entry in yaml file: ' tline_raw]) +0557 end +0558 end; continue +0559 end +0560 +0561 % import compartments: +0562 if section == 5 +0563 switch tline_key +0564 case 'annotation' +0565 readList = 'annotation'; +0566 otherwise +0567 % Annotation entries are indented deeper than the +0568 % compartment they belong to, so anything at compartment +0569 % level ends the annotation and starts a new compartment. +0570 if strcmp(readList,'annotation') && ~isempty(regexp(tline_raw,'^ {6,}','once')) +0571 [compMiriams, miriamKey, compMirNo] = gatherAnnotation(pos,compMiriams,tline_key,tline_value,miriamKey,compMirNo); +0572 else +0573 pos = pos + 1; +0574 model.comps(end+1,1) = {tline_key}; +0575 model.compNames(end+1,1) = {tline_value}; +0576 readList = ''; miriamKey = ''; +0577 end +0578 end; continue +0579 end +0580 +0581 % import ec reaction info +0582 if section == 6 +0583 switch tline_key +0584 case 'id' +0585 pos = pos + 1; +0586 model.ec = readFieldValue(model.ec, 'rxns', tline_value, pos); +0587 readList=''; +0588 case 'kcat' +0589 model.ec = readFieldValue(model.ec, 'kcat', tline_value, pos); +0590 readList=''; +0591 case 'source' +0592 model.ec = readFieldValue(model.ec, 'source', tline_value, pos); +0593 readList=''; +0594 case 'notes' +0595 model.ec = readFieldValue(model.ec, 'notes', tline_value, pos); +0596 readList=''; +0597 case 'eccodes' +0598 if isempty(tline_value) +0599 readList = 'eccodes'; +0600 else +0601 ecGecko(ecGeckoNo,1:2)={pos,tline_value}; +0602 ecGeckoNo=ecGeckoNo+1; +0603 end +0604 case 'enzymes' +0605 readList = 'enzStoich'; +0606 otherwise +0607 switch readList +0608 case 'eccodes' +0609 ecGecko(ecGeckoNo,1:2)={pos,regexprep(tline_value,'^ +- "?(.*)"?$','$1')}; +0610 ecGeckoNo=ecGeckoNo+1; +0611 case 'enzStoich' +0612 coeff = sscanf(tline_value,'%f'); +0613 enzStoich(enzStoichNo,1:3)={pos,tline_key,coeff}; +0614 enzStoichNo=enzStoichNo+1; +0615 otherwise +0616 error(['Unknown entry in yaml file: ' tline_raw]) +0617 end +0618 end; continue +0619 end +0620 +0621 % import ec enzyme info +0622 if section == 7 +0623 switch tline_key +0624 case 'genes' +0625 pos = pos + 1; +0626 model.ec = readFieldValue(model.ec, 'genes', tline_value, pos); +0627 case 'enzymes' +0628 model.ec = readFieldValue(model.ec, 'enzymes', tline_value, pos); +0629 case 'mw' +0630 model.ec = readFieldValue(model.ec, 'mw', tline_value, pos); +0631 case 'sequence' +0632 model.ec = readFieldValue(model.ec, 'sequence', tline_value, pos); +0633 case 'concs' +0634 model.ec = readFieldValue(model.ec, 'concs', tline_value, pos); +0635 otherwise +0636 error(['Unknown entry in yaml file: ' tline_raw]) +0637 end; continue +0638 end +0639 end +0640 +0641 %Parse annotations +0642 if ~isempty(metMiriams) +0643 locs = cell2mat(metMiriams(:,1)); +0644 for i=unique(locs)' +0645 model.metMiriams{i,1}.name=metMiriams(locs==i,2); +0646 model.metMiriams{i,1}.value=metMiriams(locs==i,3); +0647 end +0648 end +0649 if ~isempty(rxnMiriams) +0650 locs = cell2mat(rxnMiriams(:,1)); +0651 for i=unique(locs)' +0652 model.rxnMiriams{i,1}.name=rxnMiriams(locs==i,2); +0653 model.rxnMiriams{i,1}.value=rxnMiriams(locs==i,3); +0654 end +0655 end +0656 if ~isempty(geneMiriams) +0657 locs = cell2mat(geneMiriams(:,1)); +0658 for i=unique(locs)' +0659 model.geneMiriams{i,1}.name=geneMiriams(locs==i,2); +0660 model.geneMiriams{i,1}.value=geneMiriams(locs==i,3); +0661 end +0662 end +0663 if ~isempty(compMiriams) +0664 locs = cell2mat(compMiriams(:,1)); +0665 for i=unique(locs)' +0666 model.compMiriams{i,1}.name=compMiriams(locs==i,2); +0667 model.compMiriams{i,1}.value=compMiriams(locs==i,3); +0668 end +0669 end +0670 +0671 %Parse subSystems +0672 if ~isempty(subSystems) +0673 locs = cell2mat(subSystems(:,1)); +0674 for i=unique(locs)' +0675 model.subSystems{i,1}=subSystems(locs==i,2); +0676 end +0677 end +0678 +0679 %Parse ec-codes +0680 if ~isempty(eccodes) +0681 locs = cell2mat(eccodes(:,1)); +0682 for i=unique(locs)' +0683 eccodesCat=strjoin(eccodes(locs==i,2),';'); +0684 model.eccodes{i,1}=eccodesCat; +0685 end +0686 emptyEc=cellfun('isempty',model.eccodes); +0687 model.eccodes(emptyEc)={''}; +0688 end +0689 +0690 % follow-up data processing +0691 if verbose +0692 fprintf('\nimporting completed\nfollow-up processing...'); +0693 end +0694 [~, model.metComps] = ismember(model.metComps, model.comps); +0695 [~, model.geneComps] = ismember(model.geneComps, model.comps); +0696 [~, model.rxnComps] = ismember(model.rxnComps, model.comps); +0697 % A metabolite with no explicit compartment is still ismember'd above (it +0698 % parses as ''), so it resolves to 0 -- not the "assume first compartment" +0699 % default the emptyOrFill call further down intends -- unless it happens +0700 % to be the trailing metabolite in the file, the only case emptyOrFill +0701 % actually reaches (it only extends a too-short array, and ismember has +0702 % already made this one full-length). Catch every 0 here so the default +0703 % applies regardless of position, not just at the end of the list. +0704 model.metComps(model.metComps==0) = 1; +0705 +0706 % Fill S-matrix +0707 rxnIdx = cellfun('isempty', equations(:,1)); +0708 equations(rxnIdx,:) = ''; +0709 rxnIdx = cell2mat(equations(:,1)); +0710 [~,metIdx] = ismember(equations(:,2),model.mets); +0711 coeffs = cell2mat(equations(:,3)); +0712 model.S=sparse(max(metIdx),max(rxnIdx)); +0713 linearIndices = sub2ind([max(metIdx), max(rxnIdx)],metIdx,rxnIdx); +0714 model.S(linearIndices) = coeffs; +0715 +0716 % Convert strings to numeric +0717 model.metCharges = str2double(model.metCharges); +0718 model.lb = str2double(model.lb); +0719 model.ub = str2double(model.ub); +0720 model.rxnConfidenceScores = str2double(model.rxnConfidenceScores); +0721 model.b = zeros(length(model.mets),1); +0722 model.metDeltaG = str2double(model.metDeltaG); +0723 model.rxnDeltaG = str2double(model.rxnDeltaG); +0724 +0725 % Fill some other fields +0726 model.annotation.defaultLB = min(model.lb); +0727 model.annotation.defaultUB = max(model.ub); +0728 if numel(model.lb)<numel(model.rxns) %No LB reported = min +0729 model.lb(end+1:numel(model.rxns)-numel(model.lb),1) = double(model.annotation.defaultLB); +0730 end +0731 if numel(model.ub)<numel(model.rxns) %No UB reported = max +0732 model.ub(end+1:numel(model.rxns)-numel(model.ub),1) = double(model.annotation.defaultUB); +0733 end +0734 if ~all(cellfun('isempty',model.rev)) +0735 model.rev = str2double(model.rev); +0736 else +0737 model.rev = []; +0738 end +0739 if numel(model.rev)<numel(model.rxns) %No rev reported, assume from LB and UB +0740 model.rev(end+1:numel(model.rxns)-numel(model.rev),1) = double(model.lb<0 & model.ub>0); +0741 end +0742 +0743 % Remove empty fields, otherwise fill to correct length +0744 % Reactions +0745 for i={'rxnNames','grRules','eccodes','rxnNotes','rxnReferences',... +0746 'rxnFrom','subSystems','rxnMiriams'} % Empty strings +0747 model = emptyOrFill(model,i{1},{''},'rxns'); +0748 end +0749 for i={'c'} % Zeros +0750 model = emptyOrFill(model,i{1},0,'rxns',true); +0751 end +0752 for i={'rxnConfidenceScores','rxnDeltaG'} % NaNs +0753 model = emptyOrFill(model,i{1},NaN,'rxns'); +0754 end +0755 for i={'rxnComps'} % Ones, assume first compartment +0756 model = emptyOrFill(model,i{1},1,'rxns'); +0757 end +0758 % Metabolites +0759 for i={'metNames','inchis','metFormulas','metMiriams','metFrom','metSmiles','metNotes'} % Empty strings +0760 model = emptyOrFill(model,i{1},{''},'mets'); +0761 end +0762 for i={'unconstrained'} % Zeros +0763 model = emptyOrFill(model,i{1},0,'mets'); +0764 end +0765 for i={'metCharges','metDeltaG'} % NaNs +0766 model = emptyOrFill(model,i{1},NaN,'mets'); +0767 end +0768 for i={'metComps'} % Ones, assume first compartment +0769 model = emptyOrFill(model,i{1},1,'mets'); +0770 end +0771 % Genes +0772 for i={'geneMiriams','geneShortNames','proteins'} % Empty strings +0773 model = emptyOrFill(model,i{1},{''},'genes'); +0774 end +0775 for i={'geneComps'} % Ones, assume first compartment +0776 model = emptyOrFill(model,i{1},1,'genes'); +0777 end +0778 % Comps +0779 for i={'compNames','compMiriams'} % Empty strings +0780 model = emptyOrFill(model,i{1},{''},'comps'); +0781 end +0782 for i={'compOutside'} % First comp +0783 model = emptyOrFill(model,i{1},model.comps{1},'comps'); +0784 end +0785 % Single fields are kept, even if empty +0786 % for i={'description','name','version','date','annotation'} +0787 % if isempty(model.(i{1})) +0788 % model = rmfield(model,i{1}); +0789 % end +0790 % end +0791 +0792 if isfield(model,'subSystems') +0793 % If all entries are 1x1, then flatten +0794 if all(cellfun(@(x) numel(x) <= 1, model.subSystems)) +0795 model.subSystems = transpose([model.subSystems{:}]); +0796 end +0797 end +0798 +0799 % Make rxnGeneMat fields and map to the existing model.genes field +0800 if isfield(model,'grRules') +0801 [genes, rxnGeneMat] = getGenesFromGrRules(model.grRules); +0802 model.rxnGeneMat = sparse(numel(model.rxns),numel(model.genes)); +0803 [~,geneOrder] = ismember(genes,model.genes); +0804 if any(geneOrder == 0) +0805 error(['The grRules includes the following gene(s), that are not in '... +0806 'the list of model genes: ', genes{~geneOrder}]) +0807 end +0808 model.rxnGeneMat(:,geneOrder) = rxnGeneMat; +0809 else +0810 % Gene-less model: keep rxnGeneMat dimensionally consistent with the +0811 % number of reactions and genes, rather than leaving it empty. +0812 model.rxnGeneMat = sparse(numel(model.rxns),numel(model.genes)); +0813 end +0814 +0815 % Finalize GECKO model +0816 if isGECKO +0817 % Fill in empty fields and empty entries +0818 for i={'kcat','source','notes','eccodes'} % Even keep empty +0819 model.ec = emptyOrFill(model.ec,i{1},{''},'rxns',true); +0820 end +0821 for i={'enzymes','mw','sequence'} +0822 model.ec = emptyOrFill(model.ec,i{1},{''},'genes',true); +0823 end +0824 model.ec = emptyOrFill(model.ec,'concs',{'NaN'},'genes',true); +0825 model.ec = emptyOrFill(model.ec,'kcat',{'0'},'genes',true); +0826 % Change string to double +0827 for i={'kcat','mw','concs'} +0828 if isfield(model.ec,i{1}) +0829 model.ec.(i{1}) = str2double(model.ec.(i{1})); +0830 end +0831 end +0832 % Fill rxnEnzMat +0833 rxnIdx = cellfun('isempty', enzStoich(:,1)); +0834 enzStoich(rxnIdx,:) = ''; +0835 rxnIdx = cell2mat(enzStoich(:,1)); +0836 [~,enzIdx] = ismember(enzStoich(:,2),model.ec.enzymes); +0837 coeffs = cell2mat(enzStoich(:,3)); +0838 model.ec.rxnEnzMat = zeros(numel(model.ec.rxns), numel(model.ec.genes)); +0839 linearIndices = sub2ind([numel(model.ec.rxns), numel(model.ec.genes)], rxnIdx, enzIdx); +0840 model.ec.rxnEnzMat(linearIndices) = coeffs; +0841 %Parse ec-codes +0842 if ~isempty(ecGecko) +0843 locs = cell2mat(ecGecko(:,1)); +0844 for i=unique(locs)' +0845 ecGeckoCat=strjoin(ecGecko(locs==i,2),';'); +0846 model.ec.eccodes{i,1}=ecGeckoCat; +0847 end +0848 emptyEc=cellfun('isempty',model.ec.eccodes); +0849 model.ec.eccodes(emptyEc)={''}; +0850 end +0851 end +0852 +0853 if verbose +0854 fprintf(' Done!\n'); +0855 end +0856 end +0857 +0858 function model = emptyOrFill(model,field,emptyEntry,type,keepEmpty) +0859 if nargin<5 +0860 keepEmpty=false; +0861 end +0862 if isnumeric(emptyEntry) +0863 emptyCells=isempty(model.(field)); +0864 else +0865 emptyCells=cellfun('isempty',model.(field)); +0866 end +0867 if all(emptyCells) && ~keepEmpty +0868 model = rmfield(model, field); +0869 elseif numel(model.(field))<numel(model.(type)) +0870 model.(field)(end+1:numel(model.(type)),1)=emptyEntry; +0871 end +0872 end +0873 +0874 function model = readFieldValue(model, fieldName, value, pos) +0875 if numel(model.(fieldName))<pos-1 +0876 model.(fieldName)(end+1:pos,1) = {''}; 0877 end -0878 -0879 function [miriams,miriamKey,entryNumber] = gatherAnnotation(pos,miriams,key,value,miriamKey,entryNumber) -0880 if isempty(key) -0881 key=miriamKey; -0882 else -0883 miriamKey=key; -0884 end -0885 if ~isempty(value) -0886 miriams(entryNumber,1:3) = {pos, key, strip(value)}; -0887 entryNumber = entryNumber + 1; -0888 end -0889 end +0878 model.(fieldName)(pos,1) = {value}; +0879 end +0880 +0881 function [miriams,miriamKey,entryNumber] = gatherAnnotation(pos,miriams,key,value,miriamKey,entryNumber) +0882 if isempty(key) +0883 key=miriamKey; +0884 else +0885 miriamKey=key; +0886 end +0887 if ~isempty(value) +0888 miriams(entryNumber,1:3) = {pos, key, strip(value)}; +0889 entryNumber = entryNumber + 1; +0890 end +0891 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/queries/getIndexes.html b/docs/html/queries/getIndexes.html index ffb774b0..6fb99f30 100644 --- a/docs/html/queries/getIndexes.html +++ b/docs/html/queries/getIndexes.html @@ -40,6 +40,8 @@

    DESCRIPTION ^SOURCE CODE ^% 'rxns', 'mets', or 'genes' depending on what to retrieve. 'metnames' 0013 % queries metabolite names, while 'metcomps' allows providing specific 0014 % metabolites and their compartments in the format metaboliteName[comp]. -0015 % If a model.ec structure exists (GECKO 3), then also 'ecenzymes', -0016 % 'ecrxns' and 'ecgenes' are allowed. -0017 % -0018 % Name-Value Arguments -0019 % -------------------- -0020 % returnLogical : logical -0021 % sets whether to return a logical array or an array with the indexes -0022 % (default false). -0023 % -0024 % Returns -0025 % ------- -0026 % indexes : logical or double -0027 % can be a logical array or a double array depending on the value of -0028 % returnLogical. -0029 % -0030 % Notes -0031 % ----- -0032 % If 'ecenzymes', 'ecrxns' or 'ecgenes' are used with a GECKO 3 model, then -0033 % the indexes are from the model.ec.enzymes, model.ec.rxns or model.ec.genes -0034 % fields, respectively. -0035 % -0036 % Examples -0037 % -------- -0038 % indexes = getIndexes(model, objects, type, returnLogical); -0039 -0040 p=parseRAVENargs(varargin, {'returnLogical', false}); -0041 returnLogical=p.returnLogical; -0042 -0043 if ~islogical(objects) && ~isnumeric(objects) -0044 objects=convertCharArray(objects); -0045 end -0046 type=char(type); -0047 -0048 indexes=[]; +0015 % An error is raised if such a name matches no metabolite, or more than +0016 % one. +0017 % If a model.ec structure exists (GECKO 3), then also 'ecenzymes', +0018 % 'ecrxns' and 'ecgenes' are allowed. +0019 % +0020 % Name-Value Arguments +0021 % -------------------- +0022 % returnLogical : logical +0023 % sets whether to return a logical array or an array with the indexes +0024 % (default false). +0025 % +0026 % Returns +0027 % ------- +0028 % indexes : logical or double +0029 % can be a logical array or a double array depending on the value of +0030 % returnLogical. +0031 % +0032 % Notes +0033 % ----- +0034 % If 'ecenzymes', 'ecrxns' or 'ecgenes' are used with a GECKO 3 model, then +0035 % the indexes are from the model.ec.enzymes, model.ec.rxns or model.ec.genes +0036 % fields, respectively. +0037 % +0038 % Examples +0039 % -------- +0040 % indexes = getIndexes(model, objects, type, returnLogical); +0041 +0042 p=parseRAVENargs(varargin, {'returnLogical', false}); +0043 returnLogical=p.returnLogical; +0044 +0045 if ~islogical(objects) && ~isnumeric(objects) +0046 objects=convertCharArray(objects); +0047 end +0048 type=char(type); 0049 -0050 switch type -0051 case 'rxns' -0052 searchIn=model.rxns; -0053 case 'mets' -0054 searchIn=model.mets; -0055 case 'genes' -0056 searchIn=model.genes; -0057 case 'metnames' -0058 searchIn=model.metNames; -0059 case 'metcomps' -0060 % If provided as metaboliteName[comp], then index search is quite -0061 % different from general approach, and therefore coded separately. -0062 mets=regexprep(objects,'(^.+)\[(.+)\]$','$1'); -0063 comps=regexprep(objects,'(^.+)\[(.+)\]$','$2'); -0064 indexes=zeros(1,numel(objects)); -0065 for i=1:numel(objects) -0066 index=find(strcmp(mets(i),model.metNames)); -0067 index=index(strcmp(comps(i),model.comps(model.metComps(index)))); -0068 if ~isempty(index) -0069 indexes(i)=index; -0070 else -0071 error(['Could not find object ' objects{i} ' in the model']); -0072 end -0073 end -0074 indexes=indexes(:); -0075 if returnLogical==true -0076 tempIndexes=false(numel(model.mets),1); -0077 tempIndexes(indexes)=true; -0078 indexes=tempIndexes; +0050 indexes=[]; +0051 +0052 switch type +0053 case 'rxns' +0054 searchIn=model.rxns; +0055 case 'mets' +0056 searchIn=model.mets; +0057 case 'genes' +0058 searchIn=model.genes; +0059 case 'metnames' +0060 searchIn=model.metNames; +0061 case 'metcomps' +0062 % If provided as metaboliteName[comp], then index search is quite +0063 % different from general approach, and therefore coded separately. +0064 mets=regexprep(objects,'(^.+)\[(.+)\]$','$1'); +0065 comps=regexprep(objects,'(^.+)\[(.+)\]$','$2'); +0066 indexes=zeros(1,numel(objects)); +0067 for i=1:numel(objects) +0068 index=find(strcmp(mets(i),model.metNames)); +0069 index=index(strcmp(comps(i),model.comps(model.metComps(index)))); +0070 if isscalar(index) +0071 indexes(i)=index; +0072 elseif isempty(index) +0073 error(['Could not find object ''' objects{i} ''' in the model']); +0074 else +0075 error(['''' objects{i} ''' is ambiguous: it matches the ' ... +0076 'metabolites ' strjoin(model.mets(index),', ') ... +0077 '. Use metabolite ids (type ''mets'') instead.']); +0078 end 0079 end -0080 return %None of the remaining function needs to run if metcomps -0081 case 'ecrxns' -0082 if ~isfield(model,'ec') -0083 error('Type %s cannot be used if no model.ec structure is present.',type) -0084 end -0085 searchIn=model.ec.rxns; -0086 case 'ecenzymes' -0087 if ~isfield(model,'ec') -0088 error('Type %s cannot be used if no model.ec structure is present.',type) -0089 end -0090 searchIn=model.ec.enzymes; -0091 case 'ecgenes' -0092 if ~isfield(model,'ec') -0093 error('Type %s cannot be used if no model.ec structure is present.',type) -0094 end -0095 searchIn=model.ec.genes; -0096 otherwise -0097 if contains(objects,{'rxns','mets','metnames','metcomps','genes','ecgenes','ecenzymes','ecrxns'}) -0098 error('The second and third parameter provided to run getIndexes are likely switched. Note that the second parameter is the object to find the index for, while the third parameter is the object type (''rxns'', ''mets'', ''metnames'', ''metcomps'', ''genes'', ''ecgenes'', ''ecenzymes'' or ''ecrxns'').') -0099 else -0100 error('Incorrect value of the ''type'' parameter. Allowed values are ''rxns'', ''mets'', ''metnames'', ''metcomps'', ''genes'', ''ecgenes'', ''ecenzymes'' or ''ecrxns''.'); -0101 end -0102 end -0103 -0104 if iscell(objects) -0105 if strcmpi(type,'metnames') -0106 % metnames allows multiple matches per name (same metabolite in different -0107 % compartments), so return a cell array of index vectors. -0108 for i=1:numel(objects) -0109 indexes{i}=find(strcmp(objects(i),searchIn)); -0110 end -0111 else -0112 % Build a hash map once (O(m)) for O(1) per-object lookup instead of -0113 % O(m) strcmp per object. -0114 searchMap = containers.Map(searchIn, 1:numel(searchIn)); -0115 for i=1:numel(objects) -0116 if isKey(searchMap, objects{i}) -0117 indexes(i) = searchMap(objects{i}); -0118 else -0119 error(['Could not find object ''' objects{i} ''' in the model']); -0120 end -0121 end -0122 end -0123 else -0124 %Now it's either a logical mask or an array with indexes. We want indexes. -0125 if islogical(objects) -0126 indexes=find(objects); -0127 else -0128 indexes=objects; -0129 end -0130 end -0131 -0132 if returnLogical==true -0133 tempIndexes=false(numel(searchIn),1); -0134 tempIndexes(indexes)=true; -0135 indexes=tempIndexes; +0080 indexes=indexes(:); +0081 if returnLogical==true +0082 tempIndexes=false(numel(model.mets),1); +0083 tempIndexes(indexes)=true; +0084 indexes=tempIndexes; +0085 end +0086 return %None of the remaining function needs to run if metcomps +0087 case 'ecrxns' +0088 if ~isfield(model,'ec') +0089 error('Type %s cannot be used if no model.ec structure is present.',type) +0090 end +0091 searchIn=model.ec.rxns; +0092 case 'ecenzymes' +0093 if ~isfield(model,'ec') +0094 error('Type %s cannot be used if no model.ec structure is present.',type) +0095 end +0096 searchIn=model.ec.enzymes; +0097 case 'ecgenes' +0098 if ~isfield(model,'ec') +0099 error('Type %s cannot be used if no model.ec structure is present.',type) +0100 end +0101 searchIn=model.ec.genes; +0102 otherwise +0103 if contains(objects,{'rxns','mets','metnames','metcomps','genes','ecgenes','ecenzymes','ecrxns'}) +0104 error('The second and third parameter provided to run getIndexes are likely switched. Note that the second parameter is the object to find the index for, while the third parameter is the object type (''rxns'', ''mets'', ''metnames'', ''metcomps'', ''genes'', ''ecgenes'', ''ecenzymes'' or ''ecrxns'').') +0105 else +0106 error('Incorrect value of the ''type'' parameter. Allowed values are ''rxns'', ''mets'', ''metnames'', ''metcomps'', ''genes'', ''ecgenes'', ''ecenzymes'' or ''ecrxns''.'); +0107 end +0108 end +0109 +0110 if iscell(objects) +0111 if strcmpi(type,'metnames') +0112 % metnames allows multiple matches per name (same metabolite in different +0113 % compartments), so return a cell array of index vectors. +0114 for i=1:numel(objects) +0115 indexes{i}=find(strcmp(objects(i),searchIn)); +0116 end +0117 else +0118 % Build a hash map once (O(m)) for O(1) per-object lookup instead of +0119 % O(m) strcmp per object. +0120 searchMap = containers.Map(searchIn, 1:numel(searchIn)); +0121 for i=1:numel(objects) +0122 if isKey(searchMap, objects{i}) +0123 indexes(i) = searchMap(objects{i}); +0124 else +0125 error(['Could not find object ''' objects{i} ''' in the model']); +0126 end +0127 end +0128 end +0129 else +0130 %Now it's either a logical mask or an array with indexes. We want indexes. +0131 if islogical(objects) +0132 indexes=find(objects); +0133 else +0134 indexes=objects; +0135 end 0136 end 0137 -0138 indexes=indexes(:); -0139 if iscell(indexes) && length(indexes)==1 -0140 indexes=indexes{1}; -0141 end -0142 end +0138 if returnLogical==true +0139 tempIndexes=false(numel(searchIn),1); +0140 tempIndexes(indexes)=true; +0141 indexes=tempIndexes; +0142 end +0143 +0144 indexes=indexes(:); +0145 if iscell(indexes) && length(indexes)==1 +0146 indexes=indexes{1}; +0147 end +0148 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/reconstruction/kegg/getKEGGModelForOrganism.html b/docs/html/reconstruction/kegg/getKEGGModelForOrganism.html index e208c085..28ab0731 100644 --- a/docs/html/reconstruction/kegg/getKEGGModelForOrganism.html +++ b/docs/html/reconstruction/kegg/getKEGGModelForOrganism.html @@ -496,211 +496,219 @@

    SOURCE CODE ^'kegg.genes'; 0341 model.geneMiriams{i,1}.value{1,1}=strcat(lower(organismID),model.genes{i,1}); 0342 end -0343 %Add the description to the reactions -0344 for i=1:numel(model.rxns) -0345 if ~isempty(model.rxnNotes{i}) -0346 model.rxnNotes(i)=strcat('Included by getKEGGModelForOrganism (without HMMs).',model.rxnNotes(i)); -0347 model.rxnNotes(i)=strrep(model.rxnNotes(i),'.','. '); -0348 else -0349 model.rxnNotes(i)={'Included by getKEGGModelForOrganism (without HMMs)'}; -0350 end -0351 end -0352 fprintf('COMPLETE\n\n'); -0353 fprintf('*** Model reconstruction complete ***\n'); -0354 return; -0355 end -0356 -0357 %Query the whole proteome against the concatenated KO HMM library in a -0358 %single hmmsearch. With the profile library as the query and the -0359 %proteome as the target sequence database, per-hit E-values stay -0360 %comparable across KOs (same search direction, same effective database -0361 %size), and no per-organism phylogenetic-distance subsampling is needed -0362 %because the library sequence set is already fixed. -0363 if ispc -0364 binEnd='.exe'; -0365 elseif ismac -0366 binEnd='.mac'; -0367 else -0368 binEnd=''; -0369 end -0370 %Fetch the HMMER binary on demand if it is not already present -0371 if ~exist(fullfile(ravenPath,'src','external','hmmer',['hmmsearch' binEnd]),'file') -0372 downloadRavenBinaries({'hmmer'}); +0343 %Add the description to the reactions. The reference model has no +0344 %reaction notes, so readYAMLmodel leaves the field out +0345 if ~isfield(model,'rxnNotes') +0346 model.rxnNotes=repmat({''},numel(model.rxns),1); +0347 end +0348 for i=1:numel(model.rxns) +0349 if ~isempty(model.rxnNotes{i}) +0350 model.rxnNotes(i)=strcat('Included by getKEGGModelForOrganism (without HMMs).',model.rxnNotes(i)); +0351 model.rxnNotes(i)=strrep(model.rxnNotes(i),'.','. '); +0352 else +0353 model.rxnNotes(i)={'Included by getKEGGModelForOrganism (without HMMs)'}; +0354 end +0355 end +0356 fprintf('COMPLETE\n\n'); +0357 fprintf('*** Model reconstruction complete ***\n'); +0358 return; +0359 end +0360 +0361 %Query the whole proteome against the concatenated KO HMM library in a +0362 %single hmmsearch. With the profile library as the query and the +0363 %proteome as the target sequence database, per-hit E-values stay +0364 %comparable across KOs (same search direction, same effective database +0365 %size), and no per-organism phylogenetic-distance subsampling is needed +0366 %because the library sequence set is already fixed. +0367 if ispc +0368 binEnd='.exe'; +0369 elseif ismac +0370 binEnd='.mac'; +0371 else +0372 binEnd=''; 0373 end -0374 tblFile=[tempname '.tblout']; -0375 fprintf('Querying the user-specified FASTA file against the KEGG Orthology specific HMMs... '); -0376 [status, output]=system(['"' fullfile(ravenPath,'src','external','hmmer',['hmmsearch' binEnd]) '" --cpu "' num2str(cores) '" --tblout "' tblFile '" "' libraryFile '" "' fastaFile '"']); -0377 if status~=0 -0378 EM=['Error when querying the concatenated HMM library:\n' output]; -0379 error('RAVEN:badInput', '%s', EM); -0380 end -0381 fprintf('COMPLETE\n'); -0382 -0383 fprintf('Parsing the HMM search results... '); -0384 koGeneMat=zeros(numel(KOModel.rxns),3000); %Make room for 3000 genes -0385 genes=cell(3000,1); -0386 %Map each KO id to its row in koGeneMat -0387 koTable=java.util.Hashtable; -0388 for i=1:numel(KOModel.rxns) -0389 koTable.put(KOModel.rxns{i},i); -0390 end -0391 %Store the column index for each gene in a hash list -0392 hTable=java.util.Hashtable; -0393 geneCounter=0; -0394 fid=fopen(tblFile,'r'); -0395 while 1 -0396 tline=fgetl(fid); -0397 if ~ischar(tline) -0398 break; -0399 end -0400 %Skip the comment/header lines that hmmsearch --tblout writes -0401 if isempty(tline) || tline(1)=='#' -0402 continue; +0374 %Fetch the HMMER binary on demand if it is not already present +0375 if ~exist(fullfile(ravenPath,'src','external','hmmer',['hmmsearch' binEnd]),'file') +0376 downloadRavenBinaries({'hmmer'}); +0377 end +0378 tblFile=[tempname '.tblout']; +0379 fprintf('Querying the user-specified FASTA file against the KEGG Orthology specific HMMs... '); +0380 [status, output]=system(['"' fullfile(ravenPath,'src','external','hmmer',['hmmsearch' binEnd]) '" --cpu "' num2str(cores) '" --tblout "' tblFile '" "' libraryFile '" "' fastaFile '"']); +0381 if status~=0 +0382 EM=['Error when querying the concatenated HMM library:\n' output]; +0383 error('RAVEN:badInput', '%s', EM); +0384 end +0385 fprintf('COMPLETE\n'); +0386 +0387 fprintf('Parsing the HMM search results... '); +0388 koGeneMat=zeros(numel(KOModel.rxns),3000); %Make room for 3000 genes +0389 genes=cell(3000,1); +0390 %Map each KO id to its row in koGeneMat +0391 koTable=java.util.Hashtable; +0392 for i=1:numel(KOModel.rxns) +0393 koTable.put(KOModel.rxns{i},i); +0394 end +0395 %Store the column index for each gene in a hash list +0396 hTable=java.util.Hashtable; +0397 geneCounter=0; +0398 fid=fopen(tblFile,'r'); +0399 while 1 +0400 tline=fgetl(fid); +0401 if ~ischar(tline) +0402 break; 0403 end -0404 elements=regexp(strtrim(tline),'\s+','split'); -0405 if numel(elements)<5 +0404 %Skip the comment/header lines that hmmsearch --tblout writes +0405 if isempty(tline) || tline(1)=='#' 0406 continue; 0407 end -0408 %With the profile library as the query, the target name (col 1) is -0409 %the proteome gene, the query name (col 3) is the KO, and the -0410 %full-sequence E-value is in col 5 -0411 gene=elements{1}; -0412 ko=elements{3}; -0413 score=str2double(elements{5}); -0414 koIdx=koTable.get(ko); -0415 if isempty(koIdx) || isnan(score) || score>cutOff -0416 continue; -0417 end -0418 %If the score is exactly 0, change it to a very small value to avoid -0419 %NaN -0420 if score==0 -0421 score=10^-250; -0422 end -0423 I=hTable.get(gene); -0424 if any(I) -0425 %Keep the best (smallest) E-value for this gene-KO pair -0426 if koGeneMat(koIdx,I)==0 || score<koGeneMat(koIdx,I) -0427 koGeneMat(koIdx,I)=score; -0428 end -0429 else -0430 geneCounter=geneCounter+1; -0431 hTable.put(gene,geneCounter); -0432 genes{geneCounter}=gene; -0433 koGeneMat(koIdx,geneCounter)=score; -0434 end -0435 end -0436 fclose(fid); -0437 delete(tblFile); -0438 fprintf('COMPLETE\n'); -0439 -0440 fprintf('Removing gene, KEGG Orthology associations below minScoreRatioKO, minScoreRatioG... '); -0441 koGeneMat=koGeneMat(:,1:geneCounter); -0442 -0443 %Remove the genes for each KO that are below minScoreRatioKO. -0444 for i=1:size(koGeneMat,1) -0445 J=find(koGeneMat(i,:)); -0446 if any(J) -0447 koGeneMat(i,J(log(koGeneMat(i,J))/log(min(koGeneMat(i,J)))<minScoreRatioKO))=0; -0448 end -0449 end -0450 -0451 %Remove the KOs for each gene that are below minScoreRatioG -0452 for i=1:size(koGeneMat,2) -0453 J=find(koGeneMat(:,i)); -0454 if any(J) -0455 koGeneMat(J(log(koGeneMat(J,i))/log(min(koGeneMat(J,i)))<minScoreRatioG),i)=0; -0456 end -0457 end -0458 fprintf('COMPLETE\n'); -0459 -0460 fprintf('Adding gene annotations to the model... '); -0461 %Create the new model -0462 model.genes=genes(1:geneCounter); -0463 model.grRules=cell(numel(model.rxns),1); -0464 model.grRules(:)={''}; -0465 model.rxnGeneMat=sparse(numel(model.rxns),numel(model.genes)); -0466 -0467 %Loop through the reactions and add the corresponding genes -0468 for i=1:numel(model.rxns) -0469 if isstruct(model.rxnMiriams{i}) -0470 %Get all KOs -0471 I=find(strcmpi(model.rxnMiriams{i}.name,'kegg.orthology')); -0472 KOs=model.rxnMiriams{i}.value(I); -0473 %Find the KOs and the corresponding genes -0474 J=ismember(KOModel.rxns,KOs); -0475 [~, K]=find(koGeneMat(J,:)); -0476 -0477 if any(K) -0478 model.rxnGeneMat(i,K)=1; -0479 %Also delete KOs for which no genes were found. If no genes at -0480 %all were matched to the reaction it will be deleted later -0481 L=sum(koGeneMat(J,:),2)==0; -0482 model.rxnMiriams{i}.value(I(L))=[]; -0483 model.rxnMiriams{i}.name(I(L))=[]; -0484 end -0485 end -0486 end -0487 fprintf('COMPLETE\n'); -0488 -0489 %Find and delete all reactions without genes. This also removes genes that -0490 %are not used (which could happen because minScoreRatioG and -0491 %minScoreRatioKO). If keepSpontaneous==true, the spontaneous reactions -0492 %without genes are kept in the model. Spontaneous reactions with original -0493 %gene associations are treated in the same way, like the rest of the -0494 %reactions - if gene associations were removed during HMM search, such -0495 %reactions are deleted from the model -0496 if keepSpontaneous==true -0497 %Not the most comprise way to delete reactions without genes, but this -0498 %makes the code easier to understand. Firstly the non-spontaneous -0499 %reactions without genes are removed. After that, the second deletion -0500 %step removes spontaneous reactions, which had gene associations before -0501 %HMM search, but no longer have after it -0502 fprintf('Removing non-spontaneous reactions which after HMM search no longer have GPR rules... '); -0503 I=~any(model.rxnGeneMat,2)&~ismember(model.rxns,isSpontaneous); -0504 model=removeReactions(model,I,true,true); -0505 I=~any(model.rxnGeneMat,2)&ismember(model.rxns,spontRxnsWithGenes); -0506 model=removeReactions(model,I,true,true); -0507 else -0508 %Just simply check for any new reactions without genes and remove -0509 %it -0510 fprintf('Removing reactions which after HMM search no longer have GPR rules... '); -0511 I=~any(model.rxnGeneMat,2); -0512 model=removeReactions(model,I,true,true); -0513 end -0514 fprintf('COMPLETE\n'); -0515 -0516 fprintf('Constructing GPR rules and finalizing the model... '); -0517 %Add the gene associations as "or" -0518 for i=1:numel(model.rxns) -0519 %Find the involved genes -0520 I=find(model.rxnGeneMat(i,:)); -0521 if any(I) -0522 model.grRules{i}=['(' model.genes{I(1)}]; -0523 for j=2:numel(I) -0524 model.grRules{i}=[model.grRules{i} ' or ' model.genes{I(j)}]; -0525 end -0526 model.grRules{i}=[model.grRules{i} ')']; -0527 end -0528 end -0529 -0530 %Fix grRules and reconstruct rxnGeneMat -0531 [grRules,rxnGeneMat] = standardizeGrRules(model,false); %Give detailed output -0532 model.grRules = grRules; -0533 model.rxnGeneMat = rxnGeneMat; -0534 -0535 %Add the description to the reactions -0536 for i=1:numel(model.rxns) -0537 if ~isempty(model.rxnNotes{i}) -0538 model.rxnNotes(i)=strcat('Included by getKEGGModelForOrganism (using HMMs).',model.rxnNotes(i)); -0539 model.rxnNotes(i)=strrep(model.rxnNotes(i),'.','. '); -0540 else -0541 model.rxnNotes(i)={'Included by getKEGGModelForOrganism (using HMMs)'}; -0542 end +0408 elements=regexp(strtrim(tline),'\s+','split'); +0409 if numel(elements)<5 +0410 continue; +0411 end +0412 %With the profile library as the query, the target name (col 1) is +0413 %the proteome gene, the query name (col 3) is the KO, and the +0414 %full-sequence E-value is in col 5 +0415 gene=elements{1}; +0416 ko=elements{3}; +0417 score=str2double(elements{5}); +0418 koIdx=koTable.get(ko); +0419 if isempty(koIdx) || isnan(score) || score>cutOff +0420 continue; +0421 end +0422 %If the score is exactly 0, change it to a very small value to avoid +0423 %NaN +0424 if score==0 +0425 score=10^-250; +0426 end +0427 I=hTable.get(gene); +0428 if any(I) +0429 %Keep the best (smallest) E-value for this gene-KO pair +0430 if koGeneMat(koIdx,I)==0 || score<koGeneMat(koIdx,I) +0431 koGeneMat(koIdx,I)=score; +0432 end +0433 else +0434 geneCounter=geneCounter+1; +0435 hTable.put(gene,geneCounter); +0436 genes{geneCounter}=gene; +0437 koGeneMat(koIdx,geneCounter)=score; +0438 end +0439 end +0440 fclose(fid); +0441 delete(tblFile); +0442 fprintf('COMPLETE\n'); +0443 +0444 fprintf('Removing gene, KEGG Orthology associations below minScoreRatioKO, minScoreRatioG... '); +0445 koGeneMat=koGeneMat(:,1:geneCounter); +0446 +0447 %Remove the genes for each KO that are below minScoreRatioKO. +0448 for i=1:size(koGeneMat,1) +0449 J=find(koGeneMat(i,:)); +0450 if any(J) +0451 koGeneMat(i,J(log(koGeneMat(i,J))/log(min(koGeneMat(i,J)))<minScoreRatioKO))=0; +0452 end +0453 end +0454 +0455 %Remove the KOs for each gene that are below minScoreRatioG +0456 for i=1:size(koGeneMat,2) +0457 J=find(koGeneMat(:,i)); +0458 if any(J) +0459 koGeneMat(J(log(koGeneMat(J,i))/log(min(koGeneMat(J,i)))<minScoreRatioG),i)=0; +0460 end +0461 end +0462 fprintf('COMPLETE\n'); +0463 +0464 fprintf('Adding gene annotations to the model... '); +0465 %Create the new model +0466 model.genes=genes(1:geneCounter); +0467 model.grRules=cell(numel(model.rxns),1); +0468 model.grRules(:)={''}; +0469 model.rxnGeneMat=sparse(numel(model.rxns),numel(model.genes)); +0470 +0471 %Loop through the reactions and add the corresponding genes +0472 for i=1:numel(model.rxns) +0473 if isstruct(model.rxnMiriams{i}) +0474 %Get all KOs +0475 I=find(strcmpi(model.rxnMiriams{i}.name,'kegg.orthology')); +0476 KOs=model.rxnMiriams{i}.value(I); +0477 %Find the KOs and the corresponding genes +0478 J=ismember(KOModel.rxns,KOs); +0479 [~, K]=find(koGeneMat(J,:)); +0480 +0481 if any(K) +0482 model.rxnGeneMat(i,K)=1; +0483 %Also delete KOs for which no genes were found. If no genes at +0484 %all were matched to the reaction it will be deleted later +0485 L=sum(koGeneMat(J,:),2)==0; +0486 model.rxnMiriams{i}.value(I(L))=[]; +0487 model.rxnMiriams{i}.name(I(L))=[]; +0488 end +0489 end +0490 end +0491 fprintf('COMPLETE\n'); +0492 +0493 %Find and delete all reactions without genes. This also removes genes that +0494 %are not used (which could happen because minScoreRatioG and +0495 %minScoreRatioKO). If keepSpontaneous==true, the spontaneous reactions +0496 %without genes are kept in the model. Spontaneous reactions with original +0497 %gene associations are treated in the same way, like the rest of the +0498 %reactions - if gene associations were removed during HMM search, such +0499 %reactions are deleted from the model +0500 if keepSpontaneous==true +0501 %Not the most comprise way to delete reactions without genes, but this +0502 %makes the code easier to understand. Firstly the non-spontaneous +0503 %reactions without genes are removed. After that, the second deletion +0504 %step removes spontaneous reactions, which had gene associations before +0505 %HMM search, but no longer have after it +0506 fprintf('Removing non-spontaneous reactions which after HMM search no longer have GPR rules... '); +0507 I=~any(model.rxnGeneMat,2)&~ismember(model.rxns,isSpontaneous); +0508 model=removeReactions(model,I,true,true); +0509 I=~any(model.rxnGeneMat,2)&ismember(model.rxns,spontRxnsWithGenes); +0510 model=removeReactions(model,I,true,true); +0511 else +0512 %Just simply check for any new reactions without genes and remove +0513 %it +0514 fprintf('Removing reactions which after HMM search no longer have GPR rules... '); +0515 I=~any(model.rxnGeneMat,2); +0516 model=removeReactions(model,I,true,true); +0517 end +0518 fprintf('COMPLETE\n'); +0519 +0520 fprintf('Constructing GPR rules and finalizing the model... '); +0521 %Add the gene associations as "or" +0522 for i=1:numel(model.rxns) +0523 %Find the involved genes +0524 I=find(model.rxnGeneMat(i,:)); +0525 if any(I) +0526 model.grRules{i}=['(' model.genes{I(1)}]; +0527 for j=2:numel(I) +0528 model.grRules{i}=[model.grRules{i} ' or ' model.genes{I(j)}]; +0529 end +0530 model.grRules{i}=[model.grRules{i} ')']; +0531 end +0532 end +0533 +0534 %Fix grRules and reconstruct rxnGeneMat +0535 [grRules,rxnGeneMat] = standardizeGrRules(model,false); %Give detailed output +0536 model.grRules = grRules; +0537 model.rxnGeneMat = rxnGeneMat; +0538 +0539 %Add the description to the reactions. The reference model has no reaction +0540 %notes, so readYAMLmodel leaves the field out +0541 if ~isfield(model,'rxnNotes') +0542 model.rxnNotes=repmat({''},numel(model.rxns),1); 0543 end -0544 %Remove the temp fasta file -0545 delete(fastaFile) -0546 fprintf('COMPLETE\n\n*** Model reconstruction complete ***\n'); -0547 end +0544 for i=1:numel(model.rxns) +0545 if ~isempty(model.rxnNotes{i}) +0546 model.rxnNotes(i)=strcat('Included by getKEGGModelForOrganism (using HMMs).',model.rxnNotes(i)); +0547 model.rxnNotes(i)=strrep(model.rxnNotes(i),'.','. '); +0548 else +0549 model.rxnNotes(i)={'Included by getKEGGModelForOrganism (using HMMs)'}; +0550 end +0551 end +0552 %Remove the temp fasta file +0553 delete(fastaFile) +0554 fprintf('COMPLETE\n\n*** Model reconstruction complete ***\n'); +0555 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/solver/checkSolution.html b/docs/html/solver/checkSolution.html index 1bf7d3e3..088e67b8 100644 --- a/docs/html/solver/checkSolution.html +++ b/docs/html/solver/checkSolution.html @@ -4,7 +4,7 @@ Description of checkSolution - + @@ -21,18 +21,18 @@

    checkSolution

    PURPOSE ^

    -
    checkSolution Check if a Mosek solution is feasible and optimal.
    +
    checkSolution Check if a solver result is feasible and optimal.

    SYNOPSIS ^

    function [isFeasible, isOptimal]=checkSolution(res)

    DESCRIPTION ^

    -
     checkSolution  Check if a Mosek solution is feasible and optimal.
    +
     checkSolution  Check if a solver result is feasible and optimal.
     
      Parameters
      ----------
      res : struct
    -     the output structure from mosekopt.
    +     the output structure from optimizeProb.
     
      Returns
      -------
    @@ -43,11 +43,7 @@ 

    DESCRIPTION ^CROSS-REFERENCE INFORMATION ^

    @@ -63,12 +59,12 @@

    CROSS-REFERENCE INFORMATION ^
 
 <h2><a name=SOURCE CODE ^

    +0033 isFeasible=false; +0034 end +0035 end

    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/solver/getMinNrFluxes.html b/docs/html/solver/getMinNrFluxes.html index d649905c..2b84cf7b 100644 --- a/docs/html/solver/getMinNrFluxes.html +++ b/docs/html/solver/getMinNrFluxes.html @@ -106,7 +106,7 @@

    DESCRIPTION ^CROSS-REFERENCE INFORMATION ^

    This function calls:
      -
    • checkSolution checkSolution Check if a Mosek solution is feasible and optimal.
    • optimizeProb optimizeProb Optimize an LP or MILP formulated in COBRA terms.
    • solveLP solveLP Solve a linear programming problem.
    +
  • checkSolution checkSolution Check if a solver result is feasible and optimal.
  • optimizeProb optimizeProb Optimize an LP or MILP formulated in COBRA terms.
  • solveLP solveLP Solve a linear programming problem.
  • This function is called by:
    • solveLP solveLP Solve a linear programming problem.
    diff --git a/docs/html/solver/index.html b/docs/html/solver/index.html index b1b79505..7ebd6eed 100644 --- a/docs/html/solver/index.html +++ b/docs/html/solver/index.html @@ -19,7 +19,7 @@

    Index for solver

    Matlab files in this directory:

    -
     checkSolutioncheckSolution Check if a Mosek solution is feasible and optimal.
     getMinNrFluxesgetMinNrFluxes Find the minimal set of fluxes that satisfy the model.
     optimizeProboptimizeProb Optimize an LP or MILP formulated in COBRA terms.
     setRavenSolversetRavenSolver Set the solver for RAVEN.
     solveLPsolveLP Solve a linear programming problem.
     splitProbForConditioningsplitProbForConditioning Improve LP conditioning by splitting wide columns.
    + checkSolutioncheckSolution Check if a solver result is feasible and optimal.  getMinNrFluxesgetMinNrFluxes Find the minimal set of fluxes that satisfy the model.  optimizeProboptimizeProb Optimize an LP or MILP formulated in COBRA terms.  setRavenSolversetRavenSolver Set the solver for RAVEN.  solveLPsolveLP Solve a linear programming problem.  splitProbForConditioningsplitProbForConditioning Improve LP conditioning by splitting wide columns. diff --git a/docs/html/solver/optimizeProb.html b/docs/html/solver/optimizeProb.html index fa2f41ed..b486395d 100644 --- a/docs/html/solver/optimizeProb.html +++ b/docs/html/solver/optimizeProb.html @@ -55,6 +55,15 @@

    DESCRIPTION ^splitProbForConditioning

    @@ -103,404 +112,422 @@

    SOURCE CODE ^% time limit. Whatever solution is in res is then either absent or not 0028 % proven optimal. 0029 % -0030 % See also -0031 % -------- -0032 % splitProbForConditioning -0033 -0034 p=parseRAVENargs(varargin, {'params',[]; 'verbose',true}); -0035 params=p.params; -0036 if isempty(params) -0037 params=struct(); -0038 end -0039 verbose=p.verbose; -0040 -0041 %Set as global variable for speed improvement if optimizeProb is run many times -0042 global RAVENSOLVER; -0043 %CBT_MILP_SOLVER is a COBRA Toolbox global, set by changeCobraSolver, and -0044 %has to be declared as such to be readable in the MILP check below -0045 global CBT_MILP_SOLVER; -0046 if isempty(RAVENSOLVER) -0047 if(~ispref('RAVEN','solver')) -0048 error('RAVEN:badInput', '%s', 'RAVEN solver not defined or unknown. Try using setRavenSolver(''solver'').'); -0049 else -0050 RAVENSOLVER = getpref('RAVEN','solver'); -0051 end -0052 end -0053 solver=RAVENSOLVER; -0054 if ~all(lower(prob.vartype) == 'c') -0055 milp=true; -0056 errorText = 'glpk is not suitable for solving MILPs, '; -0057 switch solver -0058 case 'glpk' -0059 error([errorText 'select a different solver with setRavenSolver().']) -0060 case 'cobra' -0061 if strcmp(CBT_MILP_SOLVER,'glpk') -0062 error([errorText 'select a different solver with changeCobraSolver() or setRavenSolver().']) -0063 end -0064 end -0065 else -0066 milp=false; -0067 end -0068 -0069 %% Define default parameters, which will then be used to make solver- -0070 % specific solverparams structures -0071 defaultparams.feasTol = 1e-9; -0072 defaultparams.optTol = 1e-9; -0073 defaultparams.objTol = 1e-6; -0074 defaultparams.timeLimit = 1000; -0075 %defaultparams.iterationLimit = 1000; -0076 defaultparams.intTol = 1e-12; -0077 defaultparams.relMipGapTol = 1e-12; -0078 defaultparams.absMipGapTol = 1e-12; -0079 if milp -0080 defaultparams.MIPGap = 1e-12; -0081 defaultparams.Seed = 1; -0082 end -0083 -0084 %% Optionally improve numerical conditioning before solving. This is opt-in -0085 % through params.maxRatio and only applied to LPs. Columns whose coefficients -0086 % span more than maxRatio are split via auxiliary metabolites/variables, which -0087 % preserves the feasible region but reduces the spread within each column. The -0088 % added rows/columns are stripped from the result further down. -0089 condApplied = false; -0090 if isfield(params,'maxRatio') -0091 maxRatio = params.maxRatio; -0092 params = rmfield(params,'maxRatio'); -0093 if ~milp && isnumeric(maxRatio) && isscalar(maxRatio) && isfinite(maxRatio) && maxRatio>1 -0094 [prob, condInfo] = splitProbForConditioning(prob, maxRatio); -0095 condApplied = condInfo.applied; -0096 end -0097 end -0098 -0099 res.obj=[]; -0100 switch solver -0101 %% Use whatever solver is set by COBRA Toolbox changeCobraSolver -0102 case 'cobra' -0103 if milp -0104 cparams=struct('timeLimit',1e9,'printLevel',0,'intTol',1e-6,'relMipGapTol',1e-9); -0105 cparams=structUpdate(cparams,params); -0106 res=solveCobraMILP(prob,cparams); -0107 else -0108 res=solveCobraLP(prob); -0109 end -0110 %% Use Gurobi in a MATLAB environment -0111 case 'gurobi' -0112 if milp -0113 if verbose -0114 solverparams.OutputFlag = 1; -0115 else -0116 solverparams.OutputFlag = 0; -0117 end -0118 solverparams.IntFeasTol = 10^-9; %min val for gurobi -0119 solverparams.MIPGap = defaultparams.MIPGap; -0120 solverparams.Seed = defaultparams.Seed; -0121 else -0122 solverparams.OutputFlag = 0; -0123 end -0124 solverparams.DisplayInterval= 1; % Level of verbosity -0125 solverparams.TimeLimit = defaultparams.timeLimit; -0126 solverparams.FeasibilityTol = defaultparams.feasTol; -0127 solverparams.OptimalityTol = defaultparams.optTol; -0128 solverparams.Presolve = 2; -0129 % getCurrentTask requires the Parallel Computing Toolbox; without it -0130 % the call itself errors, so guard it rather than requiring the -0131 % toolbox for every gurobi solve. -0132 if exist('getCurrentTask','file') && ~isempty(getCurrentTask) % If run in parallel, then one thread per gurobi -0133 solverparams.Threads=1; -0134 end -0135 solverparams = structUpdate(solverparams,params); -0136 -0137 % Restructering problem according to gurobi format -0138 if isfield(prob, 'csense') -0139 prob.sense = renameparams(prob.csense, {'L','G','E'}, {'<','>','='}); -0140 prob = rmfield(prob, {'csense'}); -0141 end -0142 % gurobi minimises unless told otherwise, so an absent osense means -0143 % minimisation; osense is also needed below to sign the duals -0144 osense = 1; -0145 if isfield(prob, 'osense') -0146 osense = prob.osense; -0147 if osense < 0 -0148 prob.modelsense = 'max'; -0149 else -0150 prob.modelsense = 'min'; -0151 end -0152 prob = rmfield(prob, {'osense'}); +0030 % Notes +0031 % ----- +0032 % With 'glpk' selected, MILPs are solved by RAVEN's bundled GLPK, and a +0033 % RAVEN:glpkMILP warning recommends Gurobi, which is much faster and more +0034 % reliable on larger MILPs (such as ftINIT or getMinNrFluxes on +0035 % genome-scale models). +0036 % The GLPK interface of the COBRA Toolbox is not used for MILPs: with +0037 % 'cobra' selected, set a different MILP solver with changeCobraSolver. +0038 % +0039 % See also +0040 % -------- +0041 % splitProbForConditioning +0042 +0043 p=parseRAVENargs(varargin, {'params',[]; 'verbose',true}); +0044 params=p.params; +0045 if isempty(params) +0046 params=struct(); +0047 end +0048 verbose=p.verbose; +0049 +0050 %Set as global variable for speed improvement if optimizeProb is run many times +0051 global RAVENSOLVER; +0052 %CBT_MILP_SOLVER is a COBRA Toolbox global, set by changeCobraSolver, and +0053 %has to be declared as such to be readable in the MILP check below +0054 global CBT_MILP_SOLVER; +0055 if isempty(RAVENSOLVER) +0056 if(~ispref('RAVEN','solver')) +0057 error('RAVEN:badInput', '%s', 'RAVEN solver not defined or unknown. Try using setRavenSolver(''solver'').'); +0058 else +0059 RAVENSOLVER = getpref('RAVEN','solver'); +0060 end +0061 end +0062 solver=RAVENSOLVER; +0063 if ~all(lower(prob.vartype) == 'c') +0064 milp=true; +0065 switch solver +0066 case 'glpk' +0067 warning('RAVEN:glpkMILP', ['Solving a MILP with GLPK. This works for small ' ... +0068 'problems, but for larger problems Gurobi is much faster and more reliable; ' ... +0069 'select it with setRavenSolver(''gurobi''). Silence this warning with ' ... +0070 'warning(''off'',''RAVEN:glpkMILP'').']) +0071 case 'cobra' +0072 if strcmp(CBT_MILP_SOLVER,'glpk') +0073 error(['The GLPK interface of the COBRA Toolbox is not suitable for solving MILPs, ' ... +0074 'select a different solver with changeCobraSolver() or setRavenSolver().']) +0075 end +0076 end +0077 else +0078 milp=false; +0079 end +0080 +0081 %% Define default parameters, which will then be used to make solver- +0082 % specific solverparams structures +0083 defaultparams.feasTol = 1e-9; +0084 defaultparams.optTol = 1e-9; +0085 defaultparams.objTol = 1e-6; +0086 defaultparams.timeLimit = 1000; +0087 %defaultparams.iterationLimit = 1000; +0088 defaultparams.intTol = 1e-12; +0089 defaultparams.relMipGapTol = 1e-12; +0090 defaultparams.absMipGapTol = 1e-12; +0091 if milp +0092 defaultparams.MIPGap = 1e-12; +0093 defaultparams.Seed = 1; +0094 end +0095 +0096 %% Optionally improve numerical conditioning before solving. This is opt-in +0097 % through params.maxRatio and only applied to LPs. Columns whose coefficients +0098 % span more than maxRatio are split via auxiliary metabolites/variables, which +0099 % preserves the feasible region but reduces the spread within each column. The +0100 % added rows/columns are stripped from the result further down. +0101 condApplied = false; +0102 if isfield(params,'maxRatio') +0103 maxRatio = params.maxRatio; +0104 params = rmfield(params,'maxRatio'); +0105 if ~milp && isnumeric(maxRatio) && isscalar(maxRatio) && isfinite(maxRatio) && maxRatio>1 +0106 [prob, condInfo] = splitProbForConditioning(prob, maxRatio); +0107 condApplied = condInfo.applied; +0108 end +0109 end +0110 +0111 res.obj=[]; +0112 switch solver +0113 %% Use whatever solver is set by COBRA Toolbox changeCobraSolver +0114 case 'cobra' +0115 if milp +0116 cparams=struct('timeLimit',1e9,'printLevel',0,'intTol',1e-6,'relMipGapTol',1e-9); +0117 cparams=structUpdate(cparams,params); +0118 res=solveCobraMILP(prob,cparams); +0119 else +0120 res=solveCobraLP(prob); +0121 end +0122 %% Use Gurobi in a MATLAB environment +0123 case 'gurobi' +0124 if milp +0125 if verbose +0126 solverparams.OutputFlag = 1; +0127 else +0128 solverparams.OutputFlag = 0; +0129 end +0130 solverparams.IntFeasTol = 10^-9; %min val for gurobi +0131 solverparams.MIPGap = defaultparams.MIPGap; +0132 solverparams.Seed = defaultparams.Seed; +0133 else +0134 solverparams.OutputFlag = 0; +0135 end +0136 solverparams.DisplayInterval= 1; % Level of verbosity +0137 solverparams.TimeLimit = defaultparams.timeLimit; +0138 solverparams.FeasibilityTol = defaultparams.feasTol; +0139 solverparams.OptimalityTol = defaultparams.optTol; +0140 solverparams.Presolve = 2; +0141 % getCurrentTask requires the Parallel Computing Toolbox; without it +0142 % the call itself errors, so guard it rather than requiring the +0143 % toolbox for every gurobi solve. +0144 if exist('getCurrentTask','file') && ~isempty(getCurrentTask) % If run in parallel, then one thread per gurobi +0145 solverparams.Threads=1; +0146 end +0147 solverparams = structUpdate(solverparams,params); +0148 +0149 % Restructering problem according to gurobi format +0150 if isfield(prob, 'csense') +0151 prob.sense = renameparams(prob.csense, {'L','G','E'}, {'<','>','='}); +0152 prob = rmfield(prob, {'csense'}); 0153 end -0154 [prob.obj, prob.rhs, prob.vtype] = deal(prob.c, prob.b, prob.vartype); -0155 prob = rmfield(prob, {'c','b','vartype'}); -0156 -0157 resG = gurobi(prob,solverparams); -0158 -0159 % Name output fields the same as COBRA does. Every field below is -0160 % optional in the gurobi result: which ones are present depends on -0161 % the status and on the method gurobi chose (a barrier solve returns -0162 % no simplex basis, for instance). They are therefore read -0163 % individually rather than under one try/catch, which would have -0164 % reported a missing basis on an optimal solve as "infeasible". -0165 res.origStat = resG.status; -0166 if isfield(resG,'x') -0167 res.full = resG.x; -0168 end -0169 if isfield(resG,'objval') -0170 res.obj = resG.objval; -0171 end -0172 if isfield(resG,'pi') && isfield(resG,'rc') -0173 res.dual = -resG.pi*osense; -0174 res.rcost = -resG.rc*osense; -0175 end -0176 if milp && strcmp(resG.status, 'TIME_LIMIT') && isfield(resG,'x') && isfield(resG,'objval') -0177 % An incumbent was found before the time limit. It is usable but -0178 % not proven optimal, so it is reported as feasible rather than -0179 % optimal; callers that must not act on a suboptimal MILP -0180 % solution (getMinNrFluxes, solveLP) key on that distinction. -0181 resG.status = 'SUBOPTIMAL'; -0182 end -0183 switch resG.status -0184 case 'OPTIMAL' -0185 res.stat = 1; -0186 case {'UNBOUNDED','SUBOPTIMAL'} -0187 res.stat = 2; -0188 otherwise -0189 res.stat = 0; -0190 end -0191 if ~milp -0192 if isfield(resG,'vbasis') -0193 res.vbasis = resG.vbasis; -0194 end -0195 if isfield(resG,'cbasis') -0196 res.cbasis = resG.cbasis; -0197 end -0198 elseif isfield(resG,'mipgap') -0199 res.mipgap = resG.mipgap; -0200 end -0201 if res.stat>0 && ~isfield(res,'full') -0202 % A status that claims a solution without returning one -0203 res.stat = 0; -0204 end -0205 %% Use GLPK using RAVEN-provided binary -0206 case 'glpk' -0207 solverparams.scale = 1; % Auto scaling -0208 %solverparams.tmlim = defaultparams.timeLimit; -0209 solverparams.tolbnd = defaultparams.feasTol; -0210 solverparams.toldj = defaultparams.optTol; -0211 solverparams.tolint = defaultparams.intTol; -0212 solverparams.tolobj = defaultparams.objTol; -0213 solverparams.msglev = 0; % Level of verbosity -0214 solverparams = structUpdate(solverparams,params); -0215 -0216 prob.csense = renameparams(prob.csense, {'L','G','E'}, {'U','L','S'}); -0217 -0218 if milp -0219 solverparams.tmlim = solverparams.tmlim*10; -0220 solverparams.msglev = 1; % Level of verbosity -0221 disp('Issues have been observed when using GLPK for MILP solving. Be advised to carefully observe the results, or us another solver.') -0222 end -0223 -0224 % Ensure that RAVEN glpk binary is used, return to original -0225 % directory afterwards -0226 [ravenDir,currDir]=findRAVENroot(); -0227 cd(fullfile(ravenDir,'src','external','GLPKmex')) -0228 [xopt, fmin, errnum, extra] = glpk(prob.c, prob.A, prob.b, prob.lb, prob.ub, prob.csense, prob.vartype, prob.osense, solverparams); -0229 cd(currDir) +0154 % gurobi minimises unless told otherwise, so an absent osense means +0155 % minimisation; osense is also needed below to sign the duals +0156 osense = 1; +0157 if isfield(prob, 'osense') +0158 osense = prob.osense; +0159 if osense < 0 +0160 prob.modelsense = 'max'; +0161 else +0162 prob.modelsense = 'min'; +0163 end +0164 prob = rmfield(prob, {'osense'}); +0165 end +0166 [prob.obj, prob.rhs, prob.vtype] = deal(prob.c, prob.b, prob.vartype); +0167 prob = rmfield(prob, {'c','b','vartype'}); +0168 +0169 resG = gurobi(prob,solverparams); +0170 +0171 % Name output fields the same as COBRA does. Every field below is +0172 % optional in the gurobi result: which ones are present depends on +0173 % the status and on the method gurobi chose (a barrier solve returns +0174 % no simplex basis, for instance). They are therefore read +0175 % individually rather than under one try/catch, which would have +0176 % reported a missing basis on an optimal solve as "infeasible". +0177 res.origStat = resG.status; +0178 if isfield(resG,'x') +0179 res.full = resG.x; +0180 end +0181 if isfield(resG,'objval') +0182 res.obj = resG.objval; +0183 end +0184 if isfield(resG,'pi') && isfield(resG,'rc') +0185 res.dual = -resG.pi*osense; +0186 res.rcost = -resG.rc*osense; +0187 end +0188 if milp && strcmp(resG.status, 'TIME_LIMIT') && isfield(resG,'x') && isfield(resG,'objval') +0189 % An incumbent was found before the time limit. It is usable but +0190 % not proven optimal, so it is reported as feasible rather than +0191 % optimal; callers that must not act on a suboptimal MILP +0192 % solution (getMinNrFluxes, solveLP) key on that distinction. +0193 resG.status = 'SUBOPTIMAL'; +0194 end +0195 switch resG.status +0196 case 'OPTIMAL' +0197 res.stat = 1; +0198 case {'UNBOUNDED','SUBOPTIMAL'} +0199 res.stat = 2; +0200 otherwise +0201 res.stat = 0; +0202 end +0203 if ~milp +0204 if isfield(resG,'vbasis') +0205 res.vbasis = resG.vbasis; +0206 end +0207 if isfield(resG,'cbasis') +0208 res.cbasis = resG.cbasis; +0209 end +0210 elseif isfield(resG,'mipgap') +0211 res.mipgap = resG.mipgap; +0212 end +0213 if res.stat>0 && ~isfield(res,'full') +0214 % A status that claims a solution without returning one +0215 res.stat = 0; +0216 end +0217 %% Use GLPK using RAVEN-provided binary +0218 case 'glpk' +0219 solverparams.scale = 1; % Auto scaling +0220 %solverparams.tmlim = defaultparams.timeLimit; +0221 solverparams.tolbnd = defaultparams.feasTol; +0222 solverparams.toldj = defaultparams.optTol; +0223 solverparams.tolint = defaultparams.intTol; +0224 solverparams.tolobj = defaultparams.objTol; +0225 solverparams.msglev = 0; % Level of verbosity +0226 if milp +0227 solverparams.tmlim = defaultparams.timeLimit; % seconds +0228 end +0229 solverparams = structUpdate(solverparams,params); 0230 -0231 switch errnum % 1 = undefined; 2 = feasible; 3 = infeasible; 4 = no feasible solution; 5 = optimal; 6 = no unbounded solution -0232 case 5 -0233 res.stat = 1; % Optimal -0234 case 2 -0235 res.stat = 2; % Feasible, but not optimal -0236 otherwise -0237 res.stat = 0; -0238 end -0239 res.origStat = errnum; -0240 res.full = xopt; -0241 res.obj = fmin; -0242 res.dual = -extra.lambda*prob.osense; -0243 res.rcost = -extra.redcosts*prob.osense; -0244 %% Use scip -0245 case {'soplex','scip'} % Old 'soplex' option also allowed -0246 % scip takes a row range [rl,ru] rather than a right-hand side plus -0247 % a sense, so csense has to be translated into that range. Passing -0248 % [-b,b] is only the same problem when every row is an equality with -0249 % b = 0, which is the case for solveLP but for none of the MILPs -0250 % built elsewhere in RAVEN. -0251 rl = full(prob.b(:)); -0252 ru = rl; -0253 csense = prob.csense(:); -0254 rl(csense=='L') = -inf; % A*x <= b -0255 ru(csense=='G') = inf; % A*x >= b -0256 [xopt,fval,exitflag,scipStats] = scip([], prob.c, prob.A, rl, ru, prob.lb, prob.ub, prob.vartype); -0257 -0258 % [x,fval,exitflag,stats] = scip(H, f, A, rl, ru, lb, ub, xtype, sos, qc, nl, x0, opts) -0259 % -0260 % Input arguments*: -0261 % H - quadratic objective matrix (sparse, optional [NOT TRIL / TRIU]) -0262 % f - linear objective vector -0263 % A - linear constraint matrix (sparse) -0264 % rl - linear constraint lhs -0265 % ru - linear constraint rhs -0266 % lb - decision variable lower bounds -0267 % ub - decision variable upper bounds -0268 % xtype - string of variable integrality ("c" continuous, "i" integer, "b" binary) -0269 % sos - SOS structure with fields type, index and weight (see below) -0270 % qc - Quadratic Constraints structure with fields Q, l, qrl and qru (see below) -0271 % nl - Nonlinear Objective and Constraints structure (see below) -0272 % x0 - primal solution -0273 % opts - solver options (see below) -0274 % -0275 % Return arguments: -0276 % x - solution vector -0277 % fval - objective value at the solution -0278 % exitflag - exit status (see below) -0279 % stats - statistics structure -0280 % -0281 % Option Fields (all optional, see also optiset for a list): -0282 % solverOpts - specific SCIP options (list of pairs of parameter names and values) -0283 % maxiter - maximum LP solver iterations -0284 % maxnodes - maximum nodes to explore -0285 % maxtime - maximum execution time [s] -0286 % tolrfun - primal feasibility tolerance -0287 % display - solver display level [0-5] -0288 % probfile - write problem to given file -0289 % presolvedfile - write presolved problem to file -0290 % -0291 % Return Status, as returned by the MatlabSCIPInterface build that -0292 % RAVEN provides and that setRavenSolver points at -0293 % (github.com/scipopt/MatlabSCIPInterface). Verified against that -0294 % binary with a bounded, an infeasible and an unbounded problem: -0295 % 1 - Problem Solved to Optimality -0296 % 2 - Problem is Infeasible -0297 % 3 - Problem is Unbounded +0231 prob.csense = renameparams(prob.csense, {'L','G','E'}, {'U','L','S'}); +0232 +0233 % Ensure that RAVEN glpk binary is used, return to original +0234 % directory afterwards +0235 [ravenDir,currDir]=findRAVENroot(); +0236 cd(fullfile(ravenDir,'src','external','GLPKmex')) +0237 [xopt, fmin, errnum, extra] = glpk(prob.c, prob.A, prob.b, prob.lb, prob.ub, prob.csense, prob.vartype, prob.osense, solverparams); +0238 cd(currDir) +0239 +0240 switch errnum % 1 = undefined; 2 = feasible; 3 = infeasible; 4 = no feasible solution; 5 = optimal; 6 = no unbounded solution +0241 case 5 +0242 res.stat = 1; % Optimal +0243 case 2 +0244 res.stat = 2; % Feasible, but not optimal +0245 otherwise +0246 res.stat = 0; +0247 end +0248 res.origStat = errnum; +0249 res.full = xopt; +0250 res.obj = fmin; +0251 res.dual = -extra.lambda*prob.osense; +0252 res.rcost = -extra.redcosts*prob.osense; +0253 if milp +0254 % glpk reports no gap; a proven optimum has none, anything else +0255 % is not accepted by callers that test the gap +0256 if res.stat == 1 +0257 res.mipgap = 0; +0258 else +0259 res.mipgap = inf; +0260 end +0261 end +0262 %% Use scip +0263 case {'soplex','scip'} % Old 'soplex' option also allowed +0264 % scip takes a row range [rl,ru] rather than a right-hand side plus +0265 % a sense, so csense has to be translated into that range. Passing +0266 % [-b,b] is only the same problem when every row is an equality with +0267 % b = 0, which is the case for solveLP but for none of the MILPs +0268 % built elsewhere in RAVEN. +0269 rl = full(prob.b(:)); +0270 ru = rl; +0271 csense = prob.csense(:); +0272 rl(csense=='L') = -inf; % A*x <= b +0273 ru(csense=='G') = inf; % A*x >= b +0274 [xopt,fval,exitflag,scipStats] = scip([], prob.c, prob.A, rl, ru, prob.lb, prob.ub, prob.vartype); +0275 +0276 % [x,fval,exitflag,stats] = scip(H, f, A, rl, ru, lb, ub, xtype, sos, qc, nl, x0, opts) +0277 % +0278 % Input arguments*: +0279 % H - quadratic objective matrix (sparse, optional [NOT TRIL / TRIU]) +0280 % f - linear objective vector +0281 % A - linear constraint matrix (sparse) +0282 % rl - linear constraint lhs +0283 % ru - linear constraint rhs +0284 % lb - decision variable lower bounds +0285 % ub - decision variable upper bounds +0286 % xtype - string of variable integrality ("c" continuous, "i" integer, "b" binary) +0287 % sos - SOS structure with fields type, index and weight (see below) +0288 % qc - Quadratic Constraints structure with fields Q, l, qrl and qru (see below) +0289 % nl - Nonlinear Objective and Constraints structure (see below) +0290 % x0 - primal solution +0291 % opts - solver options (see below) +0292 % +0293 % Return arguments: +0294 % x - solution vector +0295 % fval - objective value at the solution +0296 % exitflag - exit status (see below) +0297 % stats - statistics structure 0298 % -0299 % The OPTI Toolbox build of scip uses a different, longer set in -0300 % which optimality is 11, infeasibility 12 and unboundedness 13, -0301 % with the lower numbers meaning "a limit was reached". Those codes -0302 % are accepted below as well, so either build reports optimality -0303 % correctly; only OPTI's 1 ("User Interrupted") is ambiguous, and -0304 % reading it as optimal is no worse than the previous mapping, -0305 % which recognised no status from the shipped build at all. -0306 -0307 res.origStat = exitflag; -0308 res.full = xopt; -0309 res.obj = fval; -0310 -0311 switch exitflag -0312 case {1, 11} % solved to optimality -0313 res.stat = 1; -0314 case {3, 13} % unbounded -0315 res.stat = 2; -0316 case {5, 6, 7, 8, 9, 10} % OPTI: a limit was reached, so a -0317 res.stat = 2; % solution may exist but is not proven optimal -0318 otherwise % 2/12 infeasible, 0 unknown, 4, 14 -0319 res.stat = 0; -0320 end -0321 -0322 if milp -0323 % Callers such as ftINIT compare this against their own -0324 % params.MIPGap, so it has to be the quantity the gurobi branch -0325 % reports there: a relative gap. It is derived from the bounds -0326 % rather than read from scipStats.BBgap, whose units are not -0327 % documented. inf where no gap can be established, so that a -0328 % caller testing "gap small enough" does not accept the solution. -0329 if res.stat == 1 -0330 res.mipgap = 0; -0331 else -0332 res.mipgap = inf; -0333 primal = scipStats.PrimalBound; -0334 if isfinite(primal) && abs(primal) > 1e-10 -0335 res.mipgap = abs(primal-scipStats.DualBound)/abs(primal); -0336 end -0337 end +0299 % Option Fields (all optional, see also optiset for a list): +0300 % solverOpts - specific SCIP options (list of pairs of parameter names and values) +0301 % maxiter - maximum LP solver iterations +0302 % maxnodes - maximum nodes to explore +0303 % maxtime - maximum execution time [s] +0304 % tolrfun - primal feasibility tolerance +0305 % display - solver display level [0-5] +0306 % probfile - write problem to given file +0307 % presolvedfile - write presolved problem to file +0308 % +0309 % Return Status, as returned by the MatlabSCIPInterface build that +0310 % RAVEN provides and that setRavenSolver points at +0311 % (github.com/scipopt/MatlabSCIPInterface). Verified against that +0312 % binary with a bounded, an infeasible and an unbounded problem: +0313 % 1 - Problem Solved to Optimality +0314 % 2 - Problem is Infeasible +0315 % 3 - Problem is Unbounded +0316 % +0317 % The OPTI Toolbox build of scip uses a different, longer set in +0318 % which optimality is 11, infeasibility 12 and unboundedness 13, +0319 % with the lower numbers meaning "a limit was reached". Those codes +0320 % are accepted below as well, so either build reports optimality +0321 % correctly; only OPTI's 1 ("User Interrupted") is ambiguous, and +0322 % reading it as optimal is no worse than the previous mapping, +0323 % which recognised no status from the shipped build at all. +0324 +0325 res.origStat = exitflag; +0326 res.full = xopt; +0327 res.obj = fval; +0328 +0329 switch exitflag +0330 case {1, 11} % solved to optimality +0331 res.stat = 1; +0332 case {3, 13} % unbounded +0333 res.stat = 2; +0334 case {5, 6, 7, 8, 9, 10} % OPTI: a limit was reached, so a +0335 res.stat = 2; % solution may exist but is not proven optimal +0336 otherwise % 2/12 infeasible, 0 unknown, 4, 14 +0337 res.stat = 0; 0338 end -0339 otherwise -0340 error('RAVEN solver not defined or unknown. Try using setRavenSolver(''solver'').'); -0341 end -0342 %Flag a solve that ended on the time limit. res.stat cannot express this on -0343 %its own: a solver that times out with an incumbent solution in hand reports -0344 %the same status as one that finished, and a solver that times out with -0345 %nothing reports the same status as an infeasible problem. The raw status in -0346 %res.origStat does distinguish the two, but only in solver-specific terms, -0347 %so it is translated here where the solver is still known. -0348 res.hitTimeLimit=hitTimeLimit(solver,res,milp); -0349 if res.stat>0 -0350 res.full=res.full(1:size(prob.a,2)); -0351 end -0352 %Strip the auxiliary rows/columns that were added for conditioning, so that -0353 %the result matches the dimensions of the original problem. res.full is -0354 %already truncated to the original variables above (prob.a is left untouched). -0355 if condApplied -0356 if isfield(res,'dual') && numel(res.dual)>condInfo.nOrigRows -0357 res.dual=res.dual(1:condInfo.nOrigRows); -0358 end -0359 if isfield(res,'rcost') && numel(res.rcost)>condInfo.nOrigCols -0360 res.rcost=res.rcost(1:condInfo.nOrigCols); -0361 end -0362 end -0363 end -0364 -0365 function s_merged=structUpdate(s_old,s_new) -0366 %Remove overlapping fields from first struct; -0367 %Obtain all unique names of remaining fields; -0368 %Merge both structs -0369 s_merged = rmfield(s_old, intersect(fieldnames(s_old), fieldnames(s_new))); -0370 names = [fieldnames(s_merged); fieldnames(s_new)]; -0371 s_merged = cell2struct([struct2cell(s_merged); struct2cell(s_new)], names, 1); -0372 end -0373 -0374 function timeLimit = hitTimeLimit(solver,res,milp) -0375 %Report whether the solver that produced res stopped on its time limit. Each -0376 %solver has its own status codes for this, and the "cobra" option hands the -0377 %status of the solver that COBRA dispatched to straight through, so that -0378 %solver is looked up as well. -0379 global CBT_LP_SOLVER CBT_MILP_SOLVER -0380 timeLimit=false; -0381 if ~isfield(res,'origStat') -0382 return; -0383 end -0384 if strcmp(solver,'cobra') -0385 if milp -0386 solver=CBT_MILP_SOLVER; -0387 else -0388 solver=CBT_LP_SOLVER; -0389 end -0390 if isempty(solver) || ~ischar(solver) -0391 return; -0392 end -0393 end -0394 origStat=res.origStat; -0395 isText=ischar(origStat) || isstring(origStat); -0396 isCode=isnumeric(origStat) && isscalar(origStat); -0397 switch lower(solver) -0398 case 'gurobi' -0399 timeLimit=isText && strcmp(origStat,'TIME_LIMIT'); -0400 case 'mosek' -0401 timeLimit=isText && strcmp(origStat,'MSK_RES_TRM_MAX_TIME'); -0402 case 'glpk' -0403 %109 from the simplex method, 209 from the interior point method -0404 timeLimit=isCode && ismember(origStat,[109 209]); -0405 case {'scip','soplex'} -0406 timeLimit=isCode && origStat==5; -0407 case {'ibm_cplex','tomlab_cplex','cplex_direct'} -0408 %11 for an LP, 107 and 108 for a MILP with and without an integer -0409 %solution found before the limit was reached -0410 timeLimit=isCode && ismember(origStat,[11 107 108]); +0339 +0340 if milp +0341 % Callers such as ftINIT compare this against their own +0342 % params.MIPGap, so it has to be the quantity the gurobi branch +0343 % reports there: a relative gap. It is derived from the bounds +0344 % rather than read from scipStats.BBgap, whose units are not +0345 % documented. inf where no gap can be established, so that a +0346 % caller testing "gap small enough" does not accept the solution. +0347 if res.stat == 1 +0348 res.mipgap = 0; +0349 else +0350 res.mipgap = inf; +0351 primal = scipStats.PrimalBound; +0352 if isfinite(primal) && abs(primal) > 1e-10 +0353 res.mipgap = abs(primal-scipStats.DualBound)/abs(primal); +0354 end +0355 end +0356 end +0357 otherwise +0358 error('RAVEN solver not defined or unknown. Try using setRavenSolver(''solver'').'); +0359 end +0360 %Flag a solve that ended on the time limit. res.stat cannot express this on +0361 %its own: a solver that times out with an incumbent solution in hand reports +0362 %the same status as one that finished, and a solver that times out with +0363 %nothing reports the same status as an infeasible problem. The raw status in +0364 %res.origStat does distinguish the two, but only in solver-specific terms, +0365 %so it is translated here where the solver is still known. +0366 res.hitTimeLimit=hitTimeLimit(solver,res,milp); +0367 if res.stat>0 +0368 res.full=res.full(1:size(prob.a,2)); +0369 end +0370 %Strip the auxiliary rows/columns that were added for conditioning, so that +0371 %the result matches the dimensions of the original problem. res.full is +0372 %already truncated to the original variables above (prob.a is left untouched). +0373 if condApplied +0374 if isfield(res,'dual') && numel(res.dual)>condInfo.nOrigRows +0375 res.dual=res.dual(1:condInfo.nOrigRows); +0376 end +0377 if isfield(res,'rcost') && numel(res.rcost)>condInfo.nOrigCols +0378 res.rcost=res.rcost(1:condInfo.nOrigCols); +0379 end +0380 end +0381 end +0382 +0383 function s_merged=structUpdate(s_old,s_new) +0384 %Remove overlapping fields from first struct; +0385 %Obtain all unique names of remaining fields; +0386 %Merge both structs +0387 s_merged = rmfield(s_old, intersect(fieldnames(s_old), fieldnames(s_new))); +0388 names = [fieldnames(s_merged); fieldnames(s_new)]; +0389 s_merged = cell2struct([struct2cell(s_merged); struct2cell(s_new)], names, 1); +0390 end +0391 +0392 function timeLimit = hitTimeLimit(solver,res,milp) +0393 %Report whether the solver that produced res stopped on its time limit. Each +0394 %solver has its own status codes for this, and the "cobra" option hands the +0395 %status of the solver that COBRA dispatched to straight through, so that +0396 %solver is looked up as well. +0397 global CBT_LP_SOLVER CBT_MILP_SOLVER +0398 timeLimit=false; +0399 if ~isfield(res,'origStat') +0400 return; +0401 end +0402 if strcmp(solver,'cobra') +0403 if milp +0404 solver=CBT_MILP_SOLVER; +0405 else +0406 solver=CBT_LP_SOLVER; +0407 end +0408 if isempty(solver) || ~ischar(solver) +0409 return; +0410 end 0411 end -0412 end -0413 -0414 function paramlist = renameparams(paramlist,old,new) -0415 if ~iscell(paramlist) -0416 wasNoCell = true; -0417 paramlist={paramlist}; -0418 else -0419 wasNoCell = false; -0420 end -0421 for i=1:numel(old) -0422 paramlist = regexprep(paramlist,old{i},new{i}); -0423 end -0424 if wasNoCell -0425 paramlist=paramlist{1}; -0426 end -0427 end +0412 origStat=res.origStat; +0413 isText=ischar(origStat) || isstring(origStat); +0414 isCode=isnumeric(origStat) && isscalar(origStat); +0415 switch lower(solver) +0416 case 'gurobi' +0417 timeLimit=isText && strcmp(origStat,'TIME_LIMIT'); +0418 case 'mosek' +0419 timeLimit=isText && strcmp(origStat,'MSK_RES_TRM_MAX_TIME'); +0420 case 'glpk' +0421 %109 from the simplex method, 209 from the interior point method +0422 timeLimit=isCode && ismember(origStat,[109 209]); +0423 case {'scip','soplex'} +0424 timeLimit=isCode && origStat==5; +0425 case {'ibm_cplex','tomlab_cplex','cplex_direct'} +0426 %11 for an LP, 107 and 108 for a MILP with and without an integer +0427 %solution found before the limit was reached +0428 timeLimit=isCode && ismember(origStat,[11 107 108]); +0429 end +0430 end +0431 +0432 function paramlist = renameparams(paramlist,old,new) +0433 if ~iscell(paramlist) +0434 wasNoCell = true; +0435 paramlist={paramlist}; +0436 else +0437 wasNoCell = false; +0438 end +0439 for i=1:numel(old) +0440 paramlist = regexprep(paramlist,old{i},new{i}); +0441 end +0442 if wasNoCell +0443 paramlist=paramlist{1}; +0444 end +0445 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/solver/setRavenSolver.html b/docs/html/solver/setRavenSolver.html index 84700cd0..c5ac5b78 100644 --- a/docs/html/solver/setRavenSolver.html +++ b/docs/html/solver/setRavenSolver.html @@ -37,11 +37,16 @@

    DESCRIPTION ^SOURCE CODE ^% solver : char 0010 % string with solver name. The following options are available: 0011 % -0012 % - glpk : uses RAVEN-provided binaries. -0013 % - gurobi : requires a working Gurobi installation. -0014 % - cobra : requires a working COBRA Toolbox installation. RAVEN -0015 % parses the problem to COBRA and uses the solver that is set by -0016 % changeCobraSolver. -0017 % -0018 % Examples -0019 % -------- -0020 % setRavenSolver(solver); -0021 solver=char(solver); -0022 switch solver -0023 case 'cobra' -0024 global CBT_LP_SOLVER; -0025 if isempty(CBT_LP_SOLVER) -0026 error('COBRA toolbox is not initialized, run ''initCobraToolbox()''') -0027 else -0028 setpref('RAVEN','solver','cobra'); -0029 end -0030 case 'mosek' -0031 error('MOSEK support has been discontinued since RAVEN 2.3.0.') -0032 case {'glpk','gurobi'} -0033 setpref('RAVEN','solver',solver) -0034 case 'soplex' -0035 error('The ''soplex'' solver is replaced by ''scip''.') -0036 case 'scip' -0037 if ~ispc -0038 try -0039 scip; % The user might have installed SCIP manually -0040 setpref('RAVEN','solver','scip') -0041 catch -0042 error('SCIP not found. RAVEN only provides the precompiled SCIP MEX binary for Windows. Instructions on how to compile the SCIP MEX file are found at https://github.com/scipopt/MatlabSCIPInterface') -0043 end -0044 else -0045 ravenDir = findRAVENroot(); -0046 if ~exist(fullfile(ravenDir,'src','external','scip','scip.mexw64'),'file') -0047 try -0048 disp('Downloading and installing RAVEN-provided SCIP MEX binary...') -0049 websave(fullfile(ravenDir,'src','external','scip_mex_win.zip'),'https://github.com/SysBioChalmers/RAVEN/releases/download/v2.8.6/scip_mex_win.zip'); % Should be updated to release with SCIP functionality -0050 catch -0051 error('Unable to download SCIP MEX binary from the RAVEN GitHub page.') -0052 end -0053 unzip(fullfile(ravenDir,'src','external','scip_mex_win.zip'),fullfile(ravenDir,'src','external','scip')); -0054 delete(fullfile(ravenDir,'src','external','scip_mex_win.zip')); -0055 end -0056 try -0057 scip; % The pre-compiled MEX file might fail -0058 setpref('RAVEN','solver','scip') -0059 catch -0060 error('Unable to use the RAVEN-provided precompiled SCIP MEX binary. Instructions on how to compile the SCIP MEX file are found at https://github.com/scipopt/MatlabSCIPInterface') -0061 end -0062 end -0063 otherwise -0064 error('Invalid solver defined') -0065 end -0066 global RAVENSOLVER -0067 RAVENSOLVER = solver; -0068 end +0012 % - glpk : uses RAVEN-provided binaries. Solves LPs and MILPs; for +0013 % MILPs a RAVEN:glpkMILP warning recommends Gurobi, which is much +0014 % faster and more reliable on larger problems. +0015 % - gurobi : requires a working Gurobi installation. +0016 % - scip : requires a SCIP MEX interface. RAVEN provides a +0017 % precompiled binary for Windows only. +0018 % - cobra : requires a working COBRA Toolbox installation. RAVEN +0019 % parses the problem to COBRA and uses the solver that is set by +0020 % changeCobraSolver. MILPs are refused when COBRA's MILP solver is +0021 % GLPK. +0022 % +0023 % Examples +0024 % -------- +0025 % setRavenSolver(solver); +0026 solver=char(solver); +0027 switch solver +0028 case 'cobra' +0029 global CBT_LP_SOLVER; +0030 if isempty(CBT_LP_SOLVER) +0031 error('COBRA toolbox is not initialized, run ''initCobraToolbox()''') +0032 else +0033 setpref('RAVEN','solver','cobra'); +0034 end +0035 case 'mosek' +0036 error('MOSEK support has been discontinued since RAVEN 2.3.0.') +0037 case {'glpk','gurobi'} +0038 setpref('RAVEN','solver',solver) +0039 case 'soplex' +0040 error('The ''soplex'' solver is replaced by ''scip''.') +0041 case 'scip' +0042 if ~ispc +0043 try +0044 scip; % The user might have installed SCIP manually +0045 setpref('RAVEN','solver','scip') +0046 catch +0047 error('SCIP not found. RAVEN only provides the precompiled SCIP MEX binary for Windows. Instructions on how to compile the SCIP MEX file are found at https://github.com/scipopt/MatlabSCIPInterface') +0048 end +0049 else +0050 ravenDir = findRAVENroot(); +0051 if ~exist(fullfile(ravenDir,'src','external','scip','scip.mexw64'),'file') +0052 try +0053 disp('Downloading and installing RAVEN-provided SCIP MEX binary...') +0054 websave(fullfile(ravenDir,'src','external','scip_mex_win.zip'),'https://github.com/SysBioChalmers/RAVEN/releases/download/v2.8.6/scip_mex_win.zip'); % Should be updated to release with SCIP functionality +0055 catch +0056 error('Unable to download SCIP MEX binary from the RAVEN GitHub page.') +0057 end +0058 unzip(fullfile(ravenDir,'src','external','scip_mex_win.zip'),fullfile(ravenDir,'src','external','scip')); +0059 delete(fullfile(ravenDir,'src','external','scip_mex_win.zip')); +0060 end +0061 try +0062 scip; % The pre-compiled MEX file might fail +0063 setpref('RAVEN','solver','scip') +0064 catch +0065 error('Unable to use the RAVEN-provided precompiled SCIP MEX binary. Instructions on how to compile the SCIP MEX file are found at https://github.com/scipopt/MatlabSCIPInterface') +0066 end +0067 end +0068 otherwise +0069 error('Invalid solver defined') +0070 end +0071 global RAVENSOLVER +0072 RAVENSOLVER = solver; +0073 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/solver/solveLP.html b/docs/html/solver/solveLP.html index 01898bc7..85788599 100644 --- a/docs/html/solver/solveLP.html +++ b/docs/html/solver/solveLP.html @@ -85,7 +85,7 @@

    DESCRIPTION ^CROSS-REFERENCE INFORMATION ^

    This function calls:
      -
    • checkSolution checkSolution Check if a Mosek solution is feasible and optimal.
    • getMinNrFluxes getMinNrFluxes Find the minimal set of fluxes that satisfy the model.
    • optimizeProb optimizeProb Optimize an LP or MILP formulated in COBRA terms.
    • solveLP solveLP Solve a linear programming problem.
    +
  • checkSolution checkSolution Check if a solver result is feasible and optimal.
  • getMinNrFluxes getMinNrFluxes Find the minimal set of fluxes that satisfy the model.
  • optimizeProb optimizeProb Optimize an LP or MILP formulated in COBRA terms.
  • solveLP solveLP Solve a linear programming problem.
  • This function is called by:
    • getMinNrFluxes getMinNrFluxes Find the minimal set of fluxes that satisfy the model.
    • solveLP solveLP Solve a linear programming problem.
    diff --git a/docs/html/tasks/checkTasks.html b/docs/html/tasks/checkTasks.html index 8de57471..d0bce4c8 100644 --- a/docs/html/tasks/checkTasks.html +++ b/docs/html/tasks/checkTasks.html @@ -36,6 +36,12 @@

    DESCRIPTION ^SOURCE CODE ^% one new equation, and one change of reaction bounds. If more bounds are 0009 % needed to define the task, then several rows can be used for each task. 0010 % -0011 % Parameters -0012 % ---------- -0013 % model : struct -0014 % a model structure. -0015 % inputFile : char -0016 % a task list in Excel format. See the function parseTaskList for -0017 % details (optional if taskStructure is supplied). -0018 % -0019 % Name-Value Arguments -0020 % -------------------- -0021 % printOutput : logical -0022 % true if the results of the test should be displayed (default true). -0023 % printOnlyFailed : logical -0024 % true if only tasks that failed should be displayed (default false). -0025 % getEssential : logical -0026 % true if the essential reactions should be calculated for all the -0027 % tasks. This option is used with runINIT (default false). -0028 % taskStructure : struct -0029 % structure with the tasks, as from parseTaskList. If this is supplied -0030 % then inputFile is ignored. -0031 % runParallel : logical -0032 % true to evaluate tasks in parallel workers, since each task is -0033 % independent of the others (see parallelWorkersRAVEN). Gurobi is -0034 % automatically pinned to one thread per worker by optimizeProb, so -0035 % this does not oversubscribe cores. Default false, matching the -0036 % previous (serial) behaviour, since checkTasks is often called on -0037 % small task lists where starting a pool would only add overhead. -0038 % -0039 % Returns -0040 % ------- -0041 % taskReport : struct -0042 % structure with the results, with fields: -0043 % -0044 % - id : cell array with the id of the task -0045 % - description : cell array with the description of the task -0046 % - ok : boolean array with true if the task was successful -0047 % essentialRxns : logical -0048 % MxN matrix with the essential reactions (M) for each task (N). An -0049 % element is true if the corresponding reaction is essential in the -0050 % corresponding task. Failed tasks and SHOULD FAIL tasks are ignored. -0051 % This is used by the INIT algorithm (if tasks are supplied). If -0052 % getEssential=false then essentialRxns=false(nRxns,nTasks). -0053 % taskStructure : struct -0054 % structure with the tasks, as from parseTaskList. -0055 % essentialFluxes : double -0056 % the fluxes of the essential rxns - same structure as essentialRxns. -0057 % -0058 % Examples -0059 % -------- -0060 % [taskReport, essentialRxns, taskStructure] = checkTasks(model, inputFile, ... -0061 % printOutput, printOnlyFailed, getEssential, taskStructure); -0062 -0063 p=parseRAVENargs(varargin, {'printOutput',true; 'printOnlyFailed',false; 'getEssential',false; 'taskStructure',[]; 'runParallel',false}); -0064 printOutput=p.printOutput; -0065 printOnlyFailed=p.printOnlyFailed; -0066 getEssential=p.getEssential; -0067 taskStructure=p.taskStructure; -0068 runParallel=p.runParallel; -0069 -0070 %Prepare the input model a little -0071 model.b=zeros(numel(model.mets),2); -0072 -0073 modelMets=upper(strcat(model.metNames,'[',model.comps(model.metComps),']')); -0074 if ~isfield(model,'unconstrained') -0075 EM='Exchange metabolites should normally not be removed from the model when using checkTasks. Inputs and outputs are defined in the task file instead. Use importModel(file,false) to import a model with exchange metabolites remaining'; -0076 warning('RAVEN:warning', '%s', EM); -0077 end +0011 % The task inputs and outputs are applied to the boundary metabolites of +0012 % the model. The model's own exchange reactions are not closed: they keep +0013 % their bounds, so in a model without boundary metabolites they stay open +0014 % during every task. To let the task define the whole boundary, run +0015 % checkTasks on closeModel(model). +0016 % +0017 % Parameters +0018 % ---------- +0019 % model : struct +0020 % a model structure. +0021 % inputFile : char +0022 % a task list in Excel format. See the function parseTaskList for +0023 % details (optional if taskStructure is supplied). +0024 % +0025 % Name-Value Arguments +0026 % -------------------- +0027 % printOutput : logical +0028 % true if the results of the test should be displayed (default true). +0029 % printOnlyFailed : logical +0030 % true if only tasks that failed should be displayed (default false). +0031 % getEssential : logical +0032 % true if the essential reactions should be calculated for all the +0033 % tasks. This option is used with runINIT (default false). +0034 % taskStructure : struct +0035 % structure with the tasks, as from parseTaskList. If this is supplied +0036 % then inputFile is ignored. +0037 % runParallel : logical +0038 % true to evaluate tasks in parallel workers, since each task is +0039 % independent of the others (see parallelWorkersRAVEN). Gurobi is +0040 % automatically pinned to one thread per worker by optimizeProb, so +0041 % this does not oversubscribe cores. Default false, matching the +0042 % previous (serial) behaviour, since checkTasks is often called on +0043 % small task lists where starting a pool would only add overhead. +0044 % +0045 % Returns +0046 % ------- +0047 % taskReport : struct +0048 % structure with the results, with fields: +0049 % +0050 % - id : cell array with the id of the task +0051 % - description : cell array with the description of the task +0052 % - ok : boolean array with true if the task was successful +0053 % essentialRxns : logical +0054 % MxN matrix with the essential reactions (M) for each task (N). An +0055 % element is true if the corresponding reaction is essential in the +0056 % corresponding task. Failed tasks and SHOULD FAIL tasks are ignored. +0057 % This is used by the INIT algorithm (if tasks are supplied). If +0058 % getEssential=false then essentialRxns=false(nRxns,nTasks). +0059 % taskStructure : struct +0060 % structure with the tasks, as from parseTaskList. +0061 % essentialFluxes : double +0062 % the fluxes of the essential rxns - same structure as essentialRxns. +0063 % +0064 % Examples +0065 % -------- +0066 % [taskReport, essentialRxns, taskStructure] = checkTasks(model, inputFile, ... +0067 % printOutput, printOnlyFailed, getEssential, taskStructure); +0068 +0069 p=parseRAVENargs(varargin, {'printOutput',true; 'printOnlyFailed',false; 'getEssential',false; 'taskStructure',[]; 'runParallel',false}); +0070 printOutput=p.printOutput; +0071 printOnlyFailed=p.printOnlyFailed; +0072 getEssential=p.getEssential; +0073 taskStructure=p.taskStructure; +0074 runParallel=p.runParallel; +0075 +0076 %Prepare the input model a little +0077 model.b=zeros(numel(model.mets),2); 0078 -0079 %Parse the task file -0080 if isempty(taskStructure) -0081 taskStructure=parseTaskList(inputFile); -0082 end -0083 -0084 nTasks=numel(taskStructure); -0085 essentialRxns=false(numel(model.rxns),nTasks); -0086 essentialFluxes = NaN(numel(model.rxns),nTasks); -0087 -0088 %Each task is independent (tModel is rebuilt from model at the top of -0089 %every iteration, nothing carries over), so this loop can run in -0090 %parallel. ids/descriptions/ok are plain arrays sliced by i and only -0091 %assembled into taskReport once the loop is done, because parfor's -0092 %classifier rejects slicing a struct field that is itself indexed with -0093 %cell/array subscripts (taskReport.id{i,1}=... is not a supported sliced -0094 %form). runParallel=false (the default) makes this run exactly like the -0095 %previous for loop, in the client, serially. -0096 ids=cell(nTasks,1); -0097 descriptions=cell(nTasks,1); -0098 ok=false(nTasks,1); -0099 -0100 nW=parallelWorkersRAVEN(runParallel); -0101 parfor (i=1:nTasks, nW) -0102 tModel=model; -0103 ids{i}=taskStructure(i).id; -0104 descriptions{i}=taskStructure(i).description; -0105 %Set the inputs -0106 if ~isempty(taskStructure(i).inputs) -0107 [I, J]=ismember(upper(taskStructure(i).inputs),modelMets); -0108 J=J(I); %Only keep the ones with matches -0109 K=ismember(upper(taskStructure(i).inputs),'ALLMETS'); -0110 L=~cellfun('isempty',strfind(upper(taskStructure(i).inputs),'ALLMETSIN')); -0111 %Check that all metabolites are either real metabolites or -0112 %ALLMETS/ALLMETSIN -0113 if ~all(I|K|L) -0114 fprintf(['ERROR: Could not find all inputs in "[' taskStructure(i).id '] ' taskStructure(i).description '"\n']); -0115 ok(i)=false; -0116 continue; -0117 end -0118 if numel(J)~=numel(unique(J)) -0119 EM=['The constraints on some input(s) in "[' taskStructure(i).id '] ' taskStructure(i).description '" are defined more than one time']; -0120 error('RAVEN:badInput', '%s', EM); -0121 end -0122 %If all metabolites should be added -0123 if any(K) -0124 %Check if ALLMETS is the first metabolite. Otherwise print a -0125 %warning since it will write over any other constraints that -0126 %are set -0127 if K(1)==0 -0128 EM=['ALLMETS is used as an input in "[' taskStructure(i).id '] ' taskStructure(i).description '" but it it not the first metabolite in the list. Constraints defined for the metabolites before it will be over-written']; -0129 warning('RAVEN:warning', '%s', EM); -0130 end -0131 %Use the first match of ALLMETS. There should only be one, but -0132 %still.. -0133 tModel.b(:,1)=taskStructure(i).UBin(find(K,1))*-1; -0134 end -0135 %If metabolites in a specific compartment should be used -0136 if any(L) -0137 L=find(L); -0138 for j=1:numel(L) -0139 %The compartment defined -0140 compartment=upper(taskStructure(i).inputs{L(j)}(11:end-1)); -0141 %Check if it exists in the model -0142 C=find(ismember(upper(model.comps),compartment)); -0143 if any(C) -0144 %Match to metabolites -0145 tModel.b(model.metComps==C,1)=taskStructure(i).UBin(L(j))*-1; -0146 else -0147 EM=['The compartment defined for ALLMETSIN in "[' taskStructure(i).id '] ' taskStructure(i).description '" does not exist']; -0148 error('RAVEN:badInput', '%s', EM); -0149 end -0150 end -0151 end -0152 %Then add the normal constraints -0153 if any(J) -0154 tModel.b(J,1)=taskStructure(i).UBin(I)*-1; -0155 tModel.b(J,2)=taskStructure(i).LBin(I)*-1; -0156 end -0157 end -0158 %Set the outputs -0159 if ~isempty(taskStructure(i).outputs) -0160 [I, J]=ismember(upper(taskStructure(i).outputs),modelMets); -0161 J=J(I); %Only keep the ones with matches -0162 K=ismember(upper(taskStructure(i).outputs),'ALLMETS'); -0163 L=~cellfun('isempty',strfind(upper(taskStructure(i).outputs),'ALLMETSIN')); -0164 %Check that all metabolites are either real metabolites or -0165 %ALLMETS/ALLMETSIN -0166 if ~all(I|K|L) -0167 fprintf(['ERROR: Could not find all outputs in "[' taskStructure(i).id '] ' taskStructure(i).description '"\n']); -0168 ok(i)=false; -0169 continue; -0170 end -0171 if numel(J)~=numel(unique(J)) -0172 EM=['The constraints on some output(s) in "[' taskStructure(i).id '] ' taskStructure(i).description '" are defined more than one time']; -0173 error('RAVEN:badInput', '%s', EM); -0174 end -0175 %If all metabolites should be added -0176 if any(K) -0177 %Check if ALLMETS is the first metabolite. Otherwise print a -0178 %warning since it will write over any other constraints that -0179 %are set -0180 if K(1)==0 -0181 EM=['ALLMETS is used as an output in "[' taskStructure(i).id '] ' taskStructure(i).description '" but it it not the first metabolite in the list. Constraints defined for the metabolites before it will be over-written']; -0182 warning('RAVEN:warning', '%s', EM); -0183 end -0184 %Use the first match of ALLMETS. There should only be one, but -0185 %still.. -0186 tModel.b(:,2)=taskStructure(i).UBout(find(K,1)); -0187 end -0188 %If metabolites in a specific compartment should be used -0189 if any(L) -0190 L=find(L); -0191 for j=1:numel(L) -0192 %The compartment defined -0193 compartment=upper(taskStructure(i).outputs{L(j)}(11:end-1)); -0194 %Check if it exists in the model -0195 C=find(ismember(upper(model.comps),compartment)); -0196 if any(C) -0197 %Match to metabolites -0198 tModel.b(model.metComps==C,2)=taskStructure(i).UBout(L(j)); -0199 else -0200 EM=['The compartment defined for ALLMETSIN in "[' taskStructure(i).id '] ' taskStructure(i).description '" does not exist']; -0201 error('RAVEN:badInput', '%s', EM); -0202 end -0203 end -0204 end -0205 %Then add the normal constraints -0206 if any(J) -0207 %Verify that IN and OUT bounds are consistent. Cannot require -0208 %that a metabolite is simultaneously input AND output at some -0209 %nonzero flux. -0210 I = find(I); % otherwise indexing becomes confusing -0211 nonzero_LBin = tModel.b(J,2) < 0; -0212 nonzero_LBout = taskStructure(i).LBout(I) > 0; -0213 if any(nonzero_LBin & nonzero_LBout) -0214 EM=['The IN LB and OUT LB in "[' taskStructure(i).id '] ' taskStructure(i).description '" cannot be nonzero for the same metabolite']; -0215 error('RAVEN:badInput', '%s', EM); -0216 end -0217 tModel.b(J(nonzero_LBout),1)=taskStructure(i).LBout(I(nonzero_LBout)); -0218 tModel.b(J,2)=taskStructure(i).UBout(I); -0219 end -0220 end -0221 %Add new rxns -0222 if ~isempty(taskStructure(i).equations) -0223 % num2str on the whole column right-aligns every row to a common -0224 % width, embedding a leading space in "TEMPORARY_ 1" once any id -0225 % reaches two digits ("TEMPORARY_10"); format each one independently. -0226 rxnIds=arrayfun(@(x) sprintf('TEMPORARY_%d',x), ... -0227 (1:numel(taskStructure(i).equations))', 'UniformOutput', false); -0228 % Built as a single struct() call, not incremental rxn.field=... -0229 % assignments: parfor's variable classifier cannot establish that a -0230 % variable built up via several dot-indexed assignments is a -0231 % same-shape temporary in every iteration, and rejects it outright. -0232 rxn=struct('equations',{taskStructure(i).equations}, ... -0233 'lb',taskStructure(i).LBequ, 'ub',taskStructure(i).UBequ, ... -0234 'rxns',{rxnIds}); -0235 %Allow for new metabolites to be added. This is because it should -0236 %be possible to add, say, a whole new pathway -0237 tModel=addRxns(tModel,rxn,3,[],true); -0238 end -0239 %Add changed bounds -0240 if ~isempty(taskStructure(i).changed) -0241 tModel=setParam(tModel,'lb',taskStructure(i).changed,taskStructure(i).LBrxn); -0242 tModel=setParam(tModel,'ub',taskStructure(i).changed,taskStructure(i).UBrxn); -0243 end -0244 -0245 %Solve and print -0246 sol=solveLP(tModel); -0247 if ~isempty(sol.x) -0248 %assign the fluxes -0249 essentialFluxes(:,i) = sol.x(1:numel(model.rxns)); -0250 -0251 if ~taskStructure(i).shouldFail -0252 ok(i)=true; -0253 if printOnlyFailed==false && printOutput==true -0254 fprintf(['PASS: [' taskStructure(i).id '] ' taskStructure(i).description '\n']); -0255 end -0256 %Calculate the essential reactions -0257 if getEssential==true -0258 [~, taskEssential]=getEssentialRxns(tModel); -0259 %This is because there could be more reactions in tModel -0260 %than in model. Built as a full local column and written -0261 %with a plain ':' row index: parfor only accepts a sliced -0262 %output write when every subscript besides the loop -0263 %variable is ':', not a data-dependent index vector. -0264 essentialCol=false(numel(model.rxns),1); -0265 essentialCol(taskEssential(taskEssential<=numel(model.rxns)))=true; -0266 essentialRxns(:,i)=essentialCol; -0267 end -0268 else -0269 ok(i)=false; -0270 if printOutput==true -0271 fprintf(['PASS (should fail): [' taskStructure(i).id '] ' taskStructure(i).description '\n']); -0272 end -0273 end -0274 else -0275 if ~taskStructure(i).shouldFail -0276 ok(i)=false; -0277 if printOutput==true -0278 fprintf(['FAIL: [' taskStructure(i).id '] ' taskStructure(i).description '\n']); -0279 end -0280 else -0281 ok(i)=true; -0282 if printOnlyFailed==false && printOutput==true -0283 fprintf(['FAIL (should fail): [' taskStructure(i).id '] ' taskStructure(i).description '\n']); -0284 end -0285 end -0286 end -0287 if taskStructure(i).printFluxes && ~isempty(sol.x) -0288 sol=solveLP(tModel,1); -0289 if ~isempty(sol.x) -0290 printFluxes(tModel,sol.x,false,10^-6,[],'%rxnID (%eqn):%flux\n'); -0291 fprintf('\n'); -0292 end -0293 end -0294 end -0295 -0296 taskReport.id=ids; -0297 taskReport.description=descriptions; -0298 taskReport.ok=ok; -0299 -0300 end +0079 modelMets=upper(strcat(model.metNames,'[',model.comps(model.metComps),']')); +0080 if ~isfield(model,'unconstrained') +0081 EM='Exchange metabolites should normally not be removed from the model when using checkTasks. Inputs and outputs are defined in the task file instead. Use importModel(file,false) to import a model with exchange metabolites remaining'; +0082 warning('RAVEN:warning', '%s', EM); +0083 end +0084 +0085 %Parse the task file +0086 if isempty(taskStructure) +0087 taskStructure=parseTaskList(inputFile); +0088 end +0089 +0090 nTasks=numel(taskStructure); +0091 essentialRxns=false(numel(model.rxns),nTasks); +0092 essentialFluxes = NaN(numel(model.rxns),nTasks); +0093 +0094 %Each task is independent (tModel is rebuilt from model at the top of +0095 %every iteration, nothing carries over), so this loop can run in +0096 %parallel. ids/descriptions/ok are plain arrays sliced by i and only +0097 %assembled into taskReport once the loop is done, because parfor's +0098 %classifier rejects slicing a struct field that is itself indexed with +0099 %cell/array subscripts (taskReport.id{i,1}=... is not a supported sliced +0100 %form). runParallel=false (the default) makes this run exactly like the +0101 %previous for loop, in the client, serially. +0102 ids=cell(nTasks,1); +0103 descriptions=cell(nTasks,1); +0104 ok=false(nTasks,1); +0105 +0106 nW=parallelWorkersRAVEN(runParallel); +0107 parfor (i=1:nTasks, nW) +0108 tModel=model; +0109 ids{i}=taskStructure(i).id; +0110 descriptions{i}=taskStructure(i).description; +0111 %Set the inputs +0112 if ~isempty(taskStructure(i).inputs) +0113 [I, J]=ismember(upper(taskStructure(i).inputs),modelMets); +0114 J=J(I); %Only keep the ones with matches +0115 K=ismember(upper(taskStructure(i).inputs),'ALLMETS'); +0116 L=~cellfun('isempty',strfind(upper(taskStructure(i).inputs),'ALLMETSIN')); +0117 %Check that all metabolites are either real metabolites or +0118 %ALLMETS/ALLMETSIN +0119 if ~all(I|K|L) +0120 fprintf(['ERROR: Could not find all inputs in "[' taskStructure(i).id '] ' taskStructure(i).description '"\n']); +0121 ok(i)=false; +0122 continue; +0123 end +0124 if numel(J)~=numel(unique(J)) +0125 EM=['The constraints on some input(s) in "[' taskStructure(i).id '] ' taskStructure(i).description '" are defined more than one time']; +0126 error('RAVEN:badInput', '%s', EM); +0127 end +0128 %If all metabolites should be added +0129 if any(K) +0130 %Check if ALLMETS is the first metabolite. Otherwise print a +0131 %warning since it will write over any other constraints that +0132 %are set +0133 if K(1)==0 +0134 EM=['ALLMETS is used as an input in "[' taskStructure(i).id '] ' taskStructure(i).description '" but it it not the first metabolite in the list. Constraints defined for the metabolites before it will be over-written']; +0135 warning('RAVEN:warning', '%s', EM); +0136 end +0137 %Use the first match of ALLMETS. There should only be one, but +0138 %still.. +0139 tModel.b(:,1)=taskStructure(i).UBin(find(K,1))*-1; +0140 end +0141 %If metabolites in a specific compartment should be used +0142 if any(L) +0143 L=find(L); +0144 for j=1:numel(L) +0145 %The compartment defined +0146 compartment=upper(taskStructure(i).inputs{L(j)}(11:end-1)); +0147 %Check if it exists in the model +0148 C=find(ismember(upper(model.comps),compartment)); +0149 if any(C) +0150 %Match to metabolites +0151 tModel.b(model.metComps==C,1)=taskStructure(i).UBin(L(j))*-1; +0152 else +0153 EM=['The compartment defined for ALLMETSIN in "[' taskStructure(i).id '] ' taskStructure(i).description '" does not exist']; +0154 error('RAVEN:badInput', '%s', EM); +0155 end +0156 end +0157 end +0158 %Then add the normal constraints +0159 if any(J) +0160 tModel.b(J,1)=taskStructure(i).UBin(I)*-1; +0161 tModel.b(J,2)=taskStructure(i).LBin(I)*-1; +0162 end +0163 end +0164 %Set the outputs +0165 if ~isempty(taskStructure(i).outputs) +0166 [I, J]=ismember(upper(taskStructure(i).outputs),modelMets); +0167 J=J(I); %Only keep the ones with matches +0168 K=ismember(upper(taskStructure(i).outputs),'ALLMETS'); +0169 L=~cellfun('isempty',strfind(upper(taskStructure(i).outputs),'ALLMETSIN')); +0170 %Check that all metabolites are either real metabolites or +0171 %ALLMETS/ALLMETSIN +0172 if ~all(I|K|L) +0173 fprintf(['ERROR: Could not find all outputs in "[' taskStructure(i).id '] ' taskStructure(i).description '"\n']); +0174 ok(i)=false; +0175 continue; +0176 end +0177 if numel(J)~=numel(unique(J)) +0178 EM=['The constraints on some output(s) in "[' taskStructure(i).id '] ' taskStructure(i).description '" are defined more than one time']; +0179 error('RAVEN:badInput', '%s', EM); +0180 end +0181 %If all metabolites should be added +0182 if any(K) +0183 %Check if ALLMETS is the first metabolite. Otherwise print a +0184 %warning since it will write over any other constraints that +0185 %are set +0186 if K(1)==0 +0187 EM=['ALLMETS is used as an output in "[' taskStructure(i).id '] ' taskStructure(i).description '" but it it not the first metabolite in the list. Constraints defined for the metabolites before it will be over-written']; +0188 warning('RAVEN:warning', '%s', EM); +0189 end +0190 %Use the first match of ALLMETS. There should only be one, but +0191 %still.. +0192 tModel.b(:,2)=taskStructure(i).UBout(find(K,1)); +0193 end +0194 %If metabolites in a specific compartment should be used +0195 if any(L) +0196 L=find(L); +0197 for j=1:numel(L) +0198 %The compartment defined +0199 compartment=upper(taskStructure(i).outputs{L(j)}(11:end-1)); +0200 %Check if it exists in the model +0201 C=find(ismember(upper(model.comps),compartment)); +0202 if any(C) +0203 %Match to metabolites +0204 tModel.b(model.metComps==C,2)=taskStructure(i).UBout(L(j)); +0205 else +0206 EM=['The compartment defined for ALLMETSIN in "[' taskStructure(i).id '] ' taskStructure(i).description '" does not exist']; +0207 error('RAVEN:badInput', '%s', EM); +0208 end +0209 end +0210 end +0211 %Then add the normal constraints +0212 if any(J) +0213 %Verify that IN and OUT bounds are consistent. Cannot require +0214 %that a metabolite is simultaneously input AND output at some +0215 %nonzero flux. +0216 I = find(I); % otherwise indexing becomes confusing +0217 nonzero_LBin = tModel.b(J,2) < 0; +0218 nonzero_LBout = taskStructure(i).LBout(I) > 0; +0219 if any(nonzero_LBin & nonzero_LBout) +0220 EM=['The IN LB and OUT LB in "[' taskStructure(i).id '] ' taskStructure(i).description '" cannot be nonzero for the same metabolite']; +0221 error('RAVEN:badInput', '%s', EM); +0222 end +0223 tModel.b(J(nonzero_LBout),1)=taskStructure(i).LBout(I(nonzero_LBout)); +0224 tModel.b(J,2)=taskStructure(i).UBout(I); +0225 end +0226 end +0227 %Add new rxns +0228 if ~isempty(taskStructure(i).equations) +0229 % num2str on the whole column right-aligns every row to a common +0230 % width, embedding a leading space in "TEMPORARY_ 1" once any id +0231 % reaches two digits ("TEMPORARY_10"); format each one independently. +0232 rxnIds=arrayfun(@(x) sprintf('TEMPORARY_%d',x), ... +0233 (1:numel(taskStructure(i).equations))', 'UniformOutput', false); +0234 % Built as a single struct() call, not incremental rxn.field=... +0235 % assignments: parfor's variable classifier cannot establish that a +0236 % variable built up via several dot-indexed assignments is a +0237 % same-shape temporary in every iteration, and rejects it outright. +0238 rxn=struct('equations',{taskStructure(i).equations}, ... +0239 'lb',taskStructure(i).LBequ, 'ub',taskStructure(i).UBequ, ... +0240 'rxns',{rxnIds}); +0241 %Allow for new metabolites to be added. This is because it should +0242 %be possible to add, say, a whole new pathway +0243 tModel=addRxns(tModel,rxn,3,[],true); +0244 end +0245 %Add changed bounds +0246 if ~isempty(taskStructure(i).changed) +0247 tModel=setParam(tModel,'lb',taskStructure(i).changed,taskStructure(i).LBrxn); +0248 tModel=setParam(tModel,'ub',taskStructure(i).changed,taskStructure(i).UBrxn); +0249 end +0250 +0251 %Solve and print +0252 sol=solveLP(tModel); +0253 if ~isempty(sol.x) +0254 %assign the fluxes +0255 essentialFluxes(:,i) = sol.x(1:numel(model.rxns)); +0256 +0257 if ~taskStructure(i).shouldFail +0258 ok(i)=true; +0259 if printOnlyFailed==false && printOutput==true +0260 fprintf(['PASS: [' taskStructure(i).id '] ' taskStructure(i).description '\n']); +0261 end +0262 %Calculate the essential reactions +0263 if getEssential==true +0264 [~, taskEssential]=getEssentialRxns(tModel); +0265 %This is because there could be more reactions in tModel +0266 %than in model. Built as a full local column and written +0267 %with a plain ':' row index: parfor only accepts a sliced +0268 %output write when every subscript besides the loop +0269 %variable is ':', not a data-dependent index vector. +0270 essentialCol=false(numel(model.rxns),1); +0271 essentialCol(taskEssential(taskEssential<=numel(model.rxns)))=true; +0272 essentialRxns(:,i)=essentialCol; +0273 end +0274 else +0275 ok(i)=false; +0276 if printOutput==true +0277 fprintf(['PASS (should fail): [' taskStructure(i).id '] ' taskStructure(i).description '\n']); +0278 end +0279 end +0280 else +0281 if ~taskStructure(i).shouldFail +0282 ok(i)=false; +0283 if printOutput==true +0284 fprintf(['FAIL: [' taskStructure(i).id '] ' taskStructure(i).description '\n']); +0285 end +0286 else +0287 ok(i)=true; +0288 if printOnlyFailed==false && printOutput==true +0289 fprintf(['FAIL (should fail): [' taskStructure(i).id '] ' taskStructure(i).description '\n']); +0290 end +0291 end +0292 end +0293 if taskStructure(i).printFluxes && ~isempty(sol.x) +0294 sol=solveLP(tModel,1); +0295 if ~isempty(sol.x) +0296 printFluxes(tModel,sol.x,false,10^-6,[],'%rxnID (%eqn):%flux\n'); +0297 fprintf('\n'); +0298 end +0299 end +0300 end +0301 +0302 taskReport.id=ids; +0303 taskReport.description=descriptions; +0304 taskReport.ok=ok; +0305 +0306 end
    Generated by m2html © 2005
    \ No newline at end of file From caef0291ead0d8fd1644e241b5adf94b38983307 Mon Sep 17 00:00:00 2001 From: Eduard Kerkhoven Date: Mon, 19 Oct 2026 00:00:09 +0200 Subject: [PATCH 25/48] Check toolbox-assets downloads against the manifest checksums (#9) fetchToolboxAsset downloads to a .part file, checks it against the SHA-256 that the toolbox-assets manifest (manifest-v3) lists for its URL, and deletes it with an error when it differs. An asset the manifest does not list, or a manifest that cannot be read, gives a warning. The issue link points to CompMetEng/RAVEN. --- src/installation/fetchToolboxAsset.m | 92 +++++++++++++++++++++++++++- tests/function_tests/tInstallation.m | 14 +++++ 2 files changed, 104 insertions(+), 2 deletions(-) diff --git a/src/installation/fetchToolboxAsset.m b/src/installation/fetchToolboxAsset.m index 1942a794..a2a9abe8 100644 --- a/src/installation/fetchToolboxAsset.m +++ b/src/installation/fetchToolboxAsset.m @@ -9,6 +9,12 @@ % them, so callers supply the release tag and asset name rather than % this function hardcoding either. % +% A download is checked against the SHA-256 checksum that the +% toolbox-assets manifest (release manifest-v3) lists for it. A file whose +% checksum differs is deleted and an error is raised. An asset that the +% manifest does not list, or a manifest that cannot be read, gives a +% warning and the file is kept unchecked. +% % Parameters % ---------- % destDir : char @@ -30,15 +36,97 @@ if isfile(localPath) return; end +url=['https://github.com/CompMetEng/toolbox-assets/releases/download/' releaseTag '/' assetName]; +partPath=[localPath '.part']; fprintf(['Downloading ' assetName '... ']); try - websave(localPath,['https://github.com/CompMetEng/toolbox-assets/releases/download/' releaseTag '/' assetName]); + websave(partPath,url); catch ME + if isfile(partPath) + delete(partPath); + end if strcmp(ME.identifier,'MATLAB:webservices:HTTP404StatusCodeError') - error('Failed to download %s, the server returned a 404 error, try again later. If the problem persists please report it on the RAVEN GitHub Issues page: https://github.com/SysBioChalmers/RAVEN/issues',assetName) + error('Failed to download %s, the server returned a 404 error, try again later. If the problem persists please report it on the RAVEN GitHub Issues page: https://github.com/CompMetEng/RAVEN/issues',assetName) else rethrow(ME); end end +expected=manifestChecksum(url); +if isempty(expected) + warning('RAVEN:fetchToolboxAsset:unverified',['The toolbox-assets manifest gives no ' ... + 'checksum for %s; the download is not verified.'],assetName); +else + actual=sha256File(partPath); + if ~strcmpi(actual,expected) + delete(partPath); + error('RAVEN:fetchToolboxAsset:checksum',['The download of %s is not the published ' ... + 'file (SHA-256 %s, expected %s) and was deleted. Try again; if it persists, ' ... + 'report it on the RAVEN GitHub Issues page: https://github.com/CompMetEng/RAVEN/issues'], ... + assetName,actual,expected) + end +end +movefile(partPath,localPath); fprintf('COMPLETE\n'); end + +function sha=manifestChecksum(url) +% The sha256 the toolbox-assets manifest lists for an asset URL, or '' when +% it lists none or cannot be read. The manifest is read once per session. +persistent manifest +sha=''; +if isempty(manifest) + try + manifest=webread('https://github.com/CompMetEng/toolbox-assets/releases/download/manifest-v3/manifest.json', ... + weboptions('ContentType','json','Timeout',30)); + catch ME + warning('RAVEN:fetchToolboxAsset:manifest','Cannot read the toolbox-assets manifest: %s',ME.message); + return + end +end +sha=findUrl(manifest,url); +end + +function sha=findUrl(node,url) +% Depth-first search for a struct with this url and a sha256 field. The +% manifest's file names are not valid MATLAB field names, so the search +% goes by value. +sha=''; +if isstruct(node) + for k=1:numel(node) + if isfield(node(k),'url') && isfield(node(k),'sha256') && strcmp(node(k).url,url) + sha=node(k).sha256; + return + end + f=fieldnames(node(k)); + for i=1:numel(f) + sha=findUrl(node(k).(f{i}),url); + if ~isempty(sha) + return + end + end + end +elseif iscell(node) + for i=1:numel(node) + sha=findUrl(node{i},url); + if ~isempty(sha) + return + end + end +end +end + +function sha=sha256File(path) +% SHA-256 of a file as lower-case hex, read in chunks. +md=java.security.MessageDigest.getInstance('SHA-256'); +fid=fopen(path,'r'); +cleanup=onCleanup(@() fclose(fid)); +while true + chunk=fread(fid,2^24,'*uint8'); + if isempty(chunk) + break + end + md.update(typecast(chunk,'int8')); +end +digest=typecast(md.digest(),'uint8'); +sha=lower(reshape(dec2hex(digest,2)',1,[])); +end diff --git a/tests/function_tests/tInstallation.m b/tests/function_tests/tInstallation.m index 611fb5ee..d8b8d7b8 100644 --- a/tests/function_tests/tInstallation.m +++ b/tests/function_tests/tInstallation.m @@ -49,6 +49,20 @@ function checkFunctionUniquenessRuns(testCase) testCase.verifyTrue(islogical(status) || isnumeric(status)); end + function fetchToolboxAssetChecksDownloads(testCase) + % A small published asset is downloaded and matches the + % checksum of the toolbox-assets manifest; a second call reuses + % the file. + d = tempname; + cleanup = onCleanup(@() rmdir(d, 's')); + p = fetchToolboxAsset(d, 'transporters-2026-07-01', 'tcdb_substrates.tsv'); + testCase.verifyTrue(isfile(p)); + testCase.verifyFalse(isfile([p '.part'])); + info = dir(p); + testCase.verifyEqual(info.bytes, 219635); + testCase.verifyEqual(fetchToolboxAsset(d, 'transporters-2026-07-01', 'tcdb_substrates.tsv'), p); + end + function addRavenToUserPathExists(testCase) testCase.verifyEqual(exist('addRavenToUserPath','file'), 2); end From 2ad099c7e8bb667c3727851dc737bb7401c8af99 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 18 Oct 2026 22:01:49 +0000 Subject: [PATCH 26/48] doc: regenerate HTML documentation for caef029 [skip ci] --- docs/html/installation/fetchToolboxAsset.html | 164 ++++++++++++++---- 1 file changed, 130 insertions(+), 34 deletions(-) diff --git a/docs/html/installation/fetchToolboxAsset.html b/docs/html/installation/fetchToolboxAsset.html index 4ee3d0d4..1a9306fa 100644 --- a/docs/html/installation/fetchToolboxAsset.html +++ b/docs/html/installation/fetchToolboxAsset.html @@ -37,6 +37,12 @@

    DESCRIPTION ^CROSS-REFERENCE INFORMATION ^
 <li><a href=downloadRavenBinaries downloadRavenBinaries - +

    SUBFUNCTIONS ^

    +

    SOURCE CODE ^

    +0012 % A download is checked against the SHA-256 checksum that the +0013 % toolbox-assets manifest (release manifest-v3) lists for it. A file whose +0014 % checksum differs is deleted and an error is raised. An asset that the +0015 % manifest does not list, or a manifest that cannot be read, gives a +0016 % warning and the file is kept unchecked. +0017 % +0018 % Parameters +0019 % ---------- +0020 % destDir : char +0021 % directory to download into (created if it does not exist yet). +0022 % releaseTag : char +0023 % the toolbox-assets release tag, e.g. 'kegg118'. +0024 % assetName : char +0025 % the release asset file name, e.g. 'kegg118_core.tar.gz'. +0026 % +0027 % Returns +0028 % ------- +0029 % localPath : char +0030 % path to the downloaded file. +0031 +0032 if ~isfolder(destDir) +0033 mkdir(destDir); +0034 end +0035 localPath=fullfile(destDir,assetName); +0036 if isfile(localPath) +0037 return; +0038 end +0039 url=['https://github.com/CompMetEng/toolbox-assets/releases/download/' releaseTag '/' assetName]; +0040 partPath=[localPath '.part']; +0041 fprintf(['Downloading ' assetName '... ']); +0042 try +0043 websave(partPath,url); +0044 catch ME +0045 if isfile(partPath) +0046 delete(partPath); +0047 end +0048 if strcmp(ME.identifier,'MATLAB:webservices:HTTP404StatusCodeError') +0049 error('Failed to download %s, the server returned a 404 error, try again later. If the problem persists please report it on the RAVEN GitHub Issues page: https://github.com/CompMetEng/RAVEN/issues',assetName) +0050 else +0051 rethrow(ME); +0052 end +0053 end +0054 expected=manifestChecksum(url); +0055 if isempty(expected) +0056 warning('RAVEN:fetchToolboxAsset:unverified',['The toolbox-assets manifest gives no ' ... +0057 'checksum for %s; the download is not verified.'],assetName); +0058 else +0059 actual=sha256File(partPath); +0060 if ~strcmpi(actual,expected) +0061 delete(partPath); +0062 error('RAVEN:fetchToolboxAsset:checksum',['The download of %s is not the published ' ... +0063 'file (SHA-256 %s, expected %s) and was deleted. Try again; if it persists, ' ... +0064 'report it on the RAVEN GitHub Issues page: https://github.com/CompMetEng/RAVEN/issues'], ... +0065 assetName,actual,expected) +0066 end +0067 end +0068 movefile(partPath,localPath); +0069 fprintf('COMPLETE\n'); +0070 end +0071 +0072 function sha=manifestChecksum(url) +0073 % The sha256 the toolbox-assets manifest lists for an asset URL, or '' when +0074 % it lists none or cannot be read. The manifest is read once per session. +0075 persistent manifest +0076 sha=''; +0077 if isempty(manifest) +0078 try +0079 manifest=webread('https://github.com/CompMetEng/toolbox-assets/releases/download/manifest-v3/manifest.json', ... +0080 weboptions('ContentType','json','Timeout',30)); +0081 catch ME +0082 warning('RAVEN:fetchToolboxAsset:manifest','Cannot read the toolbox-assets manifest: %s',ME.message); +0083 return +0084 end +0085 end +0086 sha=findUrl(manifest,url); +0087 end +0088 +0089 function sha=findUrl(node,url) +0090 % Depth-first search for a struct with this url and a sha256 field. The +0091 % manifest's file names are not valid MATLAB field names, so the search +0092 % goes by value. +0093 sha=''; +0094 if isstruct(node) +0095 for k=1:numel(node) +0096 if isfield(node(k),'url') && isfield(node(k),'sha256') && strcmp(node(k).url,url) +0097 sha=node(k).sha256; +0098 return +0099 end +0100 f=fieldnames(node(k)); +0101 for i=1:numel(f) +0102 sha=findUrl(node(k).(f{i}),url); +0103 if ~isempty(sha) +0104 return +0105 end +0106 end +0107 end +0108 elseif iscell(node) +0109 for i=1:numel(node) +0110 sha=findUrl(node{i},url); +0111 if ~isempty(sha) +0112 return +0113 end +0114 end +0115 end +0116 end +0117 +0118 function sha=sha256File(path) +0119 % SHA-256 of a file as lower-case hex, read in chunks. +0120 md=java.security.MessageDigest.getInstance('SHA-256'); +0121 fid=fopen(path,'r'); +0122 cleanup=onCleanup(@() fclose(fid)); +0123 while true +0124 chunk=fread(fid,2^24,'*uint8'); +0125 if isempty(chunk) +0126 break +0127 end +0128 md.update(typecast(chunk,'int8')); +0129 end +0130 digest=typecast(md.digest(),'uint8'); +0131 sha=lower(reshape(dec2hex(digest,2)',1,[])); +0132 end
    Generated by m2html © 2005
    \ No newline at end of file From 4be3d6abb6cd23e209d44cdc478109e1f18ba5f9 Mon Sep 17 00:00:00 2001 From: Eduard Kerkhoven Date: Mon, 19 Oct 2026 09:08:27 +0200 Subject: [PATCH 27/48] Follow-ups from the RAVEN 3 Guide review (#8) - optimizeProb solves MILPs with the COBRA Toolbox's GLPK as well, with the same RAVEN:glpkMILP warning as the bundled GLPK; getMinNrFluxes no longer refuses it. - scaleBiomassFraction takes a targetTotal for the balanced biomass total. - gapReport always reports the metabolites produced or consumed with the boundary closed, closing the reactions getExchangeRxns returns when the model has no unconstrained field, and stops when that closed model is infeasible. - MILP tests run on the bundled GLPK when neither Gurobi nor SCIP is available. - CONTRIBUTING, the documentation workflow and docs/v3_review.md refer to develop and RAVEN 3; the asset download error points to CompMetEng. --- .github/CONTRIBUTING.md | 14 ++++---- .github/workflows/documentation.yml | 2 +- docs/v3_review.md | 15 ++++---- src/biomass/scaleBiomassFraction.m | 20 +++++++---- src/deprecated/consumeSomething.m | 4 +-- src/gapfilling/gapReport.m | 52 ++++++++++++++-------------- src/solver/getMinNrFluxes.m | 6 ---- src/solver/optimizeProb.m | 25 +++++-------- tests/function_tests/RavenTestCase.m | 10 ++++-- tests/function_tests/tBiomass.m | 18 ++++++++++ tests/function_tests/tGapfilling.m | 23 +++++++++++- tests/function_tests/tManipulation.m | 3 +- tests/function_tests/tSolver.m | 27 +++++++++++++++ 13 files changed, 139 insertions(+), 80 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index e96e5fb1..d5f89431 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -5,9 +5,9 @@ Anybody is welcome to contribute to the development of the RAVEN Toolbox, but pl Each function should start with a commented section describing the function and explaining the parameters. Existing functions can clarify what style should be used. ### Bugfixes, New Features and Functions -* For any development, whether bug fixes, introducing new functions or new/updated features for existing functions: make a separate branch from `devel` and name the branch for instance after the function/feature you are fixing/developing. If you work on a fix, start the branch name with `fix/`, if you work on a feature, start the branch name with `feat/`. Examples: `fix/format_reactions` or `feat/new_algorithms`. +* For any development, whether bug fixes, introducing new functions or new/updated features for existing functions: make a separate branch from `develop` and name the branch for instance after the function/feature you are fixing/developing. If you work on a fix, start the branch name with `fix/`, if you work on a feature, start the branch name with `feat/`. Examples: `fix/format_reactions` or `feat/new_algorithms`. * Make commits to this branch while developing. Aim for backward compatibility, and try to avoid very new MATLAB functions when possible, to accommodate users with older MATLAB versions. -* When you are happy with your new function/feature, make a pull request to the `devel` branch. Also, see [Pull request](#pull-request) below. +* When you are happy with your new function/feature, make a pull request to the `develop` branch. Also, see [Pull request](#pull-request) below. ### Semantic Commits Use semantic commit messages to make it easier to show what you are aiming to do: @@ -28,14 +28,14 @@ A more detailed explanation or comments can be left in the commit description. ### External Software Update * Once the newer version for any external software (BLAST+, DIAMOND, HMMER) is available, identify the newest version which is simultaneously available for macOS, Unix/Linux and Windows -* Create a separate branch from the `devel` branch and name it e.g. `chore/updateBinaries` +* Create a separate branch from the `develop` branch and name it e.g. `chore/updateBinaries` * Commit the changes for each program and operating system separately, e.g. `chore: update HMMER (Win) to 3.2.1` * As soon as all binaries for particular program are updated through commits, update the corresponding license file, if available and place it in e.g. `src/external/blast+` directory. * Update the version number in `RAVENdir/src/external/version.txt` file. -* Do some testing to ensure that the new binaries are working correctly. Upon successful tests, create a Pull Request to the `devel` branch. +* Do some testing to ensure that the new binaries are working correctly. Upon successful tests, create a Pull Request to the `develop` branch. ### Pull Requests -* No changes should be directly committed to the `main` or `devel` branches. Commits are made to side branches, after which pull requests are made for merging with `main` or `devel`. +* No changes should be directly committed to the `main` or `develop` branches. Commits are made to side branches, after which pull requests are made for merging with `main` or `develop`. * The person making the pull request and the one accepting the merge _cannot_ be the same person. * Typically, wait a few days before merging, to allow for other developers to inspect the pull request. * A merge with the mainbranch invokes a new release (see [versioning](#versioning)). @@ -43,11 +43,11 @@ A more detailed explanation or comments can be left in the commit description. ### Versioning RAVEN Toolbox follows [semantic versioning](https://semver.org/). After each successful Pull Request (PR) to `main`, the `version.txt` file should be updated in a separate PR. When this PR is merged, a new [release](https://github.com/CompMetEng/RAVEN/releases) should be specified, with a list detailing the most significant changes since the previous release. -Ensure that the documentation is also updated (in `devel`) before creating a PR to `main`. It can be updated by running `updateDocumentation()`. +Ensure that the documentation is also updated (in `develop`) before creating a PR to `main`. It can be updated by running `updateDocumentation()`. ### Categorize Releases * PRs pushed into `main` should be patch releases by default. -* Developers who push PRs to `devel` may propose target release categories (e.g. minor or patch) that can be commented on and discussed by reviewers and other developers. +* Developers who push PRs to `develop` may propose target release categories (e.g. minor or patch) that can be commented on and discussed by reviewers and other developers. * As a result of the above step, minor releases hopefully can be determined. * To help the process of categorizing releases, some conditions are suggested qualification for minor releases: * with database updates (e.g. KEGG) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index c280ae19..7b639ab1 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -18,7 +18,7 @@ name: Documentation on: push: - branches: [develop3, develop, main] + branches: [develop, main] paths: - '**.m' - '.github/workflows/documentation.yml' diff --git a/docs/v3_review.md b/docs/v3_review.md index 03663bb2..38ae4440 100644 --- a/docs/v3_review.md +++ b/docs/v3_review.md @@ -1,7 +1,6 @@ # RAVEN 3 — development review and roadmap -A review of the RAVEN toolbox aimed at the next major release (**v3**, currently on the -`develop3` branch). The brief: make the codebase **leaner** (remove old/unused/low-value +A review of the RAVEN toolbox for RAVEN 3. The brief: make the codebase **leaner** (remove old/unused/low-value functions) while gaining **a few critical new capabilities** that increase value and usability — with an appetite for ambitious additions. @@ -11,7 +10,7 @@ This document is a proposal and a prioritized to-do list, not a record of work a ## Scope and method -- Reviewed against the **current `develop3` tree**, not `develop`. The four review lenses: +- Reviewed against the RAVEN 3 tree. The four review lenses: **(a)** unnecessary functions to remove, **(b)** missing functionality worth adding, **(c)** existing functionality worth expanding, **(d)** functions worth simplifying. - Method: subsystem-by-subsystem audit of every `.m` file outside `software/`, plus @@ -21,7 +20,7 @@ This document is a proposal and a prioritized to-do list, not a record of work a **COBRA Toolbox (MATLAB)** is wanted; **MetaNetX mapping** is explicitly wanted (an old stale branch exists); new file formats are *not* a priority. -### Already done in `develop3` +### Already done in RAVEN 3 So the review does not repeat completed work. v3 already: @@ -141,14 +140,14 @@ Plus a standalone `mapIDsViaMNXref(type, ids, fromDB, toDB)` ID translator. - **No structural matching** — maps only by existing xref IDs and fuzzy names; ignores the **InChIKey** column, which is the modern primary metabolite key. - **Pre-reorg layout** — lives in `external/MetaNetX/`, carries duplicate `struct_conversion/*` - that has since diverged on develop3. + that has since diverged in RAVEN 3. **Refactor plan** (one focused PR; core is M, the polish items are S each): -1. Relocate to the develop3 layout (e.g. `reconstruction/metanetx/` or `annotation/metanetx/`), - drop the duplicated `struct_conversion/*`, rebase on develop3's `convertMiriams`. +1. Relocate to the RAVEN 3 layout (e.g. `reconstruction/metanetx/` or `annotation/metanetx/`), + drop the duplicated `struct_conversion/*`, rebase on RAVEN 3's `convertMiriams`. 2. **Delete the committed data**; host MNXref 4.5 (gzipped TSVs, and ideally a prebuilt `.mat`) as a `toolbox-assets` release asset and add a `downloadMNXref.m` modeled exactly on the - develop3 KEGG-HMM / `downloadRavenBinaries` on-demand pattern (sentinel-file check, cache + RAVEN 3 KEGG-HMM / `downloadRavenBinaries` on-demand pattern (sentinel-file check, cache under a gitignored dir). 3. Update parsing to the documented **4.5** columns (header-driven `readtable`, handle the `#` comment block and deprecations). diff --git a/src/biomass/scaleBiomassFraction.m b/src/biomass/scaleBiomassFraction.m index 6c66f864..8881f07d 100644 --- a/src/biomass/scaleBiomassFraction.m +++ b/src/biomass/scaleBiomassFraction.m @@ -2,8 +2,8 @@ % scaleBiomassFraction Rescale a biomass component to a target value. % % Rescale a biomass component to a target g/gDW value, optionally -% rescaling a second component so that the biomass total becomes 1 g/gDW, -% whatever the total was before. +% rescaling a second component so that the biomass total becomes +% targetTotal (1 g/gDW by default), whatever the total was before. % The MATLAB counterpart of yeast-GEM's legacy scaleBioMass. % % Parameters @@ -20,10 +20,13 @@ % Name-Value Arguments % -------------------- % balanceOut : char -% Second component name to adjust so the biomass total becomes 1 -% g/gDW. It must be large enough to absorb the change: an error is -% raised if its fraction would become zero or negative, or if it is +% Second component name to adjust so the biomass total becomes +% targetTotal. It must be large enough to absorb the change: an error +% is raised if its fraction would become zero or negative, or if it is % 0 to begin with. Empty / omit to skip balancing. +% targetTotal : double +% Biomass total in g/gDW that balanceOut brings the components to +% (default 1). % % Returns % ------- @@ -33,13 +36,16 @@ % Examples % -------- % model = scaleBiomassFraction(model, biomassConfig, 'protein', 0.5, 'carbohydrate'); +% model = scaleBiomassFraction(model, biomassConfig, 'protein', 0.5, ... +% 'balanceOut', 'carbohydrate', 'targetTotal', 0.95); % % See also % -------- % getBiomassFractions -p=parseRAVENargs(varargin, {'balanceOut',''}); +p=parseRAVENargs(varargin, {'balanceOut',''; 'targetTotal',1}); balanceOut=p.balanceOut; +targetTotal=p.targetTotal; fractions = getBiomassFractions(model, biomassConfig); if ~isfield(fractions, componentName) @@ -68,7 +74,7 @@ ['Cannot balance with %s: current fraction is 0 ' ... '(pseudoreaction missing or empty).'], balanceOut); end - balanceNew = balanceCurrent + (1 - total); + balanceNew = balanceCurrent + (targetTotal - total); if balanceNew <= 0 error('scaleBiomassFraction:negativeBalance', ... ['Cannot balance with %s: its fraction would become %g g/gDW. ' ... diff --git a/src/deprecated/consumeSomething.m b/src/deprecated/consumeSomething.m index 536f8a70..0b468286 100644 --- a/src/deprecated/consumeSomething.m +++ b/src/deprecated/consumeSomething.m @@ -11,9 +11,7 @@ % % findLeakMetabolite inserts allowExcretion (a produce-only option) in % position 4, so the arguments are translated rather than forwarded -% verbatim. Forwarding them verbatim, as this wrapper previously did, -% silently shifted params into allowExcretion and ignoreIntBounds into -% params, so a requested ignoreIntBounds was dropped. +% verbatim. % % See Also % -------- diff --git a/src/gapfilling/gapReport.m b/src/gapfilling/gapReport.m index b28bdc38..7212a86d 100755 --- a/src/gapfilling/gapReport.m +++ b/src/gapfilling/gapReport.m @@ -6,8 +6,8 @@ % Runs a series of connectivity checks on the model - which reactions are % blocked, how many isolated sub-networks exist, which metabolites cannot % be produced (and the minimal set of metabolites that would need to be -% connected to fix that), and, for models with exchange reactions, which -% metabolites can be produced/consumed without any input/output. If +% connected to fix that), and which metabolites can be produced/consumed +% without any input/output, with every boundary reaction closed. If % templateModels is supplied, it also attempts to gap-fill using those % models. A formatted report is printed to the command window; the same % results are returned as outputs for further use. @@ -43,11 +43,11 @@ % matrix where n x m is true if metabolite n allows for production of % metabolite m. % canProduceWithoutInput : cell -% metabolites that could be produced even when there is no input to -% the model. +% metabolites that could be produced with every boundary reaction +% closed. % canConsumeWithoutOutput : cell -% metabolites that could be consumed even when there is no output from -% the model. +% metabolites that could be consumed with every boundary reaction +% closed. % connectedFromTemplates : cell % the reactions that could be connected using the template models. % addedFromTemplates : struct @@ -56,11 +56,15 @@ % % Notes % ----- -% The model must be feasible under its own bounds, and, when the mass -% balancing section runs, also with its boundary closed. Otherwise -% gapReport stops with a RAVEN:infeasibleModel error that names the -% reactions with a forced flux; set the bounds of ATP maintenance and any -% other forced flux to zero first. +% The boundary reactions are those getExchangeRxns returns: exchanges, +% sinks and demands. For a model that keeps its boundary metabolites (an +% unconstrained field), those metabolites are deleted for the connectivity +% checks and kept, balanced, for the closed model. +% +% The model must be feasible under its own bounds and with its boundary +% closed. Otherwise gapReport stops with a RAVEN:infeasibleModel error that +% names the reactions with a forced flux; set the bounds of ATP maintenance +% and any other forced flux to zero first. % % Examples % -------- @@ -84,19 +88,17 @@ fprintf(['Gap analysis for ' esc(model.id) ' - ' esc(model.name) '\n\n']); if isfield(model,'unconstrained') - calculateINOUT=true; closedModel=model; model=simplifyModel(model); else - canConsumeWithoutOutput={}; - canProduceWithoutInput={}; - calculateINOUT=false; + closedModel=model; + [~,boundary]=getExchangeRxns(closedModel); + closedModel.lb(boundary)=0; + closedModel.ub(boundary)=0; end requireFeasible(model,'under its own bounds'); -if calculateINOUT - requireFeasible(closedModel,'once its boundary reactions are closed'); -end +requireFeasible(closedModel,'once its boundary reactions are closed'); model2=model; model2.b=[model2.b inf(numel(model2.mets),1)]; @@ -127,14 +129,12 @@ fprintf(['\t' num2str(i) '. ' esc(minToConnect{i}) '\n']); end -if calculateINOUT==true - fprintf('\n***Mass balancing\n'); - produced=canExchange(closedModel,'produce'); - canProduceWithoutInput=closedModel.mets(produced); - consumed=canExchange(closedModel,'consume'); - canConsumeWithoutOutput=closedModel.mets(consumed); - fprintf([num2str(numel(canConsumeWithoutOutput)) ' metabolites could be consumed without any outputs\n' num2str(numel(canProduceWithoutInput)) ' metabolites could be produced without any inputs\n']); -end +fprintf('\n***Mass balancing\n'); +produced=canExchange(closedModel,'produce'); +canProduceWithoutInput=closedModel.mets(produced); +consumed=canExchange(closedModel,'consume'); +canConsumeWithoutOutput=closedModel.mets(consumed); +fprintf([num2str(numel(canConsumeWithoutOutput)) ' metabolites could be consumed without any outputs\n' num2str(numel(canProduceWithoutInput)) ' metabolites could be produced without any inputs\n']); if ~isempty(templateModels) fprintf('\n***Automated gap-filling\n'); diff --git a/src/solver/getMinNrFluxes.m b/src/solver/getMinNrFluxes.m index 700b4742..655e7287 100755 --- a/src/solver/getMinNrFluxes.m +++ b/src/solver/getMinNrFluxes.m @@ -264,12 +264,6 @@ %One binary per reaction, on the reversible model. Trades a larger %constraint matrix for half the binaries on reversible reactions. -% glpk solver as implemented by COBRA does not work well for MILP. -global CBT_MILP_SOLVER -if strcmp(getpref('RAVEN','solver'),'cobra') && strcmp(CBT_MILP_SOLVER,'glpk') - error('RAVEN:badInput', '%s', 'The current solver is set to ''cobra'', while in COBRA the MILP solver has been set to ''glpk''. The COBRA implementation of glpk is not well suitable for solving MILPs. Please install the Gurobi or an alternative MILP solver.'); -end - exitFlag=1; %The trick to make this possible using a reversible model is that for diff --git a/src/solver/optimizeProb.m b/src/solver/optimizeProb.m index 31991dad..6fc3585e 100755 --- a/src/solver/optimizeProb.m +++ b/src/solver/optimizeProb.m @@ -29,12 +29,11 @@ % % Notes % ----- -% With 'glpk' selected, MILPs are solved by RAVEN's bundled GLPK, and a -% RAVEN:glpkMILP warning recommends Gurobi, which is much faster and more -% reliable on larger MILPs (such as ftINIT or getMinNrFluxes on +% MILPs can be solved with GLPK, either RAVEN's bundled GLPK ('glpk') or +% the COBRA Toolbox's GLPK interface ('cobra' with GLPK as its MILP solver). +% A RAVEN:glpkMILP warning then recommends Gurobi, which is much faster and +% more reliable on larger MILPs (such as ftINIT or getMinNrFluxes on % genome-scale models). -% The GLPK interface of the COBRA Toolbox is not used for MILPs: with -% 'cobra' selected, set a different MILP solver with changeCobraSolver. % % See also % -------- @@ -62,17 +61,11 @@ solver=RAVENSOLVER; if ~all(lower(prob.vartype) == 'c') milp=true; - switch solver - case 'glpk' - warning('RAVEN:glpkMILP', ['Solving a MILP with GLPK. This works for small ' ... - 'problems, but for larger problems Gurobi is much faster and more reliable; ' ... - 'select it with setRavenSolver(''gurobi''). Silence this warning with ' ... - 'warning(''off'',''RAVEN:glpkMILP'').']) - case 'cobra' - if strcmp(CBT_MILP_SOLVER,'glpk') - error(['The GLPK interface of the COBRA Toolbox is not suitable for solving MILPs, ' ... - 'select a different solver with changeCobraSolver() or setRavenSolver().']) - end + if strcmp(solver,'glpk') || (strcmp(solver,'cobra') && strcmp(CBT_MILP_SOLVER,'glpk')) + warning('RAVEN:glpkMILP', ['Solving a MILP with GLPK. This works for small ' ... + 'problems, but for larger problems Gurobi is much faster and more reliable; ' ... + 'select it with setRavenSolver(''gurobi''). Silence this warning with ' ... + 'warning(''off'',''RAVEN:glpkMILP'').']) end else milp=false; diff --git a/tests/function_tests/RavenTestCase.m b/tests/function_tests/RavenTestCase.m index 245e896d..6a5f18a9 100644 --- a/tests/function_tests/RavenTestCase.m +++ b/tests/function_tests/RavenTestCase.m @@ -71,14 +71,18 @@ function assumeDependency(testCase, isAvailable, label) end function assumeMILPSolver(testCase) - % Switch to Gurobi or SCIP for this test, or skip if neither is - % available. + % Switch to Gurobi or SCIP for this test, or else stay on the + % bundled GLPK with its MILP warning silenced. if ~isempty(which('gurobi')) evalc('setRavenSolver(''gurobi'')'); else ok = false; try, evalc('setRavenSolver(''scip'')'); ok = true; catch, end %#ok - testCase.assumeTrue(ok, 'No MILP solver (gurobi/scip) available; test skipped.'); + if ~ok + evalc('setRavenSolver(''glpk'')'); + warnState = warning('off','RAVEN:glpkMILP'); + testCase.addTeardown(@() warning(warnState)); + end end testCase.addTeardown(@() evalc('setRavenSolver(''glpk'')')); end diff --git a/tests/function_tests/tBiomass.m b/tests/function_tests/tBiomass.m index 7b7c0faa..74d1154b 100644 --- a/tests/function_tests/tBiomass.m +++ b/tests/function_tests/tBiomass.m @@ -34,6 +34,24 @@ function scaleBiomassFractionBalancesToOne(testCase) testCase.verifyEqual(f.DNA, 0.3, 'AbsTol', 1e-9); end + function scaleBiomassFractionBalancesToTargetTotal(testCase) + [m, cfg] = tBiomass.twoComponentToyModel(); + out = scaleBiomassFraction(m, cfg, 'protein', 0.7, 'balanceOut', 'DNA', ... + 'targetTotal', 0.9); + f = getBiomassFractions(out, cfg); + testCase.verifyEqual(f.protein, 0.7, 'AbsTol', 1e-9); + testCase.verifyEqual(f.DNA, 0.2, 'AbsTol', 1e-9); + testCase.verifyEqual(f.total, 0.9, 'AbsTol', 1e-9); + end + + function scaleBiomassFractionRefusesNegativeBalanceAtTargetTotal(testCase) + % Protein 0.85 balances against DNA for a total of 1, but not + % for a total of 0.8. + [m, cfg] = tBiomass.twoComponentToyModel(); + testCase.verifyError(@() scaleBiomassFraction(m, cfg, 'protein', 0.85, ... + 'balanceOut', 'DNA', 'targetTotal', 0.8), 'scaleBiomassFraction:negativeBalance'); + end + function scaleBiomassFractionRefusesNegativeBalance(testCase) % Balancing protein 1.2 against DNA would need DNA at -0.2, % which would turn the DNA pseudoreaction around. diff --git a/tests/function_tests/tGapfilling.m b/tests/function_tests/tGapfilling.m index d08bb14c..5f191fff 100644 --- a/tests/function_tests/tGapfilling.m +++ b/tests/function_tests/tGapfilling.m @@ -76,7 +76,9 @@ function fillGapsProducesModel(testCase) end function gapReportRuns(testCase) - evalc('noFluxRxns = gapReport(testCase.model);'); + m = testCase.model; + m.lb(m.lb > 0) = 0; % ATP maintenance, which closing the boundary makes infeasible + evalc('noFluxRxns = gapReport(m);'); testCase.verifyClass(noFluxRxns, 'cell'); end @@ -100,11 +102,30 @@ function gapReportInfeasibleOnceClosedErrors(testCase) testCase.verifySubstring(err.message, 'Maint'); end + function gapReportInfeasibleOnceClosedWithoutUnconstrainedErrors(testCase) + % ExA: =>a Maint: a=>b (forced) ExB: b=>. Feasible with the + % exchanges open; Maint cannot run once they are closed. + m = testCase.tinyModel({'ExA';'Maint';'ExB'}, sparse([1 -1 0; 0 1 -1]), [0;1;0], [10;1000;1000]); + m.mets = {'a';'b'}; m.metNames = m.mets; m.metComps = [1;1]; m.b = zeros(2,1); + err = testCase.gapReportError(m); + testCase.verifySubstring(err.message, 'once its boundary reactions are closed'); + testCase.verifySubstring(err.message, 'Maint'); + end + + function gapReportListsLeaksWithoutUnconstrained(testCase) + % R1: a=>b and R2: b=>a+c make c from nothing once ExA is closed. + m = testCase.tinyModel({'ExA';'R1';'R2'}, sparse([1 -1 1; 0 1 -1; 0 0 1]), [0;0;0], [10;1000;1000]); + m.mets = {'a';'b';'c'}; m.metNames = m.mets; m.metComps = [1;1;1]; m.b = zeros(3,1); + evalc('[~,~,~,~,~,~,produced] = gapReport(m);'); + testCase.verifyTrue(ismember('c', produced)); + end + function gapReportKeepsPercentInModelName(testCase) % A model.name containing "%" must survive intact in the % report header, not be truncated by fprintf misreading it as % a format directive. m = testCase.model; + m.lb(m.lb > 0) = 0; m.name = 'ecoli 50% subset'; out = evalc('gapReport(m);'); testCase.verifySubstring(out, 'ecoli 50% subset'); diff --git a/tests/function_tests/tManipulation.m b/tests/function_tests/tManipulation.m index 6b07eda4..67ec0bcd 100644 --- a/tests/function_tests/tManipulation.m +++ b/tests/function_tests/tManipulation.m @@ -179,8 +179,7 @@ function addTransportOnModelWithoutAnnotationField(testCase) end function addTransportAcceptsRowOrientedMetNames(testCase) - % Regression test for SysBioChalmers/RAVEN#722: a metNames cell - % array keeps whatever orientation the caller passed it in, and + % A metNames cell array keeps whatever orientation the caller passed it in, and % a plain {'a','b'} literal is row-oriented. A single-metabolite % cell (as in addTransportAddsRxn above) cannot catch this --- % a 1x1 cell is the same shape whichever way you call it "row" diff --git a/tests/function_tests/tSolver.m b/tests/function_tests/tSolver.m index dbe99a0b..04645f97 100644 --- a/tests/function_tests/tSolver.m +++ b/tests/function_tests/tSolver.m @@ -59,6 +59,33 @@ function optimizeProbSolvesMILPWithGlpk(testCase) testCase.verifyEqual(res.mipgap, 0); end + function optimizeProbSolvesMILPWithCobraGlpk(testCase) + % The same knapsack through the COBRA Toolbox's GLPK interface. + testCase.assumeDependency(~isempty(which('changeCobraSolver')), 'COBRA Toolbox'); + global CBT_MILP_SOLVER %#ok + origMILP = CBT_MILP_SOLVER; + evalc('changeCobraSolver(''glpk'',''MILP'',0);'); + evalc('setRavenSolver(''cobra'')'); + testCase.addTeardown(@() evalc('setRavenSolver(''glpk'')')); + if ~isempty(origMILP) + testCase.addTeardown(@() changeCobraSolver(origMILP,'MILP',0)); + end + prob = struct(); + prob.c = [-5; -4; -3]; + prob.A = sparse([2 3 1]); + prob.a = prob.A; + prob.b = 4; + prob.csense = 'L'; + prob.lb = zeros(3,1); + prob.ub = ones(3,1); + prob.osense = 1; + prob.vartype = 'BBB'; + res = testCase.verifyWarning(@() optimizeProb(prob,[],false), 'RAVEN:glpkMILP'); + testCase.verifyEqual(res.stat, 1); + testCase.verifyEqual(res.obj, -8, 'AbsTol', 1e-6); + testCase.verifyEqual(res.full, [1; 0; 1], 'AbsTol', 1e-6); + end + function getMinimalMediumRunsOnGlpk(testCase) evalc('setRavenSolver(''glpk'')'); warnState = warning('off','RAVEN:glpkMILP'); From 79a7d8c8aab1a38e0ab14cede277d497a14e6f9c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 19 Oct 2026 07:10:35 +0000 Subject: [PATCH 28/48] doc: regenerate HTML documentation for 4be3d6a [skip ci] --- docs/html/biomass/scaleBiomassFraction.html | 137 ++-- docs/html/gapfilling/gapReport.html | 196 ++--- docs/html/solver/getMinNrFluxes.html | 540 +++++++------ docs/html/solver/optimizeProb.html | 822 ++++++++++---------- 4 files changed, 848 insertions(+), 847 deletions(-) diff --git a/docs/html/biomass/scaleBiomassFraction.html b/docs/html/biomass/scaleBiomassFraction.html index 64b26094..9b00536c 100644 --- a/docs/html/biomass/scaleBiomassFraction.html +++ b/docs/html/biomass/scaleBiomassFraction.html @@ -30,8 +30,8 @@

    DESCRIPTION ^
     scaleBiomassFraction  Rescale a biomass component to a target value.
     
      Rescale a biomass component to a target g/gDW value, optionally
    - rescaling a second component so that the biomass total becomes 1 g/gDW,
    - whatever the total was before.
    + rescaling a second component so that the biomass total becomes
    + targetTotal (1 g/gDW by default), whatever the total was before.
      The MATLAB counterpart of yeast-GEM's legacy scaleBioMass.
     
      Parameters
    @@ -48,10 +48,13 @@ 

    DESCRIPTION ^DESCRIPTION ^SOURCE CODE ^% scaleBiomassFraction Rescale a biomass component to a target value. 0003 % 0004 % Rescale a biomass component to a target g/gDW value, optionally -0005 % rescaling a second component so that the biomass total becomes 1 g/gDW, -0006 % whatever the total was before. +0005 % rescaling a second component so that the biomass total becomes +0006 % targetTotal (1 g/gDW by default), whatever the total was before. 0007 % The MATLAB counterpart of yeast-GEM's legacy scaleBioMass. 0008 % 0009 % Parameters @@ -101,65 +106,71 @@

    SOURCE CODE ^% Name-Value Arguments 0021 % -------------------- 0022 % balanceOut : char -0023 % Second component name to adjust so the biomass total becomes 1 -0024 % g/gDW. It must be large enough to absorb the change: an error is -0025 % raised if its fraction would become zero or negative, or if it is +0023 % Second component name to adjust so the biomass total becomes +0024 % targetTotal. It must be large enough to absorb the change: an error +0025 % is raised if its fraction would become zero or negative, or if it is 0026 % 0 to begin with. Empty / omit to skip balancing. -0027 % -0028 % Returns -0029 % ------- -0030 % model : struct -0031 % Modified model. -0032 % -0033 % Examples -0034 % -------- -0035 % model = scaleBiomassFraction(model, biomassConfig, 'protein', 0.5, 'carbohydrate'); -0036 % -0037 % See also -0038 % -------- -0039 % getBiomassFractions -0040 -0041 p=parseRAVENargs(varargin, {'balanceOut',''}); -0042 balanceOut=p.balanceOut; -0043 -0044 fractions = getBiomassFractions(model, biomassConfig); -0045 if ~isfield(fractions, componentName) -0046 error('scaleBiomassFraction:unknownComponent', ... -0047 'biomassConfig has no component named %s', componentName); -0048 end -0049 current = fractions.(componentName); -0050 if current == 0 -0051 error('scaleBiomassFraction:zeroCurrent', ... -0052 ['Cannot scale %s to %g: current fraction is 0 ' ... -0053 '(pseudoreaction missing or empty).'], componentName, newValue); +0027 % targetTotal : double +0028 % Biomass total in g/gDW that balanceOut brings the components to +0029 % (default 1). +0030 % +0031 % Returns +0032 % ------- +0033 % model : struct +0034 % Modified model. +0035 % +0036 % Examples +0037 % -------- +0038 % model = scaleBiomassFraction(model, biomassConfig, 'protein', 0.5, 'carbohydrate'); +0039 % model = scaleBiomassFraction(model, biomassConfig, 'protein', 0.5, ... +0040 % 'balanceOut', 'carbohydrate', 'targetTotal', 0.95); +0041 % +0042 % See also +0043 % -------- +0044 % getBiomassFractions +0045 +0046 p=parseRAVENargs(varargin, {'balanceOut',''; 'targetTotal',1}); +0047 balanceOut=p.balanceOut; +0048 targetTotal=p.targetTotal; +0049 +0050 fractions = getBiomassFractions(model, biomassConfig); +0051 if ~isfield(fractions, componentName) +0052 error('scaleBiomassFraction:unknownComponent', ... +0053 'biomassConfig has no component named %s', componentName); 0054 end -0055 factor = newValue / current; -0056 model = scaleBiomassPseudoreaction(model, biomassConfig, componentName, factor); -0057 -0058 if ~isempty(balanceOut) -0059 fractions = getBiomassFractions(model, biomassConfig); -0060 total = fractions.total; -0061 if ~isfield(fractions, balanceOut) -0062 error('scaleBiomassFraction:unknownComponent', ... -0063 'biomassConfig has no component named %s', balanceOut); -0064 end -0065 balanceCurrent = fractions.(balanceOut); -0066 if balanceCurrent == 0 -0067 error('scaleBiomassFraction:zeroCurrent', ... -0068 ['Cannot balance with %s: current fraction is 0 ' ... -0069 '(pseudoreaction missing or empty).'], balanceOut); +0055 current = fractions.(componentName); +0056 if current == 0 +0057 error('scaleBiomassFraction:zeroCurrent', ... +0058 ['Cannot scale %s to %g: current fraction is 0 ' ... +0059 '(pseudoreaction missing or empty).'], componentName, newValue); +0060 end +0061 factor = newValue / current; +0062 model = scaleBiomassPseudoreaction(model, biomassConfig, componentName, factor); +0063 +0064 if ~isempty(balanceOut) +0065 fractions = getBiomassFractions(model, biomassConfig); +0066 total = fractions.total; +0067 if ~isfield(fractions, balanceOut) +0068 error('scaleBiomassFraction:unknownComponent', ... +0069 'biomassConfig has no component named %s', balanceOut); 0070 end -0071 balanceNew = balanceCurrent + (1 - total); -0072 if balanceNew <= 0 -0073 error('scaleBiomassFraction:negativeBalance', ... -0074 ['Cannot balance with %s: its fraction would become %g g/gDW. ' ... -0075 'It is %g g/gDW, but the other components total %g g/gDW.'], ... -0076 balanceOut, balanceNew, balanceCurrent, total - balanceCurrent); -0077 end -0078 balanceFactor = balanceNew / balanceCurrent; -0079 model = scaleBiomassPseudoreaction(model, biomassConfig, balanceOut, balanceFactor); -0080 end -0081 end

    +0071 balanceCurrent = fractions.(balanceOut); +0072 if balanceCurrent == 0 +0073 error('scaleBiomassFraction:zeroCurrent', ... +0074 ['Cannot balance with %s: current fraction is 0 ' ... +0075 '(pseudoreaction missing or empty).'], balanceOut); +0076 end +0077 balanceNew = balanceCurrent + (targetTotal - total); +0078 if balanceNew <= 0 +0079 error('scaleBiomassFraction:negativeBalance', ... +0080 ['Cannot balance with %s: its fraction would become %g g/gDW. ' ... +0081 'It is %g g/gDW, but the other components total %g g/gDW.'], ... +0082 balanceOut, balanceNew, balanceCurrent, total - balanceCurrent); +0083 end +0084 balanceFactor = balanceNew / balanceCurrent; +0085 model = scaleBiomassPseudoreaction(model, biomassConfig, balanceOut, balanceFactor); +0086 end +0087 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/gapfilling/gapReport.html b/docs/html/gapfilling/gapReport.html index f4c84ee8..c890a733 100644 --- a/docs/html/gapfilling/gapReport.html +++ b/docs/html/gapfilling/gapReport.html @@ -32,8 +32,8 @@

    DESCRIPTION ^DESCRIPTION ^DESCRIPTION ^SOURCE CODE ^% Runs a series of connectivity checks on the model - which reactions are 0007 % blocked, how many isolated sub-networks exist, which metabolites cannot 0008 % be produced (and the minimal set of metabolites that would need to be -0009 % connected to fix that), and, for models with exchange reactions, which -0010 % metabolites can be produced/consumed without any input/output. If +0009 % connected to fix that), and which metabolites can be produced/consumed +0010 % without any input/output, with every boundary reaction closed. If 0011 % templateModels is supplied, it also attempts to gap-fill using those 0012 % models. A formatted report is printed to the command window; the same 0013 % results are returned as outputs for further use. @@ -154,11 +158,11 @@

    SOURCE CODE ^% matrix where n x m is true if metabolite n allows for production of 0044 % metabolite m. 0045 % canProduceWithoutInput : cell -0046 % metabolites that could be produced even when there is no input to -0047 % the model. +0046 % metabolites that could be produced with every boundary reaction +0047 % closed. 0048 % canConsumeWithoutOutput : cell -0049 % metabolites that could be consumed even when there is no output from -0050 % the model. +0049 % metabolites that could be consumed with every boundary reaction +0050 % closed. 0051 % connectedFromTemplates : cell 0052 % the reactions that could be connected using the template models. 0053 % addedFromTemplates : struct @@ -167,85 +171,85 @@

    SOURCE CODE ^% 0057 % Notes 0058 % ----- -0059 % The model must be feasible under its own bounds, and, when the mass -0060 % balancing section runs, also with its boundary closed. Otherwise -0061 % gapReport stops with a RAVEN:infeasibleModel error that names the -0062 % reactions with a forced flux; set the bounds of ATP maintenance and any -0063 % other forced flux to zero first. -0064 % -0065 % Examples -0066 % -------- -0067 % [noFluxRxns, noFluxRxnsRelaxed, subGraphs, notProducedMets, ... -0068 % minToConnect, neededForProductionMat, connectedFromTemplates, ... -0069 % addedFromTemplates] = gapReport(model, templateModels); -0070 -0071 p=parseRAVENargs(varargin, {'templateModels',[]}); -0072 templateModels=p.templateModels; -0073 -0074 if isempty(templateModels) -0075 templateModels=[]; -0076 connectedFromTemplates=[]; -0077 addedFromTemplates=[]; -0078 end -0079 -0080 %Escapes model text before it is spliced into an fprintf template, since -0081 %a literal "%" in e.g. a model id or metabolite name would otherwise be -0082 %misread as a format directive and swallow the remainder of the line. -0083 esc=@(s) strrep(s,'%','%%'); -0084 -0085 fprintf(['Gap analysis for ' esc(model.id) ' - ' esc(model.name) '\n\n']); -0086 if isfield(model,'unconstrained') -0087 calculateINOUT=true; -0088 closedModel=model; -0089 model=simplifyModel(model); -0090 else -0091 canConsumeWithoutOutput={}; -0092 canProduceWithoutInput={}; -0093 calculateINOUT=false; -0094 end -0095 -0096 requireFeasible(model,'under its own bounds'); -0097 if calculateINOUT -0098 requireFeasible(closedModel,'once its boundary reactions are closed'); -0099 end -0100 -0101 model2=model; -0102 model2.b=[model2.b inf(numel(model2.mets),1)]; -0103 I=haveFlux(model); -0104 noFluxRxns=model.rxns(~I); -0105 J=haveFlux(model2); -0106 noFluxRxnsRelaxed=model2.rxns(~J); -0107 bModel=removeReactions(model,~I,true,true); -0108 cModel=removeReactions(model2,~J,true,true); -0109 fprintf('***Overview\n'); -0110 fprintf([num2str(numel(model.rxns)-sum(I)) ' out of ' num2str(numel(model.rxns))... -0111 ' reactions cannot carry flux (' num2str(numel(model.rxns)-sum(J)) ' if net production of all metabolites is allowed)\n']); -0112 fprintf([num2str(numel(model.mets)-numel(bModel.mets)) ' out of ' num2str(numel(model.mets))... -0113 ' metabolites are unreachable (' num2str(numel(model.mets)-numel(cModel.mets)) ' if net production of all metabolites is allowed)\n']); -0114 -0115 fprintf('\n***Isolated subnetworks\n'); -0116 subGraphs=getAllSubGraphs(model); -0117 fprintf(['A total of ' num2str(size(subGraphs,2)) ' isolated sub-networks are present in the model\n']); -0118 for i=1:size(subGraphs,2) -0119 fprintf(['\t' num2str(i) '. ' num2str(sum(subGraphs(:,i))) ' metabolites\n']); -0120 end -0121 -0122 fprintf('\n***Metabolite connectivity\n'); -0123 [notProducedMets, ~, neededForProductionMat,minToConnect]=checkProduction(model,true,model.comps,false); -0124 fprintf(['To enable net production of all metabolites, a total of ' num2str(numel(minToConnect)) ' metabolites must be connected\n']); -0125 fprintf('Top 10 metabolites to connect:\n'); -0126 for i=1:min(10,numel(minToConnect)) -0127 fprintf(['\t' num2str(i) '. ' esc(minToConnect{i}) '\n']); -0128 end -0129 -0130 if calculateINOUT==true -0131 fprintf('\n***Mass balancing\n'); -0132 produced=canExchange(closedModel,'produce'); -0133 canProduceWithoutInput=closedModel.mets(produced); -0134 consumed=canExchange(closedModel,'consume'); -0135 canConsumeWithoutOutput=closedModel.mets(consumed); -0136 fprintf([num2str(numel(canConsumeWithoutOutput)) ' metabolites could be consumed without any outputs\n' num2str(numel(canProduceWithoutInput)) ' metabolites could be produced without any inputs\n']); -0137 end +0059 % The boundary reactions are those getExchangeRxns returns: exchanges, +0060 % sinks and demands. For a model that keeps its boundary metabolites (an +0061 % unconstrained field), those metabolites are deleted for the connectivity +0062 % checks and kept, balanced, for the closed model. +0063 % +0064 % The model must be feasible under its own bounds and with its boundary +0065 % closed. Otherwise gapReport stops with a RAVEN:infeasibleModel error that +0066 % names the reactions with a forced flux; set the bounds of ATP maintenance +0067 % and any other forced flux to zero first. +0068 % +0069 % Examples +0070 % -------- +0071 % [noFluxRxns, noFluxRxnsRelaxed, subGraphs, notProducedMets, ... +0072 % minToConnect, neededForProductionMat, connectedFromTemplates, ... +0073 % addedFromTemplates] = gapReport(model, templateModels); +0074 +0075 p=parseRAVENargs(varargin, {'templateModels',[]}); +0076 templateModels=p.templateModels; +0077 +0078 if isempty(templateModels) +0079 templateModels=[]; +0080 connectedFromTemplates=[]; +0081 addedFromTemplates=[]; +0082 end +0083 +0084 %Escapes model text before it is spliced into an fprintf template, since +0085 %a literal "%" in e.g. a model id or metabolite name would otherwise be +0086 %misread as a format directive and swallow the remainder of the line. +0087 esc=@(s) strrep(s,'%','%%'); +0088 +0089 fprintf(['Gap analysis for ' esc(model.id) ' - ' esc(model.name) '\n\n']); +0090 if isfield(model,'unconstrained') +0091 closedModel=model; +0092 model=simplifyModel(model); +0093 else +0094 closedModel=model; +0095 [~,boundary]=getExchangeRxns(closedModel); +0096 closedModel.lb(boundary)=0; +0097 closedModel.ub(boundary)=0; +0098 end +0099 +0100 requireFeasible(model,'under its own bounds'); +0101 requireFeasible(closedModel,'once its boundary reactions are closed'); +0102 +0103 model2=model; +0104 model2.b=[model2.b inf(numel(model2.mets),1)]; +0105 I=haveFlux(model); +0106 noFluxRxns=model.rxns(~I); +0107 J=haveFlux(model2); +0108 noFluxRxnsRelaxed=model2.rxns(~J); +0109 bModel=removeReactions(model,~I,true,true); +0110 cModel=removeReactions(model2,~J,true,true); +0111 fprintf('***Overview\n'); +0112 fprintf([num2str(numel(model.rxns)-sum(I)) ' out of ' num2str(numel(model.rxns))... +0113 ' reactions cannot carry flux (' num2str(numel(model.rxns)-sum(J)) ' if net production of all metabolites is allowed)\n']); +0114 fprintf([num2str(numel(model.mets)-numel(bModel.mets)) ' out of ' num2str(numel(model.mets))... +0115 ' metabolites are unreachable (' num2str(numel(model.mets)-numel(cModel.mets)) ' if net production of all metabolites is allowed)\n']); +0116 +0117 fprintf('\n***Isolated subnetworks\n'); +0118 subGraphs=getAllSubGraphs(model); +0119 fprintf(['A total of ' num2str(size(subGraphs,2)) ' isolated sub-networks are present in the model\n']); +0120 for i=1:size(subGraphs,2) +0121 fprintf(['\t' num2str(i) '. ' num2str(sum(subGraphs(:,i))) ' metabolites\n']); +0122 end +0123 +0124 fprintf('\n***Metabolite connectivity\n'); +0125 [notProducedMets, ~, neededForProductionMat,minToConnect]=checkProduction(model,true,model.comps,false); +0126 fprintf(['To enable net production of all metabolites, a total of ' num2str(numel(minToConnect)) ' metabolites must be connected\n']); +0127 fprintf('Top 10 metabolites to connect:\n'); +0128 for i=1:min(10,numel(minToConnect)) +0129 fprintf(['\t' num2str(i) '. ' esc(minToConnect{i}) '\n']); +0130 end +0131 +0132 fprintf('\n***Mass balancing\n'); +0133 produced=canExchange(closedModel,'produce'); +0134 canProduceWithoutInput=closedModel.mets(produced); +0135 consumed=canExchange(closedModel,'consume'); +0136 canConsumeWithoutOutput=closedModel.mets(consumed); +0137 fprintf([num2str(numel(canConsumeWithoutOutput)) ' metabolites could be consumed without any outputs\n' num2str(numel(canProduceWithoutInput)) ' metabolites could be produced without any inputs\n']); 0138 0139 if ~isempty(templateModels) 0140 fprintf('\n***Automated gap-filling\n'); diff --git a/docs/html/solver/getMinNrFluxes.html b/docs/html/solver/getMinNrFluxes.html index 2b84cf7b..0a960792 100644 --- a/docs/html/solver/getMinNrFluxes.html +++ b/docs/html/solver/getMinNrFluxes.html @@ -383,285 +383,279 @@

    SOURCE CODE ^%One binary per reaction, on the reversible model. Trades a larger 0265 %constraint matrix for half the binaries on reversible reactions. 0266 -0267 % glpk solver as implemented by COBRA does not work well for MILP. -0268 global CBT_MILP_SOLVER -0269 if strcmp(getpref('RAVEN','solver'),'cobra') && strcmp(CBT_MILP_SOLVER,'glpk') -0270 error('RAVEN:badInput', '%s', 'The current solver is set to ''cobra'', while in COBRA the MILP solver has been set to ''glpk''. The COBRA implementation of glpk is not well suitable for solving MILPs. Please install the Gurobi or an alternative MILP solver.'); -0271 end -0272 -0273 exitFlag=1; -0274 -0275 %The trick to make this possible using a reversible model is that for -0276 %reversible reactions, we do the following. Set the flux Vi == Vipos - Vineg. -0277 %What happens then is that if the flux is positive, Vipos will have a nonzero value, -0278 %and when the flux is negative, Vineg will have a positive value. In addition -0279 %we can add an arbitrary constant to both Vipos and Vineg. For example, if the -0280 %flux Vi is -1, Vineg can be 4 and Vipos 3. This is however not a problem, -0281 %because we can be sure that Vineg + Vipos >= abs(Vi). Since we are interested -0282 %in forcing the ints to be on when there is a flux, it does not matter if we overestimate -0283 %the flux! So, we can simply constrain the boolean Yi to Yi*Maxflux >= Vineg + Vipos. -0284 -0285 %The matrix then becomes as this: -0286 % S p n int b var -0287 % SSSSSSSS - -0288 % SSSSSSSS - -0289 % SSSSSSSS - -0290 % SSSSSSSS - -0291 % SSSSSSSS - -0292 % SSSSSSSS - -0293 % - 1 - -0294 % - 1 - -0295 % - M - -0296 % - M - -0297 % - - M - -0298 % - - M - -0299 % An example with 8 rxns and 6 metabolites. - means -1, M max flux, and S is the S matrix. -0300 % 4 rxns are to be minimized(1,3,5,7) and 1,7 are reversible. The p and n -0301 % are the Vipos and Vineg variables (2 rxns of each). The ints are the Yi for -0302 % the variables that are to be minimized (the rest of the rxns does not have any). -0303 % The mets here are the constraints, so right under the S matrix, you have -0304 % Vi == Vipos - Vineg for the reactions 1 and 7 while the two next rows represent -0305 % the non-reversible rxns 3 and 5, where we simply say that yi*M >= Vi. The last -0306 % 2 rows are the reactions yi*M >= Vipos + Vineg. To the right, we first have a -0307 % -I matrix for setting the b constraints, and under that we have rxns that are -0308 % just variables (rxns) between 0 and Inf to complete the constraints mentioned above. -0309 %Ex: yi*M >= Vipos + Vineg is impl. as yi*M - Vipos - Vineg - var == 0, 0 <= var <= Inf. -0310 % -0311 %All rows should be equal to zero, so we do not set the b vector in the problem -0312 %The reactions should be constrained as follows -0313 %S - as given in model.lb and model.ub -0314 %pos and neg - between 0 and inf -0315 %ints - between 0 and 1 -0316 %b - as stated in the model.b vector - if this has one column, that is lb and ub (fixed value), if two columns, that is lb and ub -0317 %var - between zero and inf -0318 -0319 [minLog, I]=ismember(model.rxns,toMinimize); -0320 indexes=find(minLog); -0321 revIndexes = find(minLog & (model.rev == 1)); -0322 irrevIndexes = find(minLog & (model.rev == 0)); -0323 revIndexesInInd = find(model.rev(indexes) == 1); -0324 irrevIndexesInInd = find(model.rev(indexes) == 0); -0325 -0326 %The int columns are ordered as indexes, i.e. by position in the model, -0327 %while the scores arrive ordered as toMinimize -0328 scores=scores(I(indexes)); -0329 -0330 %Add binary constraints in the following manner: - Add one unique -0331 %"metabolite" for each integer reaction as a substrate. -0332 % These metabolites can have net production -0333 %- Add reactions for the production of each of those metabolites. The -0334 % amount produced in one reaction unit must be larger than the largest -0335 % possible flux in the model (but not too large to avoid bad scaling) -0336 -0337 maxFlux=1000; -0338 %we build the total matrix as blocks: [S pos neg int b var] +0267 exitFlag=1; +0268 +0269 %The trick to make this possible using a reversible model is that for +0270 %reversible reactions, we do the following. Set the flux Vi == Vipos - Vineg. +0271 %What happens then is that if the flux is positive, Vipos will have a nonzero value, +0272 %and when the flux is negative, Vineg will have a positive value. In addition +0273 %we can add an arbitrary constant to both Vipos and Vineg. For example, if the +0274 %flux Vi is -1, Vineg can be 4 and Vipos 3. This is however not a problem, +0275 %because we can be sure that Vineg + Vipos >= abs(Vi). Since we are interested +0276 %in forcing the ints to be on when there is a flux, it does not matter if we overestimate +0277 %the flux! So, we can simply constrain the boolean Yi to Yi*Maxflux >= Vineg + Vipos. +0278 +0279 %The matrix then becomes as this: +0280 % S p n int b var +0281 % SSSSSSSS - +0282 % SSSSSSSS - +0283 % SSSSSSSS - +0284 % SSSSSSSS - +0285 % SSSSSSSS - +0286 % SSSSSSSS - +0287 % - 1 - +0288 % - 1 - +0289 % - M - +0290 % - M - +0291 % - - M - +0292 % - - M - +0293 % An example with 8 rxns and 6 metabolites. - means -1, M max flux, and S is the S matrix. +0294 % 4 rxns are to be minimized(1,3,5,7) and 1,7 are reversible. The p and n +0295 % are the Vipos and Vineg variables (2 rxns of each). The ints are the Yi for +0296 % the variables that are to be minimized (the rest of the rxns does not have any). +0297 % The mets here are the constraints, so right under the S matrix, you have +0298 % Vi == Vipos - Vineg for the reactions 1 and 7 while the two next rows represent +0299 % the non-reversible rxns 3 and 5, where we simply say that yi*M >= Vi. The last +0300 % 2 rows are the reactions yi*M >= Vipos + Vineg. To the right, we first have a +0301 % -I matrix for setting the b constraints, and under that we have rxns that are +0302 % just variables (rxns) between 0 and Inf to complete the constraints mentioned above. +0303 %Ex: yi*M >= Vipos + Vineg is impl. as yi*M - Vipos - Vineg - var == 0, 0 <= var <= Inf. +0304 % +0305 %All rows should be equal to zero, so we do not set the b vector in the problem +0306 %The reactions should be constrained as follows +0307 %S - as given in model.lb and model.ub +0308 %pos and neg - between 0 and inf +0309 %ints - between 0 and 1 +0310 %b - as stated in the model.b vector - if this has one column, that is lb and ub (fixed value), if two columns, that is lb and ub +0311 %var - between zero and inf +0312 +0313 [minLog, I]=ismember(model.rxns,toMinimize); +0314 indexes=find(minLog); +0315 revIndexes = find(minLog & (model.rev == 1)); +0316 irrevIndexes = find(minLog & (model.rev == 0)); +0317 revIndexesInInd = find(model.rev(indexes) == 1); +0318 irrevIndexesInInd = find(model.rev(indexes) == 0); +0319 +0320 %The int columns are ordered as indexes, i.e. by position in the model, +0321 %while the scores arrive ordered as toMinimize +0322 scores=scores(I(indexes)); +0323 +0324 %Add binary constraints in the following manner: - Add one unique +0325 %"metabolite" for each integer reaction as a substrate. +0326 % These metabolites can have net production +0327 %- Add reactions for the production of each of those metabolites. The +0328 % amount produced in one reaction unit must be larger than the largest +0329 % possible flux in the model (but not too large to avoid bad scaling) +0330 +0331 maxFlux=1000; +0332 %we build the total matrix as blocks: [S pos neg int b var] +0333 +0334 %s block +0335 intArray=speye(numel(model.rxns))*-1; +0336 intArrayRev=intArray(revIndexes,:); +0337 intArrayIrrev = intArray(irrevIndexes,:); +0338 sBlock=[model.S;intArrayRev;intArrayIrrev;sparse(numel(revIndexes), numel(model.rxns))]; %the S matrix and what is below 0339 -0340 %s block -0341 intArray=speye(numel(model.rxns))*-1; -0342 intArrayRev=intArray(revIndexes,:); -0343 intArrayIrrev = intArray(irrevIndexes,:); -0344 sBlock=[model.S;intArrayRev;intArrayIrrev;sparse(numel(revIndexes), numel(model.rxns))]; %the S matrix and what is below -0345 -0346 %pos/neg blocks -0347 revposorneg1 = sparse(numel(model.mets), numel(revIndexes)); -0348 revpos2 = speye(numel(revIndexes)); -0349 revneg2 = -revpos2; -0350 revposneg3 = sparse(numel(irrevIndexes), numel(revIndexes)); -0351 revposneg4 = revneg2; -0352 posBlock = [revposorneg1;revpos2;revposneg3;revposneg4]; -0353 negBlock = [revposorneg1;revneg2;revposneg3;revposneg4]; -0354 -0355 %int block -0356 int1 = sparse(numel(model.mets), numel(indexes)); -0357 int2 = sparse(numel(revIndexes), numel(indexes)); -0358 tmpEye = speye(numel(indexes))*maxFlux; -0359 int3 = tmpEye(irrevIndexesInInd,:);%we here select only the irrev indexes from the indexes -0360 int4 = tmpEye(revIndexesInInd,:); -0361 intBlock = [int1;int2;int3;int4]; -0362 -0363 %b block -0364 b1 = speye(numel(model.mets))*-1; -0365 b2 = sparse(numel(indexes) + numel(revIndexes), numel(model.mets)); -0366 bBlock = [b1;b2]; -0367 -0368 %var block -0369 var1 = sparse(numel(model.mets), numel(indexes)); -0370 var2 = sparse(numel(revIndexes), numel(indexes)); -0371 tmpEye = speye(numel(indexes))*-1; -0372 var3 = tmpEye(irrevIndexesInInd,:); -0373 var4 = tmpEye(revIndexesInInd,:); -0374 varBlock = [var1;var2;var3;var4]; -0375 -0376 prob.A = [sBlock posBlock negBlock intBlock bBlock varBlock]; -0377 prob.a = prob.A;%I think this is needed as well -0378 -0379 prob.c=[zeros(numel(model.rxns),1);zeros(numel(revIndexes)*2,1); scores(:);zeros(numel(model.mets) + numel(indexes),1)]; %Minimize the sum of reaction scores for reactions that are on -0380 -0381 %ub and lb, text copied from above -0382 %S - as given in model.lb and model.ub -0383 %pos and neg - between 0 and inf -0384 %ints - between 0 and 1 -0385 %b - as stated in the model.b vector - if this has one column, that is lb and ub (fixed value), if two columns, that is lb and ub -0386 %var - between zero and inf +0340 %pos/neg blocks +0341 revposorneg1 = sparse(numel(model.mets), numel(revIndexes)); +0342 revpos2 = speye(numel(revIndexes)); +0343 revneg2 = -revpos2; +0344 revposneg3 = sparse(numel(irrevIndexes), numel(revIndexes)); +0345 revposneg4 = revneg2; +0346 posBlock = [revposorneg1;revpos2;revposneg3;revposneg4]; +0347 negBlock = [revposorneg1;revneg2;revposneg3;revposneg4]; +0348 +0349 %int block +0350 int1 = sparse(numel(model.mets), numel(indexes)); +0351 int2 = sparse(numel(revIndexes), numel(indexes)); +0352 tmpEye = speye(numel(indexes))*maxFlux; +0353 int3 = tmpEye(irrevIndexesInInd,:);%we here select only the irrev indexes from the indexes +0354 int4 = tmpEye(revIndexesInInd,:); +0355 intBlock = [int1;int2;int3;int4]; +0356 +0357 %b block +0358 b1 = speye(numel(model.mets))*-1; +0359 b2 = sparse(numel(indexes) + numel(revIndexes), numel(model.mets)); +0360 bBlock = [b1;b2]; +0361 +0362 %var block +0363 var1 = sparse(numel(model.mets), numel(indexes)); +0364 var2 = sparse(numel(revIndexes), numel(indexes)); +0365 tmpEye = speye(numel(indexes))*-1; +0366 var3 = tmpEye(irrevIndexesInInd,:); +0367 var4 = tmpEye(revIndexesInInd,:); +0368 varBlock = [var1;var2;var3;var4]; +0369 +0370 prob.A = [sBlock posBlock negBlock intBlock bBlock varBlock]; +0371 prob.a = prob.A;%I think this is needed as well +0372 +0373 prob.c=[zeros(numel(model.rxns),1);zeros(numel(revIndexes)*2,1); scores(:);zeros(numel(model.mets) + numel(indexes),1)]; %Minimize the sum of reaction scores for reactions that are on +0374 +0375 %ub and lb, text copied from above +0376 %S - as given in model.lb and model.ub +0377 %pos and neg - between 0 and inf +0378 %ints - between 0 and 1 +0379 %b - as stated in the model.b vector - if this has one column, that is lb and ub (fixed value), if two columns, that is lb and ub +0380 %var - between zero and inf +0381 +0382 if size(model.b,2)==2 +0383 bub = model.b(:,2); +0384 else +0385 bub = model.b(:,1); +0386 end 0387 -0388 if size(model.b,2)==2 -0389 bub = model.b(:,2); -0390 else -0391 bub = model.b(:,1); -0392 end -0393 -0394 prob.lb = [model.lb;zeros(numel(revIndexes)*2,1);zeros(numel(indexes),1);model.b(:,1);zeros(numel(indexes),1)]; -0395 prob.ub = [model.ub;inf(numel(revIndexes)*2,1);ones(numel(indexes),1);bub;inf(numel(indexes),1)]; -0396 -0397 prob.b=zeros(size(prob.a,1), 1); -0398 -0399 intsIndexes = find(prob.c ~= 0); -0400 prob.x0=[]; -0401 prob.vartype=repmat('C', 1, size(prob.A,2)); -0402 prob.vartype(intsIndexes) = 'B'; -0403 prob.csense = repmat('E', 1, size(prob.A,1)); -0404 prob.osense=1; %minimize the objective -0405 -0406 %Defaults that suit this formulation, without discarding what the caller set -0407 if ~isfield(params,'intTol') -0408 params.intTol = 10^-9; -0409 end -0410 if ~isfield(params,'TimeLimit') -0411 params.TimeLimit = 300; +0388 prob.lb = [model.lb;zeros(numel(revIndexes)*2,1);zeros(numel(indexes),1);model.b(:,1);zeros(numel(indexes),1)]; +0389 prob.ub = [model.ub;inf(numel(revIndexes)*2,1);ones(numel(indexes),1);bub;inf(numel(indexes),1)]; +0390 +0391 prob.b=zeros(size(prob.a,1), 1); +0392 +0393 intsIndexes = find(prob.c ~= 0); +0394 prob.x0=[]; +0395 prob.vartype=repmat('C', 1, size(prob.A,2)); +0396 prob.vartype(intsIndexes) = 'B'; +0397 prob.csense = repmat('E', 1, size(prob.A,1)); +0398 prob.osense=1; %minimize the objective +0399 +0400 %Defaults that suit this formulation, without discarding what the caller set +0401 if ~isfield(params,'intTol') +0402 params.intTol = 10^-9; +0403 end +0404 if ~isfield(params,'TimeLimit') +0405 params.TimeLimit = 300; +0406 end +0407 if ~isfield(params,'Seed') +0408 %Although the solver reports "optimal solution found", different seeds +0409 %can return solutions with noticeably different objective values, so a +0410 %fixed one is used to keep results reproducible +0411 params.Seed = 26; 0412 end -0413 if ~isfield(params,'Seed') -0414 %Although the solver reports "optimal solution found", different seeds -0415 %can return solutions with noticeably different objective values, so a -0416 %fixed one is used to keep results reproducible -0417 params.Seed = 26; -0418 end -0419 -0420 % Optimize the problem -0421 res = optimizeProb(prob,params,verbose); -0422 [isFeasible, isOptimal]=checkSolution(res); -0423 -0424 if ~isFeasible -0425 x=[]; -0426 I=[]; -0427 exitFlag=-1; -0428 if res.hitTimeLimit -0429 %Nothing is returned either way, but the caller reports -1 as "no -0430 %feasible solution exists", which is not what happened here. -0431 EM='Time limit reached before finding a solution. Try increasing the TimeLimit parameter.'; -0432 warning('RAVEN:warning', '%s', EM); -0433 end -0434 return; -0435 end -0436 if res.hitTimeLimit || ~isOptimal -0437 %A feasible but suboptimal solution, i.e. the solver stopped before -0438 %reaching optimality. The solution is still returned, but must not be -0439 %reported as optimal: it may use more fluxes than the minimum. -0440 %Not every solver reports this in its status, gurobi for one presents a -0441 %MILP solution found before the limit as optimal, so res.hitTimeLimit is -0442 %consulted as well. -0443 exitFlag=-2; -0444 end -0445 -0446 if resolveTies -0447 %This MILP is highly degenerate: many reaction subsets reach the same -0448 %score optimum, and the solver returns an arbitrary one -- reproducible -0449 %for a fixed solver build + Seed, but fragile to a solver version bump -0450 %or an unrelated change upstream of the MILP (see raven-gecko-parity#104, -0451 %where this was demonstrated on genome-scale Human-GEM: the same Seed, -0452 %same problem, but a different MILP construction path flipped a tied -0453 %pair of reactions). This runs a lexicographic phase 2, holding the -0454 %score objective at its optimum with a floor constraint: first minimise -0455 %the count of "on" reactions (the sparsest optimum), then, among the -0456 %sparsest, minimise their summed reaction-id rank (prefer lower ids). -0457 %Ports raven-toolbox's _resolve_ties_fill (raven-toolbox#114). -0458 % -0459 %intCols identifies the int/binary variable columns directly from the -0460 %block layout (prob.c = [zeros(rxns); zeros(2*revIndexes); scores(:); -0461 %zeros(mets+indexes)]) rather than via find(prob.c~=0): a zero-scored -0462 %reaction would otherwise silently drop out of the column count and -0463 %misalign every later index into "indexes". -0464 intCols = numel(model.rxns) + 2*numel(revIndexes) + (1:numel(indexes)); -0465 primaryObj = full(prob.c(intCols)' * res.full(intCols)); -0466 tol = max(abs(primaryObj)*1e-7, 1e-7); -0467 -0468 floorRow = sparse(1,size(prob.A,2)); -0469 floorRow(intCols) = prob.c(intCols)'; -0470 tieProb = prob; -0471 tieProb.A = [prob.A; floorRow]; -0472 tieProb.a = tieProb.A; -0473 tieProb.b = [prob.b; primaryObj+tol]; -0474 tieProb.csense = [prob.csense 'L']; -0475 -0476 %An integer objective needs only a cheap absolute-gap proof, not a tiny -0477 %relative one -- RAVEN's own MIPGap default (1e-12) can be slow to prove -0478 %on a genome-scale problem where a gap below 1 already proves optimality -0479 %exactly (mirrors raven-toolbox's own MIPGap=0/MIPGapAbs=0.4 for the same -0480 %two phases). -0481 tieParams = params; -0482 tieParams.MIPGap = 0; -0483 tieParams.MIPGapAbs = 0.4; -0484 -0485 unproven = {}; -0486 -0487 %Phase 2a: fewest "on" reactions among the score-optimal solutions. -0488 countObj = zeros(size(prob.c)); -0489 countObj(intCols) = 1; -0490 tieProb.c = countObj; -0491 res2a = optimizeProb(tieProb,tieParams,verbose); -0492 [isFeasible2a, isOptimal2a] = checkSolution(res2a); -0493 if ~isOptimal2a -0494 unproven{end+1} = 'phase2a-parsimony'; -0495 end -0496 if isFeasible2a -0497 kmin = full(countObj'*res2a.full); -0498 -0499 %Phase 2b: among the sparsest, prefer lower reaction ids -- a -0500 %deterministic tie-break independent of solver/seed. -0501 countRow = sparse(1,size(tieProb.A,2)); -0502 countRow(intCols) = 1; -0503 tieProb2b = tieProb; -0504 tieProb2b.A = [tieProb.A; countRow]; -0505 tieProb2b.a = tieProb2b.A; -0506 tieProb2b.b = [tieProb.b; kmin+0.5]; -0507 tieProb2b.csense = [tieProb.csense 'L']; -0508 -0509 [~, sortOrder] = sort(model.rxns(indexes)); -0510 ranks = zeros(numel(indexes),1); -0511 ranks(sortOrder) = (1:numel(indexes))'; -0512 idObj = zeros(size(prob.c)); -0513 idObj(intCols) = ranks; -0514 tieProb2b.c = idObj; -0515 res2b = optimizeProb(tieProb2b,tieParams,verbose); -0516 [isFeasible2b, isOptimal2b] = checkSolution(res2b); -0517 if ~isOptimal2b -0518 unproven{end+1} = 'phase2b-idrank'; +0413 +0414 % Optimize the problem +0415 res = optimizeProb(prob,params,verbose); +0416 [isFeasible, isOptimal]=checkSolution(res); +0417 +0418 if ~isFeasible +0419 x=[]; +0420 I=[]; +0421 exitFlag=-1; +0422 if res.hitTimeLimit +0423 %Nothing is returned either way, but the caller reports -1 as "no +0424 %feasible solution exists", which is not what happened here. +0425 EM='Time limit reached before finding a solution. Try increasing the TimeLimit parameter.'; +0426 warning('RAVEN:warning', '%s', EM); +0427 end +0428 return; +0429 end +0430 if res.hitTimeLimit || ~isOptimal +0431 %A feasible but suboptimal solution, i.e. the solver stopped before +0432 %reaching optimality. The solution is still returned, but must not be +0433 %reported as optimal: it may use more fluxes than the minimum. +0434 %Not every solver reports this in its status, gurobi for one presents a +0435 %MILP solution found before the limit as optimal, so res.hitTimeLimit is +0436 %consulted as well. +0437 exitFlag=-2; +0438 end +0439 +0440 if resolveTies +0441 %This MILP is highly degenerate: many reaction subsets reach the same +0442 %score optimum, and the solver returns an arbitrary one -- reproducible +0443 %for a fixed solver build + Seed, but fragile to a solver version bump +0444 %or an unrelated change upstream of the MILP (see raven-gecko-parity#104, +0445 %where this was demonstrated on genome-scale Human-GEM: the same Seed, +0446 %same problem, but a different MILP construction path flipped a tied +0447 %pair of reactions). This runs a lexicographic phase 2, holding the +0448 %score objective at its optimum with a floor constraint: first minimise +0449 %the count of "on" reactions (the sparsest optimum), then, among the +0450 %sparsest, minimise their summed reaction-id rank (prefer lower ids). +0451 %Ports raven-toolbox's _resolve_ties_fill (raven-toolbox#114). +0452 % +0453 %intCols identifies the int/binary variable columns directly from the +0454 %block layout (prob.c = [zeros(rxns); zeros(2*revIndexes); scores(:); +0455 %zeros(mets+indexes)]) rather than via find(prob.c~=0): a zero-scored +0456 %reaction would otherwise silently drop out of the column count and +0457 %misalign every later index into "indexes". +0458 intCols = numel(model.rxns) + 2*numel(revIndexes) + (1:numel(indexes)); +0459 primaryObj = full(prob.c(intCols)' * res.full(intCols)); +0460 tol = max(abs(primaryObj)*1e-7, 1e-7); +0461 +0462 floorRow = sparse(1,size(prob.A,2)); +0463 floorRow(intCols) = prob.c(intCols)'; +0464 tieProb = prob; +0465 tieProb.A = [prob.A; floorRow]; +0466 tieProb.a = tieProb.A; +0467 tieProb.b = [prob.b; primaryObj+tol]; +0468 tieProb.csense = [prob.csense 'L']; +0469 +0470 %An integer objective needs only a cheap absolute-gap proof, not a tiny +0471 %relative one -- RAVEN's own MIPGap default (1e-12) can be slow to prove +0472 %on a genome-scale problem where a gap below 1 already proves optimality +0473 %exactly (mirrors raven-toolbox's own MIPGap=0/MIPGapAbs=0.4 for the same +0474 %two phases). +0475 tieParams = params; +0476 tieParams.MIPGap = 0; +0477 tieParams.MIPGapAbs = 0.4; +0478 +0479 unproven = {}; +0480 +0481 %Phase 2a: fewest "on" reactions among the score-optimal solutions. +0482 countObj = zeros(size(prob.c)); +0483 countObj(intCols) = 1; +0484 tieProb.c = countObj; +0485 res2a = optimizeProb(tieProb,tieParams,verbose); +0486 [isFeasible2a, isOptimal2a] = checkSolution(res2a); +0487 if ~isOptimal2a +0488 unproven{end+1} = 'phase2a-parsimony'; +0489 end +0490 if isFeasible2a +0491 kmin = full(countObj'*res2a.full); +0492 +0493 %Phase 2b: among the sparsest, prefer lower reaction ids -- a +0494 %deterministic tie-break independent of solver/seed. +0495 countRow = sparse(1,size(tieProb.A,2)); +0496 countRow(intCols) = 1; +0497 tieProb2b = tieProb; +0498 tieProb2b.A = [tieProb.A; countRow]; +0499 tieProb2b.a = tieProb2b.A; +0500 tieProb2b.b = [tieProb.b; kmin+0.5]; +0501 tieProb2b.csense = [tieProb.csense 'L']; +0502 +0503 [~, sortOrder] = sort(model.rxns(indexes)); +0504 ranks = zeros(numel(indexes),1); +0505 ranks(sortOrder) = (1:numel(indexes))'; +0506 idObj = zeros(size(prob.c)); +0507 idObj(intCols) = ranks; +0508 tieProb2b.c = idObj; +0509 res2b = optimizeProb(tieProb2b,tieParams,verbose); +0510 [isFeasible2b, isOptimal2b] = checkSolution(res2b); +0511 if ~isOptimal2b +0512 unproven{end+1} = 'phase2b-idrank'; +0513 end +0514 if isFeasible2b +0515 %Adopt the tie-resolved solution; exitFlag still reflects +0516 %whether the *primary* (scores) objective was proven optimal, +0517 %same as without resolveTies. +0518 res = res2b; 0519 end -0520 if isFeasible2b -0521 %Adopt the tie-resolved solution; exitFlag still reflects -0522 %whether the *primary* (scores) objective was proven optimal, -0523 %same as without resolveTies. -0524 res = res2b; -0525 end -0526 end -0527 if ~isempty(unproven) -0528 EM = ['getMinNrFluxes tie resolution did not converge (' strjoin(unproven,', ') '): the selection among equally score-optimal solutions is itself an unproven incumbent, so resolveTies has reduced but not removed the run-to-run spread. Raise params.TimeLimit for a proven tie-break.']; -0529 warning('RAVEN:warning', '%s', EM); -0530 end -0531 end +0520 end +0521 if ~isempty(unproven) +0522 EM = ['getMinNrFluxes tie resolution did not converge (' strjoin(unproven,', ') '): the selection among equally score-optimal solutions is itself an unproven incumbent, so resolveTies has reduced but not removed the run-to-run spread. Raise params.TimeLimit for a proven tie-break.']; +0523 warning('RAVEN:warning', '%s', EM); +0524 end +0525 end +0526 +0527 x=res.full(1:numel(model.rxns));%the fluxes +0528 +0529 ints = res.full(intsIndexes); +0530 %The margin for integers in gurobi is 10^-5; use 10^-3 to have some margin. +0531 I=ismember(toMinimize, model.rxns(indexes(ints > 10^-3))); 0532 -0533 x=res.full(1:numel(model.rxns));%the fluxes -0534 -0535 ints = res.full(intsIndexes); -0536 %The margin for integers in gurobi is 10^-5; use 10^-3 to have some margin. -0537 I=ismember(toMinimize, model.rxns(indexes(ints > 10^-3))); +0533 sel = (ints > 10^-12) & (ints < 0.5); +0534 if sum(sel) > 0 +0535 %This may indicate that there is a problem with the tolerances in the solver +0536 disp(['getMinNrFluxes: Some variables meant to be boolean in the MILP have intermediate values. Num vars: ' num2str(sum(sel))]) +0537 end 0538 -0539 sel = (ints > 10^-12) & (ints < 0.5); -0540 if sum(sel) > 0 -0541 %This may indicate that there is a problem with the tolerances in the solver -0542 disp(['getMinNrFluxes: Some variables meant to be boolean in the MILP have intermediate values. Num vars: ' num2str(sum(sel))]) -0543 end -0544 -0545 end +0539 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/solver/optimizeProb.html b/docs/html/solver/optimizeProb.html index b486395d..715151bd 100644 --- a/docs/html/solver/optimizeProb.html +++ b/docs/html/solver/optimizeProb.html @@ -57,12 +57,11 @@

    DESCRIPTION ^SOURCE CODE ^% 0030 % Notes 0031 % ----- -0032 % With 'glpk' selected, MILPs are solved by RAVEN's bundled GLPK, and a -0033 % RAVEN:glpkMILP warning recommends Gurobi, which is much faster and more -0034 % reliable on larger MILPs (such as ftINIT or getMinNrFluxes on -0035 % genome-scale models). -0036 % The GLPK interface of the COBRA Toolbox is not used for MILPs: with -0037 % 'cobra' selected, set a different MILP solver with changeCobraSolver. -0038 % -0039 % See also -0040 % -------- -0041 % splitProbForConditioning -0042 -0043 p=parseRAVENargs(varargin, {'params',[]; 'verbose',true}); -0044 params=p.params; -0045 if isempty(params) -0046 params=struct(); -0047 end -0048 verbose=p.verbose; -0049 -0050 %Set as global variable for speed improvement if optimizeProb is run many times -0051 global RAVENSOLVER; -0052 %CBT_MILP_SOLVER is a COBRA Toolbox global, set by changeCobraSolver, and -0053 %has to be declared as such to be readable in the MILP check below -0054 global CBT_MILP_SOLVER; -0055 if isempty(RAVENSOLVER) -0056 if(~ispref('RAVEN','solver')) -0057 error('RAVEN:badInput', '%s', 'RAVEN solver not defined or unknown. Try using setRavenSolver(''solver'').'); -0058 else -0059 RAVENSOLVER = getpref('RAVEN','solver'); -0060 end -0061 end -0062 solver=RAVENSOLVER; -0063 if ~all(lower(prob.vartype) == 'c') -0064 milp=true; -0065 switch solver -0066 case 'glpk' -0067 warning('RAVEN:glpkMILP', ['Solving a MILP with GLPK. This works for small ' ... -0068 'problems, but for larger problems Gurobi is much faster and more reliable; ' ... -0069 'select it with setRavenSolver(''gurobi''). Silence this warning with ' ... -0070 'warning(''off'',''RAVEN:glpkMILP'').']) -0071 case 'cobra' -0072 if strcmp(CBT_MILP_SOLVER,'glpk') -0073 error(['The GLPK interface of the COBRA Toolbox is not suitable for solving MILPs, ' ... -0074 'select a different solver with changeCobraSolver() or setRavenSolver().']) -0075 end -0076 end -0077 else -0078 milp=false; -0079 end -0080 -0081 %% Define default parameters, which will then be used to make solver- -0082 % specific solverparams structures -0083 defaultparams.feasTol = 1e-9; -0084 defaultparams.optTol = 1e-9; -0085 defaultparams.objTol = 1e-6; -0086 defaultparams.timeLimit = 1000; -0087 %defaultparams.iterationLimit = 1000; -0088 defaultparams.intTol = 1e-12; -0089 defaultparams.relMipGapTol = 1e-12; -0090 defaultparams.absMipGapTol = 1e-12; -0091 if milp -0092 defaultparams.MIPGap = 1e-12; -0093 defaultparams.Seed = 1; -0094 end -0095 -0096 %% Optionally improve numerical conditioning before solving. This is opt-in -0097 % through params.maxRatio and only applied to LPs. Columns whose coefficients -0098 % span more than maxRatio are split via auxiliary metabolites/variables, which -0099 % preserves the feasible region but reduces the spread within each column. The -0100 % added rows/columns are stripped from the result further down. -0101 condApplied = false; -0102 if isfield(params,'maxRatio') -0103 maxRatio = params.maxRatio; -0104 params = rmfield(params,'maxRatio'); -0105 if ~milp && isnumeric(maxRatio) && isscalar(maxRatio) && isfinite(maxRatio) && maxRatio>1 -0106 [prob, condInfo] = splitProbForConditioning(prob, maxRatio); -0107 condApplied = condInfo.applied; -0108 end -0109 end -0110 -0111 res.obj=[]; -0112 switch solver -0113 %% Use whatever solver is set by COBRA Toolbox changeCobraSolver -0114 case 'cobra' -0115 if milp -0116 cparams=struct('timeLimit',1e9,'printLevel',0,'intTol',1e-6,'relMipGapTol',1e-9); -0117 cparams=structUpdate(cparams,params); -0118 res=solveCobraMILP(prob,cparams); -0119 else -0120 res=solveCobraLP(prob); -0121 end -0122 %% Use Gurobi in a MATLAB environment -0123 case 'gurobi' -0124 if milp -0125 if verbose -0126 solverparams.OutputFlag = 1; -0127 else -0128 solverparams.OutputFlag = 0; -0129 end -0130 solverparams.IntFeasTol = 10^-9; %min val for gurobi -0131 solverparams.MIPGap = defaultparams.MIPGap; -0132 solverparams.Seed = defaultparams.Seed; -0133 else -0134 solverparams.OutputFlag = 0; -0135 end -0136 solverparams.DisplayInterval= 1; % Level of verbosity -0137 solverparams.TimeLimit = defaultparams.timeLimit; -0138 solverparams.FeasibilityTol = defaultparams.feasTol; -0139 solverparams.OptimalityTol = defaultparams.optTol; -0140 solverparams.Presolve = 2; -0141 % getCurrentTask requires the Parallel Computing Toolbox; without it -0142 % the call itself errors, so guard it rather than requiring the -0143 % toolbox for every gurobi solve. -0144 if exist('getCurrentTask','file') && ~isempty(getCurrentTask) % If run in parallel, then one thread per gurobi -0145 solverparams.Threads=1; +0032 % MILPs can be solved with GLPK, either RAVEN's bundled GLPK ('glpk') or +0033 % the COBRA Toolbox's GLPK interface ('cobra' with GLPK as its MILP solver). +0034 % A RAVEN:glpkMILP warning then recommends Gurobi, which is much faster and +0035 % more reliable on larger MILPs (such as ftINIT or getMinNrFluxes on +0036 % genome-scale models). +0037 % +0038 % See also +0039 % -------- +0040 % splitProbForConditioning +0041 +0042 p=parseRAVENargs(varargin, {'params',[]; 'verbose',true}); +0043 params=p.params; +0044 if isempty(params) +0045 params=struct(); +0046 end +0047 verbose=p.verbose; +0048 +0049 %Set as global variable for speed improvement if optimizeProb is run many times +0050 global RAVENSOLVER; +0051 %CBT_MILP_SOLVER is a COBRA Toolbox global, set by changeCobraSolver, and +0052 %has to be declared as such to be readable in the MILP check below +0053 global CBT_MILP_SOLVER; +0054 if isempty(RAVENSOLVER) +0055 if(~ispref('RAVEN','solver')) +0056 error('RAVEN:badInput', '%s', 'RAVEN solver not defined or unknown. Try using setRavenSolver(''solver'').'); +0057 else +0058 RAVENSOLVER = getpref('RAVEN','solver'); +0059 end +0060 end +0061 solver=RAVENSOLVER; +0062 if ~all(lower(prob.vartype) == 'c') +0063 milp=true; +0064 if strcmp(solver,'glpk') || (strcmp(solver,'cobra') && strcmp(CBT_MILP_SOLVER,'glpk')) +0065 warning('RAVEN:glpkMILP', ['Solving a MILP with GLPK. This works for small ' ... +0066 'problems, but for larger problems Gurobi is much faster and more reliable; ' ... +0067 'select it with setRavenSolver(''gurobi''). Silence this warning with ' ... +0068 'warning(''off'',''RAVEN:glpkMILP'').']) +0069 end +0070 else +0071 milp=false; +0072 end +0073 +0074 %% Define default parameters, which will then be used to make solver- +0075 % specific solverparams structures +0076 defaultparams.feasTol = 1e-9; +0077 defaultparams.optTol = 1e-9; +0078 defaultparams.objTol = 1e-6; +0079 defaultparams.timeLimit = 1000; +0080 %defaultparams.iterationLimit = 1000; +0081 defaultparams.intTol = 1e-12; +0082 defaultparams.relMipGapTol = 1e-12; +0083 defaultparams.absMipGapTol = 1e-12; +0084 if milp +0085 defaultparams.MIPGap = 1e-12; +0086 defaultparams.Seed = 1; +0087 end +0088 +0089 %% Optionally improve numerical conditioning before solving. This is opt-in +0090 % through params.maxRatio and only applied to LPs. Columns whose coefficients +0091 % span more than maxRatio are split via auxiliary metabolites/variables, which +0092 % preserves the feasible region but reduces the spread within each column. The +0093 % added rows/columns are stripped from the result further down. +0094 condApplied = false; +0095 if isfield(params,'maxRatio') +0096 maxRatio = params.maxRatio; +0097 params = rmfield(params,'maxRatio'); +0098 if ~milp && isnumeric(maxRatio) && isscalar(maxRatio) && isfinite(maxRatio) && maxRatio>1 +0099 [prob, condInfo] = splitProbForConditioning(prob, maxRatio); +0100 condApplied = condInfo.applied; +0101 end +0102 end +0103 +0104 res.obj=[]; +0105 switch solver +0106 %% Use whatever solver is set by COBRA Toolbox changeCobraSolver +0107 case 'cobra' +0108 if milp +0109 cparams=struct('timeLimit',1e9,'printLevel',0,'intTol',1e-6,'relMipGapTol',1e-9); +0110 cparams=structUpdate(cparams,params); +0111 res=solveCobraMILP(prob,cparams); +0112 else +0113 res=solveCobraLP(prob); +0114 end +0115 %% Use Gurobi in a MATLAB environment +0116 case 'gurobi' +0117 if milp +0118 if verbose +0119 solverparams.OutputFlag = 1; +0120 else +0121 solverparams.OutputFlag = 0; +0122 end +0123 solverparams.IntFeasTol = 10^-9; %min val for gurobi +0124 solverparams.MIPGap = defaultparams.MIPGap; +0125 solverparams.Seed = defaultparams.Seed; +0126 else +0127 solverparams.OutputFlag = 0; +0128 end +0129 solverparams.DisplayInterval= 1; % Level of verbosity +0130 solverparams.TimeLimit = defaultparams.timeLimit; +0131 solverparams.FeasibilityTol = defaultparams.feasTol; +0132 solverparams.OptimalityTol = defaultparams.optTol; +0133 solverparams.Presolve = 2; +0134 % getCurrentTask requires the Parallel Computing Toolbox; without it +0135 % the call itself errors, so guard it rather than requiring the +0136 % toolbox for every gurobi solve. +0137 if exist('getCurrentTask','file') && ~isempty(getCurrentTask) % If run in parallel, then one thread per gurobi +0138 solverparams.Threads=1; +0139 end +0140 solverparams = structUpdate(solverparams,params); +0141 +0142 % Restructering problem according to gurobi format +0143 if isfield(prob, 'csense') +0144 prob.sense = renameparams(prob.csense, {'L','G','E'}, {'<','>','='}); +0145 prob = rmfield(prob, {'csense'}); 0146 end -0147 solverparams = structUpdate(solverparams,params); -0148 -0149 % Restructering problem according to gurobi format -0150 if isfield(prob, 'csense') -0151 prob.sense = renameparams(prob.csense, {'L','G','E'}, {'<','>','='}); -0152 prob = rmfield(prob, {'csense'}); -0153 end -0154 % gurobi minimises unless told otherwise, so an absent osense means -0155 % minimisation; osense is also needed below to sign the duals -0156 osense = 1; -0157 if isfield(prob, 'osense') -0158 osense = prob.osense; -0159 if osense < 0 -0160 prob.modelsense = 'max'; -0161 else -0162 prob.modelsense = 'min'; -0163 end -0164 prob = rmfield(prob, {'osense'}); -0165 end -0166 [prob.obj, prob.rhs, prob.vtype] = deal(prob.c, prob.b, prob.vartype); -0167 prob = rmfield(prob, {'c','b','vartype'}); -0168 -0169 resG = gurobi(prob,solverparams); -0170 -0171 % Name output fields the same as COBRA does. Every field below is -0172 % optional in the gurobi result: which ones are present depends on -0173 % the status and on the method gurobi chose (a barrier solve returns -0174 % no simplex basis, for instance). They are therefore read -0175 % individually rather than under one try/catch, which would have -0176 % reported a missing basis on an optimal solve as "infeasible". -0177 res.origStat = resG.status; -0178 if isfield(resG,'x') -0179 res.full = resG.x; +0147 % gurobi minimises unless told otherwise, so an absent osense means +0148 % minimisation; osense is also needed below to sign the duals +0149 osense = 1; +0150 if isfield(prob, 'osense') +0151 osense = prob.osense; +0152 if osense < 0 +0153 prob.modelsense = 'max'; +0154 else +0155 prob.modelsense = 'min'; +0156 end +0157 prob = rmfield(prob, {'osense'}); +0158 end +0159 [prob.obj, prob.rhs, prob.vtype] = deal(prob.c, prob.b, prob.vartype); +0160 prob = rmfield(prob, {'c','b','vartype'}); +0161 +0162 resG = gurobi(prob,solverparams); +0163 +0164 % Name output fields the same as COBRA does. Every field below is +0165 % optional in the gurobi result: which ones are present depends on +0166 % the status and on the method gurobi chose (a barrier solve returns +0167 % no simplex basis, for instance). They are therefore read +0168 % individually rather than under one try/catch, which would have +0169 % reported a missing basis on an optimal solve as "infeasible". +0170 res.origStat = resG.status; +0171 if isfield(resG,'x') +0172 res.full = resG.x; +0173 end +0174 if isfield(resG,'objval') +0175 res.obj = resG.objval; +0176 end +0177 if isfield(resG,'pi') && isfield(resG,'rc') +0178 res.dual = -resG.pi*osense; +0179 res.rcost = -resG.rc*osense; 0180 end -0181 if isfield(resG,'objval') -0182 res.obj = resG.objval; -0183 end -0184 if isfield(resG,'pi') && isfield(resG,'rc') -0185 res.dual = -resG.pi*osense; -0186 res.rcost = -resG.rc*osense; +0181 if milp && strcmp(resG.status, 'TIME_LIMIT') && isfield(resG,'x') && isfield(resG,'objval') +0182 % An incumbent was found before the time limit. It is usable but +0183 % not proven optimal, so it is reported as feasible rather than +0184 % optimal; callers that must not act on a suboptimal MILP +0185 % solution (getMinNrFluxes, solveLP) key on that distinction. +0186 resG.status = 'SUBOPTIMAL'; 0187 end -0188 if milp && strcmp(resG.status, 'TIME_LIMIT') && isfield(resG,'x') && isfield(resG,'objval') -0189 % An incumbent was found before the time limit. It is usable but -0190 % not proven optimal, so it is reported as feasible rather than -0191 % optimal; callers that must not act on a suboptimal MILP -0192 % solution (getMinNrFluxes, solveLP) key on that distinction. -0193 resG.status = 'SUBOPTIMAL'; -0194 end -0195 switch resG.status -0196 case 'OPTIMAL' -0197 res.stat = 1; -0198 case {'UNBOUNDED','SUBOPTIMAL'} -0199 res.stat = 2; -0200 otherwise -0201 res.stat = 0; -0202 end -0203 if ~milp -0204 if isfield(resG,'vbasis') -0205 res.vbasis = resG.vbasis; -0206 end -0207 if isfield(resG,'cbasis') -0208 res.cbasis = resG.cbasis; -0209 end -0210 elseif isfield(resG,'mipgap') -0211 res.mipgap = resG.mipgap; -0212 end -0213 if res.stat>0 && ~isfield(res,'full') -0214 % A status that claims a solution without returning one -0215 res.stat = 0; -0216 end -0217 %% Use GLPK using RAVEN-provided binary -0218 case 'glpk' -0219 solverparams.scale = 1; % Auto scaling -0220 %solverparams.tmlim = defaultparams.timeLimit; -0221 solverparams.tolbnd = defaultparams.feasTol; -0222 solverparams.toldj = defaultparams.optTol; -0223 solverparams.tolint = defaultparams.intTol; -0224 solverparams.tolobj = defaultparams.objTol; -0225 solverparams.msglev = 0; % Level of verbosity -0226 if milp -0227 solverparams.tmlim = defaultparams.timeLimit; % seconds -0228 end -0229 solverparams = structUpdate(solverparams,params); -0230 -0231 prob.csense = renameparams(prob.csense, {'L','G','E'}, {'U','L','S'}); +0188 switch resG.status +0189 case 'OPTIMAL' +0190 res.stat = 1; +0191 case {'UNBOUNDED','SUBOPTIMAL'} +0192 res.stat = 2; +0193 otherwise +0194 res.stat = 0; +0195 end +0196 if ~milp +0197 if isfield(resG,'vbasis') +0198 res.vbasis = resG.vbasis; +0199 end +0200 if isfield(resG,'cbasis') +0201 res.cbasis = resG.cbasis; +0202 end +0203 elseif isfield(resG,'mipgap') +0204 res.mipgap = resG.mipgap; +0205 end +0206 if res.stat>0 && ~isfield(res,'full') +0207 % A status that claims a solution without returning one +0208 res.stat = 0; +0209 end +0210 %% Use GLPK using RAVEN-provided binary +0211 case 'glpk' +0212 solverparams.scale = 1; % Auto scaling +0213 %solverparams.tmlim = defaultparams.timeLimit; +0214 solverparams.tolbnd = defaultparams.feasTol; +0215 solverparams.toldj = defaultparams.optTol; +0216 solverparams.tolint = defaultparams.intTol; +0217 solverparams.tolobj = defaultparams.objTol; +0218 solverparams.msglev = 0; % Level of verbosity +0219 if milp +0220 solverparams.tmlim = defaultparams.timeLimit; % seconds +0221 end +0222 solverparams = structUpdate(solverparams,params); +0223 +0224 prob.csense = renameparams(prob.csense, {'L','G','E'}, {'U','L','S'}); +0225 +0226 % Ensure that RAVEN glpk binary is used, return to original +0227 % directory afterwards +0228 [ravenDir,currDir]=findRAVENroot(); +0229 cd(fullfile(ravenDir,'src','external','GLPKmex')) +0230 [xopt, fmin, errnum, extra] = glpk(prob.c, prob.A, prob.b, prob.lb, prob.ub, prob.csense, prob.vartype, prob.osense, solverparams); +0231 cd(currDir) 0232 -0233 % Ensure that RAVEN glpk binary is used, return to original -0234 % directory afterwards -0235 [ravenDir,currDir]=findRAVENroot(); -0236 cd(fullfile(ravenDir,'src','external','GLPKmex')) -0237 [xopt, fmin, errnum, extra] = glpk(prob.c, prob.A, prob.b, prob.lb, prob.ub, prob.csense, prob.vartype, prob.osense, solverparams); -0238 cd(currDir) -0239 -0240 switch errnum % 1 = undefined; 2 = feasible; 3 = infeasible; 4 = no feasible solution; 5 = optimal; 6 = no unbounded solution -0241 case 5 -0242 res.stat = 1; % Optimal -0243 case 2 -0244 res.stat = 2; % Feasible, but not optimal -0245 otherwise -0246 res.stat = 0; -0247 end -0248 res.origStat = errnum; -0249 res.full = xopt; -0250 res.obj = fmin; -0251 res.dual = -extra.lambda*prob.osense; -0252 res.rcost = -extra.redcosts*prob.osense; -0253 if milp -0254 % glpk reports no gap; a proven optimum has none, anything else -0255 % is not accepted by callers that test the gap -0256 if res.stat == 1 -0257 res.mipgap = 0; -0258 else -0259 res.mipgap = inf; -0260 end -0261 end -0262 %% Use scip -0263 case {'soplex','scip'} % Old 'soplex' option also allowed -0264 % scip takes a row range [rl,ru] rather than a right-hand side plus -0265 % a sense, so csense has to be translated into that range. Passing -0266 % [-b,b] is only the same problem when every row is an equality with -0267 % b = 0, which is the case for solveLP but for none of the MILPs -0268 % built elsewhere in RAVEN. -0269 rl = full(prob.b(:)); -0270 ru = rl; -0271 csense = prob.csense(:); -0272 rl(csense=='L') = -inf; % A*x <= b -0273 ru(csense=='G') = inf; % A*x >= b -0274 [xopt,fval,exitflag,scipStats] = scip([], prob.c, prob.A, rl, ru, prob.lb, prob.ub, prob.vartype); -0275 -0276 % [x,fval,exitflag,stats] = scip(H, f, A, rl, ru, lb, ub, xtype, sos, qc, nl, x0, opts) -0277 % -0278 % Input arguments*: -0279 % H - quadratic objective matrix (sparse, optional [NOT TRIL / TRIU]) -0280 % f - linear objective vector -0281 % A - linear constraint matrix (sparse) -0282 % rl - linear constraint lhs -0283 % ru - linear constraint rhs -0284 % lb - decision variable lower bounds -0285 % ub - decision variable upper bounds -0286 % xtype - string of variable integrality ("c" continuous, "i" integer, "b" binary) -0287 % sos - SOS structure with fields type, index and weight (see below) -0288 % qc - Quadratic Constraints structure with fields Q, l, qrl and qru (see below) -0289 % nl - Nonlinear Objective and Constraints structure (see below) -0290 % x0 - primal solution -0291 % opts - solver options (see below) -0292 % -0293 % Return arguments: -0294 % x - solution vector -0295 % fval - objective value at the solution -0296 % exitflag - exit status (see below) -0297 % stats - statistics structure -0298 % -0299 % Option Fields (all optional, see also optiset for a list): -0300 % solverOpts - specific SCIP options (list of pairs of parameter names and values) -0301 % maxiter - maximum LP solver iterations -0302 % maxnodes - maximum nodes to explore -0303 % maxtime - maximum execution time [s] -0304 % tolrfun - primal feasibility tolerance -0305 % display - solver display level [0-5] -0306 % probfile - write problem to given file -0307 % presolvedfile - write presolved problem to file -0308 % -0309 % Return Status, as returned by the MatlabSCIPInterface build that -0310 % RAVEN provides and that setRavenSolver points at -0311 % (github.com/scipopt/MatlabSCIPInterface). Verified against that -0312 % binary with a bounded, an infeasible and an unbounded problem: -0313 % 1 - Problem Solved to Optimality -0314 % 2 - Problem is Infeasible -0315 % 3 - Problem is Unbounded -0316 % -0317 % The OPTI Toolbox build of scip uses a different, longer set in -0318 % which optimality is 11, infeasibility 12 and unboundedness 13, -0319 % with the lower numbers meaning "a limit was reached". Those codes -0320 % are accepted below as well, so either build reports optimality -0321 % correctly; only OPTI's 1 ("User Interrupted") is ambiguous, and -0322 % reading it as optimal is no worse than the previous mapping, -0323 % which recognised no status from the shipped build at all. -0324 -0325 res.origStat = exitflag; -0326 res.full = xopt; -0327 res.obj = fval; -0328 -0329 switch exitflag -0330 case {1, 11} % solved to optimality -0331 res.stat = 1; -0332 case {3, 13} % unbounded -0333 res.stat = 2; -0334 case {5, 6, 7, 8, 9, 10} % OPTI: a limit was reached, so a -0335 res.stat = 2; % solution may exist but is not proven optimal -0336 otherwise % 2/12 infeasible, 0 unknown, 4, 14 -0337 res.stat = 0; -0338 end -0339 -0340 if milp -0341 % Callers such as ftINIT compare this against their own -0342 % params.MIPGap, so it has to be the quantity the gurobi branch -0343 % reports there: a relative gap. It is derived from the bounds -0344 % rather than read from scipStats.BBgap, whose units are not -0345 % documented. inf where no gap can be established, so that a -0346 % caller testing "gap small enough" does not accept the solution. -0347 if res.stat == 1 -0348 res.mipgap = 0; -0349 else -0350 res.mipgap = inf; -0351 primal = scipStats.PrimalBound; -0352 if isfinite(primal) && abs(primal) > 1e-10 -0353 res.mipgap = abs(primal-scipStats.DualBound)/abs(primal); -0354 end -0355 end -0356 end -0357 otherwise -0358 error('RAVEN solver not defined or unknown. Try using setRavenSolver(''solver'').'); -0359 end -0360 %Flag a solve that ended on the time limit. res.stat cannot express this on -0361 %its own: a solver that times out with an incumbent solution in hand reports -0362 %the same status as one that finished, and a solver that times out with -0363 %nothing reports the same status as an infeasible problem. The raw status in -0364 %res.origStat does distinguish the two, but only in solver-specific terms, -0365 %so it is translated here where the solver is still known. -0366 res.hitTimeLimit=hitTimeLimit(solver,res,milp); -0367 if res.stat>0 -0368 res.full=res.full(1:size(prob.a,2)); -0369 end -0370 %Strip the auxiliary rows/columns that were added for conditioning, so that -0371 %the result matches the dimensions of the original problem. res.full is -0372 %already truncated to the original variables above (prob.a is left untouched). -0373 if condApplied -0374 if isfield(res,'dual') && numel(res.dual)>condInfo.nOrigRows -0375 res.dual=res.dual(1:condInfo.nOrigRows); -0376 end -0377 if isfield(res,'rcost') && numel(res.rcost)>condInfo.nOrigCols -0378 res.rcost=res.rcost(1:condInfo.nOrigCols); -0379 end -0380 end -0381 end -0382 -0383 function s_merged=structUpdate(s_old,s_new) -0384 %Remove overlapping fields from first struct; -0385 %Obtain all unique names of remaining fields; -0386 %Merge both structs -0387 s_merged = rmfield(s_old, intersect(fieldnames(s_old), fieldnames(s_new))); -0388 names = [fieldnames(s_merged); fieldnames(s_new)]; -0389 s_merged = cell2struct([struct2cell(s_merged); struct2cell(s_new)], names, 1); -0390 end -0391 -0392 function timeLimit = hitTimeLimit(solver,res,milp) -0393 %Report whether the solver that produced res stopped on its time limit. Each -0394 %solver has its own status codes for this, and the "cobra" option hands the -0395 %status of the solver that COBRA dispatched to straight through, so that -0396 %solver is looked up as well. -0397 global CBT_LP_SOLVER CBT_MILP_SOLVER -0398 timeLimit=false; -0399 if ~isfield(res,'origStat') -0400 return; -0401 end -0402 if strcmp(solver,'cobra') -0403 if milp -0404 solver=CBT_MILP_SOLVER; -0405 else -0406 solver=CBT_LP_SOLVER; -0407 end -0408 if isempty(solver) || ~ischar(solver) -0409 return; -0410 end -0411 end -0412 origStat=res.origStat; -0413 isText=ischar(origStat) || isstring(origStat); -0414 isCode=isnumeric(origStat) && isscalar(origStat); -0415 switch lower(solver) -0416 case 'gurobi' -0417 timeLimit=isText && strcmp(origStat,'TIME_LIMIT'); -0418 case 'mosek' -0419 timeLimit=isText && strcmp(origStat,'MSK_RES_TRM_MAX_TIME'); -0420 case 'glpk' -0421 %109 from the simplex method, 209 from the interior point method -0422 timeLimit=isCode && ismember(origStat,[109 209]); -0423 case {'scip','soplex'} -0424 timeLimit=isCode && origStat==5; -0425 case {'ibm_cplex','tomlab_cplex','cplex_direct'} -0426 %11 for an LP, 107 and 108 for a MILP with and without an integer -0427 %solution found before the limit was reached -0428 timeLimit=isCode && ismember(origStat,[11 107 108]); -0429 end -0430 end -0431 -0432 function paramlist = renameparams(paramlist,old,new) -0433 if ~iscell(paramlist) -0434 wasNoCell = true; -0435 paramlist={paramlist}; -0436 else -0437 wasNoCell = false; -0438 end -0439 for i=1:numel(old) -0440 paramlist = regexprep(paramlist,old{i},new{i}); -0441 end -0442 if wasNoCell -0443 paramlist=paramlist{1}; -0444 end -0445 end +0233 switch errnum % 1 = undefined; 2 = feasible; 3 = infeasible; 4 = no feasible solution; 5 = optimal; 6 = no unbounded solution +0234 case 5 +0235 res.stat = 1; % Optimal +0236 case 2 +0237 res.stat = 2; % Feasible, but not optimal +0238 otherwise +0239 res.stat = 0; +0240 end +0241 res.origStat = errnum; +0242 res.full = xopt; +0243 res.obj = fmin; +0244 res.dual = -extra.lambda*prob.osense; +0245 res.rcost = -extra.redcosts*prob.osense; +0246 if milp +0247 % glpk reports no gap; a proven optimum has none, anything else +0248 % is not accepted by callers that test the gap +0249 if res.stat == 1 +0250 res.mipgap = 0; +0251 else +0252 res.mipgap = inf; +0253 end +0254 end +0255 %% Use scip +0256 case {'soplex','scip'} % Old 'soplex' option also allowed +0257 % scip takes a row range [rl,ru] rather than a right-hand side plus +0258 % a sense, so csense has to be translated into that range. Passing +0259 % [-b,b] is only the same problem when every row is an equality with +0260 % b = 0, which is the case for solveLP but for none of the MILPs +0261 % built elsewhere in RAVEN. +0262 rl = full(prob.b(:)); +0263 ru = rl; +0264 csense = prob.csense(:); +0265 rl(csense=='L') = -inf; % A*x <= b +0266 ru(csense=='G') = inf; % A*x >= b +0267 [xopt,fval,exitflag,scipStats] = scip([], prob.c, prob.A, rl, ru, prob.lb, prob.ub, prob.vartype); +0268 +0269 % [x,fval,exitflag,stats] = scip(H, f, A, rl, ru, lb, ub, xtype, sos, qc, nl, x0, opts) +0270 % +0271 % Input arguments*: +0272 % H - quadratic objective matrix (sparse, optional [NOT TRIL / TRIU]) +0273 % f - linear objective vector +0274 % A - linear constraint matrix (sparse) +0275 % rl - linear constraint lhs +0276 % ru - linear constraint rhs +0277 % lb - decision variable lower bounds +0278 % ub - decision variable upper bounds +0279 % xtype - string of variable integrality ("c" continuous, "i" integer, "b" binary) +0280 % sos - SOS structure with fields type, index and weight (see below) +0281 % qc - Quadratic Constraints structure with fields Q, l, qrl and qru (see below) +0282 % nl - Nonlinear Objective and Constraints structure (see below) +0283 % x0 - primal solution +0284 % opts - solver options (see below) +0285 % +0286 % Return arguments: +0287 % x - solution vector +0288 % fval - objective value at the solution +0289 % exitflag - exit status (see below) +0290 % stats - statistics structure +0291 % +0292 % Option Fields (all optional, see also optiset for a list): +0293 % solverOpts - specific SCIP options (list of pairs of parameter names and values) +0294 % maxiter - maximum LP solver iterations +0295 % maxnodes - maximum nodes to explore +0296 % maxtime - maximum execution time [s] +0297 % tolrfun - primal feasibility tolerance +0298 % display - solver display level [0-5] +0299 % probfile - write problem to given file +0300 % presolvedfile - write presolved problem to file +0301 % +0302 % Return Status, as returned by the MatlabSCIPInterface build that +0303 % RAVEN provides and that setRavenSolver points at +0304 % (github.com/scipopt/MatlabSCIPInterface). Verified against that +0305 % binary with a bounded, an infeasible and an unbounded problem: +0306 % 1 - Problem Solved to Optimality +0307 % 2 - Problem is Infeasible +0308 % 3 - Problem is Unbounded +0309 % +0310 % The OPTI Toolbox build of scip uses a different, longer set in +0311 % which optimality is 11, infeasibility 12 and unboundedness 13, +0312 % with the lower numbers meaning "a limit was reached". Those codes +0313 % are accepted below as well, so either build reports optimality +0314 % correctly; only OPTI's 1 ("User Interrupted") is ambiguous, and +0315 % reading it as optimal is no worse than the previous mapping, +0316 % which recognised no status from the shipped build at all. +0317 +0318 res.origStat = exitflag; +0319 res.full = xopt; +0320 res.obj = fval; +0321 +0322 switch exitflag +0323 case {1, 11} % solved to optimality +0324 res.stat = 1; +0325 case {3, 13} % unbounded +0326 res.stat = 2; +0327 case {5, 6, 7, 8, 9, 10} % OPTI: a limit was reached, so a +0328 res.stat = 2; % solution may exist but is not proven optimal +0329 otherwise % 2/12 infeasible, 0 unknown, 4, 14 +0330 res.stat = 0; +0331 end +0332 +0333 if milp +0334 % Callers such as ftINIT compare this against their own +0335 % params.MIPGap, so it has to be the quantity the gurobi branch +0336 % reports there: a relative gap. It is derived from the bounds +0337 % rather than read from scipStats.BBgap, whose units are not +0338 % documented. inf where no gap can be established, so that a +0339 % caller testing "gap small enough" does not accept the solution. +0340 if res.stat == 1 +0341 res.mipgap = 0; +0342 else +0343 res.mipgap = inf; +0344 primal = scipStats.PrimalBound; +0345 if isfinite(primal) && abs(primal) > 1e-10 +0346 res.mipgap = abs(primal-scipStats.DualBound)/abs(primal); +0347 end +0348 end +0349 end +0350 otherwise +0351 error('RAVEN solver not defined or unknown. Try using setRavenSolver(''solver'').'); +0352 end +0353 %Flag a solve that ended on the time limit. res.stat cannot express this on +0354 %its own: a solver that times out with an incumbent solution in hand reports +0355 %the same status as one that finished, and a solver that times out with +0356 %nothing reports the same status as an infeasible problem. The raw status in +0357 %res.origStat does distinguish the two, but only in solver-specific terms, +0358 %so it is translated here where the solver is still known. +0359 res.hitTimeLimit=hitTimeLimit(solver,res,milp); +0360 if res.stat>0 +0361 res.full=res.full(1:size(prob.a,2)); +0362 end +0363 %Strip the auxiliary rows/columns that were added for conditioning, so that +0364 %the result matches the dimensions of the original problem. res.full is +0365 %already truncated to the original variables above (prob.a is left untouched). +0366 if condApplied +0367 if isfield(res,'dual') && numel(res.dual)>condInfo.nOrigRows +0368 res.dual=res.dual(1:condInfo.nOrigRows); +0369 end +0370 if isfield(res,'rcost') && numel(res.rcost)>condInfo.nOrigCols +0371 res.rcost=res.rcost(1:condInfo.nOrigCols); +0372 end +0373 end +0374 end +0375 +0376 function s_merged=structUpdate(s_old,s_new) +0377 %Remove overlapping fields from first struct; +0378 %Obtain all unique names of remaining fields; +0379 %Merge both structs +0380 s_merged = rmfield(s_old, intersect(fieldnames(s_old), fieldnames(s_new))); +0381 names = [fieldnames(s_merged); fieldnames(s_new)]; +0382 s_merged = cell2struct([struct2cell(s_merged); struct2cell(s_new)], names, 1); +0383 end +0384 +0385 function timeLimit = hitTimeLimit(solver,res,milp) +0386 %Report whether the solver that produced res stopped on its time limit. Each +0387 %solver has its own status codes for this, and the "cobra" option hands the +0388 %status of the solver that COBRA dispatched to straight through, so that +0389 %solver is looked up as well. +0390 global CBT_LP_SOLVER CBT_MILP_SOLVER +0391 timeLimit=false; +0392 if ~isfield(res,'origStat') +0393 return; +0394 end +0395 if strcmp(solver,'cobra') +0396 if milp +0397 solver=CBT_MILP_SOLVER; +0398 else +0399 solver=CBT_LP_SOLVER; +0400 end +0401 if isempty(solver) || ~ischar(solver) +0402 return; +0403 end +0404 end +0405 origStat=res.origStat; +0406 isText=ischar(origStat) || isstring(origStat); +0407 isCode=isnumeric(origStat) && isscalar(origStat); +0408 switch lower(solver) +0409 case 'gurobi' +0410 timeLimit=isText && strcmp(origStat,'TIME_LIMIT'); +0411 case 'mosek' +0412 timeLimit=isText && strcmp(origStat,'MSK_RES_TRM_MAX_TIME'); +0413 case 'glpk' +0414 %109 from the simplex method, 209 from the interior point method +0415 timeLimit=isCode && ismember(origStat,[109 209]); +0416 case {'scip','soplex'} +0417 timeLimit=isCode && origStat==5; +0418 case {'ibm_cplex','tomlab_cplex','cplex_direct'} +0419 %11 for an LP, 107 and 108 for a MILP with and without an integer +0420 %solution found before the limit was reached +0421 timeLimit=isCode && ismember(origStat,[11 107 108]); +0422 end +0423 end +0424 +0425 function paramlist = renameparams(paramlist,old,new) +0426 if ~iscell(paramlist) +0427 wasNoCell = true; +0428 paramlist={paramlist}; +0429 else +0430 wasNoCell = false; +0431 end +0432 for i=1:numel(old) +0433 paramlist = regexprep(paramlist,old{i},new{i}); +0434 end +0435 if wasNoCell +0436 paramlist=paramlist{1}; +0437 end +0438 end
    Generated by m2html © 2005
    \ No newline at end of file From beadbc004a30c5e1a43d062ea6bb4ce3878f207d Mon Sep 17 00:00:00 2001 From: Eduard Kerkhoven Date: Thu, 22 Oct 2026 00:33:58 +0200 Subject: [PATCH 29/48] fix: quote YAML 1.1 booleans, fix replaceMets, and align task lists, gene pruning and YAML floats with raven-toolbox (#11) * fix: quote YAML 1.1 booleans such as NO in writeYAMLmodel needsQuote now quotes y, n, yes, no, on and off in any case, which YAML 1.1 loaders such as PyYAML read as booleans. This matches the quoting of write_yaml_model in raven-toolbox (CompMetEng/raven-toolbox#178). #10 * fix: removeLowScoreGenes keeps the first gene in the rule on a tie * fix: parseTaskList reads false/0 flags as false, trims ; lists, ignores header case, keeps empty CHANGED bounds as NaN * feat: checkTasks warns when a task metabolite matches several model metabolites * test: use a duplicate that leaves the task feasible * fix: writeYAMLmodel writes non-integers with the shortest digits that read back exactly * test: keep lb below ub in the float round-trip test * fix: restrict the contractModel call in replaceMets to the metabolite that is left By name, replaceMets deletes the replacement's row and keeps the renamed replaced metabolite. The ids it passed to the final contractModel call (repMetIds) were captured from the replacement only, so after the deletion none of them was found, repIdx was empty and contractModel(model,'mets',[]) checked every reaction. Duplicate reactions that do not involve the merged metabolite were merged as well. repMetIds now also holds the id of the replaced metabolite, so the restriction follows whichever of the two rows survives, and an empty list no longer reaches contractModel. Tests: unrelated duplicate reactions survive a merge by name and by id, and the new duplicate is still contracted. --- src/gapfilling/fitTasks.m | 11 ++-- src/io/writeYAMLmodel.m | 15 ++++- src/manipulation/removeLowScoreGenes.m | 12 ++-- src/manipulation/replaceMets.m | 13 ++++- src/tasks/checkTasks.m | 31 +++++++++- src/tasks/parseTaskList.m | 79 ++++++++++++++++++++------ tests/function_tests/tINIT.m | 23 ++++++++ tests/function_tests/tIO.m | 48 ++++++++++++++++ tests/function_tests/tManipulation.m | 52 +++++++++++++++++ tests/function_tests/tTasks.m | 74 ++++++++++++++++++++++++ 10 files changed, 324 insertions(+), 34 deletions(-) diff --git a/src/gapfilling/fitTasks.m b/src/gapfilling/fitTasks.m index 78868d16..d311cbbd 100755 --- a/src/gapfilling/fitTasks.m +++ b/src/gapfilling/fitTasks.m @@ -262,11 +262,14 @@ end %Add changed bounds if ~isempty(taskStructure(i).changed) - tModel=setParam(tModel,'lb',taskStructure(i).changed,taskStructure(i).LBrxn); - tModel=setParam(tModel,'ub',taskStructure(i).changed,taskStructure(i).UBrxn); + %NaN bounds mean "leave unchanged" + lbSet=~isnan(taskStructure(i).LBrxn); + ubSet=~isnan(taskStructure(i).UBrxn); + tModel=setParam(tModel,'lb',taskStructure(i).changed(lbSet),taskStructure(i).LBrxn(lbSet)); + tModel=setParam(tModel,'ub',taskStructure(i).changed(ubSet),taskStructure(i).UBrxn(ubSet)); if preMerged - tRefModel=setParam(tRefModel,'lb',taskStructure(i).changed,taskStructure(i).LBrxn); - tRefModel=setParam(tRefModel,'ub',taskStructure(i).changed,taskStructure(i).UBrxn); + tRefModel=setParam(tRefModel,'lb',taskStructure(i).changed(lbSet),taskStructure(i).LBrxn(lbSet)); + tRefModel=setParam(tRefModel,'ub',taskStructure(i).changed(ubSet),taskStructure(i).UBrxn(ubSet)); end end diff --git a/src/io/writeYAMLmodel.m b/src/io/writeYAMLmodel.m index 28879551..b9afd250 100755 --- a/src/io/writeYAMLmodel.m +++ b/src/io/writeYAMLmodel.m @@ -382,7 +382,15 @@ function emitMetaField(fid, key, value) if x == floor(x) && abs(x) < 1e16 s = sprintf('%.1f', x); % e.g. 1000 -> "1000.0" else - s = sprintf('%.15g', x); + % Shortest text that reads back as exactly x (15 to 17 significant + % digits), as Python's repr does, so 1/3 is written 0.3333333333333333 + % and a value is never rounded on the way out. + for digits = 15:17 + s = sprintf(['%.' num2str(digits) 'g'], x); + if str2double(s) == x + break + end + end end end @@ -549,7 +557,9 @@ function emitScalarCore(fid, ~, value, ~, bareNumeric) % because the text itself is not valid as a plain block scalar (leading % indicator character, a mid-string ": "/" #", leading/trailing % whitespace, or embedded control characters). Implements the YAML 1.2 -% rules for resolving implicit scalar types and analyzing scalar style. +% rules for resolving implicit scalar types and analyzing scalar style, +% and also quotes the YAML 1.1 booleans y, n, yes, no, on and off in any +% case, which YAML 1.1 loaders such as PyYAML read as booleans. if isempty(s) tf = true; return @@ -557,6 +567,7 @@ function emitScalarCore(fid, ~, value, ~, bareNumeric) resolverPattern = [ ... '^(true|True|TRUE|false|False|FALSE)$|' ... + '^([Yy]|[Nn]|[Yy][Ee][Ss]|[Nn][Oo]|[Oo][Nn]|[Oo][Ff][Ff])$|' ... '^[-+]?[0-9][0-9_]*\.[0-9_]*([eE][-+]?[0-9]+)?$|' ... '^[-+]?[0-9][0-9_]*[eE][-+]?[0-9]+$|' ... '^[-+]?\.[0-9_]+([eE][-+][0-9]+)?$|' ... diff --git a/src/manipulation/removeLowScoreGenes.m b/src/manipulation/removeLowScoreGenes.m index df2378d8..fb9b8c3d 100644 --- a/src/manipulation/removeLowScoreGenes.m +++ b/src/manipulation/removeLowScoreGenes.m @@ -19,8 +19,8 @@ % score will try to be removed. % % If all genes associated with a reaction have a negative score, then the -% least-negative gene will remain; if there is a tie, one will be selected -% at random. +% least-negative gene will remain; if there is a tie, the gene listed first +% in the rule will be kept. % % If a negative-scoring gene is a subunit in a complex, it will not be % removed; however, the entire complex may be removed. See the following @@ -149,7 +149,7 @@ % get IDs and indices of genes involved in rule -ruleGenes = unique(regexp(rule,'[^&|\(\) ]+','match')); +ruleGenes = unique(regexp(rule,'[^&|\(\) ]+','match'),'stable'); [~,geneInd] = ismember(ruleGenes,genes); % rules with one or no genes remain unchanged @@ -164,8 +164,8 @@ scoreMethod = isozymeScoring; negInd = gScores(geneInd) < 0; % NaNs will return false here if all(negInd) - % deterministic least-negative: sort descending; stable sort breaks - % ties by original gene order (left-to-right in the rule string) + % deterministic least-negative: sort descending; the stable sort + % breaks ties by the order of the genes in the rule string [~,sortOrder] = sort(gScores(geneInd),'descend'); maxInd = sortOrder(1); updatedRule = ruleGenes{maxInd}; @@ -179,7 +179,7 @@ end % update ruleGenes and their indices - ruleGenes = unique(regexp(updatedRule,'[^&|\(\) ]+','match')); + ruleGenes = unique(regexp(updatedRule,'[^&|\(\) ]+','match'),'stable'); [~,geneInd] = ismember(ruleGenes,genes); elseif ~contains(rule,'|') % rule contains enzyme complex diff --git a/src/manipulation/replaceMets.m b/src/manipulation/replaceMets.m index bfc31a9f..bcba7d0f 100755 --- a/src/manipulation/replaceMets.m +++ b/src/manipulation/replaceMets.m @@ -79,6 +79,12 @@ error('The to-be-replaced metabolite cannot be found in the model.'); end +% Either of the two rows can be the one that survives the merge: with identifiers the +% replacement stays, by name the replaced metabolite is renamed and stays while the +% replacement's row is deleted below. Both ids are kept so that contractModel is +% restricted to the metabolite that is left. +repMetIds = [repMetIds; model.mets(metIdx)]; + rxnsWithMet = find(model.S(metIdx,:)); if verbose==true fprintf('\n\nThe following reactions contain the to-be-replaced metabolite as reactant:\n') @@ -173,10 +179,13 @@ % This could now have created duplicate reactions. Contract model. % repIdx may point past the end, or at the wrong row, once idxDelete has -% shifted things above; re-resolve the replacement metabolite by id. +% shifted things above; re-resolve the metabolite that is left by id. An empty +% list must not reach contractModel, which would then check every reaction. % distReverse is passed by name rather than positionally as [], which % parseRAVENargs would take literally instead of falling back to its % documented default of true. repIdx = find(ismember(model.mets, repMetIds)); -model=contractModel(model,'mets',repIdx); +if ~isempty(repIdx) + model=contractModel(model,'mets',repIdx); +end end diff --git a/src/tasks/checkTasks.m b/src/tasks/checkTasks.m index e9e309bc..778f4eff 100755 --- a/src/tasks/checkTasks.m +++ b/src/tasks/checkTasks.m @@ -112,6 +112,13 @@ if ~isempty(taskStructure(i).inputs) [I, J]=ismember(upper(taskStructure(i).inputs),modelMets); J=J(I); %Only keep the ones with matches + %Several model metabolites can share a name[comp]. Only the first + %one is constrained, so tell the user when that happens + dupMets=duplicateMetNames(taskStructure(i).inputs(I),modelMets); + if ~isempty(dupMets) + EM=['Several model metabolites match input(s) in "[' taskStructure(i).id '] ' taskStructure(i).description '": ' strjoin(dupMets,', ') '. Only the first match is constrained']; + warning('RAVEN:warning', '%s', EM); + end K=ismember(upper(taskStructure(i).inputs),'ALLMETS'); L=~cellfun('isempty',strfind(upper(taskStructure(i).inputs),'ALLMETSIN')); %Check that all metabolites are either real metabolites or @@ -165,6 +172,13 @@ if ~isempty(taskStructure(i).outputs) [I, J]=ismember(upper(taskStructure(i).outputs),modelMets); J=J(I); %Only keep the ones with matches + %Several model metabolites can share a name[comp]. Only the first + %one is constrained, so tell the user when that happens + dupMets=duplicateMetNames(taskStructure(i).outputs(I),modelMets); + if ~isempty(dupMets) + EM=['Several model metabolites match output(s) in "[' taskStructure(i).id '] ' taskStructure(i).description '": ' strjoin(dupMets,', ') '. Only the first match is constrained']; + warning('RAVEN:warning', '%s', EM); + end K=ismember(upper(taskStructure(i).outputs),'ALLMETS'); L=~cellfun('isempty',strfind(upper(taskStructure(i).outputs),'ALLMETSIN')); %Check that all metabolites are either real metabolites or @@ -244,8 +258,11 @@ end %Add changed bounds if ~isempty(taskStructure(i).changed) - tModel=setParam(tModel,'lb',taskStructure(i).changed,taskStructure(i).LBrxn); - tModel=setParam(tModel,'ub',taskStructure(i).changed,taskStructure(i).UBrxn); + %NaN bounds mean "leave unchanged" + lbSet=~isnan(taskStructure(i).LBrxn); + ubSet=~isnan(taskStructure(i).UBrxn); + tModel=setParam(tModel,'lb',taskStructure(i).changed(lbSet),taskStructure(i).LBrxn(lbSet)); + tModel=setParam(tModel,'ub',taskStructure(i).changed(ubSet),taskStructure(i).UBrxn(ubSet)); end %Solve and print @@ -304,3 +321,13 @@ taskReport.ok=ok; end + +function dupMets = duplicateMetNames(names,modelMets) +% Return the (upper-case) names that match more than one model metabolite. +dupMets = unique(upper(names)); +isDup = false(size(dupMets)); +for k = 1:numel(dupMets) + isDup(k) = sum(strcmp(modelMets,dupMets{k})) > 1; +end +dupMets = dupMets(isDup); +end diff --git a/src/tasks/parseTaskList.m b/src/tasks/parseTaskList.m index 7a177db9..0c946d51 100755 --- a/src/tasks/parseTaskList.m +++ b/src/tasks/parseTaskList.m @@ -41,14 +41,20 @@ % Several IDs can be delimited by ";". If so, then the same bounds % are used for all reactions. If that is not wanted, then use several % rows for the task -% - CHANGED LB : lower bound for the reaction -% - CHANGED UB : upper bound for the reaction +% - CHANGED LB : lower bound for the reaction. If empty, the lower bound +% of the reaction is left as it is +% - CHANGED UB : upper bound for the reaction. If empty, the upper bound +% of the reaction is left as it is % - SHOULD FAIL : true if the correct behavior of the model is to not -% have a feasible solution given the constraints (optional, default +% have a feasible solution given the constraints. Any non-empty value +% except 0, false and no (any case) counts as true (optional, default % false) % - PRINT FLUX : true if the function should print the corresponding -% flux distribution for a task. Can be useful for testing (optional, -% default false) +% flux distribution for a task. Can be useful for testing. Read in +% the same way as SHOULD FAIL (optional, default false) +% +% Headers are matched ignoring case and surrounding whitespace. In the +% ";"-delimited cells, items are trimmed and empty items are dropped. % % Returns % ------- @@ -71,8 +77,10 @@ % reversible and 0 for irreversible) % - UBequ : array with upper bounds on equations (default, 1000) % - changed : cell array with reactions to change bounds for -% - LBrxn : array with lower bounds on changed reactions -% - UBrxn : array with upper bounds on changed reactions +% - LBrxn : array with lower bounds on changed reactions (NaN if empty, +% meaning the bound is left unchanged) +% - UBrxn : array with upper bounds on changed reactions (NaN if empty, +% meaning the bound is left unchanged) % % Examples % -------- @@ -136,8 +144,12 @@ %Captions columns={'ID';'DESCRIPTION';'IN';'IN LB';'IN UB';'OUT';'OUT LB';'OUT UB';'EQU';'EQU LB';'EQU UB';'CHANGED RXN';'CHANGED LB';'CHANGED UB';'SHOULD FAIL';'PRINT FLUX';'COMMENTS'}; -%Match the columns -[I, colI]=ismember(columns,raw(1,:)); +%Match the columns. Headers are matched ignoring case and surrounding +%whitespace, so "id" and " IN LB " are accepted. +header=raw(1,:); +isText=cellfun(@ischar,header); +header(isText)=upper(strtrim(header(isText))); +[I, colI]=ismember(columns,header); %Only ID is required. Columns that are absent get index 0 from ismember, which %cannot be used to index raw, so point them at a spare empty column instead: @@ -204,10 +216,10 @@ task.id=raw{2,colI(1)}; end task.description=raw{2,colI(2)}; -if ~isempty(raw{2,colI(15)}) +if isTruthy(raw{2,colI(15)}) task.shouldFail=true; end -if ~isempty(raw{2,colI(16)}) +if isTruthy(raw{2,colI(16)}) task.printFluxes=true; end if ~isempty(raw{2,colI(17)}) @@ -217,14 +229,14 @@ for i=2:size(raw,1) %Set the inputs if ischar(raw{i,colI(3)}) - inputs=regexp(raw{i,colI(3)},';','split'); + inputs=splitList(raw{i,colI(3)}); task.inputs=[task.inputs;inputs(:)]; task.LBin=[task.LBin;ones(numel(inputs),1)*raw{i,colI(4)}]; task.UBin=[task.UBin;ones(numel(inputs),1)*raw{i,colI(5)}]; end %Set the outputs if ischar(raw{i,colI(6)}) - outputs=regexp(raw{i,colI(6)},';','split'); + outputs=splitList(raw{i,colI(6)}); task.outputs=[task.outputs;outputs(:)]; task.LBout=[task.LBout;ones(numel(outputs),1)*raw{i,colI(7)}]; task.UBout=[task.UBout;ones(numel(outputs),1)*raw{i,colI(8)}]; @@ -249,10 +261,11 @@ end %Add changed bounds if ischar(raw{i,colI(12)}) - changed=regexp(raw{i,colI(12)},';','split'); + changed=splitList(raw{i,colI(12)}); task.changed=[task.changed;changed(:)]; - task.LBrxn=[task.LBrxn;ones(numel(changed),1)*raw{i,colI(13)}]; - task.UBrxn=[task.UBrxn;ones(numel(changed),1)*raw{i,colI(14)}]; + %An empty bound means "leave this bound as it is", stored as NaN + task.LBrxn=[task.LBrxn;ones(numel(changed),1)*boundOrNaN(raw{i,colI(13)})]; + task.UBrxn=[task.UBrxn;ones(numel(changed),1)*boundOrNaN(raw{i,colI(14)})]; end %Check if it should add more constraints @@ -271,10 +284,10 @@ task.id=raw{i+1,colI(1)}; end task.description=raw{i+1,colI(2)}; - if ~isempty(raw{i+1,colI(15)}) + if isTruthy(raw{i+1,colI(15)}) task.shouldFail=true; end - if ~isempty(raw{i+1,colI(16)}) + if isTruthy(raw{i+1,colI(16)}) task.printFluxes=true; end if ~isempty(raw{i+1,colI(17)}) @@ -286,3 +299,33 @@ %Should add more checks, such as unique IDs and missing headers end + +function tf = isTruthy(x) +% A flag cell is true unless it is empty, 0, or one of the texts "0", "false" +% and "no" (any case). +if isempty(x) + tf = false; +elseif ischar(x) || isstring(x) + tf = ~ismember(lower(strtrim(char(x))),{'','0','false','no'}); +elseif isnumeric(x) || islogical(x) + tf = any(x(:) ~= 0); +else + tf = true; +end +end + +function items = splitList(str) +% Split a ";"-delimited cell on ";", trim each item and drop empty ones, so +% that "A; B;" gives {'A';'B'}. +items = strtrim(regexp(str,';','split')); +items = items(~cellfun(@isempty,items)); +end + +function b = boundOrNaN(x) +% Bound for a changed reaction: NaN (leave unchanged) when the cell is empty. +if isempty(x) + b = NaN; +else + b = x; +end +end diff --git a/tests/function_tests/tINIT.m b/tests/function_tests/tINIT.m index 56214098..c7bd276a 100644 --- a/tests/function_tests/tINIT.m +++ b/tests/function_tests/tINIT.m @@ -62,6 +62,29 @@ function removeLowScoreGenesKeepsFieldsAligned(testCase) testCase.verifyEqual(full(mm.rxnGeneMat), ones(1, numel(mm.genes))); end + function removeLowScoreGenesTieKeepsFirstGeneInRule(testCase) + % With every isozyme negative and tied, the gene listed first in + % the rule is kept (not the alphabetically first one), and the + % order of the surviving genes follows the rule. + m = struct(); + m.id = 'test'; + m.rxns = {'R1';'R2'}; m.rxnNames = {'R1';'R2'}; + m.mets = {'A';'B'}; m.metNames = {'A';'B'}; m.metComps = [1;1]; + m.comps = {'c'}; m.compNames = {'c'}; + m.S = sparse([-1 -1;1 1]); m.lb = [0;0]; m.ub = [1000;1000]; + m.rev = [0;0]; m.c = [0;0]; m.b = [0;0]; + m.genes = {'G1';'G2';'G3';'G4'}; + m.grRules = {'G2 or G1';'G4 or G3 or G2'}; + m.rxnGeneMat = sparse([1 1 0 0;0 1 1 1]); + evalc('[mm, removed] = removeLowScoreGenes(m, [-1;-1;1;-1]);'); + testCase.verifyEqual(mm.grRules{1}, 'G2'); + testCase.verifyEqual(mm.grRules{2}, 'G3'); + testCase.verifyEqual(sort(removed), {'G1';'G4'}); + + evalc('mm = removeLowScoreGenes(m, [1;1;1;1]);'); + testCase.verifyEqual(mm.grRules, m.grRules); + end + function reverseRxnsRuns(testCase) % R1 = '=> a[s]'; R3 = 'a[c] <=> b[c] + c[c]' testModel = getTstModel(); diff --git a/tests/function_tests/tIO.m b/tests/function_tests/tIO.m index 86a06ae6..30f0ff6a 100644 --- a/tests/function_tests/tIO.m +++ b/tests/function_tests/tIO.m @@ -207,6 +207,54 @@ function writeReadYAMLRoundTrip(testCase) testCase.verifyEqual(numel(m2.rxns), numel(testCase.model.rxns)); end + function writeYAMLmodelQuotesYAML11Booleans(testCase) + % A YAML 1.1 loader such as PyYAML reads a plain y, n, yes, no, + % on or off in any case as a boolean, so these values are + % double-quoted. Longer words that start with them stay plain. + m = testCase.model; + quoted = {'NO'; 'Y'; 'n'; 'on'; 'yes'; 'off'}; + plain = {'NOS1'; 'onion'; 'off-target'}; + m.metNames(1:6) = quoted; + m.metFormulas(1:6) = quoted; + m.metNames(7:9) = plain; + f = [tempname '.yml']; + testCase.addTeardown(@() delete(f)); + evalc('writeYAMLmodel(m, f);'); + lines = splitlines(fileread(f)); + for i = 1:numel(quoted) + testCase.verifyTrue(any(strcmp(lines, [' - name: "' quoted{i} '"'])), quoted{i}); + testCase.verifyTrue(any(strcmp(lines, [' - formula: "' quoted{i} '"'])), quoted{i}); + end + for i = 1:numel(plain) + testCase.verifyTrue(any(strcmp(lines, [' - name: ' plain{i}])), plain{i}); + end + evalc('m2 = readYAMLmodel(f);'); + testCase.verifyEqual(m2.metNames(1:9), m.metNames(1:9)); + testCase.verifyEqual(m2.metFormulas(1:6), quoted); + end + + function writeYAMLmodelWritesShortestRoundTripFloats(testCase) + % Non-integers are written with as many digits as needed to read + % back exactly (as Python does), not cut at 15 digits. + m = testCase.model; + m.lb(1) = 0.1 + 0.2; + m.ub(1) = 1/3; + m.lb(2) = 1.5e-7; + m.ub(2) = 2.5; + f = [tempname '.yml']; + testCase.addTeardown(@() delete(f)); + evalc('writeYAMLmodel(m, f);'); + txt = fileread(f); + testCase.verifySubstring(txt, 'upper_bound: 0.3333333333333333'); + testCase.verifySubstring(txt, 'lower_bound: 0.30000000000000004'); + testCase.verifySubstring(txt, 'lower_bound: 1.5e-07'); + testCase.verifySubstring(txt, 'upper_bound: 2.5'); + evalc('m2 = readYAMLmodel(f);'); + testCase.verifyEqual(m2.lb(1), m.lb(1)); + testCase.verifyEqual(m2.ub(1), m.ub(1)); + testCase.verifyEqual(m2.lb(2), m.lb(2)); + end + function readYAMLmodelJoinsContinuationStartingWithDash(testCase) % A writer that wraps long scalars can break a name right % before a dash, so the continuation line starts with "-" diff --git a/tests/function_tests/tManipulation.m b/tests/function_tests/tManipulation.m index 67ec0bcd..77e57dfe 100644 --- a/tests/function_tests/tManipulation.m +++ b/tests/function_tests/tManipulation.m @@ -608,6 +608,39 @@ function replaceMetsByNameKeepsBShapeAndRuns(testCase) testCase.verifyEqual(numel(m2.mets), 2); % oxygen and o2 merged end + function replaceMetsByNameLeavesUnrelatedDuplicatesAlone(testCase) + % Merging "oxygen" into "o2" makes r1 a duplicate of r2, which is + % contracted. unrelated1 and unrelated2 are duplicates of each + % other that do not involve either metabolite; contractModel is + % restricted to the reactions of the merged metabolite, so they + % must stay. (By name the replacement's row is the one deleted, + % so the restriction has to follow the metabolite that is left.) + m = tManipulation.duplicateModel(); + evalc('m2 = replaceMets(m, ''oxygen'', ''o2'');'); + testCase.verifyEqual(numel(m2.mets), 3); + testCase.verifyTrue(all(ismember({'unrelated1';'unrelated2'}, m2.rxns))); + testCase.verifyEqual(sum(ismember({'r1';'r2'}, m2.rxns)), 1); + testCase.verifyEqual(numel(m2.rxns), 3); + end + + function replaceMetsByIdLeavesUnrelatedDuplicatesAlone(testCase) + m = tManipulation.duplicateModel(); + evalc('m2 = replaceMets(m, ''a'', ''b'', ''identifiers'', true);'); + testCase.verifyEqual(numel(m2.mets), 3); + testCase.verifyTrue(all(ismember({'unrelated1';'unrelated2'}, m2.rxns))); + testCase.verifyEqual(sum(ismember({'r1';'r2'}, m2.rxns)), 1); + end + + function replaceMetsByNameStillContractsTheNewDuplicate(testCase) + % The restriction must not switch the contraction off: r1 and + % r2 become identical once the two metabolites are one. + m = tManipulation.duplicateModel(); + evalc('m2 = replaceMets(m, ''oxygen'', ''o2'');'); + testCase.verifyLessThan(numel(m2.rxns), numel(m.rxns)); + rowO2 = find(strcmp(m2.metNames, 'o2')); + testCase.verifyEqual(numel(rowO2), 1); + end + function setExchangeBoundsFindsAllMultiExchangeMets(testCase) % Every metabolite exchanged by more than one reaction must be % reported, not just whichever one happens to line up between @@ -897,6 +930,25 @@ function expandModelLeavesDnfAlone(testCase) methods (Static, Access = private) + function m = duplicateModel() + % a and b are named oxygen and o2; r1 (a => x) and r2 (b => x) become + % duplicates once they are one metabolite. unrelated1 and unrelated2 + % (both d =>) are duplicates of each other from the start. + m = struct(); + m.id = 'dup'; + m.rxns = {'r1';'r2';'unrelated1';'unrelated2'}; + m.rxnNames = m.rxns; + m.mets = {'a';'b';'x';'d'}; + m.metNames = {'oxygen';'o2';'x';'d'}; + m.metComps = [1;1;1;1]; + m.comps = {'c'}; + m.compNames = {'cytosol'}; + m.S = sparse([-1 0 0 0; 0 -1 0 0; 1 1 0 0; 0 0 -1 -1]); + m.lb = -1000*ones(4,1); m.ub = 1000*ones(4,1); m.rev = ones(4,1); + m.c = zeros(4,1); m.b = zeros(4,1); + m.genes = {}; m.grRules = repmat({''},4,1); m.rxnGeneMat = sparse(4,0); + end + function m = twoMetModel() % Single reaction a -> b in one compartment. m = struct(); diff --git a/tests/function_tests/tTasks.m b/tests/function_tests/tTasks.m index 3b858ecf..11a71eb9 100644 --- a/tests/function_tests/tTasks.m +++ b/tests/function_tests/tTasks.m @@ -26,6 +26,80 @@ function parseTaskListReturnsStruct(testCase) testCase.verifyNotEmpty(ts); end + function checkTasksWarnsWhenSeveralMetabolitesShareAName(testCase) + m = testCase.taskTestModel(); + m.metNames{3} = 'a'; % bc is now also called a[s], like as + m.metComps(3) = 1; + task = testCase.taskTestStruct(); + lastwarn(''); + [~, report] = evalc('checkTasks(m, [], true, false, false, task);'); + testCase.verifySubstring(lastwarn, 'Several model metabolites match'); + testCase.verifyTrue(report.ok == 1); + end + + function parseTaskListReadsFlagsListsAndHeadersLeniently(testCase) + % Flags: "false"/"0"/"no" are false, other text is true. Lists + % separated by ";" are trimmed and empty items dropped. Headers + % are matched ignoring case and whitespace. An empty CHANGED + % bound is NaN (leave the bound unchanged). + rows = { + {'' 'id' 'Description' 'Should Fail' 'IN' ' in lb ' 'IN UB' 'OUT' 'OUT LB' 'OUT UB' 'EQU' 'EQU LB' 'EQU UB' 'changed rxn' 'CHANGED LB' 'CHANGED UB' 'PRINT FLUX' 'COMMENTS'} + {'' 'T1' 'first' 'false' 'A[c]; B[c];' '' '5' '' '' '' '' '' '' 'R1; R2' '' '10' '0' ''} + {'' 'T2' 'second' 'yes' ';C[c]' '2' '' '' '' '' '' '' '' 'R3' '-5' '' 'No' ''} + {'' 'T3' 'third' '0' '' '' '' 'D[c]' '' '' '' '' '' '' '' '' 'TRUE' ''} + }; + nCols = 20; + lines = cell(numel(rows),1); + for k = 1:numel(rows) + r = rows{k}; + r(end+1:nCols) = {''}; + lines{k} = strjoin(r, sprintf('\t')); + end + f = [tempname '.txt']; + fid = fopen(f,'w'); + fprintf(fid,'%s\n',lines{:}); + fclose(fid); + cleanup = onCleanup(@() delete(f)); + + ts = parseTaskList(f); + testCase.verifyEqual(numel(ts), 3); + + testCase.verifyFalse(ts(1).shouldFail); + testCase.verifyFalse(ts(1).printFluxes); + testCase.verifyEqual(ts(1).inputs, {'A[c]';'B[c]'}); + testCase.verifyEqual(ts(1).LBin, [0;0]); + testCase.verifyEqual(ts(1).UBin, [5;5]); + testCase.verifyEqual(ts(1).changed, {'R1';'R2'}); + testCase.verifyTrue(all(isnan(ts(1).LBrxn))); + testCase.verifyEqual(ts(1).UBrxn, [10;10]); + + testCase.verifyTrue(ts(2).shouldFail); + testCase.verifyFalse(ts(2).printFluxes); + testCase.verifyEqual(ts(2).inputs, {'C[c]'}); + testCase.verifyEqual(ts(2).LBin, 2); + testCase.verifyEqual(ts(2).LBrxn, -5); + testCase.verifyTrue(isnan(ts(2).UBrxn)); + + testCase.verifyFalse(ts(3).shouldFail); + testCase.verifyTrue(ts(3).printFluxes); + end + + function checkTasksLeavesNaNChangedBoundsAlone(testCase) + m = testCase.taskTestModel(); + task = testCase.taskTestStruct(); + task.changed = {'R2'}; + task.LBrxn = NaN; + task.UBrxn = 1000; + [~, report] = evalc('checkTasks(m, [], true, false, false, task);'); + testCase.verifyTrue(report.ok == 1); + + % R2 can no longer carry flux from a[s] to a[c] with UB 0, and + % its lower bound (-1000) is kept + task.UBrxn = 0; + [~, report] = evalc('checkTasks(m, [], true, false, false, task);'); + testCase.verifyTrue(report.ok == 0); + end + function parseTaskListMatchesExpected(testCase) % The text and Excel task definitions must parse to identical % structs with exactly the expected fields and values. From 7bc599d9ce3ba5743e03eca0c42fa3860fd7e8b1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 21 Oct 2026 22:35:44 +0000 Subject: [PATCH 30/48] doc: regenerate HTML documentation for beadbc0 [skip ci] --- docs/html/gapfilling/fitTasks.html | 357 +++++------ docs/html/io/writeYAMLmodel.html | 445 +++++++------- .../manipulation/removeLowScoreGenes.html | 16 +- docs/html/manipulation/replaceMets.html | 189 +++--- docs/html/tasks/checkTasks.html | 411 +++++++------ docs/html/tasks/parseTaskList.html | 555 ++++++++++-------- 6 files changed, 1039 insertions(+), 934 deletions(-) diff --git a/docs/html/gapfilling/fitTasks.html b/docs/html/gapfilling/fitTasks.html index 13408704..badf68f6 100644 --- a/docs/html/gapfilling/fitTasks.html +++ b/docs/html/gapfilling/fitTasks.html @@ -375,185 +375,188 @@

    SOURCE CODE ^end 0263 %Add changed bounds 0264 if ~isempty(taskStructure(i).changed) -0265 tModel=setParam(tModel,'lb',taskStructure(i).changed,taskStructure(i).LBrxn); -0266 tModel=setParam(tModel,'ub',taskStructure(i).changed,taskStructure(i).UBrxn); -0267 if preMerged -0268 tRefModel=setParam(tRefModel,'lb',taskStructure(i).changed,taskStructure(i).LBrxn); -0269 tRefModel=setParam(tRefModel,'ub',taskStructure(i).changed,taskStructure(i).UBrxn); -0270 end -0271 end -0272 -0273 %Solve and print. Display a warning if the problem is not solveable -0274 sol=solveLP(tModel); -0275 if isempty(sol.x) -0276 %Only do gap-filling if it cannot be solved -0277 failed=false; -0278 try -0279 if preMerged -0280 [newRxns, newModel, exitFlag]=ftINITFillGaps(tModel,model,tRefModel,false,supressWarnings,tRxnScores,params,verbose,resolveTies); -0281 else -0282 [~, ~, newRxns, newModel, exitFlag]=fillGaps(tModel,refModel,false,true,supressWarnings,rxnScores,params); -0283 end -0284 if exitFlag==-1 -0285 %No set of reactions from the reference model makes the -0286 %task feasible. Without this branch the task falls -0287 %through and reports "Added 0 reaction(s)", which is the -0288 %same thing an already-feasible task reports. -0289 EM=[taskLabel ' could not be gap-filled: no feasible solution exists using the reference model']; -0290 warning('RAVEN:warning', '%s', EM); -0291 failed=true; -0292 elseif exitFlag==-2 -0293 EM=[taskLabel ' was aborted before reaching optimality. Consider increasing params.TimeLimit']; -0294 warning('RAVEN:warning', '%s', EM); -0295 end -0296 catch e -0297 EM=[taskLabel ' could not be performed for any set of reactions: ' e.message]; -0298 warning('RAVEN:warning', '%s', EM); -0299 failed=true; -0300 end -0301 if failed -0302 failedTasks(i)=true; +0265 %NaN bounds mean "leave unchanged" +0266 lbSet=~isnan(taskStructure(i).LBrxn); +0267 ubSet=~isnan(taskStructure(i).UBrxn); +0268 tModel=setParam(tModel,'lb',taskStructure(i).changed(lbSet),taskStructure(i).LBrxn(lbSet)); +0269 tModel=setParam(tModel,'ub',taskStructure(i).changed(ubSet),taskStructure(i).UBrxn(ubSet)); +0270 if preMerged +0271 tRefModel=setParam(tRefModel,'lb',taskStructure(i).changed(lbSet),taskStructure(i).LBrxn(lbSet)); +0272 tRefModel=setParam(tRefModel,'ub',taskStructure(i).changed(ubSet),taskStructure(i).UBrxn(ubSet)); +0273 end +0274 end +0275 +0276 %Solve and print. Display a warning if the problem is not solveable +0277 sol=solveLP(tModel); +0278 if isempty(sol.x) +0279 %Only do gap-filling if it cannot be solved +0280 failed=false; +0281 try +0282 if preMerged +0283 [newRxns, newModel, exitFlag]=ftINITFillGaps(tModel,model,tRefModel,false,supressWarnings,tRxnScores,params,verbose,resolveTies); +0284 else +0285 [~, ~, newRxns, newModel, exitFlag]=fillGaps(tModel,refModel,false,true,supressWarnings,rxnScores,params); +0286 end +0287 if exitFlag==-1 +0288 %No set of reactions from the reference model makes the +0289 %task feasible. Without this branch the task falls +0290 %through and reports "Added 0 reaction(s)", which is the +0291 %same thing an already-feasible task reports. +0292 EM=[taskLabel ' could not be gap-filled: no feasible solution exists using the reference model']; +0293 warning('RAVEN:warning', '%s', EM); +0294 failed=true; +0295 elseif exitFlag==-2 +0296 EM=[taskLabel ' was aborted before reaching optimality. Consider increasing params.TimeLimit']; +0297 warning('RAVEN:warning', '%s', EM); +0298 end +0299 catch e +0300 EM=[taskLabel ' could not be performed for any set of reactions: ' e.message]; +0301 warning('RAVEN:warning', '%s', EM); +0302 failed=true; 0303 end -0304 if failed==false -0305 if ~isempty(newRxns) -0306 nAdded=nAdded+numel(newRxns); -0307 -0308 if preMerged -0309 %The reference model already contained the model, so -0310 %newModel is the model plus the added reactions -0311 model=newModel; -0312 else -0313 %Add the reactions to the base model. It is not correct -0314 %to use newModel directly, as it may contain -0315 %reactions/constraints that are specific to this task -0316 model=mergeModels({model,removeReactions(newModel,setdiff(newModel.rxns,newRxns),true,true)},'metNames',true); -0317 end -0318 -0319 %Keep track of the added reactions -0320 addedRxns(ismember(refModel.rxns,newRxns),i)=true; -0321 end -0322 if printOutput==true -0323 %Task id/description are arbitrary text and may -0324 %contain "%"; print as literal data rather than as an -0325 %fprintf format string. -0326 fprintf('%s\n', ['[' taskStructure(i).id '] ' taskStructure(i).description ': Added ' num2str(numel(newRxns)) ' reaction(s), ' num2str(nAdded) ' reactions added in total']); -0327 end -0328 end -0329 else -0330 if printOutput==true -0331 fprintf('%s\n', ['[' taskStructure(i).id '] ' taskStructure(i).description ': Added 0 reaction(s), ' num2str(nAdded) ' reactions added in total']); -0332 end -0333 end -0334 supressWarnings=true; -0335 -0336 %Print the output if chosen -0337 if taskStructure(i).printFluxes && printOutput -0338 if ~isempty(sol.x) -0339 sol=solveLP(tModel,1); -0340 printFluxes(tModel,sol.x,false,10^-5,[],'%rxnID (%eqn):%flux\n'); -0341 fprintf('\n'); -0342 else -0343 %If the problem was not solveable then the gap-filled model -0344 %should be used -0345 if failed==false -0346 sol=solveLP(newModel,1); -0347 printFluxes(newModel,sol.x,false,10^-5,[],'%rxnID (%eqn):%flux\n'); -0348 fprintf('\n'); -0349 end -0350 end -0351 end -0352 -0353 tModel=model; -0354 %Since new mets are added by merging the new reactions and not only -0355 %from the task sheet -0356 modelMets=upper(strcat(model.metNames,'[',model.comps(model.metComps),']')); -0357 else -0358 EM=['"[' taskStructure(i).id '] ' taskStructure(i).description '" is set as SHOULD FAIL. Such tasks cannot be modelled using this approach and the task is therefore ignored']; -0359 warning('RAVEN:warning', '%s', EM); -0360 end -0361 end -0362 if size(model.b,2)>1 -0363 model.b(:,2) = []; % resume field b +0304 if failed +0305 failedTasks(i)=true; +0306 end +0307 if failed==false +0308 if ~isempty(newRxns) +0309 nAdded=nAdded+numel(newRxns); +0310 +0311 if preMerged +0312 %The reference model already contained the model, so +0313 %newModel is the model plus the added reactions +0314 model=newModel; +0315 else +0316 %Add the reactions to the base model. It is not correct +0317 %to use newModel directly, as it may contain +0318 %reactions/constraints that are specific to this task +0319 model=mergeModels({model,removeReactions(newModel,setdiff(newModel.rxns,newRxns),true,true)},'metNames',true); +0320 end +0321 +0322 %Keep track of the added reactions +0323 addedRxns(ismember(refModel.rxns,newRxns),i)=true; +0324 end +0325 if printOutput==true +0326 %Task id/description are arbitrary text and may +0327 %contain "%"; print as literal data rather than as an +0328 %fprintf format string. +0329 fprintf('%s\n', ['[' taskStructure(i).id '] ' taskStructure(i).description ': Added ' num2str(numel(newRxns)) ' reaction(s), ' num2str(nAdded) ' reactions added in total']); +0330 end +0331 end +0332 else +0333 if printOutput==true +0334 fprintf('%s\n', ['[' taskStructure(i).id '] ' taskStructure(i).description ': Added 0 reaction(s), ' num2str(nAdded) ' reactions added in total']); +0335 end +0336 end +0337 supressWarnings=true; +0338 +0339 %Print the output if chosen +0340 if taskStructure(i).printFluxes && printOutput +0341 if ~isempty(sol.x) +0342 sol=solveLP(tModel,1); +0343 printFluxes(tModel,sol.x,false,10^-5,[],'%rxnID (%eqn):%flux\n'); +0344 fprintf('\n'); +0345 else +0346 %If the problem was not solveable then the gap-filled model +0347 %should be used +0348 if failed==false +0349 sol=solveLP(newModel,1); +0350 printFluxes(newModel,sol.x,false,10^-5,[],'%rxnID (%eqn):%flux\n'); +0351 fprintf('\n'); +0352 end +0353 end +0354 end +0355 +0356 tModel=model; +0357 %Since new mets are added by merging the new reactions and not only +0358 %from the task sheet +0359 modelMets=upper(strcat(model.metNames,'[',model.comps(model.metComps),']')); +0360 else +0361 EM=['"[' taskStructure(i).id '] ' taskStructure(i).description '" is set as SHOULD FAIL. Such tasks cannot be modelled using this approach and the task is therefore ignored']; +0362 warning('RAVEN:warning', '%s', EM); +0363 end 0364 end -0365 outModel=model; -0366 end -0367 -0368 function m=applyTaskInputBounds(m,I,J,K,L,task,taskLabel) -0369 %Write a task's input constraints into the b field of one model. I and J -0370 %are the ismember result of the task's input metabolites against that -0371 %model's metabolite names; K and L flag ALLMETS and ALLMETSIN entries. -0372 -0373 %If all metabolites should be added. Use the first match of ALLMETS, there -0374 %should only be one, but still.. -0375 if any(K) -0376 m.b(:,1)=task.UBin(find(K,1))*-1; -0377 end -0378 %If metabolites in a specific compartment should be used -0379 if any(L) -0380 L=find(L); -0381 for j=1:numel(L) -0382 %The compartment defined -0383 compartment=upper(task.inputs{L(j)}(11:end-1)); -0384 %Check if it exists in the model -0385 C=find(ismember(upper(m.comps),compartment)); -0386 if any(C) -0387 %Match to metabolites -0388 m.b(m.metComps==C,1)=task.UBin(L(j))*-1; -0389 else -0390 EM=['The compartment defined for ALLMETSIN in ' taskLabel ' does not exist']; -0391 error('RAVEN:badInput', '%s', EM); -0392 end -0393 end -0394 end -0395 %Then add the normal constraints -0396 if any(J(I)) -0397 m.b(J(I),1)=task.UBin(I)*-1; -0398 m.b(J(I),2)=task.LBin(I)*-1; -0399 end -0400 end -0401 -0402 function m=applyTaskOutputBounds(m,I,J,K,L,task,taskLabel) -0403 %Write a task's output constraints into the b field of one model, as -0404 %applyTaskInputBounds does for the inputs. -0405 -0406 %If all metabolites should be added. Use the first match of ALLMETS, there -0407 %should only be one, but still.. -0408 if any(K) -0409 m.b(:,2)=task.UBout(find(K,1)); -0410 end -0411 %If metabolites in a specific compartment should be used -0412 if any(L) -0413 L=find(L); -0414 for j=1:numel(L) -0415 %The compartment defined -0416 compartment=upper(task.outputs{L(j)}(11:end-1)); -0417 %Check if it exists in the model -0418 C=find(ismember(upper(m.comps),compartment)); -0419 if any(C) -0420 %Match to metabolites -0421 m.b(m.metComps==C,2)=task.UBout(L(j)); -0422 else -0423 EM=['The compartment defined for ALLMETSIN in ' taskLabel ' does not exist']; -0424 error('RAVEN:badInput', '%s', EM); -0425 end -0426 end -0427 end -0428 %Then add the normal constraints -0429 if any(J(I)) -0430 %Verify that IN and OUT bounds are consistent. Cannot require that a -0431 %metabolite is simultaneously input AND output at some nonzero flux. -0432 J = J(I); -0433 I = find(I); % otherwise indexing becomes confusing -0434 nonzero_LBin = m.b(J,2) < 0; -0435 nonzero_LBout = task.LBout(I) > 0; -0436 if any(nonzero_LBin & nonzero_LBout) -0437 EM=['The IN LB and OUT LB in ' taskLabel ' cannot be nonzero for the same metabolite']; -0438 error('RAVEN:badInput', '%s', EM); -0439 end -0440 m.b(J(nonzero_LBout),1)=task.LBout(I(nonzero_LBout)); -0441 m.b(J,2)=task.UBout(I); -0442 end -0443 end +0365 if size(model.b,2)>1 +0366 model.b(:,2) = []; % resume field b +0367 end +0368 outModel=model; +0369 end +0370 +0371 function m=applyTaskInputBounds(m,I,J,K,L,task,taskLabel) +0372 %Write a task's input constraints into the b field of one model. I and J +0373 %are the ismember result of the task's input metabolites against that +0374 %model's metabolite names; K and L flag ALLMETS and ALLMETSIN entries. +0375 +0376 %If all metabolites should be added. Use the first match of ALLMETS, there +0377 %should only be one, but still.. +0378 if any(K) +0379 m.b(:,1)=task.UBin(find(K,1))*-1; +0380 end +0381 %If metabolites in a specific compartment should be used +0382 if any(L) +0383 L=find(L); +0384 for j=1:numel(L) +0385 %The compartment defined +0386 compartment=upper(task.inputs{L(j)}(11:end-1)); +0387 %Check if it exists in the model +0388 C=find(ismember(upper(m.comps),compartment)); +0389 if any(C) +0390 %Match to metabolites +0391 m.b(m.metComps==C,1)=task.UBin(L(j))*-1; +0392 else +0393 EM=['The compartment defined for ALLMETSIN in ' taskLabel ' does not exist']; +0394 error('RAVEN:badInput', '%s', EM); +0395 end +0396 end +0397 end +0398 %Then add the normal constraints +0399 if any(J(I)) +0400 m.b(J(I),1)=task.UBin(I)*-1; +0401 m.b(J(I),2)=task.LBin(I)*-1; +0402 end +0403 end +0404 +0405 function m=applyTaskOutputBounds(m,I,J,K,L,task,taskLabel) +0406 %Write a task's output constraints into the b field of one model, as +0407 %applyTaskInputBounds does for the inputs. +0408 +0409 %If all metabolites should be added. Use the first match of ALLMETS, there +0410 %should only be one, but still.. +0411 if any(K) +0412 m.b(:,2)=task.UBout(find(K,1)); +0413 end +0414 %If metabolites in a specific compartment should be used +0415 if any(L) +0416 L=find(L); +0417 for j=1:numel(L) +0418 %The compartment defined +0419 compartment=upper(task.outputs{L(j)}(11:end-1)); +0420 %Check if it exists in the model +0421 C=find(ismember(upper(m.comps),compartment)); +0422 if any(C) +0423 %Match to metabolites +0424 m.b(m.metComps==C,2)=task.UBout(L(j)); +0425 else +0426 EM=['The compartment defined for ALLMETSIN in ' taskLabel ' does not exist']; +0427 error('RAVEN:badInput', '%s', EM); +0428 end +0429 end +0430 end +0431 %Then add the normal constraints +0432 if any(J(I)) +0433 %Verify that IN and OUT bounds are consistent. Cannot require that a +0434 %metabolite is simultaneously input AND output at some nonzero flux. +0435 J = J(I); +0436 I = find(I); % otherwise indexing becomes confusing +0437 nonzero_LBin = m.b(J,2) < 0; +0438 nonzero_LBout = task.LBout(I) > 0; +0439 if any(nonzero_LBin & nonzero_LBout) +0440 EM=['The IN LB and OUT LB in ' taskLabel ' cannot be nonzero for the same metabolite']; +0441 error('RAVEN:badInput', '%s', EM); +0442 end +0443 m.b(J(nonzero_LBout),1)=task.LBout(I(nonzero_LBout)); +0444 m.b(J,2)=task.UBout(I); +0445 end +0446 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/io/writeYAMLmodel.html b/docs/html/io/writeYAMLmodel.html index c0346294..9db69ac0 100644 --- a/docs/html/io/writeYAMLmodel.html +++ b/docs/html/io/writeYAMLmodel.html @@ -454,230 +454,241 @@

    SOURCE CODE ^if x == floor(x) && abs(x) < 1e16 0383 s = sprintf('%.1f', x); % e.g. 1000 -> "1000.0" 0384 else -0385 s = sprintf('%.15g', x); -0386 end -0387 end -0388 -0389 function writeAnnotationSimple(model, fid, miriamsField, miriamValsField, miriamNamesField, pos, indent, subIndent) -0390 % Emit a bare `annotation: !!omap` block from MIRIAM cross-references only -0391 % (used for genes and compartments, which carry no extra non-MIRIAM -0392 % annotation key and have no established reference format to follow — -0393 % compartments in particular are just a flat {id: name} map with no -0394 % per-compartment annotation at all — so the layout here follows only -0395 % readYAMLmodel's own expectation that a compartment's annotation -0396 % sub-entries are indented >=6 spaces). Keys are emitted in alphabetical -0397 % order, matching the sort order used elsewhere in this file. -0398 if nargin < 7 -0399 indent = ' '; -0400 end -0401 if nargin < 8 -0402 subIndent = length(indent)+2; -0403 end -0404 if ~isfield(model, miriamsField) || isempty(model.(miriamsField){pos}) -0405 return -0406 end -0407 names = model.(miriamNamesField); -0408 values = model.(miriamValsField); -0409 entries = collectMiriamEntries(names, values, pos); -0410 if isempty(entries) -0411 return -0412 end -0413 [~,order] = sort({entries.name}); -0414 entries = entries(order); -0415 fprintf(fid, '%s- annotation: !!omap\n', indent); -0416 emitAnnotationEntries(fid, entries, subIndent); -0417 end -0418 -0419 function entries = collectMiriamEntries(names, values, pos) -0420 entries = struct('name', {}, 'list', {}, 'forceList', {}); -0421 for j = 1:size(values, 2) -0422 v = values{pos, j}; -0423 if isempty(v); continue; end -0424 list = strip(strsplit(strrep(v, ' ', ''), ';')); -0425 entries(end+1) = struct('name', names{j}, 'list', {list}, 'forceList', false); %#ok<AGROW> -0426 end -0427 end -0428 -0429 function writeAnnotation(model, fid, kind, pos) -0430 % Emit the per-entry `annotation` block, fusing MIRIAM cross-references -0431 % with the non-MIRIAM annotation keys: `smiles` for metabolites and -0432 % `ec-code` (EC numbers) for reactions — both are written inside -0433 % `annotation` rather than as top-level entry keys. `ec-code` is always -0434 % emitted as a list, even when there is a single code. All keys (MIRIAMs -0435 % and the extra key together) are emitted in alphabetical order. -0436 switch kind -0437 case 'met' -0438 miriamsField = 'metMiriams'; -0439 miriamNamesField = 'newMetMiriamNames'; -0440 miriamValsField = 'newMetMiriams'; -0441 extraName = 'smiles'; -0442 extraField = 'metSmiles'; -0443 case 'rxn' -0444 miriamsField = 'rxnMiriams'; -0445 miriamNamesField = 'newRxnMiriamNames'; -0446 miriamValsField = 'newRxnMiriams'; -0447 extraName = 'ec-code'; -0448 extraField = 'eccodes'; -0449 otherwise -0450 error('writeAnnotation:unsupportedKind', 'Unsupported kind: %s', kind); -0451 end -0452 -0453 hasMiriams = isfield(model, miriamsField) && ~isempty(model.(miriamsField){pos}); -0454 hasExtra = isfield(model, extraField) && pos <= numel(model.(extraField)) ... -0455 && ~isempty(model.(extraField){pos}); -0456 -0457 if ~hasMiriams && ~hasExtra -0458 return; +0385 % Shortest text that reads back as exactly x (15 to 17 significant +0386 % digits), as Python's repr does, so 1/3 is written 0.3333333333333333 +0387 % and a value is never rounded on the way out. +0388 for digits = 15:17 +0389 s = sprintf(['%.' num2str(digits) 'g'], x); +0390 if str2double(s) == x +0391 break +0392 end +0393 end +0394 end +0395 end +0396 +0397 function writeAnnotationSimple(model, fid, miriamsField, miriamValsField, miriamNamesField, pos, indent, subIndent) +0398 % Emit a bare `annotation: !!omap` block from MIRIAM cross-references only +0399 % (used for genes and compartments, which carry no extra non-MIRIAM +0400 % annotation key and have no established reference format to follow — +0401 % compartments in particular are just a flat {id: name} map with no +0402 % per-compartment annotation at all — so the layout here follows only +0403 % readYAMLmodel's own expectation that a compartment's annotation +0404 % sub-entries are indented >=6 spaces). Keys are emitted in alphabetical +0405 % order, matching the sort order used elsewhere in this file. +0406 if nargin < 7 +0407 indent = ' '; +0408 end +0409 if nargin < 8 +0410 subIndent = length(indent)+2; +0411 end +0412 if ~isfield(model, miriamsField) || isempty(model.(miriamsField){pos}) +0413 return +0414 end +0415 names = model.(miriamNamesField); +0416 values = model.(miriamValsField); +0417 entries = collectMiriamEntries(names, values, pos); +0418 if isempty(entries) +0419 return +0420 end +0421 [~,order] = sort({entries.name}); +0422 entries = entries(order); +0423 fprintf(fid, '%s- annotation: !!omap\n', indent); +0424 emitAnnotationEntries(fid, entries, subIndent); +0425 end +0426 +0427 function entries = collectMiriamEntries(names, values, pos) +0428 entries = struct('name', {}, 'list', {}, 'forceList', {}); +0429 for j = 1:size(values, 2) +0430 v = values{pos, j}; +0431 if isempty(v); continue; end +0432 list = strip(strsplit(strrep(v, ' ', ''), ';')); +0433 entries(end+1) = struct('name', names{j}, 'list', {list}, 'forceList', false); %#ok<AGROW> +0434 end +0435 end +0436 +0437 function writeAnnotation(model, fid, kind, pos) +0438 % Emit the per-entry `annotation` block, fusing MIRIAM cross-references +0439 % with the non-MIRIAM annotation keys: `smiles` for metabolites and +0440 % `ec-code` (EC numbers) for reactions — both are written inside +0441 % `annotation` rather than as top-level entry keys. `ec-code` is always +0442 % emitted as a list, even when there is a single code. All keys (MIRIAMs +0443 % and the extra key together) are emitted in alphabetical order. +0444 switch kind +0445 case 'met' +0446 miriamsField = 'metMiriams'; +0447 miriamNamesField = 'newMetMiriamNames'; +0448 miriamValsField = 'newMetMiriams'; +0449 extraName = 'smiles'; +0450 extraField = 'metSmiles'; +0451 case 'rxn' +0452 miriamsField = 'rxnMiriams'; +0453 miriamNamesField = 'newRxnMiriamNames'; +0454 miriamValsField = 'newRxnMiriams'; +0455 extraName = 'ec-code'; +0456 extraField = 'eccodes'; +0457 otherwise +0458 error('writeAnnotation:unsupportedKind', 'Unsupported kind: %s', kind); 0459 end 0460 -0461 entries = struct('name', {}, 'list', {}, 'forceList', {}); -0462 if hasMiriams -0463 entries = collectMiriamEntries(model.(miriamNamesField), model.(miriamValsField), pos); -0464 end -0465 if hasExtra -0466 % EC numbers / SMILES are always emitted as a list, even when there is -0467 % a single value — unlike a MIRIAM entry, which collapses to a bare -0468 % scalar when there is only one. -0469 codes = strip(strsplit(strrep(model.(extraField){pos}, ' ', ''), ';')); -0470 codes = codes(~cellfun('isempty', codes)); -0471 if ~isempty(codes) -0472 entries(end+1) = struct('name', extraName, 'list', {codes}, 'forceList', true); %#ok<AGROW> -0473 end -0474 end -0475 if isempty(entries) -0476 return -0477 end -0478 [~,order] = sort({entries.name}); -0479 entries = entries(order); -0480 -0481 fprintf(fid, ' - annotation: !!omap\n'); -0482 emitAnnotationEntries(fid, entries, 6); -0483 end -0484 -0485 function emitAnnotationEntries(fid, entries, keyIndent) -0486 % Shared tail for writeAnnotation / writeAnnotationSimple: entries are -0487 % already sorted by name; emit each as a bare scalar (single value) or a -0488 % block list (multiple values), at the given key indent. -0489 pad = repmat(' ', 1, keyIndent); -0490 for e = entries -0491 if numel(e.list) == 1 && ~e.forceList -0492 emitScalarLine(fid, [pad '- ' e.name], e.list{1}, keyIndent) -0493 else -0494 fprintf(fid, '%s- %s:\n', pad, e.name); -0495 for k = 1:numel(e.list) -0496 emitListItem(fid, [pad ' -'], e.list{k}, keyIndent+2) -0497 end -0498 end -0499 end -0500 end -0501 -0502 function emitScalarLine(fid, prefix, value, keyIndent, bareNumeric) -0503 % Emit "PREFIX: VALUE\n" (double-quoted when needed), where PREFIX is the -0504 % " - key" part with no trailing colon and value is the raw, -0505 % unescaped scalar content. -0506 % -0507 % bareNumeric (default false): value is already a pre-formatted number -0508 % (from formatFloat) that must never be quoted — numbers are never -0509 % ambiguous once formatted. -0510 if nargin < 5 -0511 bareNumeric = false; -0512 end -0513 fprintf(fid, '%s:', prefix); -0514 emitScalarCore(fid, length(prefix)+1, value, keyIndent, bareNumeric) -0515 end -0516 -0517 function emitListItem(fid, dashPrefix, value, keyIndent) -0518 % Emit "DASHPREFIX VALUE\n" for a plain sequence entry (no key, just the -0519 % "- " marker) — e.g. a subsystem, EC code, SMILES or MIRIAM list item. -0520 % Unlike emitScalarLine, no colon is written. -0521 fprintf(fid, '%s', dashPrefix); -0522 emitScalarCore(fid, length(dashPrefix), value, keyIndent, false) +0461 hasMiriams = isfield(model, miriamsField) && ~isempty(model.(miriamsField){pos}); +0462 hasExtra = isfield(model, extraField) && pos <= numel(model.(extraField)) ... +0463 && ~isempty(model.(extraField){pos}); +0464 +0465 if ~hasMiriams && ~hasExtra +0466 return; +0467 end +0468 +0469 entries = struct('name', {}, 'list', {}, 'forceList', {}); +0470 if hasMiriams +0471 entries = collectMiriamEntries(model.(miriamNamesField), model.(miriamValsField), pos); +0472 end +0473 if hasExtra +0474 % EC numbers / SMILES are always emitted as a list, even when there is +0475 % a single value — unlike a MIRIAM entry, which collapses to a bare +0476 % scalar when there is only one. +0477 codes = strip(strsplit(strrep(model.(extraField){pos}, ' ', ''), ';')); +0478 codes = codes(~cellfun('isempty', codes)); +0479 if ~isempty(codes) +0480 entries(end+1) = struct('name', extraName, 'list', {codes}, 'forceList', true); %#ok<AGROW> +0481 end +0482 end +0483 if isempty(entries) +0484 return +0485 end +0486 [~,order] = sort({entries.name}); +0487 entries = entries(order); +0488 +0489 fprintf(fid, ' - annotation: !!omap\n'); +0490 emitAnnotationEntries(fid, entries, 6); +0491 end +0492 +0493 function emitAnnotationEntries(fid, entries, keyIndent) +0494 % Shared tail for writeAnnotation / writeAnnotationSimple: entries are +0495 % already sorted by name; emit each as a bare scalar (single value) or a +0496 % block list (multiple values), at the given key indent. +0497 pad = repmat(' ', 1, keyIndent); +0498 for e = entries +0499 if numel(e.list) == 1 && ~e.forceList +0500 emitScalarLine(fid, [pad '- ' e.name], e.list{1}, keyIndent) +0501 else +0502 fprintf(fid, '%s- %s:\n', pad, e.name); +0503 for k = 1:numel(e.list) +0504 emitListItem(fid, [pad ' -'], e.list{k}, keyIndent+2) +0505 end +0506 end +0507 end +0508 end +0509 +0510 function emitScalarLine(fid, prefix, value, keyIndent, bareNumeric) +0511 % Emit "PREFIX: VALUE\n" (double-quoted when needed), where PREFIX is the +0512 % " - key" part with no trailing colon and value is the raw, +0513 % unescaped scalar content. +0514 % +0515 % bareNumeric (default false): value is already a pre-formatted number +0516 % (from formatFloat) that must never be quoted — numbers are never +0517 % ambiguous once formatted. +0518 if nargin < 5 +0519 bareNumeric = false; +0520 end +0521 fprintf(fid, '%s:', prefix); +0522 emitScalarCore(fid, length(prefix)+1, value, keyIndent, bareNumeric) 0523 end 0524 -0525 function emitScalarCore(fid, ~, value, ~, bareNumeric) -0526 % Shared tail for emitScalarLine / emitListItem: writes a single space -0527 % after ':' or '-', then the value, double-quoted when needed (matching -0528 % Prettier's YAML default). No folding — this format never wraps a -0529 % scalar across lines, however long, so the two unused positional -0530 % arguments (column and key indent) only exist to keep every call site's -0531 % signature stable. -0532 if bareNumeric -0533 fprintf(fid, ' %s\n', value); -0534 return -0535 end -0536 if needsQuote(value) -0537 escaped = strrep(value, '\', '\\'); -0538 escaped = strrep(escaped, '"', '\"'); -0539 fprintf(fid, ' "%s"\n', escaped); -0540 else +0525 function emitListItem(fid, dashPrefix, value, keyIndent) +0526 % Emit "DASHPREFIX VALUE\n" for a plain sequence entry (no key, just the +0527 % "- " marker) — e.g. a subsystem, EC code, SMILES or MIRIAM list item. +0528 % Unlike emitScalarLine, no colon is written. +0529 fprintf(fid, '%s', dashPrefix); +0530 emitScalarCore(fid, length(dashPrefix), value, keyIndent, false) +0531 end +0532 +0533 function emitScalarCore(fid, ~, value, ~, bareNumeric) +0534 % Shared tail for emitScalarLine / emitListItem: writes a single space +0535 % after ':' or '-', then the value, double-quoted when needed (matching +0536 % Prettier's YAML default). No folding — this format never wraps a +0537 % scalar across lines, however long, so the two unused positional +0538 % arguments (column and key indent) only exist to keep every call site's +0539 % signature stable. +0540 if bareNumeric 0541 fprintf(fid, ' %s\n', value); -0542 end +0542 return 0543 end -0544 -0545 function tf = needsQuote(s) -0546 % Whether this scalar needs quoting when written: either because a plain -0547 % (bare) reading would resolve to a non-string YAML type (bool/int/float/ -0548 % null/timestamp — this writer never emits explicit type tags), or -0549 % because the text itself is not valid as a plain block scalar (leading -0550 % indicator character, a mid-string ": "/" #", leading/trailing -0551 % whitespace, or embedded control characters). Implements the YAML 1.2 -0552 % rules for resolving implicit scalar types and analyzing scalar style. -0553 if isempty(s) -0554 tf = true; -0555 return -0556 end -0557 -0558 resolverPattern = [ ... -0559 '^(true|True|TRUE|false|False|FALSE)$|' ... -0560 '^[-+]?[0-9][0-9_]*\.[0-9_]*([eE][-+]?[0-9]+)?$|' ... -0561 '^[-+]?[0-9][0-9_]*[eE][-+]?[0-9]+$|' ... -0562 '^[-+]?\.[0-9_]+([eE][-+][0-9]+)?$|' ... -0563 '^[-+]?\.(inf|Inf|INF)$|' ... -0564 '^\.(nan|NaN|NAN)$|' ... -0565 '^[-+]?0b[01_]+$|' ... -0566 '^[-+]?0o?[0-7_]+$|' ... -0567 '^[-+]?[0-9_]+$|' ... -0568 '^[-+]?0x[0-9a-fA-F_]+$|' ... -0569 '^(~|[Nn]ull|NULL)$|' ... -0570 '^[0-9]{4}-[0-9]{2}-[0-9]{2}$' ... -0571 ]; -0572 if ~isempty(regexp(s, resolverPattern, 'once')) -0573 tf = true; -0574 return -0575 end -0576 -0577 first = s(1); -0578 if any(first == '#,[]{}&*!|>''"%@`') -0579 tf = true; return -0580 end -0581 followedByWs = (numel(s) == 1) || isWs(s(2)); -0582 if (first == '?' || first == ':') && followedByWs -0583 tf = true; return -0584 end -0585 if first == '-' && followedByWs -0586 tf = true; return -0587 end -0588 if isWs(first) || isWs(s(end)) -0589 tf = true; return -0590 end -0591 if any(s == char(10)) || any(s == char(13)) -0592 tf = true; return -0593 end -0594 for i = 2:numel(s) -0595 ch = s(i); -0596 if ch == ':' && ((i == numel(s)) || isWs(s(i+1))) -0597 tf = true; return -0598 end -0599 if ch == '#' && isWs(s(i-1)) -0600 tf = true; return -0601 end -0602 end -0603 tf = false; +0544 if needsQuote(value) +0545 escaped = strrep(value, '\', '\\'); +0546 escaped = strrep(escaped, '"', '\"'); +0547 fprintf(fid, ' "%s"\n', escaped); +0548 else +0549 fprintf(fid, ' %s\n', value); +0550 end +0551 end +0552 +0553 function tf = needsQuote(s) +0554 % Whether this scalar needs quoting when written: either because a plain +0555 % (bare) reading would resolve to a non-string YAML type (bool/int/float/ +0556 % null/timestamp — this writer never emits explicit type tags), or +0557 % because the text itself is not valid as a plain block scalar (leading +0558 % indicator character, a mid-string ": "/" #", leading/trailing +0559 % whitespace, or embedded control characters). Implements the YAML 1.2 +0560 % rules for resolving implicit scalar types and analyzing scalar style, +0561 % and also quotes the YAML 1.1 booleans y, n, yes, no, on and off in any +0562 % case, which YAML 1.1 loaders such as PyYAML read as booleans. +0563 if isempty(s) +0564 tf = true; +0565 return +0566 end +0567 +0568 resolverPattern = [ ... +0569 '^(true|True|TRUE|false|False|FALSE)$|' ... +0570 '^([Yy]|[Nn]|[Yy][Ee][Ss]|[Nn][Oo]|[Oo][Nn]|[Oo][Ff][Ff])$|' ... +0571 '^[-+]?[0-9][0-9_]*\.[0-9_]*([eE][-+]?[0-9]+)?$|' ... +0572 '^[-+]?[0-9][0-9_]*[eE][-+]?[0-9]+$|' ... +0573 '^[-+]?\.[0-9_]+([eE][-+][0-9]+)?$|' ... +0574 '^[-+]?\.(inf|Inf|INF)$|' ... +0575 '^\.(nan|NaN|NAN)$|' ... +0576 '^[-+]?0b[01_]+$|' ... +0577 '^[-+]?0o?[0-7_]+$|' ... +0578 '^[-+]?[0-9_]+$|' ... +0579 '^[-+]?0x[0-9a-fA-F_]+$|' ... +0580 '^(~|[Nn]ull|NULL)$|' ... +0581 '^[0-9]{4}-[0-9]{2}-[0-9]{2}$' ... +0582 ]; +0583 if ~isempty(regexp(s, resolverPattern, 'once')) +0584 tf = true; +0585 return +0586 end +0587 +0588 first = s(1); +0589 if any(first == '#,[]{}&*!|>''"%@`') +0590 tf = true; return +0591 end +0592 followedByWs = (numel(s) == 1) || isWs(s(2)); +0593 if (first == '?' || first == ':') && followedByWs +0594 tf = true; return +0595 end +0596 if first == '-' && followedByWs +0597 tf = true; return +0598 end +0599 if isWs(first) || isWs(s(end)) +0600 tf = true; return +0601 end +0602 if any(s == char(10)) || any(s == char(13)) +0603 tf = true; return 0604 end -0605 -0606 function tf = isWs(ch) -0607 tf = ch == ' ' || ch == char(9) || ch == char(10) || ch == char(13); -0608 end +0605 for i = 2:numel(s) +0606 ch = s(i); +0607 if ch == ':' && ((i == numel(s)) || isWs(s(i+1))) +0608 tf = true; return +0609 end +0610 if ch == '#' && isWs(s(i-1)) +0611 tf = true; return +0612 end +0613 end +0614 tf = false; +0615 end +0616 +0617 function tf = isWs(ch) +0618 tf = ch == ' ' || ch == char(9) || ch == char(10) || ch == char(13); +0619 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/manipulation/removeLowScoreGenes.html b/docs/html/manipulation/removeLowScoreGenes.html index 75cea5e8..fc4034ad 100644 --- a/docs/html/manipulation/removeLowScoreGenes.html +++ b/docs/html/manipulation/removeLowScoreGenes.html @@ -47,8 +47,8 @@

    DESCRIPTION ^SOURCE CODE ^% score will try to be removed. 0020 % 0021 % If all genes associated with a reaction have a negative score, then the -0022 % least-negative gene will remain; if there is a tie, one will be selected -0023 % at random. +0022 % least-negative gene will remain; if there is a tie, the gene listed first +0023 % in the rule will be kept. 0024 % 0025 % If a negative-scoring gene is a subunit in a complex, it will not be 0026 % removed; however, the entire complex may be removed. See the following @@ -246,7 +246,7 @@

    SOURCE CODE ^% get IDs and indices of genes involved in rule -0152 ruleGenes = unique(regexp(rule,'[^&|\(\) ]+','match')); +0152 ruleGenes = unique(regexp(rule,'[^&|\(\) ]+','match'),'stable'); 0153 [~,geneInd] = ismember(ruleGenes,genes); 0154 0155 % rules with one or no genes remain unchanged @@ -261,8 +261,8 @@

    SOURCE CODE ^% NaNs will return false here 0166 if all(negInd) -0167 % deterministic least-negative: sort descending; stable sort breaks -0168 % ties by original gene order (left-to-right in the rule string) +0167 % deterministic least-negative: sort descending; the stable sort +0168 % breaks ties by the order of the genes in the rule string 0169 [~,sortOrder] = sort(gScores(geneInd),'descend'); 0170 maxInd = sortOrder(1); 0171 updatedRule = ruleGenes{maxInd}; @@ -276,7 +276,7 @@

    SOURCE CODE ^end 0180 0181 % update ruleGenes and their indices -0182 ruleGenes = unique(regexp(updatedRule,'[^&|\(\) ]+','match')); +0182 ruleGenes = unique(regexp(updatedRule,'[^&|\(\) ]+','match'),'stable'); 0183 [~,geneInd] = ismember(ruleGenes,genes); 0184 0185 elseif ~contains(rule,'|') % rule contains enzyme complex diff --git a/docs/html/manipulation/replaceMets.html b/docs/html/manipulation/replaceMets.html index 18deda2f..a441b72f 100644 --- a/docs/html/manipulation/replaceMets.html +++ b/docs/html/manipulation/replaceMets.html @@ -167,107 +167,116 @@

    SOURCE CODE ^'The to-be-replaced metabolite cannot be found in the model.'); 0080 end 0081 -0082 rxnsWithMet = find(model.S(metIdx,:)); -0083 if verbose==true -0084 fprintf('\n\nThe following reactions contain the to-be-replaced metabolite as reactant:\n') -0085 %Reaction ids are arbitrary text and may contain "%"; print as -0086 %literal data rather than as an fprintf format string. -0087 fprintf('%s\n', strjoin(model.rxns(rxnsWithMet),newline)) -0088 end -0089 -0090 model.metNames(metIdx) = model.metNames(repIdx(1)); -0091 if isfield(model,'metFormulas') -0092 model.metFormulas(metIdx) = model.metFormulas(repIdx(1)); -0093 end -0094 if isfield(model,'metMiriams') -0095 model.metMiriams(metIdx) = model.metMiriams(repIdx(1)); -0096 end -0097 if isfield(model,'metCharges') -0098 model.metCharges(metIdx) = model.metCharges(repIdx(1)); +0082 % Either of the two rows can be the one that survives the merge: with identifiers the +0083 % replacement stays, by name the replaced metabolite is renamed and stays while the +0084 % replacement's row is deleted below. Both ids are kept so that contractModel is +0085 % restricted to the metabolite that is left. +0086 repMetIds = [repMetIds; model.mets(metIdx)]; +0087 +0088 rxnsWithMet = find(model.S(metIdx,:)); +0089 if verbose==true +0090 fprintf('\n\nThe following reactions contain the to-be-replaced metabolite as reactant:\n') +0091 %Reaction ids are arbitrary text and may contain "%"; print as +0092 %literal data rather than as an fprintf format string. +0093 fprintf('%s\n', strjoin(model.rxns(rxnsWithMet),newline)) +0094 end +0095 +0096 model.metNames(metIdx) = model.metNames(repIdx(1)); +0097 if isfield(model,'metFormulas') +0098 model.metFormulas(metIdx) = model.metFormulas(repIdx(1)); 0099 end -0100 if isfield(model,'metDeltaG') -0101 model.metDeltaG(metIdx) = model.metDeltaG(repIdx(1)); +0100 if isfield(model,'metMiriams') +0101 model.metMiriams(metIdx) = model.metMiriams(repIdx(1)); 0102 end -0103 if isfield(model,'inchis') -0104 model.inchis(metIdx) = model.inchis(repIdx(1)); +0103 if isfield(model,'metCharges') +0104 model.metCharges(metIdx) = model.metCharges(repIdx(1)); 0105 end -0106 if isfield(model,'metSmiles') -0107 model.metSmiles(metIdx) = model.metSmiles(repIdx(1)); +0106 if isfield(model,'metDeltaG') +0107 model.metDeltaG(metIdx) = model.metDeltaG(repIdx(1)); 0108 end -0109 -0110 idxDelete=[]; -0111 if identifiers -0112 originalStoch = model.S(metIdx,rxnsWithMet); -0113 % Add rather than overwrite: a reaction where the replacement -0114 % metabolite is already itself a participant must keep that -0115 % contribution, not have it clobbered by the replaced metabolite's -0116 model.S(repIdx,rxnsWithMet) = model.S(repIdx,rxnsWithMet) + originalStoch; -0117 model.S(metIdx,rxnsWithMet) = 0; -0118 idxDelete = metIdx; -0119 else -0120 % Run through replacement metabolites and their compartments. If any of the -0121 % to-be-replaced metabolites is already present (checked by -0122 % metaboliteName[compartment], then the replacement metabolite is kept and -0123 % the to-be-replace metabolite ID deleted. -0124 -0125 % Build list of metaboliteName[compartment] -0126 metCompsN =cellstr(num2str(model.metComps)); -0127 map = containers.Map(cellstr(num2str(transpose(1:length(model.comps)))),model.comps); -0128 metCompsN = map.values(metCompsN); -0129 metCompsN = strcat(lower(model.metNames),'[',metCompsN,']'); +0109 if isfield(model,'inchis') +0110 model.inchis(metIdx) = model.inchis(repIdx(1)); +0111 end +0112 if isfield(model,'metSmiles') +0113 model.metSmiles(metIdx) = model.metSmiles(repIdx(1)); +0114 end +0115 +0116 idxDelete=[]; +0117 if identifiers +0118 originalStoch = model.S(metIdx,rxnsWithMet); +0119 % Add rather than overwrite: a reaction where the replacement +0120 % metabolite is already itself a participant must keep that +0121 % contribution, not have it clobbered by the replaced metabolite's +0122 model.S(repIdx,rxnsWithMet) = model.S(repIdx,rxnsWithMet) + originalStoch; +0123 model.S(metIdx,rxnsWithMet) = 0; +0124 idxDelete = metIdx; +0125 else +0126 % Run through replacement metabolites and their compartments. If any of the +0127 % to-be-replaced metabolites is already present (checked by +0128 % metaboliteName[compartment], then the replacement metabolite is kept and +0129 % the to-be-replace metabolite ID deleted. 0130 -0131 for i = 1:length(repIdx) -0132 metCompsNidx=find(strcmp(metCompsN(repIdx(i)), metCompsN)); -0133 if length(metCompsNidx)>1 -0134 for j = 2:length(metCompsNidx) -0135 model.S(metCompsNidx(1),:) = model.S(metCompsNidx(1),:) + model.S(metCompsNidx(j),:); -0136 idxDelete=[idxDelete; metCompsNidx(j)]; % Make list of metabolite IDs to delete -0137 end -0138 end -0139 end -0140 end -0141 -0142 if ~isempty(idxDelete) -0143 model.S(idxDelete,:) =[]; -0144 model.mets(idxDelete) = []; -0145 model.metNames(idxDelete) = []; -0146 model.metComps(idxDelete) = []; -0147 model.b(idxDelete,:) = []; -0148 if isfield(model,'metFormulas') -0149 model.metFormulas(idxDelete) = []; -0150 end -0151 if isfield(model,'unconstrained') -0152 model.unconstrained(idxDelete) = []; -0153 end -0154 if isfield(model,'metMiriams') -0155 model.metMiriams(idxDelete) = []; +0131 % Build list of metaboliteName[compartment] +0132 metCompsN =cellstr(num2str(model.metComps)); +0133 map = containers.Map(cellstr(num2str(transpose(1:length(model.comps)))),model.comps); +0134 metCompsN = map.values(metCompsN); +0135 metCompsN = strcat(lower(model.metNames),'[',metCompsN,']'); +0136 +0137 for i = 1:length(repIdx) +0138 metCompsNidx=find(strcmp(metCompsN(repIdx(i)), metCompsN)); +0139 if length(metCompsNidx)>1 +0140 for j = 2:length(metCompsNidx) +0141 model.S(metCompsNidx(1),:) = model.S(metCompsNidx(1),:) + model.S(metCompsNidx(j),:); +0142 idxDelete=[idxDelete; metCompsNidx(j)]; % Make list of metabolite IDs to delete +0143 end +0144 end +0145 end +0146 end +0147 +0148 if ~isempty(idxDelete) +0149 model.S(idxDelete,:) =[]; +0150 model.mets(idxDelete) = []; +0151 model.metNames(idxDelete) = []; +0152 model.metComps(idxDelete) = []; +0153 model.b(idxDelete,:) = []; +0154 if isfield(model,'metFormulas') +0155 model.metFormulas(idxDelete) = []; 0156 end -0157 if isfield(model,'metCharges') -0158 model.metCharges(idxDelete) = []; +0157 if isfield(model,'unconstrained') +0158 model.unconstrained(idxDelete) = []; 0159 end -0160 if isfield(model,'metDeltaG') -0161 model.metDeltaG(idxDelete) = []; +0160 if isfield(model,'metMiriams') +0161 model.metMiriams(idxDelete) = []; 0162 end -0163 if isfield(model,'inchis') -0164 model.inchis(idxDelete) = []; +0163 if isfield(model,'metCharges') +0164 model.metCharges(idxDelete) = []; 0165 end -0166 if isfield(model,'metSmiles') -0167 model.metSmiles(idxDelete) = []; +0166 if isfield(model,'metDeltaG') +0167 model.metDeltaG(idxDelete) = []; 0168 end -0169 if isfield(model,'metFrom') -0170 model.metFrom(idxDelete) = []; +0169 if isfield(model,'inchis') +0170 model.inchis(idxDelete) = []; 0171 end -0172 end -0173 -0174 % This could now have created duplicate reactions. Contract model. -0175 % repIdx may point past the end, or at the wrong row, once idxDelete has -0176 % shifted things above; re-resolve the replacement metabolite by id. -0177 % distReverse is passed by name rather than positionally as [], which -0178 % parseRAVENargs would take literally instead of falling back to its -0179 % documented default of true. -0180 repIdx = find(ismember(model.mets, repMetIds)); -0181 model=contractModel(model,'mets',repIdx); -0182 end +0172 if isfield(model,'metSmiles') +0173 model.metSmiles(idxDelete) = []; +0174 end +0175 if isfield(model,'metFrom') +0176 model.metFrom(idxDelete) = []; +0177 end +0178 end +0179 +0180 % This could now have created duplicate reactions. Contract model. +0181 % repIdx may point past the end, or at the wrong row, once idxDelete has +0182 % shifted things above; re-resolve the metabolite that is left by id. An empty +0183 % list must not reach contractModel, which would then check every reaction. +0184 % distReverse is passed by name rather than positionally as [], which +0185 % parseRAVENargs would take literally instead of falling back to its +0186 % documented default of true. +0187 repIdx = find(ismember(model.mets, repMetIds)); +0188 if ~isempty(repIdx) +0189 model=contractModel(model,'mets',repIdx); +0190 end +0191 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/tasks/checkTasks.html b/docs/html/tasks/checkTasks.html index d0bce4c8..b2418923 100644 --- a/docs/html/tasks/checkTasks.html +++ b/docs/html/tasks/checkTasks.html @@ -104,7 +104,9 @@

    CROSS-REFERENCE INFORMATION ^
 </ul>
 <!-- crossreference -->
 
-
+<h2><a name=SUBFUNCTIONS ^

    +

    SOURCE CODE ^

    0001 function [taskReport, essentialRxns, taskStructure, essentialFluxes]=checkTasks(model,inputFile,varargin)
    @@ -221,198 +223,225 @@ 

    SOURCE CODE ^if ~isempty(taskStructure(i).inputs) 0113 [I, J]=ismember(upper(taskStructure(i).inputs),modelMets); 0114 J=J(I); %Only keep the ones with matches -0115 K=ismember(upper(taskStructure(i).inputs),'ALLMETS'); -0116 L=~cellfun('isempty',strfind(upper(taskStructure(i).inputs),'ALLMETSIN')); -0117 %Check that all metabolites are either real metabolites or -0118 %ALLMETS/ALLMETSIN -0119 if ~all(I|K|L) -0120 fprintf(['ERROR: Could not find all inputs in "[' taskStructure(i).id '] ' taskStructure(i).description '"\n']); -0121 ok(i)=false; -0122 continue; -0123 end -0124 if numel(J)~=numel(unique(J)) -0125 EM=['The constraints on some input(s) in "[' taskStructure(i).id '] ' taskStructure(i).description '" are defined more than one time']; -0126 error('RAVEN:badInput', '%s', EM); -0127 end -0128 %If all metabolites should be added -0129 if any(K) -0130 %Check if ALLMETS is the first metabolite. Otherwise print a -0131 %warning since it will write over any other constraints that -0132 %are set -0133 if K(1)==0 -0134 EM=['ALLMETS is used as an input in "[' taskStructure(i).id '] ' taskStructure(i).description '" but it it not the first metabolite in the list. Constraints defined for the metabolites before it will be over-written']; -0135 warning('RAVEN:warning', '%s', EM); -0136 end -0137 %Use the first match of ALLMETS. There should only be one, but -0138 %still.. -0139 tModel.b(:,1)=taskStructure(i).UBin(find(K,1))*-1; -0140 end -0141 %If metabolites in a specific compartment should be used -0142 if any(L) -0143 L=find(L); -0144 for j=1:numel(L) -0145 %The compartment defined -0146 compartment=upper(taskStructure(i).inputs{L(j)}(11:end-1)); -0147 %Check if it exists in the model -0148 C=find(ismember(upper(model.comps),compartment)); -0149 if any(C) -0150 %Match to metabolites -0151 tModel.b(model.metComps==C,1)=taskStructure(i).UBin(L(j))*-1; -0152 else -0153 EM=['The compartment defined for ALLMETSIN in "[' taskStructure(i).id '] ' taskStructure(i).description '" does not exist']; -0154 error('RAVEN:badInput', '%s', EM); -0155 end -0156 end -0157 end -0158 %Then add the normal constraints -0159 if any(J) -0160 tModel.b(J,1)=taskStructure(i).UBin(I)*-1; -0161 tModel.b(J,2)=taskStructure(i).LBin(I)*-1; -0162 end -0163 end -0164 %Set the outputs -0165 if ~isempty(taskStructure(i).outputs) -0166 [I, J]=ismember(upper(taskStructure(i).outputs),modelMets); -0167 J=J(I); %Only keep the ones with matches -0168 K=ismember(upper(taskStructure(i).outputs),'ALLMETS'); -0169 L=~cellfun('isempty',strfind(upper(taskStructure(i).outputs),'ALLMETSIN')); -0170 %Check that all metabolites are either real metabolites or -0171 %ALLMETS/ALLMETSIN -0172 if ~all(I|K|L) -0173 fprintf(['ERROR: Could not find all outputs in "[' taskStructure(i).id '] ' taskStructure(i).description '"\n']); -0174 ok(i)=false; -0175 continue; -0176 end -0177 if numel(J)~=numel(unique(J)) -0178 EM=['The constraints on some output(s) in "[' taskStructure(i).id '] ' taskStructure(i).description '" are defined more than one time']; -0179 error('RAVEN:badInput', '%s', EM); -0180 end -0181 %If all metabolites should be added -0182 if any(K) -0183 %Check if ALLMETS is the first metabolite. Otherwise print a -0184 %warning since it will write over any other constraints that -0185 %are set -0186 if K(1)==0 -0187 EM=['ALLMETS is used as an output in "[' taskStructure(i).id '] ' taskStructure(i).description '" but it it not the first metabolite in the list. Constraints defined for the metabolites before it will be over-written']; -0188 warning('RAVEN:warning', '%s', EM); -0189 end -0190 %Use the first match of ALLMETS. There should only be one, but -0191 %still.. -0192 tModel.b(:,2)=taskStructure(i).UBout(find(K,1)); -0193 end -0194 %If metabolites in a specific compartment should be used -0195 if any(L) -0196 L=find(L); -0197 for j=1:numel(L) -0198 %The compartment defined -0199 compartment=upper(taskStructure(i).outputs{L(j)}(11:end-1)); -0200 %Check if it exists in the model -0201 C=find(ismember(upper(model.comps),compartment)); -0202 if any(C) -0203 %Match to metabolites -0204 tModel.b(model.metComps==C,2)=taskStructure(i).UBout(L(j)); -0205 else -0206 EM=['The compartment defined for ALLMETSIN in "[' taskStructure(i).id '] ' taskStructure(i).description '" does not exist']; -0207 error('RAVEN:badInput', '%s', EM); -0208 end -0209 end -0210 end -0211 %Then add the normal constraints -0212 if any(J) -0213 %Verify that IN and OUT bounds are consistent. Cannot require -0214 %that a metabolite is simultaneously input AND output at some -0215 %nonzero flux. -0216 I = find(I); % otherwise indexing becomes confusing -0217 nonzero_LBin = tModel.b(J,2) < 0; -0218 nonzero_LBout = taskStructure(i).LBout(I) > 0; -0219 if any(nonzero_LBin & nonzero_LBout) -0220 EM=['The IN LB and OUT LB in "[' taskStructure(i).id '] ' taskStructure(i).description '" cannot be nonzero for the same metabolite']; -0221 error('RAVEN:badInput', '%s', EM); -0222 end -0223 tModel.b(J(nonzero_LBout),1)=taskStructure(i).LBout(I(nonzero_LBout)); -0224 tModel.b(J,2)=taskStructure(i).UBout(I); -0225 end -0226 end -0227 %Add new rxns -0228 if ~isempty(taskStructure(i).equations) -0229 % num2str on the whole column right-aligns every row to a common -0230 % width, embedding a leading space in "TEMPORARY_ 1" once any id -0231 % reaches two digits ("TEMPORARY_10"); format each one independently. -0232 rxnIds=arrayfun(@(x) sprintf('TEMPORARY_%d',x), ... -0233 (1:numel(taskStructure(i).equations))', 'UniformOutput', false); -0234 % Built as a single struct() call, not incremental rxn.field=... -0235 % assignments: parfor's variable classifier cannot establish that a -0236 % variable built up via several dot-indexed assignments is a -0237 % same-shape temporary in every iteration, and rejects it outright. -0238 rxn=struct('equations',{taskStructure(i).equations}, ... -0239 'lb',taskStructure(i).LBequ, 'ub',taskStructure(i).UBequ, ... -0240 'rxns',{rxnIds}); -0241 %Allow for new metabolites to be added. This is because it should -0242 %be possible to add, say, a whole new pathway -0243 tModel=addRxns(tModel,rxn,3,[],true); -0244 end -0245 %Add changed bounds -0246 if ~isempty(taskStructure(i).changed) -0247 tModel=setParam(tModel,'lb',taskStructure(i).changed,taskStructure(i).LBrxn); -0248 tModel=setParam(tModel,'ub',taskStructure(i).changed,taskStructure(i).UBrxn); -0249 end -0250 -0251 %Solve and print -0252 sol=solveLP(tModel); -0253 if ~isempty(sol.x) -0254 %assign the fluxes -0255 essentialFluxes(:,i) = sol.x(1:numel(model.rxns)); -0256 -0257 if ~taskStructure(i).shouldFail -0258 ok(i)=true; -0259 if printOnlyFailed==false && printOutput==true -0260 fprintf(['PASS: [' taskStructure(i).id '] ' taskStructure(i).description '\n']); -0261 end -0262 %Calculate the essential reactions -0263 if getEssential==true -0264 [~, taskEssential]=getEssentialRxns(tModel); -0265 %This is because there could be more reactions in tModel -0266 %than in model. Built as a full local column and written -0267 %with a plain ':' row index: parfor only accepts a sliced -0268 %output write when every subscript besides the loop -0269 %variable is ':', not a data-dependent index vector. -0270 essentialCol=false(numel(model.rxns),1); -0271 essentialCol(taskEssential(taskEssential<=numel(model.rxns)))=true; -0272 essentialRxns(:,i)=essentialCol; -0273 end -0274 else -0275 ok(i)=false; -0276 if printOutput==true -0277 fprintf(['PASS (should fail): [' taskStructure(i).id '] ' taskStructure(i).description '\n']); +0115 %Several model metabolites can share a name[comp]. Only the first +0116 %one is constrained, so tell the user when that happens +0117 dupMets=duplicateMetNames(taskStructure(i).inputs(I),modelMets); +0118 if ~isempty(dupMets) +0119 EM=['Several model metabolites match input(s) in "[' taskStructure(i).id '] ' taskStructure(i).description '": ' strjoin(dupMets,', ') '. Only the first match is constrained']; +0120 warning('RAVEN:warning', '%s', EM); +0121 end +0122 K=ismember(upper(taskStructure(i).inputs),'ALLMETS'); +0123 L=~cellfun('isempty',strfind(upper(taskStructure(i).inputs),'ALLMETSIN')); +0124 %Check that all metabolites are either real metabolites or +0125 %ALLMETS/ALLMETSIN +0126 if ~all(I|K|L) +0127 fprintf(['ERROR: Could not find all inputs in "[' taskStructure(i).id '] ' taskStructure(i).description '"\n']); +0128 ok(i)=false; +0129 continue; +0130 end +0131 if numel(J)~=numel(unique(J)) +0132 EM=['The constraints on some input(s) in "[' taskStructure(i).id '] ' taskStructure(i).description '" are defined more than one time']; +0133 error('RAVEN:badInput', '%s', EM); +0134 end +0135 %If all metabolites should be added +0136 if any(K) +0137 %Check if ALLMETS is the first metabolite. Otherwise print a +0138 %warning since it will write over any other constraints that +0139 %are set +0140 if K(1)==0 +0141 EM=['ALLMETS is used as an input in "[' taskStructure(i).id '] ' taskStructure(i).description '" but it it not the first metabolite in the list. Constraints defined for the metabolites before it will be over-written']; +0142 warning('RAVEN:warning', '%s', EM); +0143 end +0144 %Use the first match of ALLMETS. There should only be one, but +0145 %still.. +0146 tModel.b(:,1)=taskStructure(i).UBin(find(K,1))*-1; +0147 end +0148 %If metabolites in a specific compartment should be used +0149 if any(L) +0150 L=find(L); +0151 for j=1:numel(L) +0152 %The compartment defined +0153 compartment=upper(taskStructure(i).inputs{L(j)}(11:end-1)); +0154 %Check if it exists in the model +0155 C=find(ismember(upper(model.comps),compartment)); +0156 if any(C) +0157 %Match to metabolites +0158 tModel.b(model.metComps==C,1)=taskStructure(i).UBin(L(j))*-1; +0159 else +0160 EM=['The compartment defined for ALLMETSIN in "[' taskStructure(i).id '] ' taskStructure(i).description '" does not exist']; +0161 error('RAVEN:badInput', '%s', EM); +0162 end +0163 end +0164 end +0165 %Then add the normal constraints +0166 if any(J) +0167 tModel.b(J,1)=taskStructure(i).UBin(I)*-1; +0168 tModel.b(J,2)=taskStructure(i).LBin(I)*-1; +0169 end +0170 end +0171 %Set the outputs +0172 if ~isempty(taskStructure(i).outputs) +0173 [I, J]=ismember(upper(taskStructure(i).outputs),modelMets); +0174 J=J(I); %Only keep the ones with matches +0175 %Several model metabolites can share a name[comp]. Only the first +0176 %one is constrained, so tell the user when that happens +0177 dupMets=duplicateMetNames(taskStructure(i).outputs(I),modelMets); +0178 if ~isempty(dupMets) +0179 EM=['Several model metabolites match output(s) in "[' taskStructure(i).id '] ' taskStructure(i).description '": ' strjoin(dupMets,', ') '. Only the first match is constrained']; +0180 warning('RAVEN:warning', '%s', EM); +0181 end +0182 K=ismember(upper(taskStructure(i).outputs),'ALLMETS'); +0183 L=~cellfun('isempty',strfind(upper(taskStructure(i).outputs),'ALLMETSIN')); +0184 %Check that all metabolites are either real metabolites or +0185 %ALLMETS/ALLMETSIN +0186 if ~all(I|K|L) +0187 fprintf(['ERROR: Could not find all outputs in "[' taskStructure(i).id '] ' taskStructure(i).description '"\n']); +0188 ok(i)=false; +0189 continue; +0190 end +0191 if numel(J)~=numel(unique(J)) +0192 EM=['The constraints on some output(s) in "[' taskStructure(i).id '] ' taskStructure(i).description '" are defined more than one time']; +0193 error('RAVEN:badInput', '%s', EM); +0194 end +0195 %If all metabolites should be added +0196 if any(K) +0197 %Check if ALLMETS is the first metabolite. Otherwise print a +0198 %warning since it will write over any other constraints that +0199 %are set +0200 if K(1)==0 +0201 EM=['ALLMETS is used as an output in "[' taskStructure(i).id '] ' taskStructure(i).description '" but it it not the first metabolite in the list. Constraints defined for the metabolites before it will be over-written']; +0202 warning('RAVEN:warning', '%s', EM); +0203 end +0204 %Use the first match of ALLMETS. There should only be one, but +0205 %still.. +0206 tModel.b(:,2)=taskStructure(i).UBout(find(K,1)); +0207 end +0208 %If metabolites in a specific compartment should be used +0209 if any(L) +0210 L=find(L); +0211 for j=1:numel(L) +0212 %The compartment defined +0213 compartment=upper(taskStructure(i).outputs{L(j)}(11:end-1)); +0214 %Check if it exists in the model +0215 C=find(ismember(upper(model.comps),compartment)); +0216 if any(C) +0217 %Match to metabolites +0218 tModel.b(model.metComps==C,2)=taskStructure(i).UBout(L(j)); +0219 else +0220 EM=['The compartment defined for ALLMETSIN in "[' taskStructure(i).id '] ' taskStructure(i).description '" does not exist']; +0221 error('RAVEN:badInput', '%s', EM); +0222 end +0223 end +0224 end +0225 %Then add the normal constraints +0226 if any(J) +0227 %Verify that IN and OUT bounds are consistent. Cannot require +0228 %that a metabolite is simultaneously input AND output at some +0229 %nonzero flux. +0230 I = find(I); % otherwise indexing becomes confusing +0231 nonzero_LBin = tModel.b(J,2) < 0; +0232 nonzero_LBout = taskStructure(i).LBout(I) > 0; +0233 if any(nonzero_LBin & nonzero_LBout) +0234 EM=['The IN LB and OUT LB in "[' taskStructure(i).id '] ' taskStructure(i).description '" cannot be nonzero for the same metabolite']; +0235 error('RAVEN:badInput', '%s', EM); +0236 end +0237 tModel.b(J(nonzero_LBout),1)=taskStructure(i).LBout(I(nonzero_LBout)); +0238 tModel.b(J,2)=taskStructure(i).UBout(I); +0239 end +0240 end +0241 %Add new rxns +0242 if ~isempty(taskStructure(i).equations) +0243 % num2str on the whole column right-aligns every row to a common +0244 % width, embedding a leading space in "TEMPORARY_ 1" once any id +0245 % reaches two digits ("TEMPORARY_10"); format each one independently. +0246 rxnIds=arrayfun(@(x) sprintf('TEMPORARY_%d',x), ... +0247 (1:numel(taskStructure(i).equations))', 'UniformOutput', false); +0248 % Built as a single struct() call, not incremental rxn.field=... +0249 % assignments: parfor's variable classifier cannot establish that a +0250 % variable built up via several dot-indexed assignments is a +0251 % same-shape temporary in every iteration, and rejects it outright. +0252 rxn=struct('equations',{taskStructure(i).equations}, ... +0253 'lb',taskStructure(i).LBequ, 'ub',taskStructure(i).UBequ, ... +0254 'rxns',{rxnIds}); +0255 %Allow for new metabolites to be added. This is because it should +0256 %be possible to add, say, a whole new pathway +0257 tModel=addRxns(tModel,rxn,3,[],true); +0258 end +0259 %Add changed bounds +0260 if ~isempty(taskStructure(i).changed) +0261 %NaN bounds mean "leave unchanged" +0262 lbSet=~isnan(taskStructure(i).LBrxn); +0263 ubSet=~isnan(taskStructure(i).UBrxn); +0264 tModel=setParam(tModel,'lb',taskStructure(i).changed(lbSet),taskStructure(i).LBrxn(lbSet)); +0265 tModel=setParam(tModel,'ub',taskStructure(i).changed(ubSet),taskStructure(i).UBrxn(ubSet)); +0266 end +0267 +0268 %Solve and print +0269 sol=solveLP(tModel); +0270 if ~isempty(sol.x) +0271 %assign the fluxes +0272 essentialFluxes(:,i) = sol.x(1:numel(model.rxns)); +0273 +0274 if ~taskStructure(i).shouldFail +0275 ok(i)=true; +0276 if printOnlyFailed==false && printOutput==true +0277 fprintf(['PASS: [' taskStructure(i).id '] ' taskStructure(i).description '\n']); 0278 end -0279 end -0280 else -0281 if ~taskStructure(i).shouldFail -0282 ok(i)=false; -0283 if printOutput==true -0284 fprintf(['FAIL: [' taskStructure(i).id '] ' taskStructure(i).description '\n']); -0285 end -0286 else -0287 ok(i)=true; -0288 if printOnlyFailed==false && printOutput==true -0289 fprintf(['FAIL (should fail): [' taskStructure(i).id '] ' taskStructure(i).description '\n']); +0279 %Calculate the essential reactions +0280 if getEssential==true +0281 [~, taskEssential]=getEssentialRxns(tModel); +0282 %This is because there could be more reactions in tModel +0283 %than in model. Built as a full local column and written +0284 %with a plain ':' row index: parfor only accepts a sliced +0285 %output write when every subscript besides the loop +0286 %variable is ':', not a data-dependent index vector. +0287 essentialCol=false(numel(model.rxns),1); +0288 essentialCol(taskEssential(taskEssential<=numel(model.rxns)))=true; +0289 essentialRxns(:,i)=essentialCol; 0290 end -0291 end -0292 end -0293 if taskStructure(i).printFluxes && ~isempty(sol.x) -0294 sol=solveLP(tModel,1); -0295 if ~isempty(sol.x) -0296 printFluxes(tModel,sol.x,false,10^-6,[],'%rxnID (%eqn):%flux\n'); -0297 fprintf('\n'); -0298 end -0299 end -0300 end -0301 -0302 taskReport.id=ids; -0303 taskReport.description=descriptions; -0304 taskReport.ok=ok; -0305 -0306 end

    +0291 else +0292 ok(i)=false; +0293 if printOutput==true +0294 fprintf(['PASS (should fail): [' taskStructure(i).id '] ' taskStructure(i).description '\n']); +0295 end +0296 end +0297 else +0298 if ~taskStructure(i).shouldFail +0299 ok(i)=false; +0300 if printOutput==true +0301 fprintf(['FAIL: [' taskStructure(i).id '] ' taskStructure(i).description '\n']); +0302 end +0303 else +0304 ok(i)=true; +0305 if printOnlyFailed==false && printOutput==true +0306 fprintf(['FAIL (should fail): [' taskStructure(i).id '] ' taskStructure(i).description '\n']); +0307 end +0308 end +0309 end +0310 if taskStructure(i).printFluxes && ~isempty(sol.x) +0311 sol=solveLP(tModel,1); +0312 if ~isempty(sol.x) +0313 printFluxes(tModel,sol.x,false,10^-6,[],'%rxnID (%eqn):%flux\n'); +0314 fprintf('\n'); +0315 end +0316 end +0317 end +0318 +0319 taskReport.id=ids; +0320 taskReport.description=descriptions; +0321 taskReport.ok=ok; +0322 +0323 end +0324 +0325 function dupMets = duplicateMetNames(names,modelMets) +0326 % Return the (upper-case) names that match more than one model metabolite. +0327 dupMets = unique(upper(names)); +0328 isDup = false(size(dupMets)); +0329 for k = 1:numel(dupMets) +0330 isDup(k) = sum(strcmp(modelMets,dupMets{k})) > 1; +0331 end +0332 dupMets = dupMets(isDup); +0333 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/tasks/parseTaskList.html b/docs/html/tasks/parseTaskList.html index b664cac9..6627f16b 100644 --- a/docs/html/tasks/parseTaskList.html +++ b/docs/html/tasks/parseTaskList.html @@ -69,14 +69,20 @@

    DESCRIPTION ^DESCRIPTION ^CROSS-REFERENCE INFORMATION ^
 <li><a href=checkTasks checkTasks Perform a set of simulations as defined in a task file. - +

    SUBFUNCTIONS ^

    +

    SOURCE CODE ^

    0001 function taskStruct=parseTaskList(inputFile)
    @@ -186,251 +196,294 @@ 

    SOURCE CODE ^% Several IDs can be delimited by ";". If so, then the same bounds 0042 % are used for all reactions. If that is not wanted, then use several 0043 % rows for the task -0044 % - CHANGED LB : lower bound for the reaction -0045 % - CHANGED UB : upper bound for the reaction -0046 % - SHOULD FAIL : true if the correct behavior of the model is to not -0047 % have a feasible solution given the constraints (optional, default -0048 % false) -0049 % - PRINT FLUX : true if the function should print the corresponding -0050 % flux distribution for a task. Can be useful for testing (optional, -0051 % default false) -0052 % -0053 % Returns -0054 % ------- -0055 % taskStruct : struct -0056 % array of structures with the following fields: -0057 % -0058 % - id : the id of the task -0059 % - description : the description of the task -0060 % - shouldFail : true if the task should fail -0061 % - printFluxes : true if the fluxes should be printed -0062 % - comments : string with comments -0063 % - inputs : cell array with input metabolites (in the form metName[comps]) -0064 % - LBin : array with lower bounds on inputs (default, 0) -0065 % - UBin : array with upper bounds on inputs (default, 1000) -0066 % - outputs : cell array with output metabolites (in the form metName[comps]) -0067 % - LBout : array with lower bounds on outputs (default, 0) -0068 % - UBout : array with upper bounds on outputs (default, 1000) -0069 % - equations : cell array with equations (with mets in the form metName[comps]) -0070 % - LBequ : array with lower bounds on equations (default, -1000 for -0071 % reversible and 0 for irreversible) -0072 % - UBequ : array with upper bounds on equations (default, 1000) -0073 % - changed : cell array with reactions to change bounds for -0074 % - LBrxn : array with lower bounds on changed reactions -0075 % - UBrxn : array with upper bounds on changed reactions -0076 % -0077 % Examples -0078 % -------- -0079 % taskStruct = parseTaskList(inputFile); -0080 % -0081 % Notes -0082 % ----- -0083 % This function is used for defining a set of tasks for a model to perform. -0084 % The tasks are defined by defining constraints on the model, and if the -0085 % problem is feasible, then the task is considered successful. In general, -0086 % each row can contain one constraint on uptakes, one constraint on -0087 % outputs, one new equation, and one change of reaction bounds. If more -0088 % bounds are needed to define the task, then several rows can be used for -0089 % each task. To perform the task use checkTasks or fitTasks. -0090 % -0091 % The general metabolites "ALLMETS" and "ALLMETSIN[comps]" can be used as -0092 % inputs or outputs in the similar manner to normal metabolites. This is a -0093 % convenient way to, for example, allow excretion of all metabolites to -0094 % check whether it is the synthesis of some metabolite that is limiting or -0095 % whether it is the degradation of some byproduct. One important difference -0096 % is that only the upper bounds are used for these general metabolites. -0097 % That is, you can only say that uptake or excretion is allowed, not that -0098 % it is required. This is to avoid conflicts where the constraints for the -0099 % general metabolites overwrite those of the real ones. -0100 % -0101 % See also -0102 % -------- -0103 % checkTasks, fitTasks -0104 -0105 if ~isfile(inputFile) -0106 error('Task list %s cannot be found',string(inputFile)); -0107 end -0108 -0109 %Load the tasks file -0110 convNumeric = false; -0111 if strcmp(extractAfter(inputFile,strlength(inputFile) - 4), '.txt') -0112 %load from tab delimited text file -0113 fid = fopen(inputFile); -0114 %Need to read numeric columns as strings, this is converted further -0115 %down. If not, the titles would be lost. -0116 convNumeric = true; -0117 C = textscan(fid,'%q%q%q%q%q%q%q%q%q%q%q%q%q%q%q%q%q%q%q%q%*[^\n]', 'Delimiter', '\t'); -0118 fclose(fid); -0119 raw = [C{:}];%unnest the cell array of cell arrays into a 2-dim cell array -0120 else -0121 %Read the TASKS sheet from an Excel file (readcell handles both .xls -0122 %and .xlsx). -0123 try -0124 raw=readcell(inputFile,'Sheet','TASKS'); -0125 catch -0126 error('RAVEN:badInput', '%s', ['Could not load sheet "TASKS" from ' inputFile]); -0127 end -0128 %Normalise blank cells (which readcell returns as "missing") to [], as -0129 %expected by the downstream cleanSheet/parsing. -0130 raw(cellfun(@(x) all(ismissing(x)),raw))={[]}; -0131 end -0132 -0133 %Remove all lines starting with "#" (or actually any character) and all -0134 %empty columns -0135 raw=cleanSheet(raw); -0136 -0137 %Captions -0138 columns={'ID';'DESCRIPTION';'IN';'IN LB';'IN UB';'OUT';'OUT LB';'OUT UB';'EQU';'EQU LB';'EQU UB';'CHANGED RXN';'CHANGED LB';'CHANGED UB';'SHOULD FAIL';'PRINT FLUX';'COMMENTS'}; -0139 %Match the columns -0140 [I, colI]=ismember(columns,raw(1,:)); -0141 -0142 %Only ID is required. Columns that are absent get index 0 from ismember, which -0143 %cannot be used to index raw, so point them at a spare empty column instead: -0144 %every optional field then reads as empty and its default applies. -0145 raw(:,end+1)={[]}; -0146 colI(colI==0)=size(raw,2); -0147 -0148 %If read from a text file, the numbers will be strings - fix that -0149 if convNumeric % safeguard; a no-op if the columns are already numeric -0150 numericColumns = [0 0 0 1 1 0 1 1 0 1 1 0 1 1 0 0 0] == 1; -0151 cols = colI(numericColumns); -0152 numeric = cellfun(@isnumeric,raw(:,cols)); -0153 %trick to avoid messing up the title row: -0154 numeric(1,:) = 1; -0155 for colind = 1:numel(cols) -0156 col = cols(colind); -0157 raw(~numeric(:,colind),col) = cellfun(@str2num, raw(~numeric(:,colind),col), 'UniformOutput', false); -0158 end -0159 end -0160 -0161 %Check that the ID field is present -0162 if I(1)==0 -0163 EM='The TASKS sheet must have a column named ID'; -0164 error('RAVEN:badInput', '%s', EM); -0165 end -0166 -0167 %make sure numerical fields are converted from strings -0168 -0169 %Add default bounds where needed -0170 for i=[4 5 7 8] -0171 I=cellfun(@isempty,raw(:,colI(i))); -0172 if i==5 || i==8 -0173 raw(I,colI(i))={1000}; -0174 else -0175 raw(I,colI(i))={0}; -0176 end +0044 % - CHANGED LB : lower bound for the reaction. If empty, the lower bound +0045 % of the reaction is left as it is +0046 % - CHANGED UB : upper bound for the reaction. If empty, the upper bound +0047 % of the reaction is left as it is +0048 % - SHOULD FAIL : true if the correct behavior of the model is to not +0049 % have a feasible solution given the constraints. Any non-empty value +0050 % except 0, false and no (any case) counts as true (optional, default +0051 % false) +0052 % - PRINT FLUX : true if the function should print the corresponding +0053 % flux distribution for a task. Can be useful for testing. Read in +0054 % the same way as SHOULD FAIL (optional, default false) +0055 % +0056 % Headers are matched ignoring case and surrounding whitespace. In the +0057 % ";"-delimited cells, items are trimmed and empty items are dropped. +0058 % +0059 % Returns +0060 % ------- +0061 % taskStruct : struct +0062 % array of structures with the following fields: +0063 % +0064 % - id : the id of the task +0065 % - description : the description of the task +0066 % - shouldFail : true if the task should fail +0067 % - printFluxes : true if the fluxes should be printed +0068 % - comments : string with comments +0069 % - inputs : cell array with input metabolites (in the form metName[comps]) +0070 % - LBin : array with lower bounds on inputs (default, 0) +0071 % - UBin : array with upper bounds on inputs (default, 1000) +0072 % - outputs : cell array with output metabolites (in the form metName[comps]) +0073 % - LBout : array with lower bounds on outputs (default, 0) +0074 % - UBout : array with upper bounds on outputs (default, 1000) +0075 % - equations : cell array with equations (with mets in the form metName[comps]) +0076 % - LBequ : array with lower bounds on equations (default, -1000 for +0077 % reversible and 0 for irreversible) +0078 % - UBequ : array with upper bounds on equations (default, 1000) +0079 % - changed : cell array with reactions to change bounds for +0080 % - LBrxn : array with lower bounds on changed reactions (NaN if empty, +0081 % meaning the bound is left unchanged) +0082 % - UBrxn : array with upper bounds on changed reactions (NaN if empty, +0083 % meaning the bound is left unchanged) +0084 % +0085 % Examples +0086 % -------- +0087 % taskStruct = parseTaskList(inputFile); +0088 % +0089 % Notes +0090 % ----- +0091 % This function is used for defining a set of tasks for a model to perform. +0092 % The tasks are defined by defining constraints on the model, and if the +0093 % problem is feasible, then the task is considered successful. In general, +0094 % each row can contain one constraint on uptakes, one constraint on +0095 % outputs, one new equation, and one change of reaction bounds. If more +0096 % bounds are needed to define the task, then several rows can be used for +0097 % each task. To perform the task use checkTasks or fitTasks. +0098 % +0099 % The general metabolites "ALLMETS" and "ALLMETSIN[comps]" can be used as +0100 % inputs or outputs in the similar manner to normal metabolites. This is a +0101 % convenient way to, for example, allow excretion of all metabolites to +0102 % check whether it is the synthesis of some metabolite that is limiting or +0103 % whether it is the degradation of some byproduct. One important difference +0104 % is that only the upper bounds are used for these general metabolites. +0105 % That is, you can only say that uptake or excretion is allowed, not that +0106 % it is required. This is to avoid conflicts where the constraints for the +0107 % general metabolites overwrite those of the real ones. +0108 % +0109 % See also +0110 % -------- +0111 % checkTasks, fitTasks +0112 +0113 if ~isfile(inputFile) +0114 error('Task list %s cannot be found',string(inputFile)); +0115 end +0116 +0117 %Load the tasks file +0118 convNumeric = false; +0119 if strcmp(extractAfter(inputFile,strlength(inputFile) - 4), '.txt') +0120 %load from tab delimited text file +0121 fid = fopen(inputFile); +0122 %Need to read numeric columns as strings, this is converted further +0123 %down. If not, the titles would be lost. +0124 convNumeric = true; +0125 C = textscan(fid,'%q%q%q%q%q%q%q%q%q%q%q%q%q%q%q%q%q%q%q%q%*[^\n]', 'Delimiter', '\t'); +0126 fclose(fid); +0127 raw = [C{:}];%unnest the cell array of cell arrays into a 2-dim cell array +0128 else +0129 %Read the TASKS sheet from an Excel file (readcell handles both .xls +0130 %and .xlsx). +0131 try +0132 raw=readcell(inputFile,'Sheet','TASKS'); +0133 catch +0134 error('RAVEN:badInput', '%s', ['Could not load sheet "TASKS" from ' inputFile]); +0135 end +0136 %Normalise blank cells (which readcell returns as "missing") to [], as +0137 %expected by the downstream cleanSheet/parsing. +0138 raw(cellfun(@(x) all(ismissing(x)),raw))={[]}; +0139 end +0140 +0141 %Remove all lines starting with "#" (or actually any character) and all +0142 %empty columns +0143 raw=cleanSheet(raw); +0144 +0145 %Captions +0146 columns={'ID';'DESCRIPTION';'IN';'IN LB';'IN UB';'OUT';'OUT LB';'OUT UB';'EQU';'EQU LB';'EQU UB';'CHANGED RXN';'CHANGED LB';'CHANGED UB';'SHOULD FAIL';'PRINT FLUX';'COMMENTS'}; +0147 %Match the columns. Headers are matched ignoring case and surrounding +0148 %whitespace, so "id" and " IN LB " are accepted. +0149 header=raw(1,:); +0150 isText=cellfun(@ischar,header); +0151 header(isText)=upper(strtrim(header(isText))); +0152 [I, colI]=ismember(columns,header); +0153 +0154 %Only ID is required. Columns that are absent get index 0 from ismember, which +0155 %cannot be used to index raw, so point them at a spare empty column instead: +0156 %every optional field then reads as empty and its default applies. +0157 raw(:,end+1)={[]}; +0158 colI(colI==0)=size(raw,2); +0159 +0160 %If read from a text file, the numbers will be strings - fix that +0161 if convNumeric % safeguard; a no-op if the columns are already numeric +0162 numericColumns = [0 0 0 1 1 0 1 1 0 1 1 0 1 1 0 0 0] == 1; +0163 cols = colI(numericColumns); +0164 numeric = cellfun(@isnumeric,raw(:,cols)); +0165 %trick to avoid messing up the title row: +0166 numeric(1,:) = 1; +0167 for colind = 1:numel(cols) +0168 col = cols(colind); +0169 raw(~numeric(:,colind),col) = cellfun(@str2num, raw(~numeric(:,colind),col), 'UniformOutput', false); +0170 end +0171 end +0172 +0173 %Check that the ID field is present +0174 if I(1)==0 +0175 EM='The TASKS sheet must have a column named ID'; +0176 error('RAVEN:badInput', '%s', EM); 0177 end 0178 -0179 %Create an empty task structure -0180 eTask.id=''; -0181 eTask.description=''; -0182 eTask.shouldFail=false; -0183 eTask.printFluxes=false; -0184 eTask.comments=''; -0185 eTask.inputs={}; -0186 eTask.LBin=[]; -0187 eTask.UBin=[]; -0188 eTask.outputs={}; -0189 eTask.LBout=[]; -0190 eTask.UBout=[]; -0191 eTask.equations={}; -0192 eTask.LBequ=[]; -0193 eTask.UBequ=[]; -0194 eTask.changed={}; -0195 eTask.LBrxn=[]; -0196 eTask.UBrxn=[]; -0197 -0198 %Main loop -0199 taskStruct=[]; -0200 task=eTask; -0201 if isnumeric(raw{2,colI(1)}) -0202 task.id=num2str(raw{2,colI(1)}); -0203 else -0204 task.id=raw{2,colI(1)}; -0205 end -0206 task.description=raw{2,colI(2)}; -0207 if ~isempty(raw{2,colI(15)}) -0208 task.shouldFail=true; -0209 end -0210 if ~isempty(raw{2,colI(16)}) -0211 task.printFluxes=true; -0212 end -0213 if ~isempty(raw{2,colI(17)}) -0214 task.comments=raw{2,colI(17)}; -0215 end -0216 -0217 for i=2:size(raw,1) -0218 %Set the inputs -0219 if ischar(raw{i,colI(3)}) -0220 inputs=regexp(raw{i,colI(3)},';','split'); -0221 task.inputs=[task.inputs;inputs(:)]; -0222 task.LBin=[task.LBin;ones(numel(inputs),1)*raw{i,colI(4)}]; -0223 task.UBin=[task.UBin;ones(numel(inputs),1)*raw{i,colI(5)}]; -0224 end -0225 %Set the outputs -0226 if ischar(raw{i,colI(6)}) -0227 outputs=regexp(raw{i,colI(6)},';','split'); -0228 task.outputs=[task.outputs;outputs(:)]; -0229 task.LBout=[task.LBout;ones(numel(outputs),1)*raw{i,colI(7)}]; -0230 task.UBout=[task.UBout;ones(numel(outputs),1)*raw{i,colI(8)}]; -0231 end -0232 %Add new rxns -0233 if ischar(raw{i,colI(9)}) -0234 task.equations=[task.equations;raw{i,colI(9)}]; -0235 if ~isempty(raw{i,colI(10)}) -0236 task.LBequ=[task.LBequ;raw{i,colI(10)}]; -0237 else -0238 if any(strfind(raw{i,colI(9)},'<=>')) -0239 task.LBequ=[task.LBequ;-1000]; -0240 else -0241 task.LBequ=[task.LBequ;0]; -0242 end -0243 end -0244 if ~isempty(raw{i,colI(11)}) -0245 task.UBequ=[task.UBequ;raw{i,colI(11)}]; -0246 else -0247 task.UBequ=[task.UBequ;1000]; -0248 end -0249 end -0250 %Add changed bounds -0251 if ischar(raw{i,colI(12)}) -0252 changed=regexp(raw{i,colI(12)},';','split'); -0253 task.changed=[task.changed;changed(:)]; -0254 task.LBrxn=[task.LBrxn;ones(numel(changed),1)*raw{i,colI(13)}]; -0255 task.UBrxn=[task.UBrxn;ones(numel(changed),1)*raw{i,colI(14)}]; -0256 end -0257 -0258 %Check if it should add more constraints -0259 if i<size(raw,1) -0260 if isempty(raw{i+1,colI(1)}) -0261 continue; -0262 end -0263 end -0264 -0265 taskStruct=[taskStruct;task]; -0266 task=eTask; -0267 if i<size(raw,1) -0268 if isnumeric(raw{i+1,colI(1)}) -0269 task.id=num2str(raw{i+1,colI(1)}); -0270 else -0271 task.id=raw{i+1,colI(1)}; -0272 end -0273 task.description=raw{i+1,colI(2)}; -0274 if ~isempty(raw{i+1,colI(15)}) -0275 task.shouldFail=true; -0276 end -0277 if ~isempty(raw{i+1,colI(16)}) -0278 task.printFluxes=true; -0279 end -0280 if ~isempty(raw{i+1,colI(17)}) -0281 task.comments=raw{i+1,colI(17)}; -0282 end -0283 end -0284 end -0285 -0286 %Should add more checks, such as unique IDs and missing headers -0287 -0288 end

    +0179 %make sure numerical fields are converted from strings +0180 +0181 %Add default bounds where needed +0182 for i=[4 5 7 8] +0183 I=cellfun(@isempty,raw(:,colI(i))); +0184 if i==5 || i==8 +0185 raw(I,colI(i))={1000}; +0186 else +0187 raw(I,colI(i))={0}; +0188 end +0189 end +0190 +0191 %Create an empty task structure +0192 eTask.id=''; +0193 eTask.description=''; +0194 eTask.shouldFail=false; +0195 eTask.printFluxes=false; +0196 eTask.comments=''; +0197 eTask.inputs={}; +0198 eTask.LBin=[]; +0199 eTask.UBin=[]; +0200 eTask.outputs={}; +0201 eTask.LBout=[]; +0202 eTask.UBout=[]; +0203 eTask.equations={}; +0204 eTask.LBequ=[]; +0205 eTask.UBequ=[]; +0206 eTask.changed={}; +0207 eTask.LBrxn=[]; +0208 eTask.UBrxn=[]; +0209 +0210 %Main loop +0211 taskStruct=[]; +0212 task=eTask; +0213 if isnumeric(raw{2,colI(1)}) +0214 task.id=num2str(raw{2,colI(1)}); +0215 else +0216 task.id=raw{2,colI(1)}; +0217 end +0218 task.description=raw{2,colI(2)}; +0219 if isTruthy(raw{2,colI(15)}) +0220 task.shouldFail=true; +0221 end +0222 if isTruthy(raw{2,colI(16)}) +0223 task.printFluxes=true; +0224 end +0225 if ~isempty(raw{2,colI(17)}) +0226 task.comments=raw{2,colI(17)}; +0227 end +0228 +0229 for i=2:size(raw,1) +0230 %Set the inputs +0231 if ischar(raw{i,colI(3)}) +0232 inputs=splitList(raw{i,colI(3)}); +0233 task.inputs=[task.inputs;inputs(:)]; +0234 task.LBin=[task.LBin;ones(numel(inputs),1)*raw{i,colI(4)}]; +0235 task.UBin=[task.UBin;ones(numel(inputs),1)*raw{i,colI(5)}]; +0236 end +0237 %Set the outputs +0238 if ischar(raw{i,colI(6)}) +0239 outputs=splitList(raw{i,colI(6)}); +0240 task.outputs=[task.outputs;outputs(:)]; +0241 task.LBout=[task.LBout;ones(numel(outputs),1)*raw{i,colI(7)}]; +0242 task.UBout=[task.UBout;ones(numel(outputs),1)*raw{i,colI(8)}]; +0243 end +0244 %Add new rxns +0245 if ischar(raw{i,colI(9)}) +0246 task.equations=[task.equations;raw{i,colI(9)}]; +0247 if ~isempty(raw{i,colI(10)}) +0248 task.LBequ=[task.LBequ;raw{i,colI(10)}]; +0249 else +0250 if any(strfind(raw{i,colI(9)},'<=>')) +0251 task.LBequ=[task.LBequ;-1000]; +0252 else +0253 task.LBequ=[task.LBequ;0]; +0254 end +0255 end +0256 if ~isempty(raw{i,colI(11)}) +0257 task.UBequ=[task.UBequ;raw{i,colI(11)}]; +0258 else +0259 task.UBequ=[task.UBequ;1000]; +0260 end +0261 end +0262 %Add changed bounds +0263 if ischar(raw{i,colI(12)}) +0264 changed=splitList(raw{i,colI(12)}); +0265 task.changed=[task.changed;changed(:)]; +0266 %An empty bound means "leave this bound as it is", stored as NaN +0267 task.LBrxn=[task.LBrxn;ones(numel(changed),1)*boundOrNaN(raw{i,colI(13)})]; +0268 task.UBrxn=[task.UBrxn;ones(numel(changed),1)*boundOrNaN(raw{i,colI(14)})]; +0269 end +0270 +0271 %Check if it should add more constraints +0272 if i<size(raw,1) +0273 if isempty(raw{i+1,colI(1)}) +0274 continue; +0275 end +0276 end +0277 +0278 taskStruct=[taskStruct;task]; +0279 task=eTask; +0280 if i<size(raw,1) +0281 if isnumeric(raw{i+1,colI(1)}) +0282 task.id=num2str(raw{i+1,colI(1)}); +0283 else +0284 task.id=raw{i+1,colI(1)}; +0285 end +0286 task.description=raw{i+1,colI(2)}; +0287 if isTruthy(raw{i+1,colI(15)}) +0288 task.shouldFail=true; +0289 end +0290 if isTruthy(raw{i+1,colI(16)}) +0291 task.printFluxes=true; +0292 end +0293 if ~isempty(raw{i+1,colI(17)}) +0294 task.comments=raw{i+1,colI(17)}; +0295 end +0296 end +0297 end +0298 +0299 %Should add more checks, such as unique IDs and missing headers +0300 +0301 end +0302 +0303 function tf = isTruthy(x) +0304 % A flag cell is true unless it is empty, 0, or one of the texts "0", "false" +0305 % and "no" (any case). +0306 if isempty(x) +0307 tf = false; +0308 elseif ischar(x) || isstring(x) +0309 tf = ~ismember(lower(strtrim(char(x))),{'','0','false','no'}); +0310 elseif isnumeric(x) || islogical(x) +0311 tf = any(x(:) ~= 0); +0312 else +0313 tf = true; +0314 end +0315 end +0316 +0317 function items = splitList(str) +0318 % Split a ";"-delimited cell on ";", trim each item and drop empty ones, so +0319 % that "A; B;" gives {'A';'B'}. +0320 items = strtrim(regexp(str,';','split')); +0321 items = items(~cellfun(@isempty,items)); +0322 end +0323 +0324 function b = boundOrNaN(x) +0325 % Bound for a changed reaction: NaN (leave unchanged) when the cell is empty. +0326 if isempty(x) +0327 b = NaN; +0328 else +0329 b = x; +0330 end +0331 end
    Generated by m2html © 2005
    \ No newline at end of file From 0406546e37194aa3a0bb02b96ffc0b5f7a946d37 Mon Sep 17 00:00:00 2001 From: Eduard Kerkhoven Date: Fri, 23 Oct 2026 23:13:13 +0200 Subject: [PATCH 31/48] fix: read notes maps in readYAMLmodel (#12) * fix: readYAMLmodel reads notes maps into the note text cobrapy and raven-toolbox write other tools' notes on metabolites and reactions as a map under notes. readYAMLmodel stopped with 'Unknown entry' on them; it now reads each entry into metNotes or rxnNotes. * fix: read notes-map lines by indentation, not by key A key in a notes map named like an entry field (id, name, annotation) was read as that field, and list values in the map lost their items. --- src/io/readYAMLmodel.m | 72 +++++++++++++++++++++++++++++++++++--- tests/function_tests/tIO.m | 39 +++++++++++++++++++++ 2 files changed, 107 insertions(+), 4 deletions(-) diff --git a/src/io/readYAMLmodel.m b/src/io/readYAMLmodel.m index 90a3333c..e8b5c351 100755 --- a/src/io/readYAMLmodel.m +++ b/src/io/readYAMLmodel.m @@ -195,6 +195,7 @@ end section = 0; +readList = ''; metMiriams=cell(100000,3); metMirNo=1; rxnMiriams=cell(100000,3); rxnMirNo=1; geneMiriams=cell(100000,3); genMirNo=1; @@ -346,6 +347,12 @@ % import metabolites: if section == 2 + % The lines of a notes map are indented below the entry's own + % fields; their keys are not entry fields, whatever their name. + if strcmp(readList, 'notes') && ~isempty(regexp(tline_raw, '^ {6,}', 'once')) + model = appendNote(model, 'metNotes', tline_raw, tline_key, tline_value, pos); + continue + end switch tline_key case 'id' pos = pos + 1; @@ -364,8 +371,14 @@ model = readFieldValue(model, 'metCharges', tline_value, pos); readList=''; miriamKey=''; case 'notes' - model = readFieldValue(model, 'metNotes', tline_value, pos); - readList=''; miriamKey=''; + % A notes map (other tools' keys, as cobrapy and + % raven-toolbox write them) is read into the note text. + if isempty(tline_value) || strcmp(tline_value, '!!omap') + readList = 'notes'; + else + model = readFieldValue(model, 'metNotes', tline_value, pos); + readList=''; miriamKey=''; + end case 'inchis' model = readFieldValue(model, 'inchis', tline_value, pos); readList=''; miriamKey=''; @@ -421,6 +434,12 @@ % import reactions: if section == 3 + % The lines of a notes map are indented below the entry's own + % fields; their keys are not entry fields, whatever their name. + if strcmp(readList, 'notes') && ~isempty(regexp(tline_raw, '^ {6,}', 'once')) + model = appendNote(model, 'rxnNotes', tline_raw, tline_key, tline_value, pos); + continue + end switch tline_key case 'id' pos = pos + 1; @@ -445,8 +464,12 @@ % `notes` is the canonical reaction-side key; `rxnNotes` % is the legacy RAVEN MATLAB key, kept here for % backward-compatible reads. - model = readFieldValue(model, 'rxnNotes', tline_value, pos); - readList=''; miriamKey=''; + if isempty(tline_value) || strcmp(tline_value, '!!omap') + readList = 'notes'; + else + model = readFieldValue(model, 'rxnNotes', tline_value, pos); + readList=''; miriamKey=''; + end case 'rxnFrom' model = readFieldValue(model, 'rxnFrom', tline_value, pos); readList=''; miriamKey=''; @@ -878,6 +901,47 @@ model.(fieldName)(pos,1) = {value}; end +function model = appendNote(model, fieldName, raw, key, value, pos) +% One line of a notes map: a 'note' entry adds its value (RAVEN's own note +% text), another entry adds 'key: value', a list item adds its value to the +% entry above it. Entries are separated by '; ', list items by ', '. A +% quoted list item is read whole, as the key detection would split it at +% a ': ' inside the quotes. +item = regexp(raw, '^ +- ([''"])(.*)\1$', 'tokens', 'once'); +if ~isempty(item) + key = ''; + value = item{2}; +end +if strcmp(value, '!!omap') + value = ''; +end +text = ''; +if numel(model.(fieldName)) >= pos + text = model.(fieldName){pos}; +end +if isempty(key) + if isempty(text) || endsWith(text, ':') + text = strtrim([text ' ' value]); + else + text = [text ', ' value]; + end +else + if isempty(value) + entry = [key ':']; + elseif strcmp(key, 'note') + entry = value; + else + entry = [key ': ' value]; + end + if isempty(text) + text = entry; + else + text = [text '; ' entry]; + end +end +model = readFieldValue(model, fieldName, text, pos); +end + function [miriams,miriamKey,entryNumber] = gatherAnnotation(pos,miriams,key,value,miriamKey,entryNumber) if isempty(key) key=miriamKey; diff --git a/tests/function_tests/tIO.m b/tests/function_tests/tIO.m index 30f0ff6a..fe5cb78c 100644 --- a/tests/function_tests/tIO.m +++ b/tests/function_tests/tIO.m @@ -342,6 +342,45 @@ function writeReadYAMLPreservesUnknownAnnotationFields(testCase) testCase.verifyEqual(m2.annotation.geckopyVersion, '0.2.1'); end + function readYAMLmodelReadsNotesMaps(testCase) + % cobrapy and raven-toolbox write other tools' notes as a map + % under "notes"; its entries are read into the note text. + m = testCase.model; + m.metNotes = repmat({''}, numel(m.mets), 1); + m.rxnNotes = repmat({''}, numel(m.rxns), 1); + m.metNotes{1} = 'METPLACEHOLDER'; + m.rxnNotes{1} = 'RXNPLACEHOLDER'; + f = [tempname '.yml']; + testCase.addTeardown(@() delete(f)); + evalc('writeYAMLmodel(m, f);'); + txt = fileread(f); + % The metabolite's map has a key named like an entry field and a + % quoted list item with ': ', and is followed by an annotation. + txt = regexprep(txt, '( *)- notes: METPLACEHOLDER', ... + ['$1- notes: !!omap\n$1 - enzyme_usage: Enzyme-usage pseudometabolite\n' ... + '$1 - name: other\n$1 - refs:\n$1 - "PMID: 1"\n$1 - b\n' ... + '$1- annotation: !!omap\n$1 - sbo: SBO:0000252']); + % The reaction's map comes before other fields, has keys named + % like entry fields and a list value. + txt = regexprep(txt, '( *)- notes: RXNPLACEHOLDER', ... + ['$1- notes: !!omap\n$1 - geckopy_warning: no match for g1\n$1 - id: R99\n' ... + '$1 - name: other\n$1 - refs:\n$1 - a\n$1 - b\n$1 - note: curated\n' ... + '$1- rxnFrom: test']); + fid = fopen(f, 'w'); fwrite(fid, txt); fclose(fid); + evalc('m2 = readYAMLmodel(f);'); + testCase.verifyEqual(m2.metNotes{1}, ... + 'enzyme_usage: Enzyme-usage pseudometabolite; name: other; refs: PMID: 1, b'); + testCase.verifyEqual(m2.metNames, m.metNames); + testCase.verifyTrue(any(strcmp(m2.metMiriams{1}.name, 'sbo'))); + testCase.verifyEqual(m2.rxnNotes{1}, ... + 'geckopy_warning: no match for g1; id: R99; name: other; refs: a, b; curated'); + testCase.verifyEqual(m2.rxns, m.rxns); + testCase.verifyEqual(m2.rxnNames, m.rxnNames); + testCase.verifyEqual(m2.rxnFrom{1}, 'test'); + testCase.verifyEqual(numel(m2.mets), numel(m.mets)); + testCase.verifyEqual(numel(m2.rxns), numel(m.rxns)); + end + function exportForGitWritesRepo(testCase) d = [tempname filesep]; mkdir(d); From f8d65952c87720ad909757cc2b5571bffecdb0a6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 23 Oct 2026 21:15:00 +0000 Subject: [PATCH 32/48] doc: regenerate HTML documentation for 0406546 [skip ci] --- docs/html/io/readYAMLmodel.html | 1428 ++++++++++++++++--------------- 1 file changed, 746 insertions(+), 682 deletions(-) diff --git a/docs/html/io/readYAMLmodel.html b/docs/html/io/readYAMLmodel.html index 631a0cb3..d565424d 100644 --- a/docs/html/io/readYAMLmodel.html +++ b/docs/html/io/readYAMLmodel.html @@ -61,7 +61,7 @@

    CROSS-REFERENCE INFORMATION ^
 
 <h2><a name=SUBFUNCTIONS ^

    +
  • function model = emptyOrFill(model,field,emptyEntry,type,keepEmpty)
  • function model = readFieldValue(model, fieldName, value, pos)
  • function model = appendNote(model, fieldName, raw, key, value, pos)
  • function [miriams,miriamKey,entryNumber] = gatherAnnotation(pos,miriams,key,value,miriamKey,entryNumber)
  • SOURCE CODE ^

    0001 function model=readYAMLmodel(varargin)
    @@ -261,700 +261,764 @@ 

    SOURCE CODE ^end 0196 0197 section = 0; -0198 metMiriams=cell(100000,3); metMirNo=1; -0199 rxnMiriams=cell(100000,3); rxnMirNo=1; -0200 geneMiriams=cell(100000,3); genMirNo=1; -0201 compMiriams=cell(1000,3); compMirNo=1; -0202 subSystems=cell(100000,2); subSysNo=1; -0203 eccodes=cell(100000,2); ecCodeNo=1; -0204 equations=cell(100000,3); equatiNo=1; -0205 -0206 for i=1:numel(line_key) -0207 tline_raw = line_raw{i}; -0208 tline_key = line_key{i}; -0209 tline_value = line_value{i}; -0210 % import different sections — accept the !!omap-tagged variant as -0211 % well, so a file using that tag is recognized too. -0212 switch tline_raw -0213 case {'- metaData:', '- metaData: !!omap'} -0214 section = 1; -0215 if verbose -0216 fprintf('\t%d\n', section); -0217 end -0218 continue % Go to next line -0219 case '- metabolites:' -0220 section = 2; -0221 if verbose -0222 fprintf('\t%d\n', section); -0223 end -0224 pos=0; -0225 continue -0226 case '- reactions:' -0227 section = 3; -0228 if verbose -0229 fprintf('\t%d\n', section); -0230 end -0231 pos=0; -0232 continue -0233 case '- genes:' -0234 section = 4; -0235 if verbose -0236 fprintf('\t%d\n', section); -0237 end -0238 pos=0; -0239 continue -0240 case '- genes: []' -0241 % Flow-style empty list: a gene-less model. No entries follow, -0242 % so there is nothing to switch into --- the next recognised -0243 % header is matched fresh on the next iteration regardless of -0244 % what `section` is left as. -0245 continue -0246 case '- compartments: !!omap' -0247 section = 5; -0248 if verbose -0249 fprintf('\t%d\n', section); -0250 end -0251 pos=0; -0252 continue -0253 case '- ec-rxns:' -0254 section = 6; -0255 if verbose -0256 fprintf('\t%d\n', section); -0257 end -0258 pos=0; -0259 continue -0260 case '- ec-enzymes:' -0261 section = 7; -0262 if verbose -0263 fprintf('\t%d\n', section); -0264 end -0265 pos=0; -0266 continue -0267 end -0268 -0269 % Alternate root-level keys (id, name, version, gecko_light), written -0270 % at the top level with no metaData section. RAVEN's own writer -0271 % normally puts them inside metaData, but we accept both layouts so a -0272 % YAML file using the top-level convention can be ingested directly. -0273 if isempty(regexp(tline_raw, '^ {2,}', 'once')) -0274 switch tline_key -0275 case 'id' -0276 if isempty(model.id), model.id = tline_value; end -0277 continue -0278 case 'name' -0279 if isempty(model.name), model.name = tline_value; end -0280 continue -0281 case 'version' -0282 if ~isfield(model,'version') || isempty(model.version) -0283 model.version = tline_value; -0284 end -0285 continue -0286 case 'gecko_light' -0287 if isGECKO && strcmp(tline_value,'true') -0288 model.ec.geckoLight = true; -0289 end -0290 continue -0291 end -0292 end -0293 -0294 % skip over empty keys -0295 if isempty(tline_raw) || (isempty(tline_key) && contains(tline_raw,'!!omap')) -0296 continue; -0297 end -0298 -0299 % import metaData -0300 if section == 1 -0301 switch tline_key -0302 case {'short_name','id'} %short_name used by human-GEM -0303 model.id = tline_value; -0304 case 'name' -0305 model.name = tline_value; -0306 case 'full_name' %used by human-GEM -0307 model.description = tline_value; -0308 case 'version' -0309 model.version = tline_value; -0310 case 'date' -0311 model.date = tline_value; -0312 case 'taxonomy' -0313 model.annotation.taxonomy = tline_value; -0314 case {'description','note'} %description used by human-GEM -0315 model.annotation.note = tline_value; -0316 case 'github' -0317 model.annotation.sourceUrl = tline_value; -0318 case 'sourceUrl' -0319 model.annotation.sourceUrl = tline_value; -0320 case 'givenName' -0321 model.annotation.givenName = tline_value; -0322 case 'familyName' -0323 model.annotation.familyName = tline_value; -0324 case 'authors' -0325 model.annotation.authorList = tline_value; -0326 case 'email' -0327 model.annotation.email = tline_value; -0328 case 'organization' -0329 model.annotation.organization = tline_value; -0330 case {'geckoLight','gecko_light'} -0331 if strcmp(tline_value,'true') -0332 model.ec.geckoLight = true; -0333 end -0334 otherwise -0335 % Caller-specific provenance key with no RAVEN-defined slot -0336 % (e.g. a tool-specific `_version` key): preserve it instead -0337 % of silently dropping it, so a write/read round trip is -0338 % lossless. Mirrors writeYAMLmodel.m's symmetric fallback, -0339 % which emits any model.annotation field not in its own -0340 % fixed list. -0341 if isvarname(tline_key) -0342 model.annotation.(tline_key) = tline_value; -0343 end -0344 end; continue -0345 end -0346 -0347 % import metabolites: -0348 if section == 2 -0349 switch tline_key -0350 case 'id' -0351 pos = pos + 1; -0352 model = readFieldValue(model, 'mets', tline_value,pos); -0353 readList=''; miriamKey=''; -0354 case 'name' -0355 model = readFieldValue(model, 'metNames', tline_value, pos); -0356 readList=''; miriamKey=''; -0357 case 'compartment' -0358 model = readFieldValue(model, 'metComps', tline_value, pos); -0359 readList=''; miriamKey=''; -0360 case 'formula' -0361 model = readFieldValue(model, 'metFormulas', tline_value, pos); -0362 readList=''; miriamKey=''; -0363 case 'charge' -0364 model = readFieldValue(model, 'metCharges', tline_value, pos); -0365 readList=''; miriamKey=''; -0366 case 'notes' -0367 model = readFieldValue(model, 'metNotes', tline_value, pos); -0368 readList=''; miriamKey=''; -0369 case 'inchis' -0370 model = readFieldValue(model, 'inchis', tline_value, pos); -0371 readList=''; miriamKey=''; -0372 case 'smiles' -0373 % Top-level (legacy MATLAB) and inside-annotation (current -0374 % writer) layouts both land here. The current writer -0375 % always emits smiles as a block list, even for a single -0376 % value; a state distinct from 'annotation' -0377 % ('smilesInAnnotation') reads that list so that once it -0378 % ends this can resume annotation gathering rather than -0379 % getting stuck treating every later line in the same -0380 % annotation block as another smiles entry. A single-line -0381 % "smiles: value" (legacy, or empty tline_value absent) -0382 % doesn't reset readList — preserves the annotation -0383 % gathering state if SMILES is nested inside the -0384 % annotation block alongside other entries. -0385 if isempty(tline_value) -0386 readList = 'smilesInAnnotation'; -0387 else -0388 model = readFieldValue(model, 'metSmiles', tline_value, pos); -0389 end -0390 case 'deltaG' -0391 model = readFieldValue(model, 'metDeltaG', tline_value, pos); -0392 readList=''; miriamKey=''; -0393 case 'metFrom' -0394 model = readFieldValue(model, 'metFrom', tline_value, pos); -0395 readList=''; miriamKey=''; -0396 case 'annotation' -0397 readList = 'annotation'; -0398 otherwise -0399 switch readList -0400 case 'annotation' -0401 [metMiriams, miriamKey, metMirNo] = gatherAnnotation(pos,metMiriams,tline_key,tline_value,miriamKey,metMirNo); -0402 case 'smilesInAnnotation' -0403 % A bare list item ("- OC(=O)...") has no colon, -0404 % so tline_key is empty --- still inside the list. -0405 % Anything else is the annotation block's next -0406 % entry, not another smiles value: the list has -0407 % ended (RAVEN keeps only one smiles per -0408 % metabolite, so a second list item, if any, -0409 % simply overwrites the first). -0410 if isempty(tline_key) -0411 model = readFieldValue(model, 'metSmiles', regexprep(tline_value,'^ +- "?(.*)"?$','$1'), pos); -0412 else -0413 readList = 'annotation'; -0414 [metMiriams, miriamKey, metMirNo] = gatherAnnotation(pos,metMiriams,tline_key,tline_value,miriamKey,metMirNo); -0415 end -0416 otherwise -0417 error(['Unknown entry in yaml file: ' tline_raw]) -0418 end -0419 end; continue -0420 end -0421 -0422 % import reactions: -0423 if section == 3 -0424 switch tline_key -0425 case 'id' -0426 pos = pos + 1; -0427 model = readFieldValue(model, 'rxns', tline_value,pos); -0428 readList=''; miriamKey=''; -0429 case 'name' -0430 model = readFieldValue(model, 'rxnNames', tline_value, pos); -0431 readList=''; miriamKey=''; -0432 case 'lower_bound' -0433 model.lb(pos,1) = {tline_value}; -0434 readList=''; miriamKey=''; -0435 case 'upper_bound' -0436 model.ub(pos,1) = {tline_value}; -0437 readList=''; miriamKey=''; -0438 case 'rev' -0439 model.rev(pos,1) = {tline_value}; -0440 readList=''; miriamKey=''; -0441 case 'gene_reaction_rule' -0442 model = readFieldValue(model, 'grRules', tline_value, pos); -0443 readList=''; miriamKey=''; -0444 case {'notes','rxnNotes'} -0445 % `notes` is the canonical reaction-side key; `rxnNotes` -0446 % is the legacy RAVEN MATLAB key, kept here for -0447 % backward-compatible reads. -0448 model = readFieldValue(model, 'rxnNotes', tline_value, pos); -0449 readList=''; miriamKey=''; -0450 case 'rxnFrom' -0451 model = readFieldValue(model, 'rxnFrom', tline_value, pos); -0452 readList=''; miriamKey=''; -0453 case 'deltaG' -0454 model = readFieldValue(model, 'rxnDeltaG', tline_value, pos); -0455 readList=''; miriamKey=''; -0456 case 'objective_coefficient' -0457 model.c(pos,1) = str2double(tline_value); -0458 readList=''; miriamKey=''; -0459 case 'references' -0460 model = readFieldValue(model, 'rxnReferences', tline_value, pos); -0461 readList=''; miriamKey=''; -0462 case 'confidence_score' -0463 model = readFieldValue(model, 'rxnConfidenceScores', tline_value, pos); -0464 readList=''; miriamKey=''; -0465 case 'eccodes' -0466 if isempty(tline_value) -0467 readList = 'eccodes'; -0468 else -0469 eccodes(ecCodeNo,1:2)={pos,tline_value}; -0470 ecCodeNo=ecCodeNo+1; -0471 end -0472 case 'ec-code' -0473 % EC numbers emitted inside the `annotation` block -0474 % (current writer). Captured into model.eccodes the same -0475 % way metabolite `smiles` is pulled out of the annotation -0476 % block; the legacy top-level `eccodes` key above is still -0477 % accepted for older files. A block list of codes follows -0478 % when the value is empty, read via a state distinct from -0479 % the legacy 'eccodes' above ('eccodesInAnnotation', not -0480 % 'eccodes') specifically so that once the list ends this -0481 % can resume annotation gathering rather than getting -0482 % stuck treating every later line in the same annotation -0483 % block as another EC code. -0484 if isempty(tline_value) -0485 readList = 'eccodesInAnnotation'; -0486 else -0487 eccodes(ecCodeNo,1:2)={pos,tline_value}; -0488 ecCodeNo=ecCodeNo+1; -0489 end -0490 case 'subsystem' -0491 if isempty(tline_value) -0492 readList = 'subsystem'; -0493 else -0494 subSystems(subSysNo,1:2)={pos,tline_value}; -0495 subSysNo=subSysNo+1; -0496 end -0497 case 'metabolites' -0498 readList = 'equation'; -0499 case 'annotation' -0500 readList = 'annotation'; -0501 -0502 otherwise -0503 switch readList -0504 case 'eccodes' -0505 eccodes(ecCodeNo,1:2)={pos,regexprep(tline_value,'^ +- "?(.*)"?$','$1')}; -0506 ecCodeNo=ecCodeNo+1; -0507 case 'eccodesInAnnotation' -0508 % A bare list item ("- 1.1.2.4") has no colon, so -0509 % tline_key is empty --- still inside the list. -0510 % Anything else is the annotation block's next -0511 % entry (e.g. "kegg.reaction: ..."), not another -0512 % EC code: the list has ended, so resume normal -0513 % annotation gathering for this line and all that -0514 % follow, instead of silently absorbing them here. -0515 if isempty(tline_key) -0516 eccodes(ecCodeNo,1:2)={pos,regexprep(tline_value,'^ +- "?(.*)"?$','$1')}; -0517 ecCodeNo=ecCodeNo+1; -0518 else -0519 readList = 'annotation'; -0520 [rxnMiriams, miriamKey,rxnMirNo] = gatherAnnotation(pos,rxnMiriams,tline_key,tline_value,miriamKey,rxnMirNo); -0521 end -0522 case 'subsystem' -0523 subSystems(subSysNo,1:2)={pos,regexprep(tline_value,'^ +- "?(.*)"?$','$1')}; -0524 subSysNo=subSysNo+1; -0525 case 'annotation' -0526 [rxnMiriams, miriamKey,rxnMirNo] = gatherAnnotation(pos,rxnMiriams,tline_key,tline_value,miriamKey,rxnMirNo); -0527 case 'equation' -0528 coeff = sscanf(tline_value,'%f'); -0529 equations(equatiNo,1:3)={pos,tline_key,coeff}; -0530 equatiNo=equatiNo+1; -0531 otherwise -0532 error(['Unknown entry in yaml file: ' tline_raw]) -0533 end -0534 end; continue -0535 end -0536 -0537 % import genes: -0538 if section == 4 -0539 switch tline_key -0540 case 'id' -0541 pos = pos + 1; -0542 model = readFieldValue(model, 'genes', tline_value, pos); -0543 readList = ''; -0544 miriamKey = ''; -0545 case 'name' -0546 model = readFieldValue(model, 'geneShortNames', tline_value, pos); -0547 case 'protein' -0548 model = readFieldValue(model, 'proteins', tline_value, pos); -0549 case 'annotation' -0550 readList = 'annotation'; -0551 otherwise -0552 switch readList -0553 case 'annotation' -0554 [geneMiriams, miriamKey,genMirNo] = gatherAnnotation(pos,geneMiriams,tline_key,tline_value,miriamKey,genMirNo); -0555 otherwise -0556 error(['Unknown entry in yaml file: ' tline_raw]) -0557 end -0558 end; continue -0559 end -0560 -0561 % import compartments: -0562 if section == 5 -0563 switch tline_key -0564 case 'annotation' -0565 readList = 'annotation'; -0566 otherwise -0567 % Annotation entries are indented deeper than the -0568 % compartment they belong to, so anything at compartment -0569 % level ends the annotation and starts a new compartment. -0570 if strcmp(readList,'annotation') && ~isempty(regexp(tline_raw,'^ {6,}','once')) -0571 [compMiriams, miriamKey, compMirNo] = gatherAnnotation(pos,compMiriams,tline_key,tline_value,miriamKey,compMirNo); -0572 else -0573 pos = pos + 1; -0574 model.comps(end+1,1) = {tline_key}; -0575 model.compNames(end+1,1) = {tline_value}; -0576 readList = ''; miriamKey = ''; -0577 end -0578 end; continue -0579 end -0580 -0581 % import ec reaction info -0582 if section == 6 -0583 switch tline_key -0584 case 'id' -0585 pos = pos + 1; -0586 model.ec = readFieldValue(model.ec, 'rxns', tline_value, pos); -0587 readList=''; -0588 case 'kcat' -0589 model.ec = readFieldValue(model.ec, 'kcat', tline_value, pos); -0590 readList=''; -0591 case 'source' -0592 model.ec = readFieldValue(model.ec, 'source', tline_value, pos); -0593 readList=''; -0594 case 'notes' -0595 model.ec = readFieldValue(model.ec, 'notes', tline_value, pos); -0596 readList=''; -0597 case 'eccodes' -0598 if isempty(tline_value) -0599 readList = 'eccodes'; -0600 else -0601 ecGecko(ecGeckoNo,1:2)={pos,tline_value}; -0602 ecGeckoNo=ecGeckoNo+1; -0603 end -0604 case 'enzymes' -0605 readList = 'enzStoich'; -0606 otherwise -0607 switch readList -0608 case 'eccodes' -0609 ecGecko(ecGeckoNo,1:2)={pos,regexprep(tline_value,'^ +- "?(.*)"?$','$1')}; -0610 ecGeckoNo=ecGeckoNo+1; -0611 case 'enzStoich' -0612 coeff = sscanf(tline_value,'%f'); -0613 enzStoich(enzStoichNo,1:3)={pos,tline_key,coeff}; -0614 enzStoichNo=enzStoichNo+1; -0615 otherwise -0616 error(['Unknown entry in yaml file: ' tline_raw]) -0617 end -0618 end; continue -0619 end -0620 -0621 % import ec enzyme info -0622 if section == 7 -0623 switch tline_key -0624 case 'genes' -0625 pos = pos + 1; -0626 model.ec = readFieldValue(model.ec, 'genes', tline_value, pos); +0198 readList = ''; +0199 metMiriams=cell(100000,3); metMirNo=1; +0200 rxnMiriams=cell(100000,3); rxnMirNo=1; +0201 geneMiriams=cell(100000,3); genMirNo=1; +0202 compMiriams=cell(1000,3); compMirNo=1; +0203 subSystems=cell(100000,2); subSysNo=1; +0204 eccodes=cell(100000,2); ecCodeNo=1; +0205 equations=cell(100000,3); equatiNo=1; +0206 +0207 for i=1:numel(line_key) +0208 tline_raw = line_raw{i}; +0209 tline_key = line_key{i}; +0210 tline_value = line_value{i}; +0211 % import different sections — accept the !!omap-tagged variant as +0212 % well, so a file using that tag is recognized too. +0213 switch tline_raw +0214 case {'- metaData:', '- metaData: !!omap'} +0215 section = 1; +0216 if verbose +0217 fprintf('\t%d\n', section); +0218 end +0219 continue % Go to next line +0220 case '- metabolites:' +0221 section = 2; +0222 if verbose +0223 fprintf('\t%d\n', section); +0224 end +0225 pos=0; +0226 continue +0227 case '- reactions:' +0228 section = 3; +0229 if verbose +0230 fprintf('\t%d\n', section); +0231 end +0232 pos=0; +0233 continue +0234 case '- genes:' +0235 section = 4; +0236 if verbose +0237 fprintf('\t%d\n', section); +0238 end +0239 pos=0; +0240 continue +0241 case '- genes: []' +0242 % Flow-style empty list: a gene-less model. No entries follow, +0243 % so there is nothing to switch into --- the next recognised +0244 % header is matched fresh on the next iteration regardless of +0245 % what `section` is left as. +0246 continue +0247 case '- compartments: !!omap' +0248 section = 5; +0249 if verbose +0250 fprintf('\t%d\n', section); +0251 end +0252 pos=0; +0253 continue +0254 case '- ec-rxns:' +0255 section = 6; +0256 if verbose +0257 fprintf('\t%d\n', section); +0258 end +0259 pos=0; +0260 continue +0261 case '- ec-enzymes:' +0262 section = 7; +0263 if verbose +0264 fprintf('\t%d\n', section); +0265 end +0266 pos=0; +0267 continue +0268 end +0269 +0270 % Alternate root-level keys (id, name, version, gecko_light), written +0271 % at the top level with no metaData section. RAVEN's own writer +0272 % normally puts them inside metaData, but we accept both layouts so a +0273 % YAML file using the top-level convention can be ingested directly. +0274 if isempty(regexp(tline_raw, '^ {2,}', 'once')) +0275 switch tline_key +0276 case 'id' +0277 if isempty(model.id), model.id = tline_value; end +0278 continue +0279 case 'name' +0280 if isempty(model.name), model.name = tline_value; end +0281 continue +0282 case 'version' +0283 if ~isfield(model,'version') || isempty(model.version) +0284 model.version = tline_value; +0285 end +0286 continue +0287 case 'gecko_light' +0288 if isGECKO && strcmp(tline_value,'true') +0289 model.ec.geckoLight = true; +0290 end +0291 continue +0292 end +0293 end +0294 +0295 % skip over empty keys +0296 if isempty(tline_raw) || (isempty(tline_key) && contains(tline_raw,'!!omap')) +0297 continue; +0298 end +0299 +0300 % import metaData +0301 if section == 1 +0302 switch tline_key +0303 case {'short_name','id'} %short_name used by human-GEM +0304 model.id = tline_value; +0305 case 'name' +0306 model.name = tline_value; +0307 case 'full_name' %used by human-GEM +0308 model.description = tline_value; +0309 case 'version' +0310 model.version = tline_value; +0311 case 'date' +0312 model.date = tline_value; +0313 case 'taxonomy' +0314 model.annotation.taxonomy = tline_value; +0315 case {'description','note'} %description used by human-GEM +0316 model.annotation.note = tline_value; +0317 case 'github' +0318 model.annotation.sourceUrl = tline_value; +0319 case 'sourceUrl' +0320 model.annotation.sourceUrl = tline_value; +0321 case 'givenName' +0322 model.annotation.givenName = tline_value; +0323 case 'familyName' +0324 model.annotation.familyName = tline_value; +0325 case 'authors' +0326 model.annotation.authorList = tline_value; +0327 case 'email' +0328 model.annotation.email = tline_value; +0329 case 'organization' +0330 model.annotation.organization = tline_value; +0331 case {'geckoLight','gecko_light'} +0332 if strcmp(tline_value,'true') +0333 model.ec.geckoLight = true; +0334 end +0335 otherwise +0336 % Caller-specific provenance key with no RAVEN-defined slot +0337 % (e.g. a tool-specific `_version` key): preserve it instead +0338 % of silently dropping it, so a write/read round trip is +0339 % lossless. Mirrors writeYAMLmodel.m's symmetric fallback, +0340 % which emits any model.annotation field not in its own +0341 % fixed list. +0342 if isvarname(tline_key) +0343 model.annotation.(tline_key) = tline_value; +0344 end +0345 end; continue +0346 end +0347 +0348 % import metabolites: +0349 if section == 2 +0350 % The lines of a notes map are indented below the entry's own +0351 % fields; their keys are not entry fields, whatever their name. +0352 if strcmp(readList, 'notes') && ~isempty(regexp(tline_raw, '^ {6,}', 'once')) +0353 model = appendNote(model, 'metNotes', tline_raw, tline_key, tline_value, pos); +0354 continue +0355 end +0356 switch tline_key +0357 case 'id' +0358 pos = pos + 1; +0359 model = readFieldValue(model, 'mets', tline_value,pos); +0360 readList=''; miriamKey=''; +0361 case 'name' +0362 model = readFieldValue(model, 'metNames', tline_value, pos); +0363 readList=''; miriamKey=''; +0364 case 'compartment' +0365 model = readFieldValue(model, 'metComps', tline_value, pos); +0366 readList=''; miriamKey=''; +0367 case 'formula' +0368 model = readFieldValue(model, 'metFormulas', tline_value, pos); +0369 readList=''; miriamKey=''; +0370 case 'charge' +0371 model = readFieldValue(model, 'metCharges', tline_value, pos); +0372 readList=''; miriamKey=''; +0373 case 'notes' +0374 % A notes map (other tools' keys, as cobrapy and +0375 % raven-toolbox write them) is read into the note text. +0376 if isempty(tline_value) || strcmp(tline_value, '!!omap') +0377 readList = 'notes'; +0378 else +0379 model = readFieldValue(model, 'metNotes', tline_value, pos); +0380 readList=''; miriamKey=''; +0381 end +0382 case 'inchis' +0383 model = readFieldValue(model, 'inchis', tline_value, pos); +0384 readList=''; miriamKey=''; +0385 case 'smiles' +0386 % Top-level (legacy MATLAB) and inside-annotation (current +0387 % writer) layouts both land here. The current writer +0388 % always emits smiles as a block list, even for a single +0389 % value; a state distinct from 'annotation' +0390 % ('smilesInAnnotation') reads that list so that once it +0391 % ends this can resume annotation gathering rather than +0392 % getting stuck treating every later line in the same +0393 % annotation block as another smiles entry. A single-line +0394 % "smiles: value" (legacy, or empty tline_value absent) +0395 % doesn't reset readList — preserves the annotation +0396 % gathering state if SMILES is nested inside the +0397 % annotation block alongside other entries. +0398 if isempty(tline_value) +0399 readList = 'smilesInAnnotation'; +0400 else +0401 model = readFieldValue(model, 'metSmiles', tline_value, pos); +0402 end +0403 case 'deltaG' +0404 model = readFieldValue(model, 'metDeltaG', tline_value, pos); +0405 readList=''; miriamKey=''; +0406 case 'metFrom' +0407 model = readFieldValue(model, 'metFrom', tline_value, pos); +0408 readList=''; miriamKey=''; +0409 case 'annotation' +0410 readList = 'annotation'; +0411 otherwise +0412 switch readList +0413 case 'annotation' +0414 [metMiriams, miriamKey, metMirNo] = gatherAnnotation(pos,metMiriams,tline_key,tline_value,miriamKey,metMirNo); +0415 case 'smilesInAnnotation' +0416 % A bare list item ("- OC(=O)...") has no colon, +0417 % so tline_key is empty --- still inside the list. +0418 % Anything else is the annotation block's next +0419 % entry, not another smiles value: the list has +0420 % ended (RAVEN keeps only one smiles per +0421 % metabolite, so a second list item, if any, +0422 % simply overwrites the first). +0423 if isempty(tline_key) +0424 model = readFieldValue(model, 'metSmiles', regexprep(tline_value,'^ +- "?(.*)"?$','$1'), pos); +0425 else +0426 readList = 'annotation'; +0427 [metMiriams, miriamKey, metMirNo] = gatherAnnotation(pos,metMiriams,tline_key,tline_value,miriamKey,metMirNo); +0428 end +0429 otherwise +0430 error(['Unknown entry in yaml file: ' tline_raw]) +0431 end +0432 end; continue +0433 end +0434 +0435 % import reactions: +0436 if section == 3 +0437 % The lines of a notes map are indented below the entry's own +0438 % fields; their keys are not entry fields, whatever their name. +0439 if strcmp(readList, 'notes') && ~isempty(regexp(tline_raw, '^ {6,}', 'once')) +0440 model = appendNote(model, 'rxnNotes', tline_raw, tline_key, tline_value, pos); +0441 continue +0442 end +0443 switch tline_key +0444 case 'id' +0445 pos = pos + 1; +0446 model = readFieldValue(model, 'rxns', tline_value,pos); +0447 readList=''; miriamKey=''; +0448 case 'name' +0449 model = readFieldValue(model, 'rxnNames', tline_value, pos); +0450 readList=''; miriamKey=''; +0451 case 'lower_bound' +0452 model.lb(pos,1) = {tline_value}; +0453 readList=''; miriamKey=''; +0454 case 'upper_bound' +0455 model.ub(pos,1) = {tline_value}; +0456 readList=''; miriamKey=''; +0457 case 'rev' +0458 model.rev(pos,1) = {tline_value}; +0459 readList=''; miriamKey=''; +0460 case 'gene_reaction_rule' +0461 model = readFieldValue(model, 'grRules', tline_value, pos); +0462 readList=''; miriamKey=''; +0463 case {'notes','rxnNotes'} +0464 % `notes` is the canonical reaction-side key; `rxnNotes` +0465 % is the legacy RAVEN MATLAB key, kept here for +0466 % backward-compatible reads. +0467 if isempty(tline_value) || strcmp(tline_value, '!!omap') +0468 readList = 'notes'; +0469 else +0470 model = readFieldValue(model, 'rxnNotes', tline_value, pos); +0471 readList=''; miriamKey=''; +0472 end +0473 case 'rxnFrom' +0474 model = readFieldValue(model, 'rxnFrom', tline_value, pos); +0475 readList=''; miriamKey=''; +0476 case 'deltaG' +0477 model = readFieldValue(model, 'rxnDeltaG', tline_value, pos); +0478 readList=''; miriamKey=''; +0479 case 'objective_coefficient' +0480 model.c(pos,1) = str2double(tline_value); +0481 readList=''; miriamKey=''; +0482 case 'references' +0483 model = readFieldValue(model, 'rxnReferences', tline_value, pos); +0484 readList=''; miriamKey=''; +0485 case 'confidence_score' +0486 model = readFieldValue(model, 'rxnConfidenceScores', tline_value, pos); +0487 readList=''; miriamKey=''; +0488 case 'eccodes' +0489 if isempty(tline_value) +0490 readList = 'eccodes'; +0491 else +0492 eccodes(ecCodeNo,1:2)={pos,tline_value}; +0493 ecCodeNo=ecCodeNo+1; +0494 end +0495 case 'ec-code' +0496 % EC numbers emitted inside the `annotation` block +0497 % (current writer). Captured into model.eccodes the same +0498 % way metabolite `smiles` is pulled out of the annotation +0499 % block; the legacy top-level `eccodes` key above is still +0500 % accepted for older files. A block list of codes follows +0501 % when the value is empty, read via a state distinct from +0502 % the legacy 'eccodes' above ('eccodesInAnnotation', not +0503 % 'eccodes') specifically so that once the list ends this +0504 % can resume annotation gathering rather than getting +0505 % stuck treating every later line in the same annotation +0506 % block as another EC code. +0507 if isempty(tline_value) +0508 readList = 'eccodesInAnnotation'; +0509 else +0510 eccodes(ecCodeNo,1:2)={pos,tline_value}; +0511 ecCodeNo=ecCodeNo+1; +0512 end +0513 case 'subsystem' +0514 if isempty(tline_value) +0515 readList = 'subsystem'; +0516 else +0517 subSystems(subSysNo,1:2)={pos,tline_value}; +0518 subSysNo=subSysNo+1; +0519 end +0520 case 'metabolites' +0521 readList = 'equation'; +0522 case 'annotation' +0523 readList = 'annotation'; +0524 +0525 otherwise +0526 switch readList +0527 case 'eccodes' +0528 eccodes(ecCodeNo,1:2)={pos,regexprep(tline_value,'^ +- "?(.*)"?$','$1')}; +0529 ecCodeNo=ecCodeNo+1; +0530 case 'eccodesInAnnotation' +0531 % A bare list item ("- 1.1.2.4") has no colon, so +0532 % tline_key is empty --- still inside the list. +0533 % Anything else is the annotation block's next +0534 % entry (e.g. "kegg.reaction: ..."), not another +0535 % EC code: the list has ended, so resume normal +0536 % annotation gathering for this line and all that +0537 % follow, instead of silently absorbing them here. +0538 if isempty(tline_key) +0539 eccodes(ecCodeNo,1:2)={pos,regexprep(tline_value,'^ +- "?(.*)"?$','$1')}; +0540 ecCodeNo=ecCodeNo+1; +0541 else +0542 readList = 'annotation'; +0543 [rxnMiriams, miriamKey,rxnMirNo] = gatherAnnotation(pos,rxnMiriams,tline_key,tline_value,miriamKey,rxnMirNo); +0544 end +0545 case 'subsystem' +0546 subSystems(subSysNo,1:2)={pos,regexprep(tline_value,'^ +- "?(.*)"?$','$1')}; +0547 subSysNo=subSysNo+1; +0548 case 'annotation' +0549 [rxnMiriams, miriamKey,rxnMirNo] = gatherAnnotation(pos,rxnMiriams,tline_key,tline_value,miriamKey,rxnMirNo); +0550 case 'equation' +0551 coeff = sscanf(tline_value,'%f'); +0552 equations(equatiNo,1:3)={pos,tline_key,coeff}; +0553 equatiNo=equatiNo+1; +0554 otherwise +0555 error(['Unknown entry in yaml file: ' tline_raw]) +0556 end +0557 end; continue +0558 end +0559 +0560 % import genes: +0561 if section == 4 +0562 switch tline_key +0563 case 'id' +0564 pos = pos + 1; +0565 model = readFieldValue(model, 'genes', tline_value, pos); +0566 readList = ''; +0567 miriamKey = ''; +0568 case 'name' +0569 model = readFieldValue(model, 'geneShortNames', tline_value, pos); +0570 case 'protein' +0571 model = readFieldValue(model, 'proteins', tline_value, pos); +0572 case 'annotation' +0573 readList = 'annotation'; +0574 otherwise +0575 switch readList +0576 case 'annotation' +0577 [geneMiriams, miriamKey,genMirNo] = gatherAnnotation(pos,geneMiriams,tline_key,tline_value,miriamKey,genMirNo); +0578 otherwise +0579 error(['Unknown entry in yaml file: ' tline_raw]) +0580 end +0581 end; continue +0582 end +0583 +0584 % import compartments: +0585 if section == 5 +0586 switch tline_key +0587 case 'annotation' +0588 readList = 'annotation'; +0589 otherwise +0590 % Annotation entries are indented deeper than the +0591 % compartment they belong to, so anything at compartment +0592 % level ends the annotation and starts a new compartment. +0593 if strcmp(readList,'annotation') && ~isempty(regexp(tline_raw,'^ {6,}','once')) +0594 [compMiriams, miriamKey, compMirNo] = gatherAnnotation(pos,compMiriams,tline_key,tline_value,miriamKey,compMirNo); +0595 else +0596 pos = pos + 1; +0597 model.comps(end+1,1) = {tline_key}; +0598 model.compNames(end+1,1) = {tline_value}; +0599 readList = ''; miriamKey = ''; +0600 end +0601 end; continue +0602 end +0603 +0604 % import ec reaction info +0605 if section == 6 +0606 switch tline_key +0607 case 'id' +0608 pos = pos + 1; +0609 model.ec = readFieldValue(model.ec, 'rxns', tline_value, pos); +0610 readList=''; +0611 case 'kcat' +0612 model.ec = readFieldValue(model.ec, 'kcat', tline_value, pos); +0613 readList=''; +0614 case 'source' +0615 model.ec = readFieldValue(model.ec, 'source', tline_value, pos); +0616 readList=''; +0617 case 'notes' +0618 model.ec = readFieldValue(model.ec, 'notes', tline_value, pos); +0619 readList=''; +0620 case 'eccodes' +0621 if isempty(tline_value) +0622 readList = 'eccodes'; +0623 else +0624 ecGecko(ecGeckoNo,1:2)={pos,tline_value}; +0625 ecGeckoNo=ecGeckoNo+1; +0626 end 0627 case 'enzymes' -0628 model.ec = readFieldValue(model.ec, 'enzymes', tline_value, pos); -0629 case 'mw' -0630 model.ec = readFieldValue(model.ec, 'mw', tline_value, pos); -0631 case 'sequence' -0632 model.ec = readFieldValue(model.ec, 'sequence', tline_value, pos); -0633 case 'concs' -0634 model.ec = readFieldValue(model.ec, 'concs', tline_value, pos); -0635 otherwise -0636 error(['Unknown entry in yaml file: ' tline_raw]) -0637 end; continue -0638 end -0639 end -0640 -0641 %Parse annotations -0642 if ~isempty(metMiriams) -0643 locs = cell2mat(metMiriams(:,1)); -0644 for i=unique(locs)' -0645 model.metMiriams{i,1}.name=metMiriams(locs==i,2); -0646 model.metMiriams{i,1}.value=metMiriams(locs==i,3); -0647 end -0648 end -0649 if ~isempty(rxnMiriams) -0650 locs = cell2mat(rxnMiriams(:,1)); -0651 for i=unique(locs)' -0652 model.rxnMiriams{i,1}.name=rxnMiriams(locs==i,2); -0653 model.rxnMiriams{i,1}.value=rxnMiriams(locs==i,3); -0654 end -0655 end -0656 if ~isempty(geneMiriams) -0657 locs = cell2mat(geneMiriams(:,1)); -0658 for i=unique(locs)' -0659 model.geneMiriams{i,1}.name=geneMiriams(locs==i,2); -0660 model.geneMiriams{i,1}.value=geneMiriams(locs==i,3); +0628 readList = 'enzStoich'; +0629 otherwise +0630 switch readList +0631 case 'eccodes' +0632 ecGecko(ecGeckoNo,1:2)={pos,regexprep(tline_value,'^ +- "?(.*)"?$','$1')}; +0633 ecGeckoNo=ecGeckoNo+1; +0634 case 'enzStoich' +0635 coeff = sscanf(tline_value,'%f'); +0636 enzStoich(enzStoichNo,1:3)={pos,tline_key,coeff}; +0637 enzStoichNo=enzStoichNo+1; +0638 otherwise +0639 error(['Unknown entry in yaml file: ' tline_raw]) +0640 end +0641 end; continue +0642 end +0643 +0644 % import ec enzyme info +0645 if section == 7 +0646 switch tline_key +0647 case 'genes' +0648 pos = pos + 1; +0649 model.ec = readFieldValue(model.ec, 'genes', tline_value, pos); +0650 case 'enzymes' +0651 model.ec = readFieldValue(model.ec, 'enzymes', tline_value, pos); +0652 case 'mw' +0653 model.ec = readFieldValue(model.ec, 'mw', tline_value, pos); +0654 case 'sequence' +0655 model.ec = readFieldValue(model.ec, 'sequence', tline_value, pos); +0656 case 'concs' +0657 model.ec = readFieldValue(model.ec, 'concs', tline_value, pos); +0658 otherwise +0659 error(['Unknown entry in yaml file: ' tline_raw]) +0660 end; continue 0661 end 0662 end -0663 if ~isempty(compMiriams) -0664 locs = cell2mat(compMiriams(:,1)); -0665 for i=unique(locs)' -0666 model.compMiriams{i,1}.name=compMiriams(locs==i,2); -0667 model.compMiriams{i,1}.value=compMiriams(locs==i,3); -0668 end -0669 end -0670 -0671 %Parse subSystems -0672 if ~isempty(subSystems) -0673 locs = cell2mat(subSystems(:,1)); +0663 +0664 %Parse annotations +0665 if ~isempty(metMiriams) +0666 locs = cell2mat(metMiriams(:,1)); +0667 for i=unique(locs)' +0668 model.metMiriams{i,1}.name=metMiriams(locs==i,2); +0669 model.metMiriams{i,1}.value=metMiriams(locs==i,3); +0670 end +0671 end +0672 if ~isempty(rxnMiriams) +0673 locs = cell2mat(rxnMiriams(:,1)); 0674 for i=unique(locs)' -0675 model.subSystems{i,1}=subSystems(locs==i,2); -0676 end -0677 end -0678 -0679 %Parse ec-codes -0680 if ~isempty(eccodes) -0681 locs = cell2mat(eccodes(:,1)); -0682 for i=unique(locs)' -0683 eccodesCat=strjoin(eccodes(locs==i,2),';'); -0684 model.eccodes{i,1}=eccodesCat; -0685 end -0686 emptyEc=cellfun('isempty',model.eccodes); -0687 model.eccodes(emptyEc)={''}; -0688 end -0689 -0690 % follow-up data processing -0691 if verbose -0692 fprintf('\nimporting completed\nfollow-up processing...'); -0693 end -0694 [~, model.metComps] = ismember(model.metComps, model.comps); -0695 [~, model.geneComps] = ismember(model.geneComps, model.comps); -0696 [~, model.rxnComps] = ismember(model.rxnComps, model.comps); -0697 % A metabolite with no explicit compartment is still ismember'd above (it -0698 % parses as ''), so it resolves to 0 -- not the "assume first compartment" -0699 % default the emptyOrFill call further down intends -- unless it happens -0700 % to be the trailing metabolite in the file, the only case emptyOrFill -0701 % actually reaches (it only extends a too-short array, and ismember has -0702 % already made this one full-length). Catch every 0 here so the default -0703 % applies regardless of position, not just at the end of the list. -0704 model.metComps(model.metComps==0) = 1; -0705 -0706 % Fill S-matrix -0707 rxnIdx = cellfun('isempty', equations(:,1)); -0708 equations(rxnIdx,:) = ''; -0709 rxnIdx = cell2mat(equations(:,1)); -0710 [~,metIdx] = ismember(equations(:,2),model.mets); -0711 coeffs = cell2mat(equations(:,3)); -0712 model.S=sparse(max(metIdx),max(rxnIdx)); -0713 linearIndices = sub2ind([max(metIdx), max(rxnIdx)],metIdx,rxnIdx); -0714 model.S(linearIndices) = coeffs; -0715 -0716 % Convert strings to numeric -0717 model.metCharges = str2double(model.metCharges); -0718 model.lb = str2double(model.lb); -0719 model.ub = str2double(model.ub); -0720 model.rxnConfidenceScores = str2double(model.rxnConfidenceScores); -0721 model.b = zeros(length(model.mets),1); -0722 model.metDeltaG = str2double(model.metDeltaG); -0723 model.rxnDeltaG = str2double(model.rxnDeltaG); -0724 -0725 % Fill some other fields -0726 model.annotation.defaultLB = min(model.lb); -0727 model.annotation.defaultUB = max(model.ub); -0728 if numel(model.lb)<numel(model.rxns) %No LB reported = min -0729 model.lb(end+1:numel(model.rxns)-numel(model.lb),1) = double(model.annotation.defaultLB); -0730 end -0731 if numel(model.ub)<numel(model.rxns) %No UB reported = max -0732 model.ub(end+1:numel(model.rxns)-numel(model.ub),1) = double(model.annotation.defaultUB); -0733 end -0734 if ~all(cellfun('isempty',model.rev)) -0735 model.rev = str2double(model.rev); -0736 else -0737 model.rev = []; -0738 end -0739 if numel(model.rev)<numel(model.rxns) %No rev reported, assume from LB and UB -0740 model.rev(end+1:numel(model.rxns)-numel(model.rev),1) = double(model.lb<0 & model.ub>0); -0741 end -0742 -0743 % Remove empty fields, otherwise fill to correct length -0744 % Reactions -0745 for i={'rxnNames','grRules','eccodes','rxnNotes','rxnReferences',... -0746 'rxnFrom','subSystems','rxnMiriams'} % Empty strings -0747 model = emptyOrFill(model,i{1},{''},'rxns'); -0748 end -0749 for i={'c'} % Zeros -0750 model = emptyOrFill(model,i{1},0,'rxns',true); -0751 end -0752 for i={'rxnConfidenceScores','rxnDeltaG'} % NaNs -0753 model = emptyOrFill(model,i{1},NaN,'rxns'); -0754 end -0755 for i={'rxnComps'} % Ones, assume first compartment -0756 model = emptyOrFill(model,i{1},1,'rxns'); -0757 end -0758 % Metabolites -0759 for i={'metNames','inchis','metFormulas','metMiriams','metFrom','metSmiles','metNotes'} % Empty strings -0760 model = emptyOrFill(model,i{1},{''},'mets'); +0675 model.rxnMiriams{i,1}.name=rxnMiriams(locs==i,2); +0676 model.rxnMiriams{i,1}.value=rxnMiriams(locs==i,3); +0677 end +0678 end +0679 if ~isempty(geneMiriams) +0680 locs = cell2mat(geneMiriams(:,1)); +0681 for i=unique(locs)' +0682 model.geneMiriams{i,1}.name=geneMiriams(locs==i,2); +0683 model.geneMiriams{i,1}.value=geneMiriams(locs==i,3); +0684 end +0685 end +0686 if ~isempty(compMiriams) +0687 locs = cell2mat(compMiriams(:,1)); +0688 for i=unique(locs)' +0689 model.compMiriams{i,1}.name=compMiriams(locs==i,2); +0690 model.compMiriams{i,1}.value=compMiriams(locs==i,3); +0691 end +0692 end +0693 +0694 %Parse subSystems +0695 if ~isempty(subSystems) +0696 locs = cell2mat(subSystems(:,1)); +0697 for i=unique(locs)' +0698 model.subSystems{i,1}=subSystems(locs==i,2); +0699 end +0700 end +0701 +0702 %Parse ec-codes +0703 if ~isempty(eccodes) +0704 locs = cell2mat(eccodes(:,1)); +0705 for i=unique(locs)' +0706 eccodesCat=strjoin(eccodes(locs==i,2),';'); +0707 model.eccodes{i,1}=eccodesCat; +0708 end +0709 emptyEc=cellfun('isempty',model.eccodes); +0710 model.eccodes(emptyEc)={''}; +0711 end +0712 +0713 % follow-up data processing +0714 if verbose +0715 fprintf('\nimporting completed\nfollow-up processing...'); +0716 end +0717 [~, model.metComps] = ismember(model.metComps, model.comps); +0718 [~, model.geneComps] = ismember(model.geneComps, model.comps); +0719 [~, model.rxnComps] = ismember(model.rxnComps, model.comps); +0720 % A metabolite with no explicit compartment is still ismember'd above (it +0721 % parses as ''), so it resolves to 0 -- not the "assume first compartment" +0722 % default the emptyOrFill call further down intends -- unless it happens +0723 % to be the trailing metabolite in the file, the only case emptyOrFill +0724 % actually reaches (it only extends a too-short array, and ismember has +0725 % already made this one full-length). Catch every 0 here so the default +0726 % applies regardless of position, not just at the end of the list. +0727 model.metComps(model.metComps==0) = 1; +0728 +0729 % Fill S-matrix +0730 rxnIdx = cellfun('isempty', equations(:,1)); +0731 equations(rxnIdx,:) = ''; +0732 rxnIdx = cell2mat(equations(:,1)); +0733 [~,metIdx] = ismember(equations(:,2),model.mets); +0734 coeffs = cell2mat(equations(:,3)); +0735 model.S=sparse(max(metIdx),max(rxnIdx)); +0736 linearIndices = sub2ind([max(metIdx), max(rxnIdx)],metIdx,rxnIdx); +0737 model.S(linearIndices) = coeffs; +0738 +0739 % Convert strings to numeric +0740 model.metCharges = str2double(model.metCharges); +0741 model.lb = str2double(model.lb); +0742 model.ub = str2double(model.ub); +0743 model.rxnConfidenceScores = str2double(model.rxnConfidenceScores); +0744 model.b = zeros(length(model.mets),1); +0745 model.metDeltaG = str2double(model.metDeltaG); +0746 model.rxnDeltaG = str2double(model.rxnDeltaG); +0747 +0748 % Fill some other fields +0749 model.annotation.defaultLB = min(model.lb); +0750 model.annotation.defaultUB = max(model.ub); +0751 if numel(model.lb)<numel(model.rxns) %No LB reported = min +0752 model.lb(end+1:numel(model.rxns)-numel(model.lb),1) = double(model.annotation.defaultLB); +0753 end +0754 if numel(model.ub)<numel(model.rxns) %No UB reported = max +0755 model.ub(end+1:numel(model.rxns)-numel(model.ub),1) = double(model.annotation.defaultUB); +0756 end +0757 if ~all(cellfun('isempty',model.rev)) +0758 model.rev = str2double(model.rev); +0759 else +0760 model.rev = []; 0761 end -0762 for i={'unconstrained'} % Zeros -0763 model = emptyOrFill(model,i{1},0,'mets'); +0762 if numel(model.rev)<numel(model.rxns) %No rev reported, assume from LB and UB +0763 model.rev(end+1:numel(model.rxns)-numel(model.rev),1) = double(model.lb<0 & model.ub>0); 0764 end -0765 for i={'metCharges','metDeltaG'} % NaNs -0766 model = emptyOrFill(model,i{1},NaN,'mets'); -0767 end -0768 for i={'metComps'} % Ones, assume first compartment -0769 model = emptyOrFill(model,i{1},1,'mets'); -0770 end -0771 % Genes -0772 for i={'geneMiriams','geneShortNames','proteins'} % Empty strings -0773 model = emptyOrFill(model,i{1},{''},'genes'); +0765 +0766 % Remove empty fields, otherwise fill to correct length +0767 % Reactions +0768 for i={'rxnNames','grRules','eccodes','rxnNotes','rxnReferences',... +0769 'rxnFrom','subSystems','rxnMiriams'} % Empty strings +0770 model = emptyOrFill(model,i{1},{''},'rxns'); +0771 end +0772 for i={'c'} % Zeros +0773 model = emptyOrFill(model,i{1},0,'rxns',true); 0774 end -0775 for i={'geneComps'} % Ones, assume first compartment -0776 model = emptyOrFill(model,i{1},1,'genes'); +0775 for i={'rxnConfidenceScores','rxnDeltaG'} % NaNs +0776 model = emptyOrFill(model,i{1},NaN,'rxns'); 0777 end -0778 % Comps -0779 for i={'compNames','compMiriams'} % Empty strings -0780 model = emptyOrFill(model,i{1},{''},'comps'); -0781 end -0782 for i={'compOutside'} % First comp -0783 model = emptyOrFill(model,i{1},model.comps{1},'comps'); +0778 for i={'rxnComps'} % Ones, assume first compartment +0779 model = emptyOrFill(model,i{1},1,'rxns'); +0780 end +0781 % Metabolites +0782 for i={'metNames','inchis','metFormulas','metMiriams','metFrom','metSmiles','metNotes'} % Empty strings +0783 model = emptyOrFill(model,i{1},{''},'mets'); 0784 end -0785 % Single fields are kept, even if empty -0786 % for i={'description','name','version','date','annotation'} -0787 % if isempty(model.(i{1})) -0788 % model = rmfield(model,i{1}); -0789 % end -0790 % end -0791 -0792 if isfield(model,'subSystems') -0793 % If all entries are 1x1, then flatten -0794 if all(cellfun(@(x) numel(x) <= 1, model.subSystems)) -0795 model.subSystems = transpose([model.subSystems{:}]); -0796 end +0785 for i={'unconstrained'} % Zeros +0786 model = emptyOrFill(model,i{1},0,'mets'); +0787 end +0788 for i={'metCharges','metDeltaG'} % NaNs +0789 model = emptyOrFill(model,i{1},NaN,'mets'); +0790 end +0791 for i={'metComps'} % Ones, assume first compartment +0792 model = emptyOrFill(model,i{1},1,'mets'); +0793 end +0794 % Genes +0795 for i={'geneMiriams','geneShortNames','proteins'} % Empty strings +0796 model = emptyOrFill(model,i{1},{''},'genes'); 0797 end -0798 -0799 % Make rxnGeneMat fields and map to the existing model.genes field -0800 if isfield(model,'grRules') -0801 [genes, rxnGeneMat] = getGenesFromGrRules(model.grRules); -0802 model.rxnGeneMat = sparse(numel(model.rxns),numel(model.genes)); -0803 [~,geneOrder] = ismember(genes,model.genes); -0804 if any(geneOrder == 0) -0805 error(['The grRules includes the following gene(s), that are not in '... -0806 'the list of model genes: ', genes{~geneOrder}]) -0807 end -0808 model.rxnGeneMat(:,geneOrder) = rxnGeneMat; -0809 else -0810 % Gene-less model: keep rxnGeneMat dimensionally consistent with the -0811 % number of reactions and genes, rather than leaving it empty. -0812 model.rxnGeneMat = sparse(numel(model.rxns),numel(model.genes)); -0813 end +0798 for i={'geneComps'} % Ones, assume first compartment +0799 model = emptyOrFill(model,i{1},1,'genes'); +0800 end +0801 % Comps +0802 for i={'compNames','compMiriams'} % Empty strings +0803 model = emptyOrFill(model,i{1},{''},'comps'); +0804 end +0805 for i={'compOutside'} % First comp +0806 model = emptyOrFill(model,i{1},model.comps{1},'comps'); +0807 end +0808 % Single fields are kept, even if empty +0809 % for i={'description','name','version','date','annotation'} +0810 % if isempty(model.(i{1})) +0811 % model = rmfield(model,i{1}); +0812 % end +0813 % end 0814 -0815 % Finalize GECKO model -0816 if isGECKO -0817 % Fill in empty fields and empty entries -0818 for i={'kcat','source','notes','eccodes'} % Even keep empty -0819 model.ec = emptyOrFill(model.ec,i{1},{''},'rxns',true); -0820 end -0821 for i={'enzymes','mw','sequence'} -0822 model.ec = emptyOrFill(model.ec,i{1},{''},'genes',true); -0823 end -0824 model.ec = emptyOrFill(model.ec,'concs',{'NaN'},'genes',true); -0825 model.ec = emptyOrFill(model.ec,'kcat',{'0'},'genes',true); -0826 % Change string to double -0827 for i={'kcat','mw','concs'} -0828 if isfield(model.ec,i{1}) -0829 model.ec.(i{1}) = str2double(model.ec.(i{1})); -0830 end -0831 end -0832 % Fill rxnEnzMat -0833 rxnIdx = cellfun('isempty', enzStoich(:,1)); -0834 enzStoich(rxnIdx,:) = ''; -0835 rxnIdx = cell2mat(enzStoich(:,1)); -0836 [~,enzIdx] = ismember(enzStoich(:,2),model.ec.enzymes); -0837 coeffs = cell2mat(enzStoich(:,3)); -0838 model.ec.rxnEnzMat = zeros(numel(model.ec.rxns), numel(model.ec.genes)); -0839 linearIndices = sub2ind([numel(model.ec.rxns), numel(model.ec.genes)], rxnIdx, enzIdx); -0840 model.ec.rxnEnzMat(linearIndices) = coeffs; -0841 %Parse ec-codes -0842 if ~isempty(ecGecko) -0843 locs = cell2mat(ecGecko(:,1)); -0844 for i=unique(locs)' -0845 ecGeckoCat=strjoin(ecGecko(locs==i,2),';'); -0846 model.ec.eccodes{i,1}=ecGeckoCat; -0847 end -0848 emptyEc=cellfun('isempty',model.ec.eccodes); -0849 model.ec.eccodes(emptyEc)={''}; -0850 end -0851 end -0852 -0853 if verbose -0854 fprintf(' Done!\n'); -0855 end -0856 end -0857 -0858 function model = emptyOrFill(model,field,emptyEntry,type,keepEmpty) -0859 if nargin<5 -0860 keepEmpty=false; -0861 end -0862 if isnumeric(emptyEntry) -0863 emptyCells=isempty(model.(field)); -0864 else -0865 emptyCells=cellfun('isempty',model.(field)); -0866 end -0867 if all(emptyCells) && ~keepEmpty -0868 model = rmfield(model, field); -0869 elseif numel(model.(field))<numel(model.(type)) -0870 model.(field)(end+1:numel(model.(type)),1)=emptyEntry; -0871 end -0872 end -0873 -0874 function model = readFieldValue(model, fieldName, value, pos) -0875 if numel(model.(fieldName))<pos-1 -0876 model.(fieldName)(end+1:pos,1) = {''}; -0877 end -0878 model.(fieldName)(pos,1) = {value}; +0815 if isfield(model,'subSystems') +0816 % If all entries are 1x1, then flatten +0817 if all(cellfun(@(x) numel(x) <= 1, model.subSystems)) +0818 model.subSystems = transpose([model.subSystems{:}]); +0819 end +0820 end +0821 +0822 % Make rxnGeneMat fields and map to the existing model.genes field +0823 if isfield(model,'grRules') +0824 [genes, rxnGeneMat] = getGenesFromGrRules(model.grRules); +0825 model.rxnGeneMat = sparse(numel(model.rxns),numel(model.genes)); +0826 [~,geneOrder] = ismember(genes,model.genes); +0827 if any(geneOrder == 0) +0828 error(['The grRules includes the following gene(s), that are not in '... +0829 'the list of model genes: ', genes{~geneOrder}]) +0830 end +0831 model.rxnGeneMat(:,geneOrder) = rxnGeneMat; +0832 else +0833 % Gene-less model: keep rxnGeneMat dimensionally consistent with the +0834 % number of reactions and genes, rather than leaving it empty. +0835 model.rxnGeneMat = sparse(numel(model.rxns),numel(model.genes)); +0836 end +0837 +0838 % Finalize GECKO model +0839 if isGECKO +0840 % Fill in empty fields and empty entries +0841 for i={'kcat','source','notes','eccodes'} % Even keep empty +0842 model.ec = emptyOrFill(model.ec,i{1},{''},'rxns',true); +0843 end +0844 for i={'enzymes','mw','sequence'} +0845 model.ec = emptyOrFill(model.ec,i{1},{''},'genes',true); +0846 end +0847 model.ec = emptyOrFill(model.ec,'concs',{'NaN'},'genes',true); +0848 model.ec = emptyOrFill(model.ec,'kcat',{'0'},'genes',true); +0849 % Change string to double +0850 for i={'kcat','mw','concs'} +0851 if isfield(model.ec,i{1}) +0852 model.ec.(i{1}) = str2double(model.ec.(i{1})); +0853 end +0854 end +0855 % Fill rxnEnzMat +0856 rxnIdx = cellfun('isempty', enzStoich(:,1)); +0857 enzStoich(rxnIdx,:) = ''; +0858 rxnIdx = cell2mat(enzStoich(:,1)); +0859 [~,enzIdx] = ismember(enzStoich(:,2),model.ec.enzymes); +0860 coeffs = cell2mat(enzStoich(:,3)); +0861 model.ec.rxnEnzMat = zeros(numel(model.ec.rxns), numel(model.ec.genes)); +0862 linearIndices = sub2ind([numel(model.ec.rxns), numel(model.ec.genes)], rxnIdx, enzIdx); +0863 model.ec.rxnEnzMat(linearIndices) = coeffs; +0864 %Parse ec-codes +0865 if ~isempty(ecGecko) +0866 locs = cell2mat(ecGecko(:,1)); +0867 for i=unique(locs)' +0868 ecGeckoCat=strjoin(ecGecko(locs==i,2),';'); +0869 model.ec.eccodes{i,1}=ecGeckoCat; +0870 end +0871 emptyEc=cellfun('isempty',model.ec.eccodes); +0872 model.ec.eccodes(emptyEc)={''}; +0873 end +0874 end +0875 +0876 if verbose +0877 fprintf(' Done!\n'); +0878 end 0879 end 0880 -0881 function [miriams,miriamKey,entryNumber] = gatherAnnotation(pos,miriams,key,value,miriamKey,entryNumber) -0882 if isempty(key) -0883 key=miriamKey; -0884 else -0885 miriamKey=key; -0886 end -0887 if ~isempty(value) -0888 miriams(entryNumber,1:3) = {pos, key, strip(value)}; -0889 entryNumber = entryNumber + 1; -0890 end -0891 end

    +0881 function model = emptyOrFill(model,field,emptyEntry,type,keepEmpty) +0882 if nargin<5 +0883 keepEmpty=false; +0884 end +0885 if isnumeric(emptyEntry) +0886 emptyCells=isempty(model.(field)); +0887 else +0888 emptyCells=cellfun('isempty',model.(field)); +0889 end +0890 if all(emptyCells) && ~keepEmpty +0891 model = rmfield(model, field); +0892 elseif numel(model.(field))<numel(model.(type)) +0893 model.(field)(end+1:numel(model.(type)),1)=emptyEntry; +0894 end +0895 end +0896 +0897 function model = readFieldValue(model, fieldName, value, pos) +0898 if numel(model.(fieldName))<pos-1 +0899 model.(fieldName)(end+1:pos,1) = {''}; +0900 end +0901 model.(fieldName)(pos,1) = {value}; +0902 end +0903 +0904 function model = appendNote(model, fieldName, raw, key, value, pos) +0905 % One line of a notes map: a 'note' entry adds its value (RAVEN's own note +0906 % text), another entry adds 'key: value', a list item adds its value to the +0907 % entry above it. Entries are separated by '; ', list items by ', '. A +0908 % quoted list item is read whole, as the key detection would split it at +0909 % a ': ' inside the quotes. +0910 item = regexp(raw, '^ +- ([''"])(.*)\1$', 'tokens', 'once'); +0911 if ~isempty(item) +0912 key = ''; +0913 value = item{2}; +0914 end +0915 if strcmp(value, '!!omap') +0916 value = ''; +0917 end +0918 text = ''; +0919 if numel(model.(fieldName)) >= pos +0920 text = model.(fieldName){pos}; +0921 end +0922 if isempty(key) +0923 if isempty(text) || endsWith(text, ':') +0924 text = strtrim([text ' ' value]); +0925 else +0926 text = [text ', ' value]; +0927 end +0928 else +0929 if isempty(value) +0930 entry = [key ':']; +0931 elseif strcmp(key, 'note') +0932 entry = value; +0933 else +0934 entry = [key ': ' value]; +0935 end +0936 if isempty(text) +0937 text = entry; +0938 else +0939 text = [text '; ' entry]; +0940 end +0941 end +0942 model = readFieldValue(model, fieldName, text, pos); +0943 end +0944 +0945 function [miriams,miriamKey,entryNumber] = gatherAnnotation(pos,miriams,key,value,miriamKey,entryNumber) +0946 if isempty(key) +0947 key=miriamKey; +0948 else +0949 miriamKey=key; +0950 end +0951 if ~isempty(value) +0952 miriams(entryNumber,1:3) = {pos, key, strip(value)}; +0953 entryNumber = entryNumber + 1; +0954 end +0955 end
    Generated by m2html © 2005
    \ No newline at end of file From edf622910b9c50ce554eccbb6933f08fe5765571 Mon Sep 17 00:00:00 2001 From: Eduard Kerkhoven Date: Wed, 28 Oct 2026 22:24:04 +0100 Subject: [PATCH 33/48] fix: align RAVEN with raven-toolbox (parity review) (#13) * exportForGit: lossless txt table, no main-branch check The txt table is headed 'Rxn id' (the column holds identifiers), writes lower bound, upper bound and objective coefficient with the digits needed to read them back instead of two decimals, and pads no field. The mainBranchFlag option is removed together with its check of the toolbox branch. * Align gapFillMILP bigM, getModelFromHomology and generateNewIds gapFillMILP couples with finite reaction bounds and uses bigM only for infinite ones, getModelFromHomology no longer fails when a preferred order removes no gene, and generateNewIds takes the numerically largest id. * parseTaskList: separate default columns, comment rows, orphan rows, numeric check, no .xls Each absent optional column gets its own empty spare column, so the bounds default to 0 and 1000 and the comments stay empty. Leading rows that start with a non-empty cell are removed before the header row is read. Rows before the first task id are skipped with a warning. A bound that is not a number raises RAVEN:badInput. The old binary .xls format raises RAVEN:badInput with the advice to save as .xlsx or tab-delimited .txt. * setGAM, scaleBiomassPseudoreaction: refuse a missing NGAM reaction or proton metabolite Both errors are raised before the model is changed. * reporterMetabolites: corrected score 0 when the background standard deviation is 0 * ftINIT: remove genes of removed reactions from the output model * sortModel: keep reaction directions unless sortReversible is requested * reporterMetabolites: treat rounding-level background spread as zero * setGAM: check a given NGAM reaction also without a value * parseTaskList: ignore empty rows, report file rows, read numbers typed as text * exportForGit: keep the mainBranchFlag option in its position Calls such as exportForGit(model,prefix,path,formats,'',false) and (...,false,false) keep their meaning. The check of the toolbox branch is as before. * Round 2 review fixes: reporterMetabolites spread rule, numeric text in task lists, empty NGAM reaction reporterMetabolites tests the spread of the Z-scores instead of sigma. parseTaskList accepts only plain numbers in numeric columns and states how its row number is counted. setGAM treats an empty ngamRxn as not given. * sampleACHR: project step directions onto the null space of S The running centre carried the rounding error of S*v into every later step, so the residual grew exponentially with the number of steps (up to 9 at 100 steps between samples). Directions are projected onto a null-space basis computed once. * Tests: exportForGit flag position and refusal, parseTaskList refused text; branch error identifier * reporterMetabolites: take the moments on shifted Z-scores; parseTaskList: refuse NaN outside the bounds of changed reactions * sampleACHR: correct towards S*v = 0 through a sparse factor over the free reactions, keep steps inside the bounds * reporterMetabolites: take the moments on Z-scores shifted by their mean * sampleACHR: warn when the samples keep a residual; tests for the fixed-reaction step, the correction interval, the relative direction tolerance and the final clip * reporterMetabolites: refuse P-values outside [0,1]; drop metabolites without a score from all result fields by one mask * prepINITModel refuses repeated task ids; curateModelFromTables: overwriteWithEmpty option and empty bound cells count as not given * setGAM refuses ngamValue without ngamRxn; generateNewIds counts only finite integer suffixes; exportForGit validates its logical flags * exportForGit creates the output folder, with missing parents, also when subDirs is false * Fix round after review: rev cells true/false, complex bounds refused, blank MIRIAM and gene rule cells count as empty, strict overwriteWithEmpty, generateNewIds below 2^53, prepINITModel task id checks * tCuration: shorter test name, quoted blank cells * Table cells are read without evaluating text; blank-only cells count as empty; overwriteWithEmpty needs a real value; empty task ids are reported separately; generateNewIds counts integers below 2^52 * curateModelFromTables: a NaN charge cell counts as not given * curateModelFromTables reads only plain decimal numbers, Inf, NaN and true/false (no commas, hex or expressions), also in the index and coefficient columns; a NaN confidence score counts as not given; exportForGit flags must be real * curateModelFromTables: a charge must be a finite whole number, a rev 0, 1, true or false, and a coefficient row with content needs a coefficient; help text states the per-column meaning of empty cells * curateModelFromTables: a rxnIdx must be a whole number of at least 1 * curateModelFromTables: rxnIdx test without a NaN row in the info table * curateModelFromTables: rows with values need a rxnIdx, the reaction tables must match exactly by index value, true and false only in lb, ub and rev, finite non-zero coefficients, RAVEN:badInput for file and column errors, empty overwriteWithEmpty means the default * curateModelFromTables: a malformed gene rule raises RAVEN:badInput; the help states how a lower bound above the upper bound is handled * curateModelFromTables: a gene id listed more than once in genesInfo raises RAVEN:badInput * curateModelFromTables: gene rules are written in the standard format before they are applied, so every spelling that parseGrRule reads is accepted * curateModelFromTables: a repeated rxnIdx in rxnsInfo raises; spaces inside the quotes of a header cell are ignored * tCuration: pass the metabolite table positionally in the padded header test * prepINITModel: repeated and empty task ids are accepted; parseTaskList help states that ids may repeat * tTasks: checkTasks reports tasks with a repeated id one by one --- src/INIT/ftINIT.m | 2 +- src/INIT/prepINITModel.m | 15 + src/analysis/reporterMetabolites.m | 57 +- src/analysis/sampleACHR.m | 72 +- src/biomass/scaleBiomassPseudoreaction.m | 7 +- src/biomass/setGAM.m | 25 +- src/curation/curateModelFromTables.m | 371 ++++++++- src/gapfilling/gapFillMILP.m | 18 +- src/io/exportForGit.m | 68 +- src/manipulation/contractModel.m | 2 +- src/manipulation/generateNewIds.m | 22 +- src/manipulation/sortModel.m | 6 +- src/queries/constructEquations.m | 2 +- .../homology/getModelFromHomology.m | 4 +- src/tasks/parseTaskList.m | 117 ++- tests/function_tests/tAnalysis.m | 155 ++++ tests/function_tests/tBiomass.m | 45 ++ tests/function_tests/tCuration.m | 722 ++++++++++++++++++ tests/function_tests/tGapfilling.m | 29 + tests/function_tests/tINIT.m | 64 ++ tests/function_tests/tIO.m | 92 ++- tests/function_tests/tManipulation.m | 56 ++ tests/function_tests/tReconstruction.m | 104 +++ tests/function_tests/tSampling.m | 215 ++++++ tests/function_tests/tTasks.m | 263 ++++++- tests/function_tests/test_data/test_tasks.xls | Bin 27136 -> 0 bytes .../test_data/test_tasks_repeated_ids.txt | 6 + 27 files changed, 2406 insertions(+), 133 deletions(-) delete mode 100644 tests/function_tests/test_data/test_tasks.xls create mode 100644 tests/function_tests/test_data/test_tasks_repeated_ids.txt diff --git a/src/INIT/ftINIT.m b/src/INIT/ftINIT.m index d4dcbd07..1c66ba91 100644 --- a/src/INIT/ftINIT.m +++ b/src/INIT/ftINIT.m @@ -448,7 +448,7 @@ %be performed also without manipulating the b vector in the model (which is what is done in the gap-filling). exchRxns = getExchangeRxns(prepData.refModel); deletedRxnsInINIT = setdiff(prepData.refModel.rxns,union(union(initModel.rxns, addedRxnsForTasks), exchRxns)); -outModel = removeReactions(prepData.refModel, deletedRxnsInINIT, true); %we skip removing the genes for now, I'm not sure it is desirable +outModel = removeReactions(prepData.refModel, deletedRxnsInINIT, true, true); % If requested, attempt to remove negative-score genes from the model, % depending on their role (isozyme or complex subunit) in each grRule. diff --git a/src/INIT/prepINITModel.m b/src/INIT/prepINITModel.m index 21ed6d83..e129778c 100644 --- a/src/INIT/prepINITModel.m +++ b/src/INIT/prepINITModel.m @@ -12,6 +12,10 @@ % taskStruct : struct % the essential tasks. Can be loaded with for example % taskStruct = parseTaskList('../data/metabolicTasks_Essential.txt'). +% The task ids must be character vectors: a non-text id or a missing id +% field raises RAVEN:badInput. Ids may repeat (for example as category +% codes) and may be empty; each task is one row group of the task list, +% and a task that cannot be performed is removed by its position. % % Name-Value Arguments % -------------------- @@ -58,6 +62,17 @@ extComp=p.extComp; skipScaling=p.skipScaling; runParallel=p.runParallel; + +if ~isempty(taskStruct) + if ~isfield(taskStruct,'id') + error('RAVEN:badInput','The task structure must have an id field'); + end + taskIds = {taskStruct.id}; + if ~all(cellfun(@(x) ischar(x) && (isrow(x) || isempty(x)),taskIds)) + error('RAVEN:badInput','The task ids must be character vectors'); + end +end + disp('Step 1: Gene rules') [origRefModel.grRules, origRefModel.rxnGeneMat] = standardizeGrRules(origRefModel, true); diff --git a/src/analysis/reporterMetabolites.m b/src/analysis/reporterMetabolites.m index d2a2ae83..47256351 100755 --- a/src/analysis/reporterMetabolites.m +++ b/src/analysis/reporterMetabolites.m @@ -11,7 +11,8 @@ % genes : cell % a cell array of gene names (should match with model.genes). % genePValues : double -% P-values for differential expression of the genes. +% P-values for differential expression of the genes. Values must lie in +% [0, 1]; NaN values are ignored, any other value raises an error. % % Name-Value Arguments % -------------------- @@ -78,6 +79,15 @@ error('RAVEN:badInput', '%s', EM); end +%All P-values that are given must lie in [0,1]; NaN means that no P-value +%is available and is removed below +badP=~isnan(genePValues) & (genePValues<0 | genePValues>1); +if any(badP) + firstBad=find(badP,1); + EM=sprintf('The P-values must lie between 0 and 1, but %d of them do not. The first is %g (gene %s)',nnz(badP),genePValues(firstBad),genes{firstBad}); + error('RAVEN:badInput', '%s', EM); +end + %Remove the genes which are not in the model genePValues(~ismember(genes,model.genes))=[]; if any(geneFoldChanges) @@ -103,6 +113,14 @@ %For each metabolite, calculate the aggregate Z-score and keep track of the %number of neighbouring genes metZScores=nan(numel(model.mets),1); +%Sums are taken on Z-scores relative to their mean (found in two passes, the +%second relative to the first gene's Z-score), which keeps the rounding error +%proportional to the spread of the Z-scores instead of to their size +metZShifted=nan(numel(model.mets),1); +zCentre=0; +if ~isempty(geneZScores) + zCentre=geneZScores(1)+mean(geneZScores-geneZScores(1)); +end metNGenes=nan(numel(model.mets),1); meanZ=nan(numel(model.mets),1); stdZ=nan(numel(model.mets),1); @@ -124,28 +142,43 @@ %Calculate the aggregated Z-score for the metabolite if any(K) metZScores(i)=sum(geneZScores(K))/sqrt(numel(K)); + metZShifted(i)=sum(geneZScores(K)-zCentre)/sqrt(numel(K)); meanZ(i)=mean(geneZScores(K)); stdZ(i)=std(geneZScores(K)); metNGenes(i)=numel(K); end end -%Remove the metabolites which have no Z-scores -mets=model.mets(~isnan(metZScores)); -metNames=model.metNames(~isnan(metZScores)); -metZScores(isnan(metZScores))=[]; -metNGenes(isnan(metNGenes))=[]; -meanZ(isnan(meanZ))=[]; -stdZ(isnan(stdZ))=[]; +%Remove the metabolites which have no Z-scores, from all fields alike +noScore=isnan(metZScores); +mets=model.mets(~noScore); +metNames=model.metNames(~noScore); +metZShifted(noScore)=[]; +metZScores(noScore)=[]; +metNGenes(noScore)=[]; +meanZ(noScore)=[]; +stdZ(noScore)=[]; %Correct for background using the closed-form moments of the sampling %distribution. For n genes drawn with replacement from the scored-gene pool %(mean mu, population std sigma), the aggregate Z = sum(z)/sqrt(n) has: % E[Z] = sqrt(n)*mu and Std[Z] = sigma (independent of n) -%so the corrected score is (metZ - sqrt(n)*mu) / sigma. -mu = mean(geneZScores); -sigma = std(geneZScores,1); -metZScores = (metZScores - sqrt(metNGenes) .* mu) ./ sigma; +%so the corrected score is (metZ - sqrt(n)*mu) / sigma. When sigma is 0 (all +%scored genes have the same Z-score) no metabolite stands out from the +%background, and the corrected score is 0 (P-value 0.5). The moments are +%taken on Z-scores shifted by their mean, so that mu and the numerator keep +%their precision when the Z-scores are close to each other or the first one is +%an outlier. Identical Z-scores then give exactly 0 for the numerator, and the +%test of the spread of the Z-scores (0 up to 100*eps(max(abs(Z)))) only +%prevents a division of 0 by 0. +zShifted = geneZScores - zCentre; +muShifted = mean(zShifted); +sigma = std(zShifted,1); +if max(geneZScores)-min(geneZScores) > 100*eps(max(abs(geneZScores))) + metZScores = (metZShifted - sqrt(metNGenes) .* muShifted) ./ sigma; +else + metZScores = zeros(size(metZScores)); +end %Calculate the P-values (upper-tail of the standard normal). Uses the base %MATLAB erfc so this does not depend on the Statistics Toolbox normcdf: diff --git a/src/analysis/sampleACHR.m b/src/analysis/sampleACHR.m index c2432d78..355a06cb 100644 --- a/src/analysis/sampleACHR.m +++ b/src/analysis/sampleACHR.m @@ -5,6 +5,15 @@ % {v : S*v = 0, lb <= v <= ub}, using the ACHR algorithm (Kaufman & Smith 1998). % No rounding is performed, so on strongly elongated polytopes (e.g. % enzyme-constrained models) mixing is slower than sampleCHRR; prefer CHRR there. +% Reactions whose bounds or warmup values coincide never move. The remaining +% reactions are corrected towards S*v = 0 every few steps and before each +% recorded sample, with a sparse Cholesky factor of the row-normalised S*S' +% (no dense null-space basis), so the rounding error in S*v does not grow with +% the number of steps and samples stay inside the bounds. The guarantee +% S*v = 0 holds for a well-conditioned S and warmup points that are feasible +% for the model. When the largest residual of a recorded sample exceeds 1e-9 +% times max(1, max(abs(v))) (nearly dependent rows of S, a noisy or infeasible +% warmup), sampleACHR issues the warning sampleACHR:steadyStateNotRestored. % % Parameters % ---------- @@ -41,18 +50,28 @@ ub = model.ub(:); nW = size(warmup, 1); +% Reactions that cannot move: fixed bounds, or identical value in all warmup +% points. Directions and corrections act on the other reactions only. +spread = (max(warmup, [], 1) - min(warmup, [], 1))'; +free = (ub - lb > 1e-9) & (spread > 1e-9); +proj = buildSteadyStateCorrection(model.S, free, lb, ub); +corrEvery = min(thinning, 20); + center = mean(warmup, 1)'; % nRxns-by-1 -prev = center; +prev = restoreSteadyState(center, proj); +center = prev; nTotal = 0; -dirTol = 1e-12; solutions = zeros(nRxns, nSamples); rec = 0; +worstResidual = 0; nSteps = thinning*nSamples; for stepno = 1:nSteps pidx = randi(nW); delta = warmup(pidx, :)' - center; + delta(~free) = 0; + dirTol = 1e-12*max(1, max(abs(delta))); posD = delta > dirTol; negD = delta < -dirTol; @@ -66,16 +85,63 @@ if tHi > tLo alpha = tLo + rand*(tHi - tLo); - prev = prev + alpha*delta; + prev = min(max(prev + alpha*delta, lb), ub); end % Running centre (quasi-Markovian ACHR: centre converges and steadies). center = (nTotal*center + prev) / (nTotal + 1); nTotal = nTotal + 1; + if mod(stepno, corrEvery) == 0 || mod(stepno, thinning) == 0 + prev = restoreSteadyState(prev, proj); + center = restoreSteadyState(center, proj); + end + if mod(stepno, thinning) == 0 rec = rec + 1; solutions(:, rec) = prev; + worstResidual = max(worstResidual, ... + max(abs(model.S*prev))/max(1, max(abs(prev)))); + end +end +if worstResidual > 1e-9 + warning('sampleACHR:steadyStateNotRestored', ... + ['The largest residual of S*v over the samples is %.3g times max(1, max(abs(v))), ' ... + 'above 1e-9. The rows of S are nearly dependent or the warmup points are not ' ... + 'feasible for the model.'], worstResidual); +end +end + +function proj = buildSteadyStateCorrection(S, free, lb, ub) +% Sparse factor of the row-normalised S(:,free)*S(:,free)' with a small ridge, +% used to move a flux vector onto S*v = 0 through the free reactions only. +Sf = S(:, free); +rowNorm = sqrt(full(sum(Sf.^2, 2))); +keep = rowNorm > 0; +rowNorm = rowNorm(keep); +Sn = spdiags(1./rowNorm, 0, numel(rowNorm), numel(rowNorm))*Sf(keep, :); +A = Sn*Sn' + 1e-10*speye(size(Sn, 1)); +[R, flag, p] = chol(A, 'vector'); +if flag ~= 0 + error('sampleACHR:factorization', 'The stoichiometric matrix could not be factorized.'); +end +proj = struct('S', S, 'free', free, 'keep', keep, 'rowNorm', rowNorm, ... + 'Sn', Sn, 'R', R, 'Rt', R', 'p', p, 'lb', lb, 'ub', ub); +end + +function v = restoreSteadyState(v, proj) +% Removes the residual of S*v through the free reactions (a few ridge-regularised +% projection passes), then clips v to its bounds. +tol = 1e-12*max(1, max(abs(v))); +for it = 1:8 + r = proj.S*v; + if max(abs(r)) <= tol + break end + rn = r(proj.keep) ./ proj.rowNorm; + x = zeros(size(rn)); + x(proj.p) = proj.R \ (proj.Rt \ rn(proj.p)); + v(proj.free) = v(proj.free) - proj.Sn'*x; end +v = min(max(v, proj.lb), proj.ub); end diff --git a/src/biomass/scaleBiomassPseudoreaction.m b/src/biomass/scaleBiomassPseudoreaction.m index cfb6d89f..e50649f9 100644 --- a/src/biomass/scaleBiomassPseudoreaction.m +++ b/src/biomass/scaleBiomassPseudoreaction.m @@ -45,6 +45,12 @@ 'No reaction named %s in model.', comp.pseudoreaction_name); end +Hc = find(strcmp(model.mets, biomassConfig.proton_met)); +if isempty(Hc) + error('scaleBiomassPseudoreaction:missingProton', ... + 'Proton metabolite %s not found in model.', biomassConfig.proton_met); +end + for i = 1:length(model.mets) S_ir = model.S(i, rxnPos); isProd = strcmp(model.metNames{i}, componentName); @@ -54,7 +60,6 @@ end % Rebalance H+ to keep charge neutrality. -Hc = find(strcmp(model.mets, biomassConfig.proton_met)); model.S(Hc, rxnPos) = 0; model.S(Hc, rxnPos) = -chargeResidual(model, rxnPos); end diff --git a/src/biomass/setGAM.m b/src/biomass/setGAM.m index 2d7da9f4..c0c492e0 100644 --- a/src/biomass/setGAM.m +++ b/src/biomass/setGAM.m @@ -8,9 +8,11 @@ % % For every metabolite in the biomass pseudoreaction whose model.metNames % entry is in cofactorMetNames, the stoichiometric coefficient is set to -% ±value preserving the sign of the current coefficient. Yeast-GEM scales -% ATP, ADP, H2O, H+ and phosphate (with ATP and H2O on the substrate side, -% ADP / H+ / phosphate on the product side). +% ±value preserving the sign of the current coefficient. An error is raised +% before the model is changed if ngamRxn is given and is not in the model, +% and if ngamValue is given without ngamRxn (RAVEN:badInput). +% Yeast-GEM scales ATP, ADP, H2O, H+ and phosphate (with ATP and H2O on the +% substrate side, ADP / H+ / phosphate on the product side). % % Parameters % ---------- @@ -54,6 +56,15 @@ p=parseRAVENargs(varargin, {'ngamRxn',[]; 'ngamValue',[]}); ngamRxn=p.ngamRxn; ngamValue=p.ngamValue; +%An empty id counts as not given +if ~isempty(ngamRxn) + ngamRxn=cellstr(ngamRxn); + ngamRxn=ngamRxn(~cellfun(@isempty,ngamRxn)); +end +if ~isempty(ngamValue) && isempty(ngamRxn) + error('RAVEN:badInput', ... + 'ngamValue is given without ngamRxn. Give the NGAM reaction id as ''ngamRxn''.'); +end bioPos = strcmp(model.rxns, biomassRxn); if ~any(bioPos) @@ -61,6 +72,12 @@ 'Reaction %s not found in model.', biomassRxn); end +setNgam = ~isempty(ngamRxn) && ~isempty(ngamValue); +if ~isempty(ngamRxn) && ~all(ismember(ngamRxn, model.rxns)) + error('setGAM:missingNgamRxn', ... + 'NGAM reaction %s not found in model.', strjoin(ngamRxn, ', ')); +end + for i = 1:length(model.mets) S_ix = model.S(i, bioPos); isCofactor = any(strcmp(cofactorMetNames, model.metNames{i})); @@ -69,7 +86,7 @@ end end -if ~isempty(ngamRxn) && ~isempty(ngamValue) +if setNgam model = setParam(model, 'eq', ngamRxn, ngamValue); end end diff --git a/src/curation/curateModelFromTables.m b/src/curation/curateModelFromTables.m index 2b0e2d0d..8ef9aba9 100644 --- a/src/curation/curateModelFromTables.m +++ b/src/curation/curateModelFromTables.m @@ -7,17 +7,22 @@ % If the *.tsv files contain metabolites, reactions and/or genes that are % already present in the model, then information in the model will be % overwritten. Note that this includes empty annotations in the *.tsv -% files! Metabolites are matched by metaboliteName[comp]; reactions by the -% stoichiometry of its reactants and products; genes by their gene name. +% files, unless 'overwriteWithEmpty' is false! Metabolites are matched by +% metaboliteName[comp]; reactions by the stoichiometry of its reactants and +% products; genes by their gene name. % This function can therefore be used to add new entities in the model, or % curate those already existing in the model. % -% Columns are identified by their header (case-insensitive), so their order +% Columns are identified by their header (case-insensitive, and spaces around +% a header are ignored, also inside its quotes), so their order % does not matter and optional columns can be left out. Every field that a % table supplies is written to new and existing entities alike; a field the % model does not have yet is created, with its default value for the other % entities, unless all supplied values for it are empty. An empty grRules -% cell leaves the rule of an existing reaction unchanged. +% cell leaves the rule of an existing reaction unchanged, and so does an +% empty lb, ub or rev cell: an empty bound means that no bound is given. +% A new reaction without a lb gets a lower bound of -Inf, and one without an +% ub gets model.annotation.defaultUB (Inf if the model has none). % % Parameters % ---------- @@ -44,12 +49,27 @@ % Path to a *.tsv file with reaction information, or 'none'. Columns: % rxnIdx (or index), rxnNames (both required), grRules, lb, ub, rev, % subSystems, eccodes, rxnNotes, rxnReferences, rxnConfidenceScores, -% and MIRIAM. +% and MIRIAM. Each rxnIdx may be used by one row only (the rxnsCoeffs +% file has one row per metabolite of the reaction and repeats it). % metPrefix : char % Prefix used to mint fresh metabolite ids (e.g. 's_' for yeast-GEM, % 'M_' for the BiGG default) (default 'M_'). % rxnPrefix : char % Prefix used to mint fresh reaction ids (default 'R_'). +% overwriteWithEmpty : logical +% if true, an empty cell in a row of an existing metabolite, gene or +% reaction empties the corresponding field of the model. If false, an +% empty cell leaves the existing value unchanged and only the cells with a +% value are written (default true; [] also means the default). Must be a +% scalar logical or 0/1. A cell with only blanks counts as empty in both +% modes. Empty grRules cells never overwrite. An empty or NaN lb, ub or +% rev cell always keeps the existing value. For the charge and the +% confidence score, an empty or NaN cell is treated alike: when +% overwriteWithEmpty is true and at least one other row of the column has +% a value, it becomes 0; a column in which no row has a value is not +% written, and the existing values stay. The same holds for a MIRIAM +% column: a column in which no row has a value does not clear the +% annotations of existing entities. % % Returns % ------- @@ -66,8 +86,51 @@ % In all three info tables, any column whose header is not one of the % listed core fields is treated as a MIRIAM annotation namespace and % stored on the matching entity. +% +% Cell values: the charge, confidence score, lb, ub, rev, rxnIdx and +% coefficient cells are read as plain decimal numbers (digits with an +% optional sign, decimal point and exponent), Inf or NaN in any case. Only +% the lb, ub and rev cells also accept true and false in any case (read as +% 1 and 0). Any other text, including text with a comma, hexadecimal numbers +% and expressions, raises RAVEN:badInput; no expression is evaluated. A +% charge must be a finite whole number (or empty or NaN). A rev must be 0, 1, +% true or false (or empty or NaN). The rev column sets the reversibility of +% existing reactions only, independently of lb and ub, which are written as +% given; for new reactions the reversibility is derived from lb and ub. +% +% Gene rules: a grRules cell must be a well-formed gene rule (genes joined by +% and/or in any case, or by &, &&, | and ||, with balanced parentheses); a +% rule with an operator that lacks an operand, empty parentheses or two genes +% without an operator between them raises RAVEN:badInput before the model is +% changed. A valid rule is stored in RAVEN's standard format. Whether the +% genes exist is checked when the rule is applied. +% +% Genes: a gene id that the genesInfo table lists more than once raises +% RAVEN:badInput, whether the model has the gene or not. +% +% Bounds: a lower bound above the upper bound is written as given. solveLP +% rejects such a model when it is run ("Invalid set of bounds for +% reaction(s): ..."). +% +% Reaction tables: a row of the rxnsCoeffs table without a rxnIdx, rxnNames, +% metNames and comps value is ignored; a row with any of them needs a +% rxnIdx and a coefficient that is a finite number other than 0. A row of +% the rxnsInfo table without any value is ignored; a row with a value needs +% a rxnIdx. A rxnIdx is a whole number of at least 1; the indices (and +% names) of the two reaction tables must match exactly, compared by their +% numeric value (1, 1.0 and 1e0 are the same index). Otherwise RAVEN:badInput +% is raised. -p=parseRAVENargs(varargin, {'genesInfo','none'; 'rxnsCoeffs','none'; 'rxnsInfo','none'; 'metPrefix',[]; 'rxnPrefix',[]}); +p=parseRAVENargs(varargin, {'genesInfo','none'; 'rxnsCoeffs','none'; 'rxnsInfo','none'; 'metPrefix',[]; 'rxnPrefix',[]; 'overwriteWithEmpty',true}); +overwriteWithEmpty=p.overwriteWithEmpty; +if isempty(overwriteWithEmpty) + overwriteWithEmpty=true; +end +if ~((islogical(overwriteWithEmpty) || isnumeric(overwriteWithEmpty)) && isscalar(overwriteWithEmpty) ... + && isreal(overwriteWithEmpty) && ~isnan(overwriteWithEmpty) && ismember(overwriteWithEmpty,[0 1])) + error('RAVEN:badInput','overwriteWithEmpty must be a scalar logical or 0/1'); +end +overwriteWithEmpty=logical(overwriteWithEmpty); genesInfo=p.genesInfo; rxnsCoeffs=p.rxnsCoeffs; rxnsInfo=p.rxnsInfo; @@ -80,7 +143,7 @@ rxnPrefix = 'R_'; end if xor(strcmp(rxnsCoeffs,'none'), strcmp(rxnsInfo,'none')) - error('Provide both a ''rxnsInfo'' and a ''rxnsCoeffs'' file') + error('RAVEN:badInput','Provide both a ''rxnsInfo'' and a ''rxnsCoeffs'' file') end newModel=model; @@ -101,8 +164,11 @@ metsToAdd.(f{1}) = data(:,col.(f{1})); end if isfield(col,'metCharges') - metsToAdd.metCharges = cellfun(@str2num,data(:,col.metCharges),'UniformOutput',false); + metsToAdd.metCharges = cellfun(@(x) parseChargeCell(x,'metsInfo'),data(:,col.metCharges),'UniformOutput',false); + isNanCharge = cellfun(@(x) isscalar(x) && isnan(x),metsToAdd.metCharges); + metsToAdd.metCharges(isNanCharge) = {[]}; % a NaN charge counts as not given emptyEntry = cellfun(@isempty,metsToAdd.metCharges); + emptyCharge = emptyEntry; if all(emptyEntry) metsToAdd = rmfield(metsToAdd,'metCharges'); else % If some charges are given, assume 0 for those without charges specified @@ -130,9 +196,17 @@ % Overwrite annotation of existing entries if any(existingMets) for f = optFields - newModel = writeExistingField(newModel,f{1},existingMetsIdx,metsToAdd.(f{1})(existingMets)); + skip = []; + if ~overwriteWithEmpty + if strcmp(f{1},'metCharges') + skip = emptyCharge(existingMets); + else + skip = emptyCells(metsToAdd.(f{1})(existingMets)); + end + end + newModel = writeExistingField(newModel,f{1},existingMetsIdx,metsToAdd.(f{1})(existingMets),skip); end - newModel = extractAndAddMiriam(newModel,header(miriamCols),data(:,miriamCols),existingMets,existingMetsIdx,'met'); + newModel = extractAndAddMiriam(newModel,header(miriamCols),data(:,miriamCols),existingMets,existingMetsIdx,'met',~overwriteWithEmpty); warning(['The following metabolites are already present in the model, '... 'their annotation will be overwritten to match the metsInfo file. '... 'If you do not particularly want to curate their annotations, it '... @@ -163,6 +237,7 @@ 'genes', {'genes','gene'}; ... 'geneShortNames', {'geneShortNames','geneShortName'}}); genesToAdd.genes = data(:,col.genes); + checkGenesListedOnce(genesToAdd.genes); if isfield(col,'geneShortNames') genesToAdd.geneShortNames = data(:,col.geneShortNames); end @@ -173,9 +248,13 @@ 'will be overwritten to match the genesInfo file: \n\t\t%s'],... strjoin(genesToAdd.genes(notNewGene),'\n\t\t')); if isfield(genesToAdd,'geneShortNames') - newModel = writeExistingField(newModel,'geneShortNames',existingGene,genesToAdd.geneShortNames(notNewGene)); + skip = []; + if ~overwriteWithEmpty + skip = emptyCells(genesToAdd.geneShortNames(notNewGene)); + end + newModel = writeExistingField(newModel,'geneShortNames',existingGene,genesToAdd.geneShortNames(notNewGene),skip); end - newModel = extractAndAddMiriam(newModel,header(miriamCols),data(:,miriamCols),notNewGene,existingGene,'gene'); + newModel = extractAndAddMiriam(newModel,header(miriamCols),data(:,miriamCols),notNewGene,existingGene,'gene',~overwriteWithEmpty); end % Continue with new genes @@ -205,12 +284,19 @@ 'metNames', {'metNames','metName'}; ... 'comps', {'comps','comp'}; ... 'coefficient', {'coefficient','coefficients','coeffs','stoichCoeffs'}}); - rxnCheck.coeffsIdx = str2double(data(:,col.rxnIdx)); + % A row without a rxnIdx, rxnNames, metNames and comps value is ignored + rowNo = (1:size(data,1))'; + keyCells = data(:,[col.rxnIdx,col.rxnNames,col.metNames,col.comps]); + ignoredRow = all(reshape(emptyCells(keyCells(:)),size(keyCells)),2); + data(ignoredRow,:) = []; + rowNo(ignoredRow) = []; + rxnCheck.coeffsIdx = parseIndexCells(data(:,col.rxnIdx),'rxnIdx','rxnsCoeffs'); rxns.rxnNames = data(:,col.rxnNames); rxns.metNames = data(:,col.metNames); rxns.comps = data(:,col.comps); - rxns.coeffs = str2double(data(:,col.coefficient)); - rxnCheck.coeffs = strcat(data(:,col.rxnIdx),'***',rxns.rxnNames); + rxns.coeffs = parseNumericCells(data(:,col.coefficient),'coefficient','rxnsCoeffs'); + checkCoefficientsGiven(rxnCheck.coeffsIdx,rxns.coeffs,rowNo); + rxnCheck.coeffs = indexKeys(rxnCheck.coeffsIdx,rxns.rxnNames); % ... and then additional reaction-specific data. [header,data] = readCurationTable(rxnsInfo); @@ -226,26 +312,50 @@ 'rxnNotes', {'rxnNotes'}; ... 'rxnReferences', {'rxnReferences'}; ... 'rxnConfidenceScores', {'rxnConfidenceScores'}}); - rxnCheck.rxnsIdx = str2double(data(:,col.rxnIdx)); - rxnCheck.rxns = strcat(data(:,col.rxnIdx),'***',data(:,col.rxnNames)); + % A row without any value is ignored; a row with a value needs a rxnIdx + rowNo = (1:size(data,1))'; + ignoredRow = all(reshape(emptyCells(data(:)),size(data)),2); + data(ignoredRow,:) = []; + rowNo(ignoredRow) = []; + rxnCheck.rxnsIdx = parseIndexCells(data(:,col.rxnIdx),'rxnIdx','rxnsInfo'); + noIdx = find(isnan(rxnCheck.rxnsIdx),1); + if ~isempty(noIdx) + error('RAVEN:badInput','Data row %d of the rxnsInfo file has values but its rxnIdx is empty or NaN',rowNo(noIdx)); + end + [uniqueIdx,~,idxGroup] = unique(rxnCheck.rxnsIdx); + repeated = find(accumarray(idxGroup,1)>1,1); + if ~isempty(repeated) + error('RAVEN:badInput','The rxnIdx %s is used by data rows %s of the rxnsInfo file; each reaction needs its own index', ... + num2str(uniqueIdx(repeated)),strjoin(arrayfun(@num2str,rowNo(idxGroup==repeated)','UniformOutput',false),', ')); + end + rxnCheck.rxns = indexKeys(rxnCheck.rxnsIdx,data(:,col.rxnNames)); notMatching = setxor(rxnCheck.coeffs,rxnCheck.rxns); - if numel(notMatching)>1 - error(['The following reactions ánd/or their indices are not matched '... + if ~isempty(notMatching) + error('RAVEN:badInput',['The following reactions and/or their indices are not matched '... 'between the rxnsInfo and rxnsCoeffs files:\n\t\t%s'],... - strjoin(regexprep(notMatching,'^\d+***',''),'\n\t\t')) + strjoin(regexprep(notMatching,'^(\d+)\*\*\*(.*)$','index $1 ($2)'),'\n\t\t')) end rxnsToAdd.rxnNames = data(:,col.rxnNames); for f = intersect({'grRules','subSystems','eccodes','rxnNotes','rxnReferences'},fieldnames(col)','stable') rxnsToAdd.(f{1}) = data(:,col.(f{1})); end + if isfield(col,'grRules') + rxnsToAdd.grRules = standardGrRules(rxnsToAdd.grRules,rxnsToAdd.rxnNames,rowNo); + end for f = intersect({'lb','ub','rev'},fieldnames(col)','stable') - rxnsToAdd.(f{1}) = cellfun(@str2num,data(:,col.(f{1}))); + rxnsToAdd.(f{1}) = parseNumericCells(data(:,col.(f{1})),f{1},'rxnsInfo'); + end + if isfield(rxnsToAdd,'rev') + checkRevValues(rxnsToAdd.rev,data(:,col.rev)); end if isfield(col,'rxnConfidenceScores') - rxnsToAdd.rxnConfidenceScores = cellfun(@str2num,data(:,col.rxnConfidenceScores),'UniformOutput',false); + rxnsToAdd.rxnConfidenceScores = cellfun(@(x) parseNumberCell(x,'rxnConfidenceScores','rxnsInfo'),data(:,col.rxnConfidenceScores),'UniformOutput',false); + isNanScore = cellfun(@(x) isscalar(x) && isnan(x),rxnsToAdd.rxnConfidenceScores); + rxnsToAdd.rxnConfidenceScores(isNanScore) = {[]}; % a NaN score counts as not given emptyEntry = cellfun(@isempty,rxnsToAdd.rxnConfidenceScores); + emptyScore = emptyEntry; if all(emptyEntry) rxnsToAdd = rmfield(rxnsToAdd,'rxnConfidenceScores'); else % If some rxnConfidenceScores are given, assume 0 for those without rxnConfidenceScores specified @@ -266,7 +376,7 @@ try newMetComp = getIndexes(newModel,newMetComp,'metcomps'); catch - error(['Not all metabolites in reaction "%s" are present in the '... + error('RAVEN:badInput',['Not all metabolites in reaction "%s" are present in the '... 'model or in the provided table with new metabolites.'],rxnsToAdd.rxnNames{i}) end rxnsToAdd.mets{i}(j) = newModel.mets(newMetComp); @@ -298,25 +408,41 @@ 'reactions, their annotation will be overwritten to match the '... 'rxnCoeffs file: \n\t\t(Existing reaction ID): New reaction name\n\t\t%s'],... strjoin(strcat('(',newModel.rxns(existingRxn),{'): '},rxnsToAdd.rxnNames(notNewRxn)),'\n\t\t')); - newModel.rxnNames(existingRxn) = rxnsToAdd.rxnNames(notNewRxn); + nameValues = rxnsToAdd.rxnNames(notNewRxn); + nameIdx = existingRxn; + if ~overwriteWithEmpty + nameIdx = nameIdx(~emptyCells(nameValues)); + nameValues = nameValues(~emptyCells(nameValues)); + end + newModel.rxnNames(nameIdx) = nameValues; for f = intersect({'lb','ub','rev'},fieldnames(rxnsToAdd)','stable') - newModel.(f{1})(existingRxn) = rxnsToAdd.(f{1})(notNewRxn); + values = rxnsToAdd.(f{1})(notNewRxn); + given = ~isnan(values); + newModel.(f{1})(existingRxn(given)) = values(given); end for f = optFields values = rxnsToAdd.(f{1})(notNewRxn); + skip = []; + if ~overwriteWithEmpty + if strcmp(f{1},'rxnConfidenceScores') + skip = emptyScore(notNewRxn); + else + skip = emptyCells(values); + end + end if strcmp(f{1},'subSystems') && isfield(newModel,'subSystems') ... && any(cellfun(@iscell,newModel.subSystems)) values = cellfun(@(x) {x}, values, 'UniformOutput', false); end - newModel = writeExistingField(newModel,f{1},existingRxn,values); + newModel = writeExistingField(newModel,f{1},existingRxn,values,skip); end if isfield(rxnsToAdd,'grRules') - emptyEntries = cellfun(@isempty,rxnsToAdd.grRules(notNewRxn)); + emptyEntries = emptyCells(rxnsToAdd.grRules(notNewRxn)); if ~all(emptyEntries) newModel = changeGrRules(newModel,newModel.rxns(existingRxn(~emptyEntries)),rxnsToAdd.grRules(notNewRxn(~emptyEntries)),true); end end - newModel = extractAndAddMiriam(newModel,header(miriamCols),data(:,miriamCols),notNewRxn,existingRxn,'rxn'); + newModel = extractAndAddMiriam(newModel,header(miriamCols),data(:,miriamCols),notNewRxn,existingRxn,'rxn',~overwriteWithEmpty); end % Continue with new reactions @@ -327,6 +453,16 @@ for f = intersect({'lb','ub','rev','grRules'},fieldnames(rxnsToAdd)','stable') rxnsToAdd.(f{1}) = rxnsToAdd.(f{1})(toAdd); end + if isfield(rxnsToAdd,'lb') + rxnsToAdd.lb(isnan(rxnsToAdd.lb)) = -inf; + end + if isfield(rxnsToAdd,'ub') + defaultUB = inf; + if isfield(newModel,'annotation') && isfield(newModel.annotation,'defaultUB') + defaultUB = newModel.annotation.defaultUB; + end + rxnsToAdd.ub(isnan(rxnsToAdd.ub)) = defaultUB; + end for f = optFields rxnsToAdd.(f{1}) = rxnsToAdd.(f{1})(toAdd); if iscell(rxnsToAdd.(f{1})) && all(cellfun(@isempty,rxnsToAdd.(f{1}))) @@ -349,16 +485,16 @@ % array of text with one column per header field. fid = fopen(fileName); if fid==-1 - error('Could not open the file "%s"',fileName); + error('RAVEN:badInput','Could not open the file "%s"',fileName); end headerLine = fgetl(fid); if ~ischar(headerLine) fclose(fid); - error('The file "%s" is empty',fileName); + error('RAVEN:badInput','The file "%s" is empty',fileName); end headerLine = strrep(headerLine,char(65279),''); % byte order mark header = strtrim(strsplit(headerLine,'\t')); -header = regexprep(header,'^"(.*)"$','$1'); +header = strtrim(regexprep(header,'^"(.*)"$','$1')); nCols = numel(header); raw = textscan(fid,repmat('%q',1,nCols),'Delimiter','\t'); fclose(fid); @@ -367,6 +503,8 @@ for i=1:nCols data(1:numel(raw{i}),i) = raw{i}; end +blank = cellfun(@(x) ischar(x) && isempty(strtrim(x)),data); +data(blank) = {''}; end function [col,miriamCols] = matchColumns(header,fileName,required,spec) @@ -384,15 +522,23 @@ end missing = setdiff(required,fieldnames(col)','stable'); if ~isempty(missing) - error('The file "%s" has no column with header: %s',fileName,strjoin(missing,', ')); + error('RAVEN:badInput','The file "%s" has no column with header: %s',fileName,strjoin(missing,', ')); end miriamCols = find(~isCore & ~cellfun(@isempty,header)); end -function model = writeExistingField(model,field,modelIdx,values) +function model = writeExistingField(model,field,modelIdx,values,skip) % Write the values of an optional field onto existing entities. A field % the model does not have is created from its ravenModelFields default, -% unless all supplied values are empty. +% unless all supplied values are empty. Entries flagged in the logical +% vector skip are not written. +if nargin>4 && ~isempty(skip) + modelIdx = modelIdx(~skip(:)'); + values = values(~skip(:)); + if isempty(modelIdx) + return + end +end if ~isfield(model,field) if iscell(values) && all(cellfun(@isempty,values)) return @@ -409,9 +555,25 @@ model.(field)(modelIdx) = values; end -function newModel = extractAndAddMiriam(model,miriamName,miriamData,inputIndex,modelIndex,type) +function newModel = extractAndAddMiriam(model,miriamName,miriamData,inputIndex,modelIndex,type,skipEmpty) +% Replace the MIRIAM annotations of the entities in modelIndex with the +% values of the table rows in inputIndex. With skipEmpty, an empty cell +% leaves the annotation of that entity unchanged. newModel=model; miriamValues = miriamData(inputIndex,:); +if nargin>6 && skipEmpty + for i=1:numel(miriamName) + given = ~emptyCells(miriamValues(:,i)); + if any(given) + if ~isfield(newModel,[type 'Miriams']) + newModel.([type 'Miriams'])=cell(numel(newModel.([type 's'])),1); + end + idx = modelIndex(:); + newModel = editMiriam(newModel,type,idx(given),miriamName{i},miriamValues(given,i),'replace'); + end + end + return +end emptyMiriam = all(cellfun(@isempty,miriamValues),1); miriamName(emptyMiriam) = []; miriamValues(:,emptyMiriam) = []; @@ -422,3 +584,142 @@ newModel = editMiriam(newModel,type,modelIndex,miriamName{i},miriamValues(:,i),'replace'); end end + +function empty = emptyCells(values) +% Logical column vector that is true for the empty entries of a cell array +% of text (a cell with only blanks counts as empty) or of a numeric cell. +empty = cellfun(@(x) isempty(x) || (ischar(x) && isempty(strtrim(x))),values(:)); +end + +function values = parseIndexCells(cells,field,fileLabel) +% Reaction indices from table cells: NaN where the cell is empty or says NaN +% (the row is ignored), otherwise a finite whole number of at least 1. +values = parseNumericCells(cells,field,fileLabel); +bad = find(~isnan(values) & (~isfinite(values) | values~=round(values) | values<1),1); +if ~isempty(bad) + error('RAVEN:badInput','The %s value "%s" in the %s file is not a whole number of at least 1',field,strtrim(cells{bad}),fileLabel); +end +end + +function values = parseNumericCells(cells,field,fileLabel) +% Numbers from table cells, NaN where the cell is empty or says NaN. A cell +% that is not a number (or true/false) raises an error. +values = nan(numel(cells),1); +for i=1:numel(cells) + v = parseNumberCell(cells{i},field,fileLabel); + if ~isempty(v) + values(i) = v; + end +end +end + +function v = parseChargeCell(text,fileLabel) +% Charge from a table cell: [] for an empty cell, NaN for NaN, otherwise a +% finite whole number. A fractional or infinite charge raises an error. +v = parseNumberCell(text,'charge',fileLabel); +if ~isempty(v) && ~isnan(v) && (~isfinite(v) || v~=round(v)) + error('RAVEN:badInput','The charge value "%s" in the %s file is not a finite whole number',strtrim(text),fileLabel); +end +end + +function checkCoefficientsGiven(idx,coeffs,rowNo) +% Every row of the rxnsCoeffs table that is kept has a rxnIdx and a +% coefficient that is a finite number other than 0. +noIdx = find(isnan(idx),1); +if ~isempty(noIdx) + error('RAVEN:badInput',['Data row %d of the rxnsCoeffs file has values but its rxnIdx is empty or NaN'],rowNo(noIdx)); +end +missing = find(isnan(coeffs),1); +if ~isempty(missing) + error('RAVEN:badInput',['The coefficient in data row %d of the rxnsCoeffs file is empty or NaN; ' ... + 'every row with a reaction, metabolite or compartment needs a coefficient'],rowNo(missing)); +end +bad = find(~isfinite(coeffs) | coeffs==0,1); +if ~isempty(bad) + error('RAVEN:badInput','The coefficient in data row %d of the rxnsCoeffs file must be a finite non-zero number',rowNo(bad)); +end +end + +function keys = indexKeys(idx,names) +% One key per table row from the numeric reaction index and the name, so that +% the indices of two tables are compared by value. +keys = cell(numel(idx),1); +for i=1:numel(idx) + keys{i} = sprintf('%.0f***%s',idx(i),names{i}); +end +end + +function checkGenesListedOnce(genes) +% Raise RAVEN:badInput for a gene id that the genesInfo table lists more than +% once, whether the model has the gene or not: two rows for one gene would +% say two things about it, and the second row would add a second gene with +% the same id. +[ids,~,j] = unique(genes); +repeated = ids(accumarray(j,1) > 1); +if ~isempty(repeated) + error('RAVEN:badInput',['The following genes are listed more than once '... + 'in the genesInfo file:\n\t\t%s'],strjoin(repeated,'\n\t\t')); +end +end + + +function rules = standardGrRules(rules,rxnNames,rowNo) +% Raise RAVEN:badInput for a grRules cell that is not a well-formed gene rule +% (unbalanced parentheses, an operator without two operands, empty +% parentheses, two genes without an operator between them) and write each +% other rule in RAVEN's standard format (" and " / " or ", parentheses only +% where needed), so that addRxns and changeGrRules read every spelling that +% parseGrRule reads. Empty cells are left as they are. +for i = find(~emptyCells(rules))' + try + rules{i} = grRuleToString(parseGrRule(rules{i})); + catch err + if strcmp(err.identifier,'RAVEN:badGrRule') + error('RAVEN:badInput','The grRules value "%s" of reaction "%s" (data row %d of the rxnsInfo file) is not a valid gene rule: %s',... + rules{i},rxnNames{i},rowNo(i),err.message); + end + rethrow(err); + end +end +end + + +function checkRevValues(values,cells) +% A rev cell is 0, 1, true or false; an empty or NaN cell is not given. +bad = find(~isnan(values) & values~=0 & values~=1,1); +if ~isempty(bad) + error('RAVEN:badInput','The rev value "%s" in the rxnsInfo file is not 0, 1, true or false',strtrim(cells{bad})); +end +end + +function v = parseNumberCell(text,field,fileLabel) +% Real number from a table cell: [] for an empty cell, 1 or 0 for true or +% false in any case (lb, ub and rev cells only), NaN or +/-Inf for those words in any case, otherwise a +% plain decimal number (digits with an optional sign, decimal point and +% exponent). Any other text raises an error; nothing is evaluated. +text = strtrim(text); +if isempty(text) + v = []; + return +end +word = lower(text); +if any(strcmp(word,{'true','false'})) && ~ismember(field,{'lb','ub','rev'}) + error('RAVEN:badInput','The %s value "%s" in the %s file is not a number',field,text,fileLabel); +end +if strcmp(word,'true') + v = 1; +elseif strcmp(word,'false') + v = 0; +elseif ~isempty(regexp(word,'^[+-]?nan$','once')) + v = NaN; +elseif ~isempty(regexp(word,'^[+-]?inf$','once')) + v = Inf; + if word(1)=='-' + v = -Inf; + end +elseif ~isempty(regexp(text,'^[+-]?([0-9]+\.?[0-9]*|\.[0-9]+)([eE][+-]?[0-9]+)?$','once')) + v = str2double(text); +else + error('RAVEN:badInput','The %s value "%s" in the %s file is not a number',field,text,fileLabel); +end +end diff --git a/src/gapfilling/gapFillMILP.m b/src/gapfilling/gapFillMILP.m index 34da6e53..fb574dbf 100644 --- a/src/gapfilling/gapFillMILP.m +++ b/src/gapfilling/gapFillMILP.m @@ -27,8 +27,8 @@ % minimum objective value to achieve. If empty, runs FBA on the merged % model and uses 10% of the maximum as the floor. % 'bigM' (default 1000) -% Big-M constant for coupling constraints. Must be larger than any -% expected flux magnitude. +% Big-M constant for the coupling constraints of universal reactions +% with an infinite bound. A finite bound is used as it is. % 'params' (default []) % solver parameter struct passed to optimizeProb. Fields such as % params.TimeLimit and params.intTol can be used to tune the MILP. @@ -160,13 +160,15 @@ % (3) Universal upper coupling: v_k - ub_k * y_db_k <= 0 [nUniv rows, 'L'] % (forces v_k <= 0 when y_db_k = 0) -% Coupling coefficients are capped at the Big-M constant: an unbounded -% universal reaction (ub = Inf, RAVEN's own default when no upper-bound -% annotation is set) would otherwise put an Inf coefficient into the -% constraint matrix, and Inf*0 (y_db_k = 0) is NaN, not 0. +% Coupling coefficients are the reaction bounds. An infinite bound is replaced +% by the Big-M constant: an unbounded universal reaction (ub = Inf, RAVEN's own +% default when no upper-bound annotation is set) would otherwise put an Inf +% coefficient into the constraint matrix, and Inf*0 (y_db_k = 0) is NaN, not 0. yDbCols = nMergedRxns + (1:nUniv); -uniUb = min(mergedModel.ub(univIdx), M); -uniLb = max(mergedModel.lb(univIdx), -M); +uniUb = mergedModel.ub(univIdx); +uniUb(isinf(uniUb)) = M; +uniLb = mergedModel.lb(univIdx); +uniLb(isinf(uniLb)) = -M; A_uniUp = sparse(... [1:nUniv, 1:nUniv], ... [univIdx(:)', yDbCols], ... diff --git a/src/io/exportForGit.m b/src/io/exportForGit.m index 2c9838ed..59f289fa 100755 --- a/src/io/exportForGit.m +++ b/src/io/exportForGit.m @@ -6,6 +6,10 @@ % model as SBML L3V1 FBCv2 (both XML and YAML), COBRA text, Matlab MAT-file % and Microsoft Excel formats. % +% The flags mainBranchFlag, subDirs, COBRAtext and neverPrefixIDs accept a +% scalar logical or 0/1, or an empty value for the default. Text and other +% types raise RAVEN:badInput. +% % Parameters % ---------- % model : struct @@ -17,7 +21,8 @@ % prefix for all filenames (default 'model'). % path : char % path where the directory structure should be generated and populated -% with all files (default current working directory). +% with all files (default current working directory). The folder, with +% any missing parent folders, is created if it does not exist. % formats : cell % cell array of strings specifying in what file formats the model % should be exported (default all formats as {'mat', 'txt', 'xlsx', @@ -45,15 +50,10 @@ % COBRAtext, neverPrefixIDs); p=parseRAVENargs(varargin, {'prefix',[]; 'path',[]; 'formats',[]; 'mainBranchFlag',[]; 'subDirs',[]; 'COBRAtext',[]; 'neverPrefixIDs',false}); prefix=p.prefix; path=p.path; formats=p.formats; mainBranchFlag=p.mainBranchFlag; subDirs=p.subDirs; COBRAtext=p.COBRAtext; neverPrefixIDs=p.neverPrefixIDs; -if isempty(COBRAtext) - COBRAtext=false; -end -if isempty(subDirs) - subDirs=true; -end -if isempty(mainBranchFlag) - mainBranchFlag=false; -end +COBRAtext=checkFlag(COBRAtext,'COBRAtext',false); +subDirs=checkFlag(subDirs,'subDirs',true); +mainBranchFlag=checkFlag(mainBranchFlag,'mainBranchFlag',false); +neverPrefixIDs=checkFlag(neverPrefixIDs,'neverPrefixIDs',false); if isempty(formats) formats={'mat', 'txt', 'xlsx', 'xml', 'yml'}; else @@ -98,7 +98,7 @@ delete('tempModelForLibSBMLversion.xml'); end -% Make models folder, no warnings if folder already exists +% Make the folders, no warnings if they already exist if subDirs path=fullfile(path,'model'); filePath=strcat(path,filesep,{'txt','yml','mat','xlsx','xml'}); @@ -107,6 +107,7 @@ [~,~,~]=mkdir(fullfile(path,formats{i})); end else + [~,~,~]=mkdir(path); filePath=cell(1,5); filePath(:)={path}; end @@ -125,12 +126,12 @@ eqns=constructEquations(model,model.rxns); grRules=model.grRules; end - fprintf(fid, 'Rxn name\tFormula\tGene-reaction association\tLB\tUB\tObjective\n'); + fprintf(fid, 'Rxn id\tFormula\tGene-reaction association\tLB\tUB\tObjective\n'); for i = 1:numel(model.rxns) fprintf(fid, '%s\t', model.rxns{i}); - fprintf(fid, '%s \t', eqns{i}); + fprintf(fid, '%s\t', strtrim(eqns{i})); fprintf(fid, '%s\t', grRules{i}); - fprintf(fid, '%6.2f\t%6.2f\t%6.2f\n', model.lb(i), model.ub(i), model.c(i)); + fprintf(fid, '%s\t%s\t%s\n', shortestNumber(model.lb(i)), shortestNumber(model.ub(i)), shortestNumber(model.c(i))); end fclose(fid); end @@ -173,6 +174,32 @@ fclose(fid); end +function s = shortestNumber(x) +% shortestNumber The shortest text that reads back as exactly x, so a bound +% is never rounded on the way out (1/3 is 0.3333333333333333, 1000 is 1000). +if isinf(x) + if x > 0 + s = 'Inf'; + else + s = '-Inf'; + end + return +elseif isnan(x) + s = 'NaN'; + return +end +if x == floor(x) && abs(x) < 1e15 + s = sprintf('%d', x); + return +end +for digits = 1:17 + s = sprintf(['%.' num2str(digits) 'g'], x); + if str2double(s) == x + break + end +end +end + function version = getToolboxVersion(toolbox,fileID,mainBranchFlag) % getToolboxVersion Return the version of RAVEN or GECKO. % @@ -209,7 +236,7 @@ currentBranch = strtrim(currentBranch); if ~any([strcmp(currentBranch, "main"), strcmp(currentBranch, "master")]) cd(currentPath); - error(['ERROR: ' toolbox ' not in main (or master) branch. Check-out this branch of ' toolbox ' before submitting model for Git.']) + error('RAVEN:notMainBranch',['ERROR: ' toolbox ' not in main (or master) branch. Check-out this branch of ' toolbox ' before submitting model for Git.']) end end %Read the version from the toolbox version.txt; report 'unknown' otherwise @@ -232,3 +259,14 @@ slashPos = strfind(path,'/'); %MAC/Linux end end + +function flag = checkFlag(flag, name, default) +%An empty value gives the default; otherwise a scalar logical or 0/1 is required +if isempty(flag) + flag = default; +elseif (islogical(flag) || isnumeric(flag)) && isscalar(flag) && isreal(flag) && (flag == 0 || flag == 1) + flag = logical(flag); +else + error('RAVEN:badInput', '%s must be a scalar logical or 0/1.', name); +end +end diff --git a/src/manipulation/contractModel.m b/src/manipulation/contractModel.m index cc0996f9..d99108ed 100755 --- a/src/manipulation/contractModel.m +++ b/src/manipulation/contractModel.m @@ -50,7 +50,7 @@ %First sort the model so that reversible reactions are in the same %direction -modelS=sortModel(model); +modelS=sortModel(model,true); %Get a list of duplicate reactions if distReverse diff --git a/src/manipulation/generateNewIds.m b/src/manipulation/generateNewIds.m index cf16c6c3..e6227fc0 100755 --- a/src/manipulation/generateNewIds.m +++ b/src/manipulation/generateNewIds.m @@ -2,7 +2,10 @@ % generateNewIds Generate a list of new metabolite or reaction ids. % % The ids are sequentially numbered with a defined prefix. The model is -% queried for the highest existing number of that type of id. +% queried for the highest existing number of that type of id. Suffixes are +% read with str2double. A suffix counts as a number when the result is a +% finite integer value below 2^52; text, Inf, NaN, fractions (r_2.5) and +% numbers of 2^52 or more (r_1e300, r_1e400) do not count. % % Parameters % ---------- @@ -52,10 +55,19 @@ if ~isempty(existingIds) existingIds=regexprep(existingIds,['^' escapedPrefix],''); - existingIds=sort(existingIds); - lastId=existingIds{end}; - numLength=length(lastId); - lastId=str2double(lastId); + numbers=str2double(existingIds); + notInteger=~isfinite(numbers) | imag(numbers)~=0 | real(numbers)~=floor(real(numbers)) | abs(real(numbers))>=flintmax/2; + numbers=real(numbers); + numbers(notInteger)=NaN; + if all(isnan(numbers)) + [~,I]=sort(existingIds); + lastId=existingIds{I(end)}; + numLength=length(lastId); + lastId=NaN; + else + [lastId,I]=max(numbers); + numLength=length(existingIds{I}); + end else lastId=0; fprintf(['No ' type ' ids with prefix "' prefix ... diff --git a/src/manipulation/sortModel.m b/src/manipulation/sortModel.m index 40ed0efc..481924b5 100755 --- a/src/manipulation/sortModel.m +++ b/src/manipulation/sortModel.m @@ -1,5 +1,5 @@ function model=sortModel(model,varargin) -% sortModel Standardize a model's internal ordering: by default orients +% sortModel Standardize a model's internal ordering: can orient % reversible reactions consistently; can also reorder metabolites within % reaction equations and/or reactions within each subsystem, depending on % which options are enabled. @@ -13,7 +13,7 @@ % -------------------- % sortReversible : logical % sorts the reversible reactions so that the metabolite that is first -% in lexicographical order is a reactant (default true). +% in lexicographical order is a reactant (default false). % sortMetName : logical % sort the metabolite names in the equation, also uses compartment % abbreviation (default false). @@ -37,7 +37,7 @@ % -------- % model = sortModel(model, sortReversible, sortMetName, sortReactionOrder); -p=parseRAVENargs(varargin, {'sortReversible',true; 'sortMetName',false; 'sortReactionOrder',false; 'seed',[]}); +p=parseRAVENargs(varargin, {'sortReversible',false; 'sortMetName',false; 'sortReactionOrder',false; 'seed',[]}); if ~isempty(p.seed) rng(p.seed); end diff --git a/src/queries/constructEquations.m b/src/queries/constructEquations.m index a439bccf..de3368a5 100755 --- a/src/queries/constructEquations.m +++ b/src/queries/constructEquations.m @@ -61,7 +61,7 @@ %Sort reversible equations if sortRevRxns==true - model=sortModel(model); + model=sortModel(model,true); end %Sort metabolite names, including compartment diff --git a/src/reconstruction/homology/getModelFromHomology.m b/src/reconstruction/homology/getModelFromHomology.m index db277511..13dfb33d 100755 --- a/src/reconstruction/homology/getModelFromHomology.m +++ b/src/reconstruction/homology/getModelFromHomology.m @@ -493,7 +493,9 @@ %Remove all the genes that were already found and add the other %ones to allUsedGenes - models{useOrderIndexes(i)}=removeGenes(models{useOrderIndexes(i)},allGenes{i+1}(genesToDelete),true,true,false); + if ~isempty(genesToDelete) + models{useOrderIndexes(i)}=removeGenes(models{useOrderIndexes(i)},allGenes{i+1}(genesToDelete),true,true,false); + end allUsedGenes(usedGenes)=true; %Remove the deleted genes from finalMappings and allGenes. diff --git a/src/tasks/parseTaskList.m b/src/tasks/parseTaskList.m index 0c946d51..dde8b8f5 100755 --- a/src/tasks/parseTaskList.m +++ b/src/tasks/parseTaskList.m @@ -5,14 +5,16 @@ % ---------- % inputFile : char % a task list in either Excel (*.xlsx, with a sheet named TASKS with -% all relevant content) or tab-delimited (*.txt) format. The file may -% contain the following column headers (note, all rows starting with a -% non-empty cell are removed; the first row after that is considered -% the headers): +% all relevant content) or tab-delimited (*.txt) format. The old binary +% *.xls format is not supported. The file may contain the following +% column headers (note, all rows starting with a non-empty cell are +% removed; the first row after that is considered the headers; the +% header row itself must therefore start with an empty cell): % -% - ID : the only required header. Each task must have a unique id -% (string or numeric). Tasks can span multiple rows, only the first -% row in each task should have an id +% - ID : the only required header (string or numeric). Ids may repeat, +% for example as category codes. A task starts at a row with a +% non-empty id and can span multiple rows; only the first row of a task +% should have an id % - DESCRIPTION : description of the task % - IN : allowed input(s) for the task. Metabolite names should be on % the form "model.metName[model.comps]". Several inputs can be @@ -115,19 +117,19 @@ end %Load the tasks file -convNumeric = false; if strcmp(extractAfter(inputFile,strlength(inputFile) - 4), '.txt') %load from tab delimited text file fid = fopen(inputFile); %Need to read numeric columns as strings, this is converted further %down. If not, the titles would be lost. - convNumeric = true; C = textscan(fid,'%q%q%q%q%q%q%q%q%q%q%q%q%q%q%q%q%q%q%q%q%*[^\n]', 'Delimiter', '\t'); fclose(fid); raw = [C{:}];%unnest the cell array of cell arrays into a 2-dim cell array else - %Read the TASKS sheet from an Excel file (readcell handles both .xls - %and .xlsx). + if endsWith(lower(char(inputFile)),'.xls') + error('RAVEN:badInput', '%s', [inputFile ': the old binary .xls format is not supported. Save the task list as .xlsx (sheet ''TASKS'') or as a tab-delimited .txt file.']); + end + %Read the TASKS sheet from an Excel file try raw=readcell(inputFile,'Sheet','TASKS'); catch @@ -138,6 +140,27 @@ raw(cellfun(@(x) all(ismissing(x)),raw))={[]}; end +%Rows without any content carry no information (a header must not be +%mistaken for one of them) +if iscell(raw) + emptyRow=all(cellfun(@(x) isempty(x) || (ischar(x) && isempty(strtrim(x))),raw),2); + raw=raw(~emptyRow,:); +end + +%Leading rows that start with a non-empty cell are comments; the first row +%with an empty first cell is the header row +if iscell(raw) + if isempty(raw) + error('RAVEN:badInput', '%s', 'The TASKS sheet must have a column named ID'); + end + firstBlank=cellfun(@(x) isempty(x) || (ischar(x) && isempty(strtrim(x))),raw(:,1)); + first=find(firstBlank,1); + if isempty(first) + error('RAVEN:badInput', '%s', 'The TASKS sheet must have a column named ID'); + end + raw=raw(first:end,:); +end + %Remove all lines starting with "#" (or actually any character) and all %empty columns raw=cleanSheet(raw); @@ -152,21 +175,51 @@ [I, colI]=ismember(columns,header); %Only ID is required. Columns that are absent get index 0 from ismember, which -%cannot be used to index raw, so point them at a spare empty column instead: -%every optional field then reads as empty and its default applies. -raw(:,end+1)={[]}; -colI(colI==0)=size(raw,2); +%cannot be used to index raw, so point each of them at its own spare empty +%column instead: every optional field then reads as empty and its default +%applies. +absent=find(colI==0); +nOld=size(raw,2); +raw(:,nOld+(1:numel(absent)))={[]}; +colI(absent)=nOld+(1:numel(absent)); -%If read from a text file, the numbers will be strings - fix that -if convNumeric % safeguard; a no-op if the columns are already numeric - numericColumns = [0 0 0 1 1 0 1 1 0 1 1 0 1 1 0 0 0] == 1; - cols = colI(numericColumns); - numeric = cellfun(@isnumeric,raw(:,cols)); - %trick to avoid messing up the title row: - numeric(1,:) = 1; - for colind = 1:numel(cols) - col = cols(colind); - raw(~numeric(:,colind),col) = cellfun(@str2num, raw(~numeric(:,colind),col), 'UniformOutput', false); +%Text cells (every cell of a text file, and numbers typed as text in Excel) +%are converted to numbers; text that is not a number stays and is refused +%below +numericColumns = [0 0 0 1 1 0 1 1 0 1 1 0 1 1 0 0 0] == 1; +cols = colI(numericColumns); +numeric = cellfun(@isnumeric,raw(:,cols)); +%trick to avoid messing up the title row: +numeric(1,:) = 1; +for colind = 1:numel(cols) + col = cols(colind); + for r = find(~numeric(:,colind))' + x = raw{r,col}; + if ischar(x) && isempty(strtrim(x)) + raw{r,col} = []; + elseif ischar(x) + %Only plain numbers are accepted; str2double alone would also read + %'1,5' as 15, '1,000' as 1000 and '2i' as a complex number + if ~isempty(regexpi(strtrim(x),'^([+-]?(\d+\.?\d*|\.\d+)(e[+-]?\d+)?|[+-]?inf|nan)$','once')) + raw{r,col} = str2double(x); + end + end + end +end + +%Every bound that is given must be a number +numericNames = columns([4 5 7 8 10 11 13 14]); +numericCols = colI([4 5 7 8 10 11 13 14]); +for k = 1:numel(numericCols) + for r = 2:size(raw,1) + x = raw{r,numericCols(k)}; + if ~isempty(x) && ~(isnumeric(x) && isscalar(x)) + error('RAVEN:badInput', 'The value in column %s of task list data row %d is not a number (rows are counted after the header, comment rows and empty rows are removed)', numericNames{k}, r-1); + end + %NaN means "leave unchanged" only in the bounds of changed reactions + if k <= 6 && isnumeric(x) && isscalar(x) && isnan(x) + error('RAVEN:badInput', 'The value in column %s of task list data row %d is NaN, which is only accepted in the bounds of changed reactions (rows are counted after the header, comment rows and empty rows are removed)', numericNames{k}, r-1); + end end end @@ -176,7 +229,19 @@ error('RAVEN:badInput', '%s', EM); end -%make sure numerical fields are converted from strings +%Rows before the first task id belong to no task +firstId=find(~cellfun(@isempty,raw(2:end,colI(1))),1)+1; +if isempty(firstId) + firstId=size(raw,1)+1; +end +if firstId>2 + warning('RAVEN:warning', 'Skipping %d row(s) before the first task id', firstId-2); + raw(2:firstId-1,:)=[]; +end +if size(raw,1)<2 + taskStruct=[]; + return; +end %Add default bounds where needed for i=[4 5 7 8] diff --git a/tests/function_tests/tAnalysis.m b/tests/function_tests/tAnalysis.m index fe54823a..9b49a086 100644 --- a/tests/function_tests/tAnalysis.m +++ b/tests/function_tests/tAnalysis.m @@ -311,6 +311,161 @@ function reporterMetabolitesUsesPopulationStd(testCase) testCase.verifyEqual(rm.metPValues(strcmp(rm.mets,'A')), 0.5*erfc(expectedA/sqrt(2)), 'AbsTol', 1e-12); end + function reporterMetabolitesGivesZeroScoreWhenBackgroundStdIsZero(testCase) + % All scored genes have the same Z-score, so the background + % standard deviation is 0 and no metabolite stands out. + m = struct(); + m.mets = {'A';'B'}; + m.metNames = {'A';'B'}; + m.genes = {'g1';'g2';'g3'}; + m.S = sparse([1 0; 0 1]); + m.rxnGeneMat = sparse([1 1 0; 0 0 1]); + rm = reporterMetabolites(m, m.genes, [0.2; 0.2; 0.2]); + testCase.verifyEqual(rm.metZScores, zeros(2,1)); + testCase.verifyEqual(rm.metPValues, 0.5*ones(2,1)); + end + + function reporterMetabolitesGivesZeroScoreForRoundedIdenticalZScores(testCase) + % The mean of identical Z-scores can differ from them by + % rounding, which must not read as a background spread. These + % gene counts leave a rounding residual. + for n = [7 8 15 20000] + m = struct(); + m.mets = {'A'}; + m.metNames = {'A'}; + m.genes = arrayfun(@(i) sprintf('g%d',i), 1:n, 'UniformOutput', false)'; + m.S = sparse(1); + m.rxnGeneMat = sparse(ones(1, n)); + for pv = [0.2 0.37] + rm = reporterMetabolites(m, m.genes, pv*ones(n,1)); + testCase.verifyEqual(rm.metZScores, 0); + testCase.verifyEqual(rm.metPValues, 0.5); + end + end + end + + function reporterMetabolitesKeepsTinyRealSpreadOfZScores(testCase) + % A spread far above rounding is a background: three genes + % whose Z-scores differ by 1e-10 are scored against it. + m = struct(); + m.mets = {'A'}; + m.metNames = {'A'}; + m.genes = {'g1';'g2';'g3'}; + m.S = sparse(1); + m.rxnGeneMat = sparse([1 1 0]); + z = 1e-10*[1;2;3]; + pvals = 0.5*erfc(z/sqrt(2)); + rm = reporterMetabolites(m, m.genes, pvals); + zz = sqrt(2)*erfcinv(2*pvals); + expected = ((zz(1)+zz(2))/sqrt(2) - sqrt(2)*mean(zz)) / std(zz,1); + testCase.verifyEqual(rm.metZScores, expected, 'RelTol', 1e-6); + end + + function reporterMetabolitesKeepsPrecisionOfCloseZScores(testCase) + % 20000 genes with the same p-value except one that is 1000 + % ulps lower: the Z-scores differ by about 1e-13, so the scores + % need the shifted moments. A has 10 of the identical genes, + % B has the shifted gene. + n = 20000; + pv = 0.37*ones(n,1); + pv(n) = 0.37 - 1000*eps(0.37); + z = sqrt(2)*erfcinv(2*pv); + m = struct(); + m.mets = {'A';'B'}; + m.metNames = {'A';'B'}; + m.genes = arrayfun(@(i) sprintf('g%d',i), 1:n, 'UniformOutput', false)'; + m.S = sparse([1 0; 0 1]); + G = zeros(2,n); G(1,1:10) = 1; G(2,n) = 1; + m.rxnGeneMat = sparse(G); + rm = reporterMetabolites(m, m.genes, pv); + [~,ia] = ismember({'A','B'}, rm.mets); + sgn = sign(z(n)-z(1)); + testCase.verifyEqual(rm.metZScores(ia(1)), -sgn*sqrt(10/(n-1)), 'RelTol', 1e-3); + expectedB = sgn*sqrt(n-1); + testCase.verifyEqual(rm.metZScores(ia(2)), expectedB, 'RelTol', 1e-3); + end + + function reporterMetabolitesKeepsPrecisionWithOutlierFirst(testCase) + % The first gene has a Z-score of about 37 and the other 19999 + % genes differ from each other by about 1e-13 (the last one has a + % p-value 1000 ulps lower). Moments taken relative to the first + % gene's Z-score are off by about 5e-10 (relative); moments + % relative to the mean are within 1e-13. The reference shifts by + % the second gene's Z-score. + n = 20000; + pv = 0.37*ones(n,1); + pv(1) = 1e-300; + pv(n) = 0.37 - 1000*eps(0.37); + z = sqrt(2)*erfcinv(2*pv); + m = struct(); + m.mets = {'A';'B'}; + m.metNames = {'A';'B'}; + m.genes = arrayfun(@(i) sprintf('g%d',i), 1:n, 'UniformOutput', false)'; + m.S = sparse([1 0; 0 1]); + G = zeros(2,n); G(1,2:11) = 1; G(2,n) = 1; + m.rxnGeneMat = sparse(G); + rm = reporterMetabolites(m, m.genes, pv); + [~,ia] = ismember({'A','B'}, rm.mets); + zr = z - z(2); + mu = mean(zr); + sigma = std(zr,1); + expectedA = (sum(zr(2:11))/sqrt(10) - sqrt(10)*mu)/sigma; + expectedB = (zr(n) - mu)/sigma; + testCase.verifyEqual(rm.metZScores(ia(1)), expectedA, 'RelTol', 1e-11); + testCase.verifyEqual(rm.metZScores(ia(2)), expectedB, 'RelTol', 1e-11); + end + + function reporterMetabolitesRefusesPValuesOutsideZeroOne(testCase) + % Three metabolites around the gene sets {1,2}, {3,4} and {5,6}. + m = struct(); + m.mets = {'A';'B';'C'}; + m.metNames = {'A';'B';'C'}; + m.genes = {'g1';'g2';'g3';'g4';'g5';'g6'}; + m.S = sparse(eye(3)); + m.rxnGeneMat = sparse([1 1 0 0 0 0; 0 0 1 1 0 0; 0 0 0 0 1 1]); + bad = {[0.2 0.4 0.7 0.3 0.1 1+eps], ... + [Inf 0.2 0.4 0.7 -Inf 0.1], ... + [0.2 0.4 0.7 0.3 0.1 -eps], ... + [0.2 0.4 0.7 0.3 0.1 -Inf]}; + for i = 1:numel(bad) + testCase.verifyError(@() reporterMetabolites(m, m.genes, bad{i}'), 'RAVEN:badInput'); + end + % The message gives the number of values and the first one. + try + reporterMetabolites(m, m.genes, [0.2 1.5 0.7 0.3 -0.1 0.1]'); + testCase.verifyFail('No error for P-values outside [0,1]'); + catch ME + testCase.verifyEqual(ME.identifier, 'RAVEN:badInput'); + testCase.verifySubstring(ME.message, '2 of them'); + testCase.verifySubstring(ME.message, '1.5 (gene g2)'); + end + % A value that is outside the range is refused also for a gene + % that is not in the model. + testCase.verifyError(@() reporterMetabolites(m, [m.genes; {'gX'}], [0.2 0.4 0.7 0.3 0.1 0.5 2]'), 'RAVEN:badInput'); + % 0 and 1 are valid, NaN is ignored + rm = reporterMetabolites(m, m.genes, [0 0.4 0.7 0.3 NaN 1]'); + testCase.verifyEqual(numel(rm.mets), 3); + testCase.verifyEqual(rm.metNGenes(strcmp(rm.mets,'C')), 1); + end + + function reporterMetabolitesFieldsStayAlignedWithoutScoredGenes(testCase) + % Metabolite C has no scored gene. All result fields drop it + % together, so that they stay aligned with the metabolites. + m = struct(); + m.mets = {'A';'B';'C'}; + m.metNames = {'A';'B';'C'}; + m.genes = {'g1';'g2';'g3';'g4'}; + m.S = sparse(eye(3)); + m.rxnGeneMat = sparse([1 1 0 0; 0 0 1 0; 0 0 0 0]); + rm = reporterMetabolites(m, m.genes, [0.01; 0.2; 0.5; 0.9]); + testCase.verifyEqual(sort(rm.mets), {'A';'B'}); + for f = {'metNames','metZScores','metPValues','metNGenes','meanZ','stdZ'} + testCase.verifyEqual(numel(rm.(f{1})), 2, f{1}); + end + testCase.verifyEqual(rm.metNGenes(strcmp(rm.mets,'A')), 2); + testCase.verifyEqual(rm.metNGenes(strcmp(rm.mets,'B')), 1); + end + function FSEOFRuns(testCase) biomassRxn = testCase.model.rxns{find(testCase.model.c == 1, 1)}; [~, exchIdx] = getExchangeRxns(testCase.model); diff --git a/tests/function_tests/tBiomass.m b/tests/function_tests/tBiomass.m index 74d1154b..30aa4eff 100644 --- a/tests/function_tests/tBiomass.m +++ b/tests/function_tests/tBiomass.m @@ -76,6 +76,51 @@ function scaleBiomassPseudoreactionRebalancesProton(testCase) testCase.verifyEqual(full(out.S(strcmp(out.mets,'h_c'), 1)), -1, 'AbsTol', 1e-9); end + function scaleBiomassPseudoreactionRefusesMissingProton(testCase) + [m, cfg] = tBiomass.protonToyModel(); + cfg.proton_met = 'nohydrogen_c'; + testCase.verifyError(@() scaleBiomassPseudoreaction(m, cfg, 'protein', 0.5), ... + 'scaleBiomassPseudoreaction:missingProton'); + end + + function setGAMRefusesMissingNgamRxn(testCase) + % The model is not changed when the NGAM reaction is not found. + biomassRxn = testCase.model.rxns{find(testCase.model.c == 1, 1)}; + testCase.verifyError(@() setGAM(testCase.model, 50, biomassRxn, ... + {'ATP','ADP','H2O','H+','Phosphate'}, 'ngamRxn', 'noSuchRxn', 'ngamValue', 1), ... + 'setGAM:missingNgamRxn'); + end + + function setGAMRefusesMissingNgamRxnWithoutValue(testCase) + % The reaction is checked whenever it is given. + biomassRxn = testCase.model.rxns{find(testCase.model.c == 1, 1)}; + testCase.verifyError(@() setGAM(testCase.model, 50, biomassRxn, ... + {'ATP','ADP','H2O','H+','Phosphate'}, 'ngamRxn', 'noSuchRxn'), ... + 'setGAM:missingNgamRxn'); + end + + function setGAMTreatsEmptyNgamRxnAsNotGiven(testCase) + % An empty id sets no NGAM and is no missing reaction. + biomassRxn = testCase.model.rxns{find(testCase.model.c == 1, 1)}; + cof = {'ATP','ADP','H2O','H+','Phosphate'}; + ref = setGAM(testCase.model, 50, biomassRxn, cof); + for empty = {'', {''}, "", {'';''}} + m = setGAM(testCase.model, 50, biomassRxn, cof, 'ngamRxn', empty{1}); + testCase.verifyEqual(m.S, ref.S); + testCase.verifyEqual(m.lb, ref.lb); + testCase.verifyEqual(m.ub, ref.ub); + end + end + + function setGAMRefusesNgamValueWithoutNgamRxn(testCase) + % A value needs a reaction to be set on; an empty id is no reaction. + biomassRxn = testCase.model.rxns{find(testCase.model.c == 1, 1)}; + cof = {'ATP','ADP','H2O','H+','Phosphate'}; + testCase.verifyError(@() setGAM(testCase.model, 50, biomassRxn, cof, 'ngamValue', 2), 'RAVEN:badInput'); + testCase.verifyError(@() setGAM(testCase.model, 50, biomassRxn, cof, 'ngamRxn', '', 'ngamValue', 2), 'RAVEN:badInput'); + testCase.verifyError(@() setGAM(testCase.model, 50, biomassRxn, cof, 'ngamRxn', {''}, 'ngamValue', 2), 'RAVEN:badInput'); + end + function scaleBiomassPseudoreactionRefusesUnknownCharge(testCase) % An unset charge on a participating metabolite leaves the charge % balance unknown. Treating it as neutral would silently write the diff --git a/tests/function_tests/tCuration.m b/tests/function_tests/tCuration.m index 845dc6c5..234a47cf 100644 --- a/tests/function_tests/tCuration.m +++ b/tests/function_tests/tCuration.m @@ -107,6 +107,686 @@ function curateModelFromTablesWritesExistingGeneFields(testCase) testCase.verifyEqual(out.geneMiriams{1}.value, {'P12345'}); end + function curateModelFromTablesRefusesGenesListedTwice(testCase) + % A gene id listed twice in genesInfo raises RAVEN:badInput, for a + % new gene and for a gene the model has; distinct ids are added. + model = tCuration.toyModel(); + model.genes = {'g1'}; + model.rxnGeneMat = sparse(1,1); + for rows = {{'g2\ta','g2\tb'},{'g1\ta','g1\tb'},{'g2\ta','g3\tb','g2\t'}} + genesF = tCuration.writeTsv(testCase, [{'genes\tgeneShortNames'}, rows{1}]); + err = []; + try + curateModelFromTables(model,'none','genesInfo',genesF); + catch err + end + testCase.verifyNotEmpty(err); + testCase.verifyEqual(err.identifier,'RAVEN:badInput'); + testCase.verifySubstring(err.message,'listed more than once'); + end + genesF = tCuration.writeTsv(testCase, {'genes\tgeneShortNames','g2\ta','g3\tb'}); + evalc('out = curateModelFromTables(model,''none'',''genesInfo'',genesF);'); + testCase.verifyEqual(sort(out.genes),{'g1';'g2';'g3'}); + end + + function curateModelFromTablesRefusesRepeatedRxnIdx(testCase) + % A rxnIdx used by two rows of rxnsInfo raises RAVEN:badInput and + % names the index and the rows; 1, 1.0 and 1e0 are one index. + model = tCuration.toyModel(); + coeffsF = tCuration.writeTsv(testCase, { ... + 'rxnIdx\trxnNames\tmetNames\tcomps\tcoefficient', ... + '1\tnewA\tA\tc\t-1', '1\tnewA\tC\tc\t1', ... + '1\tnewB\tA\tc\t-1', '1\tnewB\tC\tc\t1'}); + for second = {'1','1.0','1e0'} + infoF = tCuration.writeTsv(testCase, { ... + 'rxnIdx\trxnNames', '1\tnewA', [second{1} '\tnewB']}); + err = []; + try + curateModelFromTables(model,'none','rxnsCoeffs',coeffsF,'rxnsInfo',infoF); + catch err + end + testCase.verifyNotEmpty(err); + testCase.verifyEqual(err.identifier,'RAVEN:badInput'); + testCase.verifySubstring(err.message,'rxnIdx 1 is used by data rows 1, 2'); + end + end + + function curateModelFromTablesTrimsPaddedQuotedHeaders(testCase) + % Spaces inside the quotes of a header cell are ignored, so + % " comps " and " metNames" name the core columns. + model = tCuration.toyModel(); + metsF = tCuration.writeTsv(testCase, { ... + '" metNames"\t" comps "\t formula ', 'newMet\tc\tH2O'}); + evalc('out = curateModelFromTables(model,metsF);'); + testCase.verifyNumElements(out.mets, numel(model.mets)+1); + testCase.verifyEqual(out.metFormulas{end}, 'H2O'); + end + + function curateModelFromTablesOverwritesExistingWithEmptyCells(testCase) + % By default an empty cell empties the field of an existing + % metabolite, gene or reaction; an empty charge becomes 0. + model = tCuration.existingFieldsModel(); + [metsF, genesF, coeffsF, infoF] = tCuration.emptyCellTables(testCase); + evalc(['out = curateModelFromTables(model,metsF,''genesInfo'',genesF,' ... + '''rxnsCoeffs'',coeffsF,''rxnsInfo'',infoF);']); + + testCase.verifyEqual(out.metFormulas{1}, ''); + testCase.verifyEqual(out.metFormulas{2}, 'F2'); + testCase.verifyEqual(out.metCharges(1:2), [0;5]); + testCase.verifyEqual(out.geneShortNames, {''}); + testCase.verifyEqual(out.rxnNotes, {''}); + testCase.verifyEqual(out.eccodes, {''}); + end + + function curateModelFromTablesKeepsExistingWithEmptyCellsWhenAsked(testCase) + % overwriteWithEmpty=false writes only the cells with a value. + model = tCuration.existingFieldsModel(); + [metsF, genesF, coeffsF, infoF] = tCuration.emptyCellTables(testCase); + evalc(['out = curateModelFromTables(model,metsF,''genesInfo'',genesF,' ... + '''rxnsCoeffs'',coeffsF,''rxnsInfo'',infoF,''overwriteWithEmpty'',false);']); + + testCase.verifyEqual(out.metFormulas{1}, 'CH4'); + testCase.verifyEqual(out.metFormulas{2}, 'F2'); + testCase.verifyEqual(out.metCharges(1:2), [1;5]); + testCase.verifyEqual(out.geneShortNames, {'OLD'}); + testCase.verifyEqual(out.rxnNotes, {'old note'}); + testCase.verifyEqual(out.rxnConfidenceScores, 3); + testCase.verifyEqual(out.eccodes, {'1.1.1.1'}); + testCase.verifyEqual(out.rxnNames, {'ATP hydrolysis with proton'}); + testCase.verifyEqual(out.rxnMiriams{1}.value, {'R00001'}); + testCase.verifyEqual(out.metMiriams{1}.value, {'C00001'}); + end + + function curateModelFromTablesTreatsNaNChargeAsNotGiven(testCase) + % A NaN charge counts like an empty charge cell: 0 when other + % charges are given and overwriteWithEmpty is true, unchanged + % when it is false; a new metabolite gets 0. + model = tCuration.existingFieldsModel(); + metsF = tCuration.writeTsv(testCase, { ... + 'metNames\tcomps\tcharge', 'A\tc\tNaN', 'B\tc\t5', 'Z\tc\tnan'}); + evalc('out = curateModelFromTables(model,metsF);'); + idxA = find(strcmp(out.metNames,'A')); + idxZ = find(strcmp(out.metNames,'Z')); + testCase.verifyEqual(out.metCharges(idxA), 0); + testCase.verifyEqual(out.metCharges(idxZ), 0); + testCase.verifyFalse(any(isnan(out.metCharges))); + + evalc('out = curateModelFromTables(model,metsF,''overwriteWithEmpty'',false);'); + testCase.verifyEqual(out.metCharges(idxA), 1); + testCase.verifyEqual(out.metCharges(strcmp(out.metNames,'B')), 5); + testCase.verifyEqual(out.metCharges(idxZ), 0); + end + + function curateModelFromTablesRejectsNonLogicalOverwriteWithEmpty(testCase) + model = tCuration.toyModel(); + metsF = tCuration.writeTsv(testCase, {'metNames\tcomps', 'A\tc'}); + err = []; + try + curateModelFromTables(model,metsF,'overwriteWithEmpty','yes'); + catch err + end + testCase.verifyNotEmpty(err); + testCase.verifyEqual(err.identifier, 'RAVEN:badInput'); + end + + function curateModelFromTablesTreatsEmptyBoundsAsNotGiven(testCase) + % An empty lb or ub cell leaves the bound of an existing + % reaction unchanged. A new reaction gets -Inf for a missing lb + % and the default upper bound for a missing ub. + model = tCuration.toyModel(); + model.lb = -5; + model.ub = 7; + model.annotation.defaultUB = 500; + coeffsF = tCuration.writeTsv(testCase, { ... + 'rxnIdx\trxnNames\tmetNames\tcomps\tcoefficient', ... + '1\tdupRxn\tA\tc\t-1', '1\tdupRxn\tB\tc\t-1', ... + '1\tdupRxn\tC\tc\t1', '1\tdupRxn\tH\tc\t1', ... + '2\tnewRxn\tA\tc\t-1', '2\tnewRxn\tB\tc\t-1', ... + '2\tnewRxn\tC\tc\t1', ... + '3\tboundRxn\tA\tc\t-1', '3\tboundRxn\tC\tc\t1'}); + infoF = tCuration.writeTsv(testCase, { ... + 'rxnIdx\trxnNames\tlb\tub\trev', ... + '1\tdupRxn\t\t\t', '2\tnewRxn\t\t\t', '3\tboundRxn\t-2\t3\t1'}); + evalc(['out = curateModelFromTables(model,''none'',''rxnsCoeffs'',' ... + 'coeffsF,''rxnsInfo'',infoF);']); + + testCase.verifyEqual(out.lb, [-5; -Inf; -2]); + testCase.verifyEqual(out.ub, [7; 500; 3]); + end + + function curateModelFromTablesRefusesTextAsBound(testCase) + model = tCuration.toyModel(); + coeffsF = tCuration.writeTsv(testCase, { ... + 'rxnIdx\trxnNames\tmetNames\tcomps\tcoefficient', ... + '1\tnewRxn\tA\tc\t-1', '1\tnewRxn\tC\tc\t1'}); + infoF = tCuration.writeTsv(testCase, { ... + 'rxnIdx\trxnNames\tlb\tub', '1\tnewRxn\tlow\t1000'}); + err = []; + try + curateModelFromTables(model,'none','rxnsCoeffs',coeffsF,'rxnsInfo',infoF); + catch err + end + testCase.verifyNotEmpty(err); + testCase.verifyEqual(err.identifier, 'RAVEN:badInput'); + testCase.verifySubstring(err.message, 'low'); + end + + function curateModelFromTablesReadsTrueFalseRev(testCase) + % A rev cell can be written as true or false in any case. It sets + % the reversibility of an existing reaction; for a new reaction + % the reversibility follows from lb. + model = tCuration.toyModel(); + coeffsF = tCuration.writeTsv(testCase, { ... + 'rxnIdx\trxnNames\tmetNames\tcomps\tcoefficient', ... + '1\tdupRxn\tA\tc\t-1', '1\tdupRxn\tB\tc\t-1', ... + '1\tdupRxn\tC\tc\t1', '1\tdupRxn\tH\tc\t1', ... + '2\tnewRxn\tA\tc\t-1', '2\tnewRxn\tC\tc\t1'}); + infoF = tCuration.writeTsv(testCase, { ... + 'rxnIdx\trxnNames\tlb\tub\trev', ... + '1\tdupRxn\t\t\tTRUE', '2\tnewRxn\t-3\t4\tfalse'}); + evalc(['out = curateModelFromTables(model,''none'',''rxnsCoeffs'',' ... + 'coeffsF,''rxnsInfo'',infoF);']); + testCase.verifyNumElements(out.rxns, 2); + testCase.verifyEqual(out.rev(1), 1); + testCase.verifyEqual(out.lb(2), -3); + testCase.verifyEqual(out.ub(2), 4); + testCase.verifyEqual(out.rev(2), 1); + model.rev = 1; + infoF = tCuration.writeTsv(testCase, { ... + 'rxnIdx\trxnNames\trev', '1\tdupRxn\tFalse'}); + coeffs1F = tCuration.writeTsv(testCase, { ... + 'rxnIdx\trxnNames\tmetNames\tcomps\tcoefficient', ... + '1\tdupRxn\tA\tc\t-1', '1\tdupRxn\tB\tc\t-1', ... + '1\tdupRxn\tC\tc\t1', '1\tdupRxn\tH\tc\t1'}); + evalc(['out = curateModelFromTables(model,''none'',''rxnsCoeffs'',' ... + 'coeffs1F,''rxnsInfo'',infoF);']); + testCase.verifyEqual(out.rev(1), 0); + end + + function curateModelFromTablesRefusesCommasHexAndOtherNumberText(testCase) + % Only plain decimal numbers, Inf, NaN and true/false are read; + % a decimal comma, hexadecimal and similar text raise + % RAVEN:badInput. + model = tCuration.toyModel(); + coeffsF = tCuration.dupRxnCoeffs(testCase); + for txt = {'1,5','-0,5','1,000','0x10','1d3','1_0','Infinity','1+2i','1i','--5','abc'} + infoF = tCuration.writeTsv(testCase, {'rxnIdx\trxnNames\tlb','1\tdupRxn\t' txt{1}}); + testCase.verifyError(@() curateModelFromTables(model,'none','rxnsCoeffs',coeffsF,'rxnsInfo',infoF),'RAVEN:badInput'); + metsF = tCuration.writeTsv(testCase, {'metNames\tcomps\tcharge',['A\tc\t' txt{1}]}); + testCase.verifyError(@() curateModelFromTables(model,metsF),'RAVEN:badInput'); + end + end + + function curateModelFromTablesReadsPlainDecimalNumbers(testCase) + model = tCuration.toyModel(); + coeffsF = tCuration.dupRxnCoeffs(testCase); + texts = {'+5','.5','5.','1e3',' -2.5E-1 ','TRUE','false'}; + expected = [5 0.5 5 1000 -0.25 1 0]; + for i = 1:numel(texts) + infoF = tCuration.writeTsv(testCase, {'rxnIdx\trxnNames\tlb',['1\tdupRxn\t' texts{i}]}); + evalc('out = curateModelFromTables(model,''none'',''rxnsCoeffs'',coeffsF,''rxnsInfo'',infoF);'); + testCase.verifyEqual(out.lb(1), expected(i)); + end + infoF = tCuration.writeTsv(testCase, {'rxnIdx\trxnNames\tlb\tub','1\tdupRxn\tNaN\t-INF'}); + evalc('out = curateModelFromTables(model,''none'',''rxnsCoeffs'',coeffsF,''rxnsInfo'',infoF);'); + testCase.verifyEqual(out.lb(1), 0); + testCase.verifyEqual(out.ub(1), -Inf); + end + + function curateModelFromTablesRefusesFractionalAndInfiniteCharge(testCase) + % A charge is a finite whole number; empty and NaN are not given. + model = tCuration.toyModel(); + for txt = {'0.5','2.5','1e-3','Inf','-Inf'} + metsF = tCuration.writeTsv(testCase, {'metNames\tcomps\tcharge',['A\tc\t' txt{1}]}); + err = []; + try + curateModelFromTables(model,metsF); + catch err + end + testCase.verifyNotEmpty(err); + testCase.verifyEqual(err.identifier,'RAVEN:badInput'); + testCase.verifySubstring(err.message,'not a finite whole number'); + end + for txt = {'2','-1','3.0','1e1',' 2 ','0'} + metsF = tCuration.writeTsv(testCase, {'metNames\tcomps\tcharge',['Anew\tc\t' txt{1}]}); + evalc('out = curateModelFromTables(model,metsF);'); + testCase.verifyEqual(out.metCharges(end), round(str2double(txt{1}))); + end + metsF = tCuration.writeTsv(testCase, {'metNames\tcomps\tcharge','Anew\tc\tNaN','Bnew\tc\t4'}); + evalc('out = curateModelFromTables(model,metsF);'); + testCase.verifyEqual(out.metCharges(end-1:end), [0; 4]); + end + + function curateModelFromTablesRefusesEmptyCoefficientInRowWithContent(testCase) + % A row of the coefficient table that names a reaction or a + % metabolite needs a coefficient; a row without any content is + % ignored. + model = tCuration.toyModel(); + infoF = tCuration.writeTsv(testCase, {'rxnIdx\trxnNames','1\tdupRxn'}); + for coefficient = {'','NaN'} + coeffsF = tCuration.writeTsv(testCase, { ... + 'rxnIdx\trxnNames\tmetNames\tcomps\tcoefficient', ... + '1\tdupRxn\tA\tc\t-1', '1\tdupRxn\tB\tc\t-1', ... + '1\tdupRxn\tC\tc\t1', ['1\tdupRxn\tH\tc\t' coefficient{1}]}); + err = []; + try + curateModelFromTables(model,'none','rxnsCoeffs',coeffsF,'rxnsInfo',infoF); + catch err + end + testCase.verifyNotEmpty(err); + testCase.verifyEqual(err.identifier,'RAVEN:badInput'); + testCase.verifySubstring(err.message,'coefficient'); + end + coeffsF = tCuration.writeTsv(testCase, { ... + 'rxnIdx\trxnNames\tmetNames\tcomps\tcoefficient', ... + '1\tdupRxn\tA\tc\t-1', '1\tdupRxn\tB\tc\t-1', ... + '1\tdupRxn\tC\tc\t1', '1\tdupRxn\tH\tc\t1', '\t\t\t\t'}); + evalc('out = curateModelFromTables(model,''none'',''rxnsCoeffs'',coeffsF,''rxnsInfo'',infoF);'); + testCase.verifyEqual(numel(out.rxns), 1); + end + + function curateModelFromTablesRefusesMalformedGeneRules(testCase) + % A malformed grRules cell raises RAVEN:badInput for existing and + % new reactions; well-formed rules and empty cells are accepted. + model = tCuration.toyModel(); + model.genes = {'G1';'G2';'G3'}; + model.rxnGeneMat = sparse(1,3); + coeffsF = tCuration.writeTsv(testCase, { ... + 'rxnIdx\trxnNames\tmetNames\tcomps\tcoefficient', ... + '1\tdupRxn\tA\tc\t-1', '1\tdupRxn\tB\tc\t-1', ... + '1\tdupRxn\tC\tc\t1', '1\tdupRxn\tH\tc\t1', ... + '2\tnewRxn\tA\tc\t-1', '2\tnewRxn\tC\tc\t1'}); + malformed = {'G1 and and (','(G1 or G2','G1 G2','and G1','G1 or','()', ... + 'G1 and (G2 or )','G1)','G1 and or G2','(G1) (G2)','G1 xor G2'}; + for k = 1:numel(malformed) + for row = {['1\tdupRxn\t' malformed{k}],['2\tnewRxn\t' malformed{k}]} + lines = {'rxnIdx\trxnNames\tgrRules','1\tdupRxn\t','2\tnewRxn\t'}; + lines{2 + strncmp(row{1},'2',1)} = row{1}; + infoF = tCuration.writeTsv(testCase, lines); + err = []; + try + curateModelFromTables(model,'none','rxnsCoeffs',coeffsF,'rxnsInfo',infoF); + catch err + end + testCase.verifyNotEmpty(err, malformed{k}); + testCase.verifyEqual(err.identifier,'RAVEN:badInput', malformed{k}); + testCase.verifySubstring(err.message,'is not a valid gene rule'); + testCase.verifySubstring(err.message,'data row'); + end + end + valid = {'G1','(G1 and G2) or G3','G1 AND G2','(G1)','G1 And G2','G1 & G2','G1 && G2', ... + 'G1 | G2','G1 || G2','G1 and(G2 or G3)','((G1 and G2) or G3)',' G1 ',''}; + for k = 1:numel(valid) + infoF = tCuration.writeTsv(testCase, {'rxnIdx\trxnNames\tgrRules',['1\tdupRxn\t' valid{k}],['2\tnewRxn\t' valid{k}]}); + evalc('out = curateModelFromTables(model,''none'',''rxnsCoeffs'',coeffsF,''rxnsInfo'',infoF);'); + testCase.verifyEqual(numel(out.rxns), 2, valid{k}); + end + end + + function curateModelFromTablesRefusesRevOtherThanZeroOrOne(testCase) + % A rev cell is 0, 1, true or false (any case); empty and NaN + % are not given. Other values raise an error for existing and + % new reactions. + model = tCuration.toyModel(); + coeffsF = tCuration.writeTsv(testCase, { ... + 'rxnIdx\trxnNames\tmetNames\tcomps\tcoefficient', ... + '1\tdupRxn\tA\tc\t-1', '1\tdupRxn\tB\tc\t-1', ... + '1\tdupRxn\tC\tc\t1', '1\tdupRxn\tH\tc\t1', ... + '2\tnewRxn\tA\tc\t-1', '2\tnewRxn\tC\tc\t1'}); + for txt = {'2','0.5','Inf','-1','-Inf'} + for row = {['1\tdupRxn\t' txt{1}],['2\tnewRxn\t' txt{1}]} + lines = {'rxnIdx\trxnNames\trev','1\tdupRxn\t','2\tnewRxn\t'}; + lines{2 + strncmp(row{1},'2',1)} = row{1}; + infoF = tCuration.writeTsv(testCase, lines); + err = []; + try + curateModelFromTables(model,'none','rxnsCoeffs',coeffsF,'rxnsInfo',infoF); + catch err + end + testCase.verifyNotEmpty(err); + testCase.verifyEqual(err.identifier,'RAVEN:badInput'); + testCase.verifySubstring(err.message,'not 0, 1, true or false'); + end + end + for txt = {'0','1','true','FALSE','NaN',''} + infoF = tCuration.writeTsv(testCase, {'rxnIdx\trxnNames\trev',['1\tdupRxn\t' txt{1}],['2\tnewRxn\t' txt{1}]}); + evalc('out = curateModelFromTables(model,''none'',''rxnsCoeffs'',coeffsF,''rxnsInfo'',infoF);'); + testCase.verifyEqual(numel(out.rxns), 2); + end + end + + function curateModelFromTablesRefusesRxnIdxThatIsNotAWholeNumberFromOne(testCase) + % A reaction index is a whole number of at least 1. + model = tCuration.toyModel(); + coeffsGood = tCuration.dupRxnCoeffs(testCase); + infoGood = tCuration.writeTsv(testCase, {'rxnIdx\trxnNames','1\tdupRxn'}); + for txt = {'1.5','Inf','0','-1','-Inf'} + coeffsF = tCuration.writeTsv(testCase, { ... + 'rxnIdx\trxnNames\tmetNames\tcomps\tcoefficient', ... + '1\tdupRxn\tA\tc\t-1', '1\tdupRxn\tB\tc\t-1', ... + '1\tdupRxn\tC\tc\t1', '1\tdupRxn\tH\tc\t1', ... + [txt{1} '\tother\tA\tc\t-1']}); + err = []; + try + curateModelFromTables(model,'none','rxnsCoeffs',coeffsF,'rxnsInfo',infoGood); + catch err + end + testCase.verifyNotEmpty(err); + testCase.verifyEqual(err.identifier,'RAVEN:badInput'); + testCase.verifySubstring(err.message,'whole number of at least 1'); + infoF = tCuration.writeTsv(testCase, {'rxnIdx\trxnNames',['1\tdupRxn'],[txt{1} '\tother']}); + err = []; + try + curateModelFromTables(model,'none','rxnsCoeffs',coeffsGood,'rxnsInfo',infoF); + catch err + end + testCase.verifyNotEmpty(err); + testCase.verifyEqual(err.identifier,'RAVEN:badInput'); + testCase.verifySubstring(err.message,'whole number of at least 1'); + end + evalc('out = curateModelFromTables(model,''none'',''rxnsCoeffs'',coeffsGood,''rxnsInfo'',infoGood);'); + testCase.verifyEqual(numel(out.rxns), 1); + end + + function curateModelFromTablesRefusesTextInIndexAndCoefficientColumns(testCase) + % A coefficient or index that is not a number raises + % RAVEN:badInput. + model = tCuration.toyModel(); + infoF = tCuration.writeTsv(testCase, {'rxnIdx\trxnNames','1\tdupRxn'}); + coeffsF = tCuration.writeTsv(testCase, { ... + 'rxnIdx\trxnNames\tmetNames\tcomps\tcoefficient', ... + '1\tdupRxn\tA\tc\t-1', '1\tdupRxn\tB\tc\tabc', ... + '1\tdupRxn\tC\tc\t1', '1\tdupRxn\tH\tc\t1'}); + testCase.verifyError(@() curateModelFromTables(model,'none','rxnsCoeffs',coeffsF,'rxnsInfo',infoF),'RAVEN:badInput'); + coeffsF = tCuration.dupRxnCoeffs(testCase); + infoF = tCuration.writeTsv(testCase, {'rxnIdx\trxnNames','x1\tdupRxn'}); + testCase.verifyError(@() curateModelFromTables(model,'none','rxnsCoeffs',coeffsF,'rxnsInfo',infoF),'RAVEN:badInput'); + end + + function curateModelFromTablesTreatsNaNScoreAsNotGiven(testCase) + % A NaN confidence score counts like an empty cell, so the + % existing score is not replaced by NaN. + model = tCuration.existingFieldsModel(); + coeffsF = tCuration.dupRxnCoeffs(testCase); + infoF = tCuration.writeTsv(testCase, {'rxnIdx\trxnNames\trxnConfidenceScores','1\tdupRxn\tNaN'}); + for flag = [true false] + evalc('out = curateModelFromTables(model,''none'',''rxnsCoeffs'',coeffsF,''rxnsInfo'',infoF,''overwriteWithEmpty'',flag);'); + testCase.verifyEqual(out.rxnConfidenceScores(1), 3); + end + end + + function curateModelFromTablesReadsNumbersWithoutEvaluatingText(testCase) + % Cell text is read as a number or true/false; no expression is + % evaluated, and other text raises RAVEN:badInput. + model = tCuration.toyModel(); + d = tempname; mkdir(d); + testCase.addTeardown(@() rmdir(d,'s')); + marker = fullfile(d,'executed'); + coeffsF = tCuration.writeTsv(testCase, { ... + 'rxnIdx\trxnNames\tmetNames\tcomps\tcoefficient', ... + '1\tdupRxn\tA\tc\t-1', '1\tdupRxn\tB\tc\t-1', ... + '1\tdupRxn\tC\tc\t1', '1\tdupRxn\tH\tc\t1'}); + cells = {sprintf('system(''touch %s'')',marker), 'pi', '1/2', '[3]'}; + for i = 1:numel(cells) + infoF = tCuration.writeTsv(testCase, { ... + 'rxnIdx\trxnNames\tlb', ['1\tdupRxn\t' strrep(cells{i},'\','\\')]}); + err = []; + try + curateModelFromTables(model,'none','rxnsCoeffs',coeffsF,'rxnsInfo',infoF); + catch err + end + testCase.verifyNotEmpty(err); + testCase.verifyEqual(err.identifier, 'RAVEN:badInput'); + end + testCase.verifyEqual(exist(marker,'file'), 0); + metsF = tCuration.writeTsv(testCase, {'metNames\tcomps\tcharge', 'A\tc\tpi'}); + err = []; + try + curateModelFromTables(model,metsF); + catch err + end + testCase.verifyEqual(err.identifier, 'RAVEN:badInput'); + infoF = tCuration.writeTsv(testCase, { ... + 'rxnIdx\trxnNames\trxnConfidenceScores', '1\tdupRxn\t2+1'}); + err = []; + try + curateModelFromTables(model,'none','rxnsCoeffs',coeffsF,'rxnsInfo',infoF); + catch err + end + testCase.verifyEqual(err.identifier, 'RAVEN:badInput'); + infoF = tCuration.writeTsv(testCase, { ... + 'rxnIdx\trxnNames\tlb\tub\trxnConfidenceScores', '1\tdupRxn\t -1e1 \t-Inf\t3'}); + evalc(['out = curateModelFromTables(model,''none'',''rxnsCoeffs'',' ... + 'coeffsF,''rxnsInfo'',infoF);']); + testCase.verifyEqual(out.lb(1), -10); + testCase.verifyEqual(out.ub(1), -Inf); + testCase.verifyEqual(out.rxnConfidenceScores(1), 3); + end + + function curateModelFromTablesBlankCellsCountAsEmptyWhenOverwriting(testCase) + % A cell with only blanks is an empty cell: it removes the + % MIRIAM namespace and empties the formula of an existing + % metabolite. + model = tCuration.existingFieldsModel(); + metsF = tCuration.writeTsv(testCase, { ... + 'metNames\tcomps\tformula\tkegg.compound', ... + 'A\tc\t" "\t" "', 'B\tc\tF2\tC00002'}); + evalc('out = curateModelFromTables(model,metsF);'); + testCase.verifyEqual(out.metFormulas{1}, ''); + testCase.verifyTrue(isempty(out.metMiriams{1}) || ~isfield(out.metMiriams{1},'name') ... + || ~any(strcmp(out.metMiriams{1}.name,'kegg.compound'))); + testCase.verifyEqual(out.metMiriams{2}.value, {'C00002'}); + end + + function curateModelFromTablesRefusesComplexBoundAndKeepsOnNaN(testCase) + % A complex bound is refused; a NaN bound counts as not given. + model = tCuration.toyModel(); + model.lb = -5; + model.ub = 7; + coeffsF = tCuration.writeTsv(testCase, { ... + 'rxnIdx\trxnNames\tmetNames\tcomps\tcoefficient', ... + '1\tdupRxn\tA\tc\t-1', '1\tdupRxn\tB\tc\t-1', ... + '1\tdupRxn\tC\tc\t1', '1\tdupRxn\tH\tc\t1'}); + complexF = tCuration.writeTsv(testCase, { ... + 'rxnIdx\trxnNames\tlb', '1\tdupRxn\t1i'}); + err = []; + try + curateModelFromTables(model,'none','rxnsCoeffs',coeffsF,'rxnsInfo',complexF); + catch err + end + testCase.verifyNotEmpty(err); + testCase.verifyEqual(err.identifier, 'RAVEN:badInput'); + nanF = tCuration.writeTsv(testCase, { ... + 'rxnIdx\trxnNames\tlb', '1\tdupRxn\tNaN'}); + evalc(['out = curateModelFromTables(model,''none'',''rxnsCoeffs'',' ... + 'coeffsF,''rxnsInfo'',nanF);']); + testCase.verifyEqual(out.lb, -5); + end + + function curateModelFromTablesRejectsOverwriteWithEmptyNotZeroOrOne(testCase) + model = tCuration.toyModel(); + metsF = tCuration.writeTsv(testCase, {'metNames\tcomps', 'A\tc'}); + for bad = {2, NaN, [true false], {true}, complex(1,0)} + err = []; + try + curateModelFromTables(model,metsF,'overwriteWithEmpty',bad{1}); + catch err + end + testCase.verifyNotEmpty(err); + testCase.verifyEqual(err.identifier, 'RAVEN:badInput'); + end + end + + function curateModelFromTablesBlankMiriamAndGrRulesCountAsEmpty(testCase) + % A cell with only blanks leaves the MIRIAM annotation of an + % existing metabolite when overwriteWithEmpty is false, and + % never replaces a gene rule. + model = tCuration.existingFieldsModel(); + model.grRules = {'g1'}; + model.rxnGeneMat = sparse(1,1,1); + metsF = tCuration.writeTsv(testCase, { ... + 'metNames\tcomps\tkegg.compound', 'A\tc\t" "'}); + coeffsF = tCuration.writeTsv(testCase, { ... + 'rxnIdx\trxnNames\tmetNames\tcomps\tcoefficient', ... + '1\t\tA\tc\t-1', '1\t\tB\tc\t-1', ... + '1\t\tC\tc\t1', '1\t\tH\tc\t1'}); + infoF = tCuration.writeTsv(testCase, { ... + 'rxnIdx\trxnNames\tgrRules', '1\t\t" "'}); + evalc(['out = curateModelFromTables(model,metsF,''rxnsCoeffs'',coeffsF,' ... + '''rxnsInfo'',infoF,''overwriteWithEmpty'',false);']); + testCase.verifyEqual(out.metMiriams{1}.value, {'C00001'}); + testCase.verifyEqual(out.grRules, {'g1'}); + end + + function curateModelFromTablesRefusesRowsWithValuesButNoRxnIdx(testCase) + % A row of the rxnsInfo or rxnsCoeffs table that has values needs + % a rxnIdx; a row without any value is ignored. + model = tCuration.toyModel(); + coeffsGood = tCuration.dupRxnCoeffs(testCase); + infoGood = tCuration.writeTsv(testCase, {'rxnIdx\trxnNames','1\tdupRxn'}); + for idx = {'','NaN'} + infoF = tCuration.writeTsv(testCase, {'rxnIdx\trxnNames','1\tdupRxn',[idx{1} '\tskipped']}); + err = []; + try + curateModelFromTables(model,'none','rxnsCoeffs',coeffsGood,'rxnsInfo',infoF); + catch err + end + testCase.verifyNotEmpty(err); + testCase.verifyEqual(err.identifier,'RAVEN:badInput'); + testCase.verifySubstring(err.message,'rxnIdx'); + coeffsF = tCuration.writeTsv(testCase, { ... + 'rxnIdx\trxnNames\tmetNames\tcomps\tcoefficient', ... + '1\tdupRxn\tA\tc\t-1', '1\tdupRxn\tB\tc\t-1', ... + '1\tdupRxn\tC\tc\t1', '1\tdupRxn\tH\tc\t1', ... + [idx{1} '\tother\tA\tc\t-1']}); + err = []; + try + curateModelFromTables(model,'none','rxnsCoeffs',coeffsF,'rxnsInfo',infoGood); + catch err + end + testCase.verifyNotEmpty(err); + testCase.verifyEqual(err.identifier,'RAVEN:badInput'); + testCase.verifySubstring(err.message,'rxnIdx'); + end + infoF = tCuration.writeTsv(testCase, {'rxnIdx\trxnNames','1\tdupRxn','\t'}); + coeffsF = tCuration.writeTsv(testCase, { ... + 'rxnIdx\trxnNames\tmetNames\tcomps\tcoefficient', ... + '1\tdupRxn\tA\tc\t-1', '1\tdupRxn\tB\tc\t-1', ... + '1\tdupRxn\tC\tc\t1', '1\tdupRxn\tH\tc\t1', '\t\t\t\t7'}); + evalc('out = curateModelFromTables(model,''none'',''rxnsCoeffs'',coeffsF,''rxnsInfo'',infoF);'); + testCase.verifyEqual(numel(out.rxns), 1); + end + + function curateModelFromTablesRequiresMatchingReactionTables(testCase) + % The reactions of the rxnsInfo and rxnsCoeffs tables match + % exactly; every mismatch is reported with RAVEN:badInput. + model = tCuration.toyModel(); + coeffsGood = tCuration.dupRxnCoeffs(testCase); + infoGood = tCuration.writeTsv(testCase, {'rxnIdx\trxnNames','1\tdupRxn'}); + coeffsExtra = tCuration.writeTsv(testCase, { ... + 'rxnIdx\trxnNames\tmetNames\tcomps\tcoefficient', ... + '1\tdupRxn\tA\tc\t-1', '1\tdupRxn\tB\tc\t-1', ... + '1\tdupRxn\tC\tc\t1', '1\tdupRxn\tH\tc\t1', ... + '2\tnewRxn\tA\tc\t-1', '2\tnewRxn\tC\tc\t1'}); + infoExtra = tCuration.writeTsv(testCase, {'rxnIdx\trxnNames','1\tdupRxn','2\tnewRxn'}); + infoRenamed = tCuration.writeTsv(testCase, {'rxnIdx\trxnNames','1\tother'}); + cases = {coeffsExtra, infoGood, 'newRxn'; coeffsGood, infoExtra, 'newRxn'; ... + coeffsGood, infoRenamed, 'other'}; + for i = 1:size(cases,1) + err = []; + try + curateModelFromTables(model,'none','rxnsCoeffs',cases{i,1},'rxnsInfo',cases{i,2}); + catch err + end + testCase.verifyNotEmpty(err); + testCase.verifyEqual(err.identifier,'RAVEN:badInput'); + testCase.verifySubstring(err.message,cases{i,3}); + end + evalc('out = curateModelFromTables(model,''none'',''rxnsCoeffs'',coeffsExtra,''rxnsInfo'',infoExtra);'); + testCase.verifyEqual(numel(out.rxns), 2); + end + + function curateModelFromTablesMatchesRxnIdxByValue(testCase) + % The indices 1, 1.0 and 1e0 of the two reaction tables are the + % same index. + model = tCuration.toyModel(); + coeffsF = tCuration.dupRxnCoeffs(testCase); + for txt = {'1.0','1e0','01','+1'} + infoF = tCuration.writeTsv(testCase, {'rxnIdx\trxnNames\tlb',['' txt{1} '\tdupRxn\t5']}); + evalc('out = curateModelFromTables(model,''none'',''rxnsCoeffs'',coeffsF,''rxnsInfo'',infoF);'); + testCase.verifyEqual(numel(out.rxns), 1); + testCase.verifyEqual(out.lb(1), 5); + end + end + + function curateModelFromTablesAcceptsTrueFalseOnlyInBoundsAndRev(testCase) + % The words true and false are numbers in the lb, ub and rev + % cells only. + model = tCuration.toyModel(); + coeffsF = tCuration.dupRxnCoeffs(testCase); + infoGood = tCuration.writeTsv(testCase, {'rxnIdx\trxnNames','1\tdupRxn'}); + for txt = {'true','FALSE'} + metsF = tCuration.writeTsv(testCase, {'metNames\tcomps\tcharge',['A\tc\t' txt{1}]}); + testCase.verifyError(@() curateModelFromTables(model,metsF),'RAVEN:badInput'); + infoF = tCuration.writeTsv(testCase, {'rxnIdx\trxnNames\trxnConfidenceScores',['1\tdupRxn\t' txt{1}]}); + testCase.verifyError(@() curateModelFromTables(model,'none','rxnsCoeffs',coeffsF,'rxnsInfo',infoF),'RAVEN:badInput'); + infoF = tCuration.writeTsv(testCase, {'rxnIdx\trxnNames',[txt{1} '\tdupRxn']}); + testCase.verifyError(@() curateModelFromTables(model,'none','rxnsCoeffs',coeffsF,'rxnsInfo',infoF),'RAVEN:badInput'); + coeffsBad = tCuration.writeTsv(testCase, { ... + 'rxnIdx\trxnNames\tmetNames\tcomps\tcoefficient', ... + '1\tdupRxn\tA\tc\t-1', '1\tdupRxn\tB\tc\t-1', ... + '1\tdupRxn\tC\tc\t1', ['1\tdupRxn\tH\tc\t' txt{1}]}); + testCase.verifyError(@() curateModelFromTables(model,'none','rxnsCoeffs',coeffsBad,'rxnsInfo',infoGood),'RAVEN:badInput'); + end + end + + function curateModelFromTablesRefusesInfiniteAndZeroCoefficient(testCase) + % A coefficient is a finite number other than 0. + model = tCuration.toyModel(); + infoF = tCuration.writeTsv(testCase, {'rxnIdx\trxnNames','1\tdupRxn'}); + for txt = {'Inf','-Inf','0','0.0'} + coeffsF = tCuration.writeTsv(testCase, { ... + 'rxnIdx\trxnNames\tmetNames\tcomps\tcoefficient', ... + '1\tdupRxn\tA\tc\t-1', '1\tdupRxn\tB\tc\t-1', ... + '1\tdupRxn\tC\tc\t1', ['1\tdupRxn\tH\tc\t' txt{1}]}); + err = []; + try + curateModelFromTables(model,'none','rxnsCoeffs',coeffsF,'rxnsInfo',infoF); + catch err + end + testCase.verifyNotEmpty(err); + testCase.verifyEqual(err.identifier,'RAVEN:badInput'); + testCase.verifySubstring(err.message,'finite non-zero'); + end + end + + function curateModelFromTablesTreatsEmptyOverwriteWithEmptyAsDefault(testCase) + % An empty overwriteWithEmpty means the default, true. + model = tCuration.existingFieldsModel(); + metsF = tCuration.writeTsv(testCase, {'metNames\tcomps\tformula','A\tc\t'}); + evalc('out = curateModelFromTables(model,metsF,''overwriteWithEmpty'',[]);'); + testCase.verifyEqual(out.metFormulas{1}, ''); + end + + function curateModelFromTablesRaisesBadInputForUnreadableFiles(testCase) + % Problems with the table files are reported with RAVEN:badInput. + model = tCuration.toyModel(); + testCase.verifyError(@() curateModelFromTables(model,fullfile(tempdir,'does_not_exist_curate.tsv')),'RAVEN:badInput'); + emptyF = tCuration.writeTsv(testCase, {}); + testCase.verifyError(@() curateModelFromTables(model,emptyF),'RAVEN:badInput'); + metsF = tCuration.writeTsv(testCase, {'metNames\tformula', 'A\tC1'}); + testCase.verifyError(@() curateModelFromTables(model,metsF),'RAVEN:badInput'); + coeffsF = tCuration.dupRxnCoeffs(testCase); + testCase.verifyError(@() curateModelFromTables(model,'none','rxnsCoeffs',coeffsF),'RAVEN:badInput'); + metsOK = tCuration.writeTsv(testCase, {'metNames\tcomps','A\tc'}); + missingF = tCuration.writeTsv(testCase, {'rxnIdx\trxnNames\tmetNames\tcomps','1\tdupRxn\tA\tc'}); + infoF = tCuration.writeTsv(testCase, {'rxnIdx\trxnNames','1\tdupRxn'}); + testCase.verifyError(@() curateModelFromTables(model,metsOK,'rxnsCoeffs',missingF,'rxnsInfo',infoF),'RAVEN:badInput'); + coeffsMetMissing = tCuration.writeTsv(testCase, { ... + 'rxnIdx\trxnNames\tmetNames\tcomps\tcoefficient','1\tdupRxn\tNotThere\tc\t-1'}); + testCase.verifyError(@() curateModelFromTables(model,'none','rxnsCoeffs',coeffsMetMissing,'rxnsInfo',infoF),'RAVEN:badInput'); + end + function curateModelFromTablesRequiresKeyColumns(testCase) model = tCuration.toyModel(); metsF = tCuration.writeTsv(testCase, {'metNames\tformula', 'A\tC1'}); @@ -117,6 +797,14 @@ function curateModelFromTablesRequiresKeyColumns(testCase) methods (Static, Access = private) + function f = dupRxnCoeffs(testCase) + % Coefficients of A + B -> C + H as reaction 1, "dupRxn". + f = tCuration.writeTsv(testCase, { ... + 'rxnIdx\trxnNames\tmetNames\tcomps\tcoefficient', ... + '1\tdupRxn\tA\tc\t-1', '1\tdupRxn\tB\tc\t-1', ... + '1\tdupRxn\tC\tc\t1', '1\tdupRxn\tH\tc\t1'}); + end + function model = toyModel() % Single reaction: A + B -> C + H, all in one compartment. model = struct(); @@ -145,6 +833,40 @@ function curateModelFromTablesRequiresKeyColumns(testCase) model.rxnConfidenceScores = 0; end + function model = existingFieldsModel() + % toyModel with every curated field filled in. + model = tCuration.toyModel(); + model.metFormulas = {'CH4';'F2';'';''}; + model.metCharges = [1;2;3;4]; + model.genes = {'g1'}; + model.geneShortNames = {'OLD'}; + model.rxnGeneMat = sparse(1,1); + model.eccodes = {'1.1.1.1'}; + model.rxnNotes = {'old note'}; + model.rxnConfidenceScores = 3; + model.metMiriams = {struct('name',{{'kegg.compound'}},'value',{{'C00001'}});[];[];[]}; + model.rxnMiriams = {struct('name',{{'kegg.reaction'}},'value',{{'R00001'}})}; + end + + function [metsF, genesF, coeffsF, infoF] = emptyCellTables(testCase) + % Rows for existing entities: A has no formula, charge or MIRIAM + % value (B has a formula and a charge); the gene has no short + % name; the reaction has no name, EC code, note, confidence + % score or MIRIAM value. + metsF = tCuration.writeTsv(testCase, { ... + 'metNames\tcomps\tformula\tcharge\tkegg.compound', ... + 'A\tc\t\t\t', 'B\tc\tF2\t5\t'}); + genesF = tCuration.writeTsv(testCase, { ... + 'genes\tgeneShortNames', 'g1\t'}); + coeffsF = tCuration.writeTsv(testCase, { ... + 'rxnIdx\trxnNames\tmetNames\tcomps\tcoefficient', ... + '1\t\tA\tc\t-1', '1\t\tB\tc\t-1', ... + '1\t\tC\tc\t1', '1\t\tH\tc\t1'}); + infoF = tCuration.writeTsv(testCase, { ... + 'rxnIdx\trxnNames\teccodes\trxnNotes\trxnConfidenceScores\tkegg.reaction', ... + '1\t\t\t\t\t'}); + end + function [coeffsF, infoF] = writeRxnTables(testCase, metNames, coeffs) % Write a one-reaction rxnsCoeffs/rxnsInfo pair for 'newRxn'. d = tempname; mkdir(d); diff --git a/tests/function_tests/tGapfilling.m b/tests/function_tests/tGapfilling.m index 5f191fff..acd68455 100644 --- a/tests/function_tests/tGapfilling.m +++ b/tests/function_tests/tGapfilling.m @@ -464,6 +464,35 @@ function gapFillMILPKeepsForcedFlux(testCase) testCase.verifyGreaterThanOrEqual(sol.f, 8 - 1e-6); end + function gapFillMILPUsesFiniteBoundsAsBigM(testCase) + % A universal reaction with ub 5000 can carry a growth floor of + % 1500 although bigM is 1000: only infinite bounds use bigM. + evalc('setRavenSolver(''glpk'')'); + warnState = warning('off','RAVEN:glpkMILP'); + testCase.addTeardown(@() warning(warnState)); + gapModel = struct(); + gapModel.id='gapModel'; gapModel.rxns={'ExA';'Bio'}; gapModel.rxnNames=gapModel.rxns; + gapModel.mets={'a';'b'}; gapModel.metNames=gapModel.mets; gapModel.metComps=[1;1]; + gapModel.comps={'c'}; gapModel.compNames={'c'}; + gapModel.S=sparse([1 0; 0 -1]); % ExA: =>a Bio: b=> + gapModel.lb=[0;0]; gapModel.ub=[5000;5000]; gapModel.rev=[0;0]; + gapModel.c=[0;1]; gapModel.b=zeros(2,1); + gapModel.genes={}; gapModel.grRules={'';''}; gapModel.rxnGeneMat=sparse(2,0); + + modelDB = struct(); + modelDB.id='DB'; modelDB.rxns={'T1'}; modelDB.rxnNames=modelDB.rxns; + modelDB.mets={'a';'b'}; modelDB.metNames=modelDB.mets; modelDB.metComps=[1;1]; + modelDB.comps={'c'}; modelDB.compNames={'c'}; + modelDB.S=sparse([-1;1]); % T1: a=>b + modelDB.lb=0; modelDB.ub=5000; modelDB.rev=0; modelDB.c=0; modelDB.b=zeros(2,1); + modelDB.genes={}; modelDB.grRules={''}; modelDB.rxnGeneMat=sparse(1,0); + + evalc(['[addedRxns,newModel,exitFlag] = gapFillMILP(gapModel, modelDB, ' ... + '''minGrowth'', 1500, ''bigM'', 1000, ''verbose'', false);']); + testCase.verifyEqual(exitFlag, 1); + testCase.verifyEqual(addedRxns, {'T1'}); + end + function gapFillMILPKeepsDraftDirections(testCase) % A draft reaction running in the wrong direction is not % reversed: with only that repair available the MILP has no diff --git a/tests/function_tests/tINIT.m b/tests/function_tests/tINIT.m index c7bd276a..4564e29d 100644 --- a/tests/function_tests/tINIT.m +++ b/tests/function_tests/tINIT.m @@ -19,6 +19,54 @@ function getINITStepsReturnsSteps(testCase) testCase.verifyNotEmpty(steps); end + function prepINITModelRefusesTaskIdsThatAreNotText(testCase) + % Non-text ids and a missing id field are reported as RAVEN:badInput. + bad = {struct('id',{"a","a"}), struct('id',{1,1}), struct('name',{'a','b'})}; + for i = 1:numel(bad) + err = []; + try + prepINITModel(struct(), bad{i}); + catch err + end + testCase.verifyNotEmpty(err); + testCase.verifyEqual(err.identifier, 'RAVEN:badInput'); + end + end + + function prepINITModelAcceptsRepeatedAndEmptyTaskIds(testCase) + % Ids may repeat and may be empty: a task is one row group of the + % task list. A task that cannot be performed is removed by its + % position, so the task with the same id that can be performed stays. + testCase.assumeMILPSolver(); + testModel = getTstModel(); + base = getTstModelTasks(); + second = base; second.id = 'ER'; second.description = 'second'; + first = base; first.id = 'BS'; first.description = 'first'; + failing = base; failing.id = 'BS'; failing.description = 'cannot be performed'; + failing.inputs = {}; failing.LBin = []; failing.UBin = []; + third = base; third.id = ''; third.description = 'empty id'; + fourth = base; fourth.id = ''; fourth.description = 'empty id again'; + tasks = [first, second, failing, third, fourth]; + evalc('prepData = prepINITModel(testModel, tasks, {}, false, {}, ''s'');'); + testCase.verifyEqual({prepData.taskStruct.description}, ... + {'first','second','empty id','empty id again'}); + testCase.verifyEqual({prepData.taskStruct.id}, {'BS','ER','',''}); + end + + function prepINITModelKeepsTasksOfARepeatedIdListLikeHumanGEM(testCase) + % A task list in Human-GEM's layout: category codes as ids, several + % tasks per code, comment rows above the header. + testCase.assumeMILPSolver(); + taskFile = fullfile(fileparts(mfilename('fullpath')), ... + 'test_data','test_tasks_repeated_ids.txt'); + tasks = parseTaskList(taskFile); + testCase.verifyEqual({tasks.id}, {'ER','BS','ER','BS'}); + evalc('prepData = prepINITModel(getTstModel(), tasks, {}, false, {}, ''s'');'); + testCase.verifyEqual({prepData.taskStruct.description}, ... + {'Produce e from a','Produce e from a, second','Produce e from a, third'}); + testCase.verifyEqual({prepData.taskStruct.id}, {'ER','BS','BS'}); + end + function getExprForRxnScoreRuns(testCase) expr = getExprForRxnScore(rand(10, 1), 1); testCase.verifyNotEmpty(expr); @@ -276,6 +324,22 @@ function ftINITPipelineRuns(testCase) {'R1';'R2';'R4';'R6';'R7';'R8'}))); end + function ftINITWithoutRemoveGenesDropsGenesOfRemovedReactions(testCase) + % Genes that no remaining reaction uses are not in the output, + % also when removeGenes is false. + testCase.assumeMILPSolver(); + testModel = getTstModel(); + evalc('prepData = prepINITModel(testModel, {}, {}, false, {}, ''s'');'); + arrayData.genes = testModel.genes; + arrayData.tissues = {'a'}; + arrayData.levels = getExprForRxnScore(getTstModelRxnScores()); + arrayData.threshold = 1; + evalc(['resModel = ftINIT(prepData,arrayData.tissues{1},[],[],' ... + 'arrayData,[],getINITSteps(),false,true,struct(),false);']); + testCase.verifyLessThan(numel(resModel.genes), numel(testModel.genes)); + testCase.verifyTrue(all(full(any(resModel.rxnGeneMat,1)))); + end + function ftINITResolveTiesAndProveAbsGapStillRunCorrectly(testCase) % resolveTies and proveAbsGap are threaded through ftINIT -> % ftINITInternalAlg (main extraction) and, for resolveTies, also diff --git a/tests/function_tests/tIO.m b/tests/function_tests/tIO.m index fe5cb78c..556d3401 100644 --- a/tests/function_tests/tIO.m +++ b/tests/function_tests/tIO.m @@ -18,14 +18,94 @@ function exportForGitWritesDependencies(testCase) testCase.verifySubstring(dep, 'RAVEN_toolbox'); end - function exportForGitMainBranchFlagRejectsNonMain(testCase) - % mainBranchFlag should error when RAVEN is *not* on main/master, - % which this test, run from a feature branch, always satisfies. + function exportForGitKeepsPositionOfMainBranchFlag(testCase) + % The fifth positional argument is mainBranchFlag and the sixth + % is subDirs, as in calls written for earlier versions: with + % only the flag given, subDirs keeps its default and the files + % are written in subfolders. + f = fullfile(testCase.ravenRoot,'tutorials','data','empty.xml'); + evalc('model = importModel(f);'); + outDir = tempname; mkdir(outDir); + c = onCleanup(@() rmdir(outDir,'s')); + evalc("exportForGit(model,'pre',outDir,{'xml'},false)"); + testCase.verifyTrue(isfile(fullfile(outDir,'model','xml','pre.xml'))); + testCase.verifyFalse(isfile(fullfile(outDir,'pre.xml'))); + + % The call used for Human-GEM: empty subDirs slot, flag false + flatDir = tempname; mkdir(flatDir); + c2 = onCleanup(@() rmdir(flatDir,'s')); + evalc("exportForGit(model,'Human-GEM',flatDir,{'xml','txt'},'',false)"); + testCase.verifyTrue(isfile(fullfile(flatDir,'Human-GEM.xml'))); + testCase.verifyTrue(isfile(fullfile(flatDir,'Human-GEM.txt'))); + testCase.verifyFalse(isfolder(fullfile(flatDir,'model'))); + end + + function exportForGitRefusesFlagsThatAreNotLogical(testCase) + % Text and other types raise before anything is written; a + % numeric 0 or 1 is accepted as a flag. + f = fullfile(testCase.ravenRoot,'tutorials','data','empty.xml'); + evalc('model = importModel(f);'); + outDir = tempname; mkdir(outDir); + c = onCleanup(@() rmdir(outDir,'s')); + for flag = {'mainBranchFlag','subDirs','COBRAtext','neverPrefixIDs'} + for bad = {'false', "true", {1}, 2, [1 0], NaN, complex(1,0)} + testCase.verifyError(@() exportForGit(model,'path',outDir,'formats',{'xml'},flag{1},bad{1}), 'RAVEN:badInput'); + end + end + d = dir(outDir); + testCase.verifyEmpty(setdiff({d.name},{'.','..'})); + evalc("exportForGit(model,'path',outDir,'formats',{'xml'},'mainBranchFlag',0,'subDirs',0,'COBRAtext',0,'neverPrefixIDs',0)"); + testCase.verifyTrue(isfile(fullfile(outDir,'model.xml'))); + end + + function exportForGitMainBranchFlagRefusesOtherBranch(testCase) + % Skipped when the checkout under test is on main or master. + [~,branch] = system(['git -C "' testCase.ravenRoot '" rev-parse --abbrev-ref HEAD']); + testCase.assumeFalse(any(strcmp(strtrim(branch),{'main','master'})), 'checkout is on the main branch'); + f = fullfile(testCase.ravenRoot,'tutorials','data','empty.xml'); + evalc('model = importModel(f);'); + outDir = tempname; mkdir(outDir); + c = onCleanup(@() rmdir(outDir,'s')); + testCase.verifyError(@() exportForGit(model,'path',outDir,'formats',{'xml'},'mainBranchFlag',true,'subDirs',false), 'RAVEN:notMainBranch'); + end + + function exportForGitCreatesMissingPath(testCase) + % A path that does not exist, with missing parent folders, is + % created with and without the per-format subfolders. + f = fullfile(testCase.ravenRoot,'tutorials','data','empty.xml'); + evalc('model = importModel(f);'); + root = tempname; mkdir(root); + c = onCleanup(@() rmdir(root,'s')); + flatDir = fullfile(root,'a','b','flat'); + evalc("exportForGit(model,'path',flatDir,'formats',{'xml'},'subDirs',false)"); + testCase.verifyTrue(isfile(fullfile(flatDir,'model.xml'))); + testCase.verifyTrue(isfile(fullfile(flatDir,'dependencies.txt'))); + treeDir = fullfile(root,'c','d','tree'); + evalc("exportForGit(model,'path',treeDir,'formats',{'xml'},'subDirs',true)"); + testCase.verifyTrue(isfile(fullfile(treeDir,'model','xml','model.xml'))); + end + + function exportForGitTxtKeepsBoundsExactly(testCase) + % The txt file is a lossless table: bounds and objective are + % written with the digits needed to read them back unchanged. + m = testCase.taskTestModel(); + m.lb(2) = -1/3; m.ub(2) = 0.1; m.ub(3) = 1e-7; outDir = tempname; mkdir(outDir); c = onCleanup(@() rmdir(outDir,'s')); - testCase.verifyError(@() evalc(['exportForGit(testCase.model,''path'',outDir,' ... - '''formats'',{''xml''},''subDirs'',false,''mainBranchFlag'',true);']), ... - ?MException); + evalc("exportForGit(m,'path',outDir,'formats',{'txt'},'subDirs',false)"); + lines = strsplit(strtrim(fileread(fullfile(outDir,'model.txt'))), newline); + testCase.verifyEqual(strsplit(lines{1}, sprintf('\t'), 'CollapseDelimiters', false), ... + {'Rxn id','Formula','Gene-reaction association','LB','UB','Objective'}); + fields = cellfun(@(l) strsplit(l, sprintf('\t'), 'CollapseDelimiters', false), lines(2:end), 'UniformOutput', false); + ids = cellfun(@(f) f{1}, fields, 'UniformOutput', false); + [~, order] = ismember(ids, m.rxns); + lb = cellfun(@(f) str2double(f{4}), fields); + ub = cellfun(@(f) str2double(f{5}), fields); + testCase.verifyEqual(lb(:), m.lb(order)); + testCase.verifyEqual(ub(:), m.ub(order)); + testCase.verifyTrue(any(strcmp(cellfun(@(f) f{4}, fields, 'UniformOutput', false), '0.3333333333333333')) ... + || any(strcmp(cellfun(@(f) f{4}, fields, 'UniformOutput', false), '-0.3333333333333333'))); + testCase.verifyFalse(any(cellfun(@(f) endsWith(f{2}, ' '), fields))); end function importModelHandlesSpeciesAndReactionEdgeCases(testCase) diff --git a/tests/function_tests/tManipulation.m b/tests/function_tests/tManipulation.m index 77e57dfe..e8cfa3b7 100644 --- a/tests/function_tests/tManipulation.m +++ b/tests/function_tests/tManipulation.m @@ -440,6 +440,47 @@ function generateNewIdsEscapesRegexPrefix(testCase) testCase.verifyEqual(ids{1}, 'p.002'); end + function generateNewIdsFollowsNumericallyLargestId(testCase) + % 'R_9' sorts after 'R_10' as text; the next id comes from 10. + m.rxns = {'R_9'; 'R_10'}; + ids = generateNewIds(m, 'rxns', 'R_', 'quantity', 2); + testCase.verifyEqual(ids, {'R_11'; 'R_12'}); + end + + function generateNewIdsKeepsPaddingOfLargestId(testCase) + m.rxns = {'R_0009'; 'R_0010'}; + ids = generateNewIds(m, 'rxns', 'R_'); + testCase.verifyEqual(ids, {'R_0011'}); + end + + function generateNewIdsIgnoresSuffixesThatAreNotIntegers(testCase) + % Inf, fractions and overflowing numbers are not counted as + % numbers; the next id follows the other suffixes. + m.rxns = {'r_inf'}; + testCase.verifyEqual(generateNewIds(m, 'rxns', 'r_'), {'r_001'}); + m.rxns = {'r_9.5'; 'r_3'}; + testCase.verifyEqual(generateNewIds(m, 'rxns', 'r_'), {'r_4'}); + m.rxns = {'r_1e400'; 'r_0003'}; + testCase.verifyEqual(generateNewIds(m, 'rxns', 'r_'), {'r_0004'}); + m.rxns = {'r_1d2'; 'r_0003'}; + testCase.verifyEqual(generateNewIds(m, 'rxns', 'r_'), {'r_0004'}); + m.rxns = {'r_2.5'}; + testCase.verifyEqual(generateNewIds(m, 'rxns', 'r_'), {'r_001'}); + end + + function generateNewIdsIgnoresSuffixesBeyondDoublePrecision(testCase) + % Numbers of 2^52 or more cannot be told from fractions and + % cannot be incremented exactly. + m.rxns = {'r_1e300'; 'r_0003'}; + testCase.verifyEqual(generateNewIds(m, 'rxns', 'r_'), {'r_0004'}); + m.rxns = {'r_9007199254740992'; 'r_0003'}; + testCase.verifyEqual(generateNewIds(m, 'rxns', 'r_'), {'r_0004'}); + m.rxns = {'r_4503599627370496.5'; 'r_0003'}; + testCase.verifyEqual(generateNewIds(m, 'rxns', 'r_'), {'r_0004'}); + m.rxns = {'r_4503599627370495'; 'r_0003'}; + testCase.verifyEqual(generateNewIds(m, 'rxns', 'r_'), {'r_4503599627370496'}); + end + function mergeCompartmentsSingleComp(testCase) evalc('m2 = mergeCompartments(testCase.model);'); testCase.verifyNumElements(m2.comps, 1); @@ -738,6 +779,21 @@ function sortModelPreservesCounts(testCase) testCase.verifyEqual(numel(m2.mets), numel(testCase.model.mets)); end + function sortModelKeepsDirectionByDefault(testCase) + % Only sortReversible=true reorients a reversible reaction; the + % default leaves the stoichiometry as given. + m = struct(); + m.id = 'toy'; m.rxns = {'R1'}; m.mets = {'A[c]';'B[c]'}; + m.metNames = {'A';'B'}; m.comps = {'c'}; m.metComps = [1;1]; + m.S = sparse([1;-1]); m.lb = -1000; m.ub = 1000; m.rev = 1; + m.c = 0; m.b = [0;0]; m.genes = cell(0,1); + m.rxnGeneMat = sparse(1,0); m.grRules = {''}; + m2 = sortModel(m); + testCase.verifyEqual(full(m2.S), [1;-1]); + m3 = sortModel(m, 'sortReversible', true); + testCase.verifyEqual(full(m3.S), [-1;1]); + end + function sortReactionOrderSeedIsReproducible(testCase) % The local search proposes swaps randomly; a given seed must % make its result reproducible across runs. diff --git a/tests/function_tests/tReconstruction.m b/tests/function_tests/tReconstruction.m index 52573b22..53eb1742 100644 --- a/tests/function_tests/tReconstruction.m +++ b/tests/function_tests/tReconstruction.m @@ -85,6 +85,66 @@ function getModelFromHomologyComplexPolicyAndOptions(testCase) testCase.verifyEqual(flag.rxnNotes{1}, 'Included by getModelFromHomology'); end + function getModelFromHomologyComplexPolicyOnNestedRules(testCase) + % An isozyme group whose members all lack an ortholog leaves one flagged + % subunit; "drop" removes every reaction that keeps a flagged gene. + % Columns: rule, mapped template genes, policy, genes in the rule ({} = reaction removed). + cases = {'(A or B) and C', {'C'}, 'flag', {'OLD_T_B', 'nC'}; ... + '(A or B) and C', {'C'}, 'keep', {'nC'}; ... + '(A or B) and C', {'C'}, 'drop', {}; ... + 'A or (B and C)', {'A', 'C'}, 'drop', {}; ... + '(A and B) or C', {'C'}, 'drop', {}}; + for k = 1:size(cases, 1) + t = tReconstruction.homologyTemplate('T', {'R1'}, cases(k, 1)); + pairs = cell(numel(cases{k, 2}), 2); + for q = 1:numel(cases{k, 2}) + pairs(q, :) = {cases{k, 2}{q}, ['n' cases{k, 2}{q}]}; + end + draft = tReconstruction.homologyDraft({t}, {pairs}, cases{k, 3}, {}); + if isempty(cases{k, 4}) + testCase.verifyFalse(ismember('R1', draft.rxns), cases{k, 1}); + else + testCase.verifyEqual(sort(draft.genes), sort(cases{k, 4}(:)), cases{k, 1}); + testCase.verifyFalse(contains(draft.grRules{strcmp(draft.rxns, 'R1')}, ' or '), cases{k, 1}); + end + end + % the flagged subunit stays next to its isozyme in the other branch + t = tReconstruction.homologyTemplate('T', {'R1'}, {'A or (B and C)'}); + draft = tReconstruction.homologyDraft({t}, {{'A', 'nA'; 'C', 'nC'}}, 'flag', {}); + testCase.verifyTrue(contains(draft.grRules{1}, 'OLD_T_B')); + testCase.verifyTrue(contains(draft.grRules{1}, 'nA')); + draft = tReconstruction.homologyDraft({t}, {{'A', 'nA'; 'C', 'nC'}}, 'keep', {}); + testCase.verifyTrue(contains(draft.grRules{1}, 'nA')); + testCase.verifyTrue(contains(draft.grRules{1}, 'nC')); + testCase.verifyFalse(contains(draft.grRules{1}, 'OLD_')); + end + + function getModelFromHomologyPreferredOrderDropsSharedGenes(testCase) + % A template gene that maps to a gene already taken by an earlier model is + % removed from the later model altogether, including its other mappings. + t1 = tReconstruction.homologyTemplate('T1', {'R_T1'}, {'Y'}); + t2 = tReconstruction.homologyTemplate('T2', {'R_T2'}, {'X'}); + pairs = {{'Y', 'N1'}, {'X', 'N1'; 'X', 'N2'}}; + draft = tReconstruction.homologyDraft({t1, t2}, pairs, 'flag', {'T1', 'T2'}); + testCase.verifyEqual(draft.rxns, {'R_T1'}); + draft = tReconstruction.homologyDraft({t1, t2}, pairs, 'flag', {'T2', 'T1'}); + testCase.verifyEqual(draft.rxns, {'R_T2'}); + testCase.verifyEqual(sort(draft.genes), {'N1'; 'N2'}); + end + + function getModelFromHomologyPreferredOrderWithoutSharedGenes(testCase) + % Templates with the same reaction id and no shared gene: the order does not + % change the result, and no empty gene list reaches removeGenes. + t1 = tReconstruction.homologyTemplate('T1', {'R1'}, {'Y'}); + t2 = tReconstruction.homologyTemplate('T2', {'R1'}, {'X'}); + pairs = {{'Y', 'N1'}, {'X', 'N2'}}; + none = tReconstruction.homologyDraft({t1, t2}, pairs, 'flag', {}); + ordered = tReconstruction.homologyDraft({t1, t2}, pairs, 'flag', {'T2', 'T1'}); + testCase.verifyEqual(sort(ordered.rxns), sort(none.rxns)); + testCase.verifyEqual(numel(ordered.rxns), 2); + testCase.verifyEqual(ordered.grRules{strcmp(ordered.rxns, 'R1')}, 'N1'); + end + function getModelFromHomologyRejectsGeneIdsThatSplit(testCase) src = testCase.model; src.id = 'srcModel'; newGenes = cellstr(strcat('sp|P', string(1:10)', '|X_BUG')); @@ -265,6 +325,50 @@ function getPhylDistNeedsData(testCase) end methods (Static) + + function t = homologyTemplate(id, rxns, rules) + % One-reaction-per-rule template converting a_c to b_c; genes come from the rules. + n = numel(rxns); + tokens = cellfun(@(r) regexp(r, '[A-Za-z0-9_]+', 'match'), rules, 'UniformOutput', false); + genes = unique(setdiff([tokens{:}], {'and', 'or'}), 'stable')'; + t = struct(); + t.id = id; t.name = id; + t.mets = {'a_c'; 'b_c'}; t.metNames = {'a'; 'b'}; + t.comps = {'c'}; t.compNames = {'cytosol'}; t.metComps = [1; 1]; + t.rxns = rxns(:); t.rxnNames = rxns(:); + t.S = sparse(repmat([-1; 1], 1, n)); + t.lb = zeros(n, 1); t.ub = 1000*ones(n, 1); t.c = zeros(n, 1); + t.b = zeros(2, 1); t.rev = zeros(n, 1); + t.grRules = rules(:); + t.genes = genes; + t.rxnGeneMat = sparse(n, numel(genes)); + for i = 1:n + t.rxnGeneMat(i, ismember(genes, tokens{i})) = 1; + end + end + + function draft = homologyDraft(templates, pairsPerTemplate, policy, order) + % Draft for organism "N" from fixed ortholog pairs, one list per template. + blast = []; + for k = 1:numel(templates) + list = pairsPerTemplate{k}; + % makeFakeBlastStructure wants at least 10 pairs; the padding genes are in no template + for pad = size(list, 1)+1:10 + list(pad, :) = {sprintf('pad_%s_%d', templates{k}.id, pad), sprintf('npad_%s_%d', templates{k}.id, pad)}; + end + fake = makeFakeBlastStructure(list, templates{k}.id, 'N'); + if isempty(blast) + blast = fake; + else + blast = [blast, fake]; %#ok + end + end + args = {'complexPolicy', policy, 'minLen', 0, 'minIde', 0}; + if ~isempty(order) + args = [args, {'preferredOrder', order}]; + end + evalc('draft = getModelFromHomology(templates, blast, ''N'', args{:});'); + end function file = suppOrthTable(rows) % SuppOrth table with input order templateGEM, bug, other if nargin < 1 diff --git a/tests/function_tests/tSampling.m b/tests/function_tests/tSampling.m index 797ee052..9defc2e1 100644 --- a/tests/function_tests/tSampling.m +++ b/tests/function_tests/tSampling.m @@ -83,13 +83,140 @@ function sampleCHRRReturnsInfo(testCase) end function sampleACHRReturnsSteadyState(testCase) + lastwarn(''); evalc('sols = sampleACHR(testCase.model, 10, 5, [], 1);'); + testCase.verifyNotEqual(tSampling.lastWarningId(), 'sampleACHR:steadyStateNotRestored'); nRxns = numel(testCase.model.rxns); testCase.verifySize(sols, [nRxns, 10]); resid = testCase.model.S * sols; testCase.verifyLessThan(max(abs(resid(:))), 1e-6); end + function sampleACHRKeepsSteadyStateOverManySteps(testCase) + % The residual of S*v must stay at rounding level when the chain + % runs for many steps: with 100 steps between samples, a step that + % is not confined to the null space of S lets the residual grow + % by orders of magnitude over 1000 samples. + m = tSampling.parallelModel(); + evalc('warmup = sampleWarmupPoints(m, ''runParallel'', false);'); + lastwarn(''); + evalc('sols = sampleACHR(m, 1000, 100, warmup, 1);'); + testCase.verifyNotEqual(tSampling.lastWarningId(), 'sampleACHR:steadyStateNotRestored'); + resid = m.S * sols; + testCase.verifyLessThan(max(abs(resid(:))), 1e-9); + testCase.verifyGreaterThanOrEqual(min(sols, [], 2), m.lb - 1e-9); + testCase.verifyLessThanOrEqual(max(sols, [], 2), m.ub + 1e-9); + testCase.verifyGreaterThan(std(sols(2, :)), 0.1); + end + + function sampleACHRMovesOnModelWithFixedReaction(testCase) + % Fixed (and thereby forced) reactions keep an exact zero step; the chain + % must still move at (nearly) every step, stay inside the bounds + % and keep S*v at rounding level. + m = tSampling.fixedReactionModel(); + evalc('warmup = sampleWarmupPoints(m, ''runParallel'', false);'); + lastwarn(''); + evalc('sols = sampleACHR(m, 3000, 1, warmup, 1);'); + testCase.verifyNotEqual(tSampling.lastWarningId(), 'sampleACHR:steadyStateNotRestored'); + moved = mean(any(diff(sols, 1, 2) ~= 0, 1)); + testCase.verifyGreaterThan(moved, 0.9); + testCase.verifyLessThan(max(abs(m.S * sols), [], 'all'), 1e-9); + testCase.verifyGreaterThanOrEqual(min(sols, [], 2), m.lb - 1e-9); + testCase.verifyLessThanOrEqual(max(sols, [], 2), m.ub + 1e-9); + testCase.verifyEqual(sols([3 5], :), 700*ones(2, 3000), 'AbsTol', 1e-9); + evalc('sols = sampleACHR(m, 300, 100, warmup, 2);'); + testCase.verifyLessThan(max(abs(m.S * sols), [], 'all'), 1e-9); + testCase.verifyGreaterThan(std(sols(2, :)), 0.1); + end + + function sampleACHRKeepsNearlyConstantReactionConstant(testCase) + % A reaction whose warmup values differ by less than 1e-9 does not move: + % its recorded value is the warmup mean in every sample, and the + % steady state is kept through the other reactions. + m = tSampling.nearlyFixedReactionModel(); + evalc('warmup = sampleWarmupPoints(m, ''runParallel'', false);'); + s = rng; testCase.addTeardown(@() rng(s)); + rng(5); + warmup(:, end) = 3 + 5e-10*rand(size(warmup, 1), 1); + sols = sampleACHR(m, 200, 20, warmup, 1); + testCase.verifyEqual(max(sols(end, :)) - min(sols(end, :)), 0); + testCase.verifyLessThan(max(abs(m.S * sols), [], 'all'), 1e-9); + testCase.verifyGreaterThan(std(sols(2, :)), 0.1); + end + + function sampleACHRStaysAtSteadyStateOverMillionsOfSteps(testCase) + % One sample after two million steps: the residual of S*v must not grow + % between recorded samples, which needs the correction every few steps + % (a correction only before the recorded sample leaves a residual of + % about 0.3 here). + m = tSampling.parallelModel(); + evalc('warmup = sampleWarmupPoints(m, ''runParallel'', false);'); + sols = sampleACHR(m, 1, 2e6, warmup, 1); + testCase.verifyLessThan(max(abs(m.S * sols), [], 'all'), 1e-9); + testCase.verifyGreaterThanOrEqual(sols, m.lb - 1e-9); + testCase.verifyLessThanOrEqual(sols, m.ub + 1e-9); + end + + function sampleACHRMovesFromWarmupWithRoundingNoise(testCase) + % Warmup values that differ from a vertex by 1e-9 to 3e-9 must not stall + % the chain: reactions with no warmup spread get an exact zero step, + % directions below a tolerance relative to the largest component are + % ignored, and the correction keeps the point inside the polytope. The + % fractions of moving steps are 0.99 and 0.96; without one of these + % three, they drop to 0.6 or below at one of the two noise levels. + m = testCase.model; + evalc('warmup = sampleWarmupPoints(m, ''runParallel'', false);'); + s = rng; testCase.addTeardown(@() rng(s)); + rng(3); + noise = randn(size(warmup)); + sols = sampleACHR(m, 300, 20, warmup + 1e-9*noise, 1); + testCase.verifyGreaterThan(mean(any(diff(sols, 1, 2) ~= 0, 1)), 0.97); + sols = sampleACHR(m, 300, 20, warmup + 3e-9*noise, 1); + testCase.verifyGreaterThan(mean(any(diff(sols, 1, 2) ~= 0, 1)), 0.93); + testCase.verifyLessThan(max(abs(m.S * sols), [], 'all'), 1e-8); + end + + function sampleACHRWarnsForWarmupThatIsInfeasibleForTheModel(testCase) + % Warmup points of the model without the fixed ATP maintenance flux do + % not satisfy the model with it; the samples keep a residual far above + % rounding and sampleACHR says so. + m = testCase.model; + evalc('warmup = sampleWarmupPoints(m, ''runParallel'', false);'); + i = find(contains(lower(m.rxns), 'atpm'), 1); + testCase.assertNotEmpty(i); + m.lb(i) = 20; + m.ub(i) = 20; + testCase.verifyWarning(@() sampleACHR(m, 200, 20, warmup, 1), ... + 'sampleACHR:steadyStateNotRestored'); + end + + function sampleACHRWarnsForNoisyWarmup(testCase) + % Warmup values that are off by 1e-5 leave a residual above 1e-9 after + % the clip to the bounds. + m = testCase.model; + evalc('warmup = sampleWarmupPoints(m, ''runParallel'', false);'); + s = rng; testCase.addTeardown(@() rng(s)); + rng(3); + noisy = warmup + 1e-5*randn(size(warmup)); + testCase.verifyWarning(@() sampleACHR(m, 300, 20, noisy, 1), ... + 'sampleACHR:steadyStateNotRestored'); + end + + function sampleACHRWarnsForNearlyDependentRows(testCase) + % Two rows of S that differ by 1e-7 times a column: the correction + % cannot separate them, the implied constraint is lost and the + % residual stays near 3e-7. + d = 1e-7; + S = [1 -1 -1 0 0; + 1 -(1+d) -1 0 d; + 0 1 1 -1 0; + 0 0 0 1 -1]; + m = tSampling.modelFromMatrix(S, zeros(5, 1), [10; 6; 6; 1000; 1000]); + evalc('warmup = sampleWarmupPoints(m, ''runParallel'', false);'); + testCase.verifyWarning(@() sampleACHR(m, 500, 100, warmup, 1), ... + 'sampleACHR:steadyStateNotRestored'); + end + % ---- randomSampling dispatch ------------------------------------ function randomSamplingDefaultsToACHR(testCase) @@ -171,6 +298,94 @@ function randomSamplingLoopDetectionUsesModelBounds(testCase) methods (Static, Access = private) + function m = modelFromMatrix(S, lb, ub) + % A model with the given stoichiometric matrix and bounds, without genes. + [nMets, nRxns] = size(S); + m = struct(); + m.id = 'fromMatrix'; + m.rxns = arrayfun(@(i) sprintf('R%d', i), (1:nRxns)', 'UniformOutput', false); + m.rxnNames = m.rxns; + m.mets = arrayfun(@(i) sprintf('m%d', i), (1:nMets)', 'UniformOutput', false); + m.metNames = m.mets; + m.metComps = ones(nMets, 1); + m.comps = {'c'}; + m.compNames= {'cytosol'}; + m.S = sparse(S); + m.lb = lb; + m.ub = ub; + m.rev = zeros(nRxns, 1); + m.c = zeros(nRxns, 1); + m.b = zeros(nMets, 1); + m.genes = {}; m.grRules = repmat({''}, nRxns, 1); m.rxnGeneMat = sparse(nRxns, 0); + m.metFormulas = repmat({'C'}, nMets, 1); + end + + function id = lastWarningId() + [~, id] = lastwarn; + end + + function m = nearlyFixedReactionModel() + % The parallel model plus a reaction without metabolites. + m = tSampling.parallelModel(); + m.rxns{end+1, 1} = 'NF'; + m.rxnNames = m.rxns; + m.S(:, end+1) = 0; + m.lb(end+1, 1) = 0; + m.ub(end+1, 1) = 10; + m.rev(end+1, 1) = 0; + m.c(end+1, 1) = 0; + m.grRules{end+1, 1} = ''; + m.rxnGeneMat(end+1, :) = 0; + end + + function m = parallelModel() + m = struct(); + m.id = 'parallel'; + m.rxns = {'EX_in';'R1';'R2';'EX_out'}; + m.rxnNames = m.rxns; + m.mets = {'a';'b'}; + m.metNames = {'a';'b'}; + m.metComps = [1;1]; + m.comps = {'c'}; + m.compNames= {'cytosol'}; + % EX_in R1 R2 EX_out + m.S = sparse([ 1 -1 -1 0; % a + 0 1 1 -1]); % b + m.lb = [0;0;0;0]; + m.ub = [10;6;6;1000]; + m.rev = [0;0;0;0]; + m.c = [0;0;0;0]; + m.b = zeros(2,1); + m.genes = {}; m.grRules = {'';'';'';''}; m.rxnGeneMat = sparse(4,0); + m.metFormulas = {'C';'C'}; + end + + function m = fixedReactionModel() + % EX_in -> a -> R1 -> b -> EX_out, a -> R2 -> c -> FIX, with FIX + % fixed at 700. R2 is forced to 700 through c, so R2 and FIX do + % not move, and the null-space basis carries rounding noise on them. + m = struct(); + m.id = 'fixedReaction'; + m.rxns = {'EX_in';'R1';'R2';'EX_out';'FIX'}; + m.rxnNames = m.rxns; + m.mets = {'a';'b';'c'}; + m.metNames = m.mets; + m.metComps = [1;1;1]; + m.comps = {'c'}; + m.compNames= {'cytosol'}; + % EX_in R1 R2 EX_out FIX + m.S = sparse([ 1 -1 -1 0 0; % a + 0 1 0 -1 0; % b + 0 0 1 0 -1]); % c + m.lb = [0;0;0;0;700]; + m.ub = [1000;1000;1000;1000;700]; + m.rev = zeros(5, 1); + m.c = zeros(5, 1); + m.b = zeros(3, 1); + m.genes = {}; m.grRules = repmat({''}, 5, 1); m.rxnGeneMat = sparse(5, 0); + m.metFormulas = repmat({'C'}, 3, 1); + end + function m = loopModel() m = struct(); m.id = 'loop'; diff --git a/tests/function_tests/tTasks.m b/tests/function_tests/tTasks.m index 11a71eb9..545c843d 100644 --- a/tests/function_tests/tTasks.m +++ b/tests/function_tests/tTasks.m @@ -10,6 +10,20 @@ function checkTasksSucceedsOnFullModel(testCase) testCase.verifyTrue(report.ok == 1); end + function checkTasksReportsTasksWithARepeatedIdOneByOne(testCase) + % Ids may repeat (category codes in Human-GEM's task list): the + % report has one entry per task, in the order of the task list. checkTasks + % already did this; the test pins it. + m = testCase.taskTestModel(); + task = testCase.taskTestStruct(); + first = task; first.id = 'BS'; + second = task; second.id = 'BS'; + second.inputs = {}; second.LBin = []; second.UBin = []; + [~, report] = evalc('checkTasks(m, [], true, false, false, [first, second]);'); + testCase.verifyEqual(double(report.ok(:)'), [1 0]); + testCase.verifyEqual(report.id(:)', {'BS','BS'}); + end + function checkTasksFailsWithoutKeyReaction(testCase) m = removeReactions(testCase.taskTestModel(), {'R2'}); task = testCase.taskTestStruct(); @@ -37,6 +51,226 @@ function checkTasksWarnsWhenSeveralMetabolitesShareAName(testCase) testCase.verifyTrue(report.ok == 1); end + function parseTaskListDefaultsMissingOptionalColumns(testCase) + % Absent optional columns take their documented defaults: bounds + % 0 (lower) and 1000 (upper), and empty comments. + f = tTasks.writeTaskFile({ + {'' 'ID' 'IN' 'OUT'} + {'' 'T1' 'A[c]' 'B[c]'} + }); + ts = parseTaskList(f); + testCase.verifyEqual(ts.LBin, 0); + testCase.verifyEqual(ts.UBin, 1000); + testCase.verifyEqual(ts.LBout, 0); + testCase.verifyEqual(ts.UBout, 1000); + testCase.verifyEqual(ts.comments, ''); + end + + function parseTaskListSkipsLeadingCommentRows(testCase) + % Rows starting with a non-empty cell are comments, also when + % they come before the header row. + f = tTasks.writeTaskFile({ + {'# first comment'} + {'# second comment'} + {'' 'ID' 'IN'} + {'' 'T1' 'A[c]'} + }); + ts = parseTaskList(f); + testCase.verifyEqual(numel(ts), 1); + testCase.verifyEqual(ts.id, 'T1'); + testCase.verifyEqual(ts.inputs, {'A[c]'}); + end + + function parseTaskListWarnsAndSkipsRowsBeforeFirstId(testCase) + f = tTasks.writeTaskFile({ + {'' 'ID' 'IN'} + {'' '' 'X[c]'} + {'' 'T1' 'A[c]'} + }); + lastwarn(''); + ts = parseTaskList(f); + testCase.verifySubstring(lastwarn, 'before the first task id'); + testCase.verifyEqual(numel(ts), 1); + testCase.verifyEqual(ts.id, 'T1'); + testCase.verifyEqual(ts.inputs, {'A[c]'}); + end + + function parseTaskListRefusesNonNumericBound(testCase) + f = tTasks.writeTaskFile({ + {'' 'ID' 'IN' 'IN LB'} + {'' 'T1' 'A[c]' 'abc'} + }); + testCase.verifyError(@() parseTaskList(f), 'RAVEN:badInput'); + end + + function parseTaskListRefusesXls(testCase) + % The message names the supported formats, also for an upper + % case extension. + for ext = {'.xls','.XLS'} + f = [tempname ext{1}]; + fid = fopen(f,'w'); fclose(fid); + cleanup = onCleanup(@() delete(f)); %#ok + try + parseTaskList(f); + msg = ''; + id = ''; + catch err + msg = err.message; + id = err.identifier; + end + testCase.verifyEqual(id, 'RAVEN:badInput'); + testCase.verifySubstring(msg, '.xlsx'); + testCase.verifySubstring(msg, '.txt'); + end + end + + function parseTaskListIgnoresEmptyRows(testCase) + % A row without content, also one of white space, is neither a + % header nor a task row. + f = tTasks.writeTaskFile({ + {'# comment' '' '' ''} + {'' '' '' ''} + {' ' ' ' '' ''} + {'' 'ID' 'IN' 'IN LB'} + {'' '' '' ''} + {'' 'T1' 'A[c]' '2'} + }); + ts = parseTaskList(f); + testCase.verifyEqual(numel(ts), 1); + testCase.verifyEqual(ts.id, 'T1'); + testCase.verifyEqual(ts.LBin, 2); + + x = [tempname '.xlsx']; + writecell({'# comment' '' '' ''; '' '' '' ''; '' 'ID' 'IN' 'IN LB'; '' 'T1' 'A[c]' 2}, x, 'Sheet', 'TASKS'); + cleanupX = onCleanup(@() delete(x)); %#ok + ts = parseTaskList(x); + testCase.verifyEqual(numel(ts), 1); + testCase.verifyEqual(ts.id, 'T1'); + testCase.verifyEqual(ts.LBin, 2); + end + + function parseTaskListReportsDataRowOfBadNumber(testCase) + % The row is counted after the header, comment rows and empty + % rows are removed, and the message says so. + f = tTasks.writeTaskFile({ + {'# c1' '' '' ''} + {'' '' '' ''} + {'' 'ID' 'IN' 'IN LB'} + {'# c3' '' '' ''} + {'' 'T1' 'A[c]' '1'} + {'' 'T2' 'A[c]' 'abc'} + }); + try + parseTaskList(f); + msg = ''; + catch err + msg = err.message; + end + testCase.verifySubstring(msg, 'task list data row 2 is not a number'); + testCase.verifySubstring(msg, 'counted after'); + end + + function parseTaskListRefusesNaNOutsideChangedBounds(testCase) + % NaN means "leave unchanged" in the bounds of changed + % reactions only; in the other bound columns it is refused. + for col = {'IN LB','IN UB','OUT LB','OUT UB','EQU LB','EQU UB'} + for val = {'NaN','nan'} + f = tTasks.writeTaskFile({ + {'' 'ID' 'IN' col{1}} + {'' 'T1' 'A[c]' val{1}} + }); + try + parseTaskList(f); + testCase.verifyFail(['parseTaskList accepted ' val{1} ' in ' col{1}]); + catch err + testCase.verifyEqual(err.identifier, 'RAVEN:badInput', [col{1} ' ' val{1}]); + testCase.verifySubstring(err.message, col{1}); + testCase.verifySubstring(err.message, 'NaN'); + end + end + end + f = tTasks.writeTaskFile({ + {'' 'ID' 'CHANGED RXN' 'CHANGED LB' 'CHANGED UB'} + {'' 'T1' 'R1' 'NaN' '5'} + }); + ts = parseTaskList(f); + testCase.verifyTrue(isnan(ts(1).LBrxn)); + testCase.verifyEqual(ts(1).UBrxn, 5); + end + + function parseTaskListRefusesTextThatOnlyLooksLikeANumber(testCase) + % str2double reads '1,5' as 15 and '2i' as a complex number. + for bad = {'1,5','1,000','2i','0x10'} + f = tTasks.writeTaskFile({ + {'' 'ID' 'IN' 'IN LB'} + {'' 'T1' 'A[c]' bad{1}} + }); + testCase.verifyError(@() parseTaskList(f), 'RAVEN:badInput', bad{1}); + end + x = [tempname '.xlsx']; + writecell({'' 'ID' 'IN' 'IN LB'; '' 'T1' 'A[c]' '1,5'; '# note' '' '' ''}, x, 'Sheet', 'TASKS'); + cleanupX = onCleanup(@() delete(x)); %#ok + try + parseTaskList(x); + testCase.verifyFail('parseTaskList accepted 1,5 in an xlsx file'); + catch err + testCase.verifyEqual(err.identifier, 'RAVEN:badInput'); + testCase.verifySubstring(err.message, 'is not a number'); + end + f = tTasks.writeTaskFile({ + {'' 'ID' 'IN' 'IN LB' 'IN UB'} + {'' 'T1' 'A[c]' '-1e3' 'Inf'} + {'' 'T2' 'A[c]' ' 2.5 ' '.5'} + }); + ts = parseTaskList(f); + testCase.verifyEqual(ts(1).LBin, -1000); + testCase.verifyEqual(ts(1).UBin, Inf); + testCase.verifyEqual(ts(2).LBin, 2.5); + testCase.verifyEqual(ts(2).UBin, 0.5); + end + + function parseTaskListConvertsNumbersTypedAsText(testCase) + % A bound that Excel holds as text is read as the number, as in + % a tab-delimited file; other text is still refused. + f = [tempname '.xlsx']; + % a filled first column keeps the empty header cell in the sheet range + writecell({'' 'ID' 'IN' 'IN LB'; '' 'T1' 'A[c]' '2'; '# note' '' '' ''}, f, 'Sheet', 'TASKS'); + cleanup = onCleanup(@() delete(f)); %#ok + ts = parseTaskList(f); + testCase.verifyEqual(ts.LBin, 2); + + g = [tempname '.xlsx']; + writecell({'' 'ID' 'IN' 'IN LB'; '' 'T1' 'A[c]' 'abc'; '# note' '' '' ''}, g, 'Sheet', 'TASKS'); + cleanupG = onCleanup(@() delete(g)); %#ok + testCase.verifyError(@() parseTaskList(g), 'RAVEN:badInput'); + end + + function parseTaskListExcelMatchesText(testCase) + % The same task definitions give the same structs from Excel and + % from a tab-delimited file. + txtFile = fullfile(testCase.ravenRoot,'tests','function_tests', ... + 'test_data','test_tasks.txt'); + testCase.assumeDependency(exist(txtFile,'file')==2, 'test_tasks.txt'); + cells = readcell(txtFile, 'FileType', 'text', 'Delimiter', '\t', 'TextType', 'char'); + cells(cellfun(@(x) all(ismissing(x)), cells)) = {[]}; + x = [tempname '.xlsx']; + writecell(cells, x, 'Sheet', 'TASKS'); + cleanup = onCleanup(@() delete(x)); %#ok + testCase.verifyEqual(parseTaskList(x), parseTaskList(txtFile)); + end + + function parseTaskListReadsXlsx(testCase) + f = [tempname '.xlsx']; + % a filled first column keeps the empty header cell in the sheet range + writecell({'' 'ID' 'IN' 'IN LB'; '' 'T1' 'A[c];B[c]' 2; '' 'T2' 'C[c]' ''; '# note' '' '' ''}, f, 'Sheet', 'TASKS'); + cleanup = onCleanup(@() delete(f)); + ts = parseTaskList(f); + testCase.verifyEqual(numel(ts), 2); + testCase.verifyEqual(ts(1).inputs, {'A[c]';'B[c]'}); + testCase.verifyEqual(ts(1).LBin, [2;2]); + testCase.verifyEqual(ts(2).LBin, 0); + end + function parseTaskListReadsFlagsListsAndHeadersLeniently(testCase) % Flags: "false"/"0"/"no" are false, other text is true. Lists % separated by ";" are trimmed and empty items dropped. Headers @@ -101,16 +335,13 @@ function checkTasksLeavesNaNChangedBoundsAlone(testCase) end function parseTaskListMatchesExpected(testCase) - % The text and Excel task definitions must parse to identical - % structs with exactly the expected fields and values. + % The text task definitions must parse to structs with exactly the + % expected fields and values. dataDir = fullfile(testCase.ravenRoot,'tests','function_tests','test_data'); txtFile = fullfile(dataDir,'test_tasks.txt'); - xlsFile = fullfile(dataDir,'test_tasks.xls'); testCase.assumeDependency(exist(txtFile,'file')==2, 'test_tasks.txt'); - testCase.assumeDependency(exist(xlsFile,'file')==2, 'test_tasks.xls'); taskStruct = parseTaskList(txtFile); - taskStructExcel = parseTaskList(xlsFile); expTask1.id = 'ER'; expTask1.description = 'Aerobic rephosphorylation of ATP from glucose'; @@ -132,10 +363,8 @@ function parseTaskListMatchesExpected(testCase) testCase.verifyNotEqual(taskStruct(1), expTask1); expTask1.UBrxn = 60; testCase.verifyEqual(taskStruct(1), expTask1); - testCase.verifyEqual(taskStructExcel(1), expTask1); testCase.verifyEqual(length(taskStruct), 2); - testCase.verifyEqual(length(taskStructExcel), 2); expTask2.id = 'BS'; expTask2.description = 'ATP de novo synthesis'; @@ -155,11 +384,23 @@ function parseTaskListMatchesExpected(testCase) expTask2.LBrxn = []; expTask2.UBrxn = []; testCase.verifyEqual(taskStruct(2), expTask2); - testCase.verifyEqual(taskStructExcel(2), expTask2); - - % the two formats must produce identical results - testCase.verifyEqual(taskStruct, taskStructExcel); end end + + methods (Static, Access = private) + function f = writeTaskFile(rows) + % Write rows (cell arrays of cells) as a tab-delimited task list. + % The file is deleted when the calling test ends. + lines = cell(numel(rows),1); + for k = 1:numel(rows) + lines{k} = strjoin(rows{k}, sprintf('\t')); + end + f = [tempname '.txt']; + fid = fopen(f,'w'); + fprintf(fid,'%s\n',lines{:}); + fclose(fid); + evalin('caller', sprintf('cleanup = onCleanup(@() delete(''%s''));', f)); + end + end end diff --git a/tests/function_tests/test_data/test_tasks.xls b/tests/function_tests/test_data/test_tasks.xls deleted file mode 100644 index fc50cf00e8f4bc159ed8df9c4486cf9c08603de1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 27136 zcmeG_2Ut{BvuBsKGyy@xhF6LpWu>d2(p8WaiW(7dfu$(0xGV?>1Z+_(#1@Su7L;f- zqR|)=dy9sMy+zU3jlD%f%pcu1bMG$f-Yvp=|Np-4eUIyR?%p$J&di*dIdjTA7f+wE zxUuHjt~Uwm8ccM^JEo)q)Yl`UV^0{= z6E4x`5polLx9T2*J0Z6qjUfdDup&j-f_!RdQK3YTSy(8_r~Z>y2rYwvgEAZd#~TDs z9I^tS7NiR;Sx5SAN#D27czfvkAkvZiRdI%+myt4{Kh!2r?-eJA#+ysu?dW?4xO2!( zy3Q3E;z?}DbHL}3dFbj~(#e1mN(3aABm$tDt+M{>YtwcCggqh@++ueQ~-C3?`Sh`pP=y~h~JZi3h@ zV2ry$&Q{kRMgm)9jO2J>MhXkfff$gUGBmyii&ithR<{ob?4W|DNKpZKFmWLR2uXE! zp%i3Fbq5*Q5X7-_w`1A>7^&_-sqVJAZln)!QlOw%m&T%NwcrmSZX{R%zgZZ?ii$uP z-@rgNXCLB9`YKam(P-fx7$}$E2C)J^BjmM|qJNp&0Se)I?eKrA9#FR&Lg5c{aTz`1 z%!k(hcQUx{Dw?kS7IC$(Tr_)gmZP6%eIO6_}}98#tgKZQ`KY z)(U8yG<3a&$p=p1_P}X zf?53mqk)d*{-8^D6k#U196fJ^-crtE6T#5Y3L@x+nu%bXX$28(T1P~?b-K5Ph?lo^>-1f4_BHFFfyLCjgTgRz&M6_GSxtR#gmsSvc zsnR?Qt%C*9V<)98;c-4|@ah8?2K3;CxPO-u^+aC2eA!IIKt{NtqCzU)U*)NR87jbl zHhmeXftB;vLb(7gBNR^T@KW2d+S*zbp#lQ~RfJN6$%R4~LmIc87)>d{RA;fSfUb33 zz@8_&wY7qKTPyI}S^>6sWNkoeqXCr^t2!<505oRw#0s#AP_7{Nv7^rveT+S~@oXl7 z=ES7L6@3SR+QSIGxoH{4H4u>oYA1rPhTcnrJ>0PE>H|bt96?1ARd@D<3N3vsFtAv? z5~U4GRowaC*T>jK@oQFv+Kw;p(9%jyvrww;(NBrUijf-JL;7fi0ugp^$2TA?y;YO}TMVMSD z#c(4RN)e_Sg8V_K7SB`Blxl?lr3!3U<&^W1Si`|UsGzHPErWVzcU7hCD;p0~B^lH# z3A!5?Ls_2-xgFvC9W3s=JRB)^DmXn|m@n~Mz>6jea3qyo99>(fDe)p6#F0?M9a1n! zhkrtnPrqlt&dnhv#6VY=dHLo`sUAL~nt+`n?oA*Y3pN`sRW^27*f4>JW}_#}{O8U^ z^=vw_*?6n6>8*tg6RK!7`ohd7PoAh}W65UYqsqob3mXh(GNl;^Go>#Z)U&Z-v+-4B z!`H$FL!gWePndb_QImQ$o!D&rRM~iGVZ#J9T4{#D%v;Z{sApr1Y<#qPT2El((+W>( zi2Cm6@9NofX0uV_Y4%#!D0*5$)L+Lpt7l`wW~0W_oV2h}^t6Vk#>PhVY`U=7sPQyc zEo>A$tszQ!Z>f4VUD<5Zcv@dAY!p4MA!_fXr|Q{sW3y4?Y2I4cD0*5$)X^K?sb|w2 z+4yPqGz!@GwZhZ9ysN*~V4EInHflW0K?@s2PxJDAwRM{M*4VPysPQysEo>A$&C9#K zzFs|>o@_R1Jk3oD8%0m^@|IrCRL_QDvr*$|9$MHadYYH_@GtggJ7Q zPKw~*kpQM%IYb0gFq|>t*o#DQVk~EXy9zV|eFATYpb%Fz96(mg@sx6gO1XtxGdN!X zZzDpph3ONhP+@NF0$v46uFg^;kSiM|gXv@fObtWfDHr}JN~P9)TfbQf1}t}_c)A5Ji~;8G+RMuL>) zI_{OaP^UMmj;@pkb&BA;mzbXWrjyd<8vIHFsKIB|Fp!$4)KKQt3C@7Pn~d<1f?Pqq zKq3;CQi;M6$pYR`Wl~&UsW}NJC2-&lZJ$ruUj%}bg3c2`oD%Q{T&BpakH(g{mm#zb z7U)AoB5>3IWxAZMQe83>lqO=iD()@FX#q1itqyRm2=>Q@2?ZHB`B@aK6C|IK)0SaSJuXnfRxHxHPcl zhnvt4B<`(99AY4wctlQl^p@_Adl_=0;fBZmu=|_C1PS6&R8IZ`VTP>#4`&Hu z191rto#jLC7lO$ZBY;k`H#Wy2dGx^z4t-FZ3F!y&lNe!t!V1is?hu$`fH1aH z3_gW{g;~+i@f3DAreJyXdh+ z658UESWG(s=~!9^sy0^)`4}OUY>t2yNtm2m5+)~?gvnvR^sm$d-8m?Wpr;3hh=VqZ zn-~75GIZ4dbk)QhNiJy1Q@NIo8JH&#yh8$pLw%`wI|oS&q)CV*=mD&~2c0f72~;>^ zeO=nAl=c@;@A5dpv{s%&7qcLPAn=fp0&zZuEGE1LDhtXrhDbdUIEcZK z2Kx{KV-ZP zz6NoG1Y1H8VbH(B!V^N{BjOSxVq+keFf2AHDvTNu91(>GF<{(?7%D1+`ArIe03Mr^ zh|Zdn2w>R*4G;ey39@i!;P{Xff)krz!7)R_!>IUCF*uu$<)T6qGD(<;i;sv&q=rN# zje?eh#zsen$0Q~IJU)C#czk$FXm|qHF?>{9RB%i%N}B+UvM&TsygwD5FBE5$QiA*p zN-WF}l>|@`q5f1vzCY%Si{G z1=*rPNa9jv28R`c6XU2%u_%wq$}LJ46$)XXkM&9}ObL+X;StuU!@Ob<7D^XUU=AUb zFDe#MN;2dZrRNGolAH`-;acFD3oU}_SRs`T2k+&GA3?ikd8BS=nnD1Ov)2p>Z3KXg<>HE?lK-ef}k=56NFTTKq8RB z{Fz5?fuDfm5+`_Z^a1>q!|!_d&4l+tc(VOlc-_+o`by_nLxKX?Wi9*VlEpl}$zp{P zEQlMfqiY3&27-H$2fvx^huB2A1075FO%4@->*q^S6H5z(g(>`!yxec*ACEcgVIJNz z?4PN3-6L0Q3*tFGm~yQ8SWVf1pO-pV*Z;b0ebT?0qRu9UY@n=j-7b4Ktqiy=-rLh) z$KfwltdHFCGQ`#4QMaKfwb%Q_Ufw*SYI0Ybfr47IFJA51x5v5PjLL|rW%0Ei-z&(9 z+BT?qp19|{b!Y$X&pqy2+`IB^Wrq_*A6&J0R5ic?Hs>}SwHpCp1T8(=hc;E6|Lq&20XhaID zF=c(#UR?kC0nn2l=pP~!Nfiro3;Xi%KJBwp;}3h7%y>3^@6p}18X1}?)=y%F`*+%`tw?YGtXWuLyw7+6X z{q%L~9eK$m1)r|{xN%47p13n>2cGckRy*u_kIy@r&Ac4DC%!Hu<4nEz*~sulpEQSx z_46mDUh4V9$}TtjqRzdxj=$7zT-|3I>ZRKcdo-+!Jy@~D?QXbke!oRgId{_5Z0LJC zVeh;R!j0!`Pw)8Q&BeFS6xD`KC|ztl6&g4T5+3`2{+%c6t8kBobw`#^j2>dr5N&jr zG7q|0zGl+A%l*=KJBO!jJ6^Z@PZGMKYw)qvXIC{|x_f(SzmwOkE-W8z`k~tj1JhZ; z6a1MwpO?9<@3!H}uE449oBHqOT3(O%x$uK>`wR1?Z{Bf@y12vjLa%3T z*T(+nFn-YNp(F2~YwB|={E=&KBW{27!f)TL0*CYQRY z3k{bq$oQ>zRkeQYSC0xNFX&U_b}(qGZq;+0^TqbPtPSpZ ztvcXwtLay+Q$gO*vo?nPqkq4fGmC#v6m@(@q@(k(nGSUeSKC}2yzIo!x&ihN&JG$A z@GPN!wC)le%ViyEC*+QDe*9{Wi$&Q_q09U`&-^~+i>zPxL;tER=rQYuO5?npHSeyz zy!qD4|L3_IeofzW+4OvB{*rD{rzVU)DH+V~{d(YaWB=(DdaKhqUlP5V`bpeh3B+gV zfk3=|4Rq=eIL&qyLbEzU+L=}Cwrv6GuguxzT4a5pK<0PR;KV=Qep6H zQu4Kx4^k(4m2I6WO7tJJMex_i)iW2|DKkiUG4-P##^!OBdVZ8UeP@~I^oF}9`#Ep; zv}V^GEAPcK%Xe6>?d;Zr1abRxbeMQ#{N$``Zc=x<$fJEG#?%j-awvKAw23x#=Ut{o z_IP)E)%xnhA!j&4mhHHs7xmZ4d3SCW0TFj3ayh)rks{7iE zy5v35ySw*S{C)Jzqn)d+?|(CRxun~vqB1k5KeiY~4))=EQ*8O^q`*CIZ#V6H_Q^kg zo=Uww>YqvRJ{ND=cT9ge@W_&rmez-Fxw#&={VLOXEzjaGH~dE9tzBko&P_HOIrB`H#oRm*Ao z+`<{op7B>x`9Y(rP6%xTQ}3nE>Nxpc|AU9-U;1^x(Zv@AIUSk#sLRrIbG9!nSZTR_ z_Do*~doMquoODT4+Rw*-8DbuvcjVsVrRRj_zkg`E&trZ;IyJsm;Gy%p+ZLmZtQyBu z$3?xaTxDl*d02;$55kVU{;@LZl&3JJV%C72mj^})_KrRq8oa;Ze;CPP- z-wz!b@Mp=hPs@im_n6h`r}bl=TpsXh-2u0s@;^(Qm4bZ`uQvEgtym4 zm4%1-%sI{V`!i{z|I$CMzFrYvd{nT1%cq<7Ij-d2_VzomGGSBFo2X?~21C=l$G(_7 zvE-&~xs3SDz=>e(x& zvwZ@O-1`1U{oQ$atE=j)d?xZ6^W&P=N{1PyWf3M4!h|=JpUv~05ee$JmXwZ&% zcjguhTw*%HY~z{e*>&?L{@Pfy#c#pOHjlQLGFZdr> zo%7L*Q|mrX7AH^G+<3*wDuv4(d*syFqAh34ea0`ZHyOX+MV-m;6DNnB`0d-VH#(iK zJLlkY!R%uG&RZj1@g7M$N;aJ|D{f3O4&DBGUwruJ^5>`bM{IpzZ2p<;p0_FS2a{ZG z&JLY5F4QOS;{{QEH>^whyiC@qULlOU@^sUz>n{#1>$%t^dVlK6hhuBczY2IAf2`@z zg?*`;U+vm)>GY^DtC4RXrynO{L(U|6fDPad(%N-TAiixy#rYI4e^fmQByhTlGDZ zbb~<~*JFw!8#5Q7Aqs=DY;F>5bjZVMTGNzCJdx|2_%d&!semg!!Ntq`GvTHQ{=)Gr)MR;sjTb2_OlUp?ACr2{PXV9!1_VJ^q6$es<27YSTR5WnFFvn3NXY& zWadk6bEPl6jk#7>Okc1+N8qzm@@EKvEeZTK+63+y#EgEnfIERbY##aN>lbIE)8YbC zX}Ajwcct%BXhl z&`xX^EPhpB=!A-RZ)xA+fS2IKkTZj+4>{z?a@blXX9zhoFOxHZ9Ozb-Glm@cJ5y!?Ib8WMIaA2t8lA~?fE?`1$Z}?oL#;Ap z=8(fSF*yr(M*T9mj*!DoN|>A_LCtW!SdTdO*9V!%a8M_`XdD~hg*xH9+5tGQHBR532b#+;Q44keXJkYz z*f-};3l6dzTC_zAxI1ZvUzgMMbfHCN`0*Z{!;N(_f*(5|O*lgaIb6OnIn)B|A<1&` z_UeG|V_3C^8t1uk8aD(;-1n4==1U8=~oKrAT4~@A}cd(7L$0+5_;=qv1jTUI^kc!ypHI z7@l}b0h1VK@j8<%1!~|EcqBk4j0w?9?f=#LJD9kj|FJJ@%8lu(UcQOVHs3U#M_iq@ zUxp1VasU$cLo5%1gti_A2^%#I683~LNZ89(L&E;`4J6#H`T-Jhx&xKaHrS&g;qQpV z!`^B>{6P`O(s1lC>_0XlFGKH$&Pf-G3PqU`Dq4`9lP?h!W>euMuu}?UL69o9I?jGQ z>l}xEzBaIas?9}?%tBDwezj|$T?6eJXxBiy2HG{yu7P$9v}>SU1MM1U*Fd`l{{Lw} z+xnk-;lza%{BGt8m%#ep?M)r7|Bpbzy*6C`$3en9l2MRww<-e??zQ1M9M|>|NVx9D z{Q!KgKMfM@2h4$F0%;{A#r*)-7N9R?t!_@!LIBdli~Va0Oi&KoC8Uqe5fgwQMciFL z?2#hzgu-m0P*O;5(I9gB)vked4YX^ZT?6eJXxBiy2HG{yu7P$9v}>SU1MM34f2aXm z661;)*W9@N#`8D0p2m0l_)Z_!`gqy_SJSv=$Mrj&lEHO9uJQ3448HTnbw0kg$Mapd z*2nz-Je`8)KJc^$p7FqU_jsNISLb*tg@V)z68`@&_VDWf$q`a-NKTNPA@zZT<*xAS z2FV=~A5vdP9+2>xV=R9Q34hzKwrL<9K2gN4E-Coz9RJP}ern#9%bMuXM_7<6`Vqy$ zJ9AD1eumH))(!0>2aN>oV)&>yjh17A9Ng+(I3Lx_A7yB5#krvjrXES4KiI^-C4g Date: Wed, 28 Oct 2026 22:24:24 +0100 Subject: [PATCH 34/48] fix: keep each subsystem with its reaction when readYAMLmodel flattens them (#14) * fix: keep each subsystem with its reaction when readYAMLmodel flattens them A reaction without a subsystem held an empty entry in model.subSystems, and flattening the entries with [model.subSystems{:}] dropped the empty ones, so the vector was shorter than model.rxns and every subsystem after a gap sat on the wrong reaction. A reaction without a subsystem now gets an empty entry first, and the entries are flattened one by one, so model.subSystems keeps one entry per reaction. * test: check the subsystems that readYAMLmodel returns against checkModelStruct, for single and several subsystems per reaction --- src/io/readYAMLmodel.m | 5 ++- tests/function_tests/tIO.m | 71 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 1 deletion(-) diff --git a/src/io/readYAMLmodel.m b/src/io/readYAMLmodel.m index e8b5c351..3db80cd6 100755 --- a/src/io/readYAMLmodel.m +++ b/src/io/readYAMLmodel.m @@ -813,9 +813,12 @@ % end if isfield(model,'subSystems') +% A reaction without subsystem holds [] or '' here: give it an empty entry, +% so every reaction keeps its place when the entries are flattened + model.subSystems(cellfun(@isempty, model.subSystems)) = {{''}}; % If all entries are 1x1, then flatten if all(cellfun(@(x) numel(x) <= 1, model.subSystems)) - model.subSystems = transpose([model.subSystems{:}]); + model.subSystems = cellfun(@(x) x{1}, model.subSystems, 'UniformOutput', false); end end diff --git a/tests/function_tests/tIO.m b/tests/function_tests/tIO.m index 556d3401..093445f1 100644 --- a/tests/function_tests/tIO.m +++ b/tests/function_tests/tIO.m @@ -287,6 +287,77 @@ function writeReadYAMLRoundTrip(testCase) testCase.verifyEqual(numel(m2.rxns), numel(testCase.model.rxns)); end + function readYAMLmodelKeepsSubsystemsAligned(testCase) + % Reactions without a subsystem sit between, before and after + % those with one: each subsystem stays with its own reaction, + % and the result is a column of chars that checkModelStruct + % accepts. + m = testCase.model; + n = numel(m.rxns); + m.subSystems = repmat({{''}}, n, 1); + withSubsystem = [2 5 n-3]; + names = {'Alpha'; 'Beta'; 'Gamma'}; + for i = 1:numel(withSubsystem) + m.subSystems{withSubsystem(i)} = names(i); + end + f = [tempname '.yml']; + testCase.addTeardown(@() delete(f)); + evalc('writeYAMLmodel(m, f);'); + evalc('m2 = readYAMLmodel(f);'); + testCase.verifyTrue(iscolumn(m2.subSystems)); + testCase.verifyEqual(numel(m2.subSystems), n); + testCase.verifyTrue(iscellstr(m2.subSystems)); + expected = repmat({''}, n, 1); + expected(withSubsystem) = names; + testCase.verifyEqual(string(m2.subSystems), string(expected)); + ws = warning('off', 'RAVEN:modelError'); + testCase.addTeardown(@() warning(ws)); + try + evalc('checkModelStruct(m2, ''throwErrors'', true);'); + catch err + testCase.verifyFail(['checkModelStruct refused the model: ' err.message]); + end + end + + function readYAMLmodelKeepsNestedSubsystemsAligned(testCase) + % Reactions with several subsystems sit next to reactions with + % one and with none: every entry is a cell of chars, each stays + % with its own reaction, and checkModelStruct accepts the model. + m = testCase.model; + n = numel(m.rxns); + m.subSystems = repmat({{''}}, n, 1); + nested = {3, {'Alpha'; 'Delta'}; ... + 6, {'Beta'}; ... + n-2, {'Gamma'; 'Epsilon'; 'Zeta'}}; + for i = 1:size(nested,1) + m.subSystems{nested{i,1}} = nested{i,2}; + end + f = [tempname '.yml']; + testCase.addTeardown(@() delete(f)); + evalc('writeYAMLmodel(m, f);'); + evalc('m2 = readYAMLmodel(f);'); + testCase.verifyTrue(iscolumn(m2.subSystems)); + testCase.verifyEqual(numel(m2.subSystems), n); + testCase.verifyTrue(all(cellfun(@iscell, m2.subSystems))); + for i = 1:n + row = find([nested{:,1}] == i); + if isempty(row) + testCase.verifyTrue(isempty([m2.subSystems{i}{:}]), ... + sprintf('reaction %d has no subsystem', i)); + else + testCase.verifyEqual(string(m2.subSystems{i}), ... + string(nested{row,2}), sprintf('reaction %d', i)); + end + end + ws = warning('off', 'RAVEN:modelError'); + testCase.addTeardown(@() warning(ws)); + try + evalc('checkModelStruct(m2, ''throwErrors'', true);'); + catch err + testCase.verifyFail(['checkModelStruct refused the model: ' err.message]); + end + end + function writeYAMLmodelQuotesYAML11Booleans(testCase) % A YAML 1.1 loader such as PyYAML reads a plain y, n, yes, no, % on or off in any case as a boolean, so these values are From 9a2829ac8afd60ef6f725d1fdd2cebbc1e4f0796 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 28 Oct 2026 21:28:01 +0000 Subject: [PATCH 35/48] doc: regenerate HTML documentation for c37ceff [skip ci] --- docs/html/INIT/ftINIT.html | 2 +- docs/html/INIT/prepINITModel.html | 645 ++++----- docs/html/analysis/reporterMetabolites.html | 432 +++--- docs/html/analysis/sampleACHR.html | 223 ++- .../biomass/scaleBiomassPseudoreaction.html | 93 +- docs/html/biomass/setGAM.html | 155 ++- docs/html/curation/curateModelFromTables.html | 1191 +++++++++++------ docs/html/gapfilling/gapFillMILP.html | 188 +-- docs/html/io/exportForGit.html | 397 +++--- docs/html/io/readYAMLmodel.html | 279 ++-- docs/html/manipulation/contractModel.html | 4 +- docs/html/manipulation/generateNewIds.html | 159 ++- docs/html/manipulation/index.html | 2 +- docs/html/manipulation/permuteModel.html | 2 +- docs/html/manipulation/sortModel.html | 14 +- docs/html/queries/constructEquations.html | 2 +- .../homology/getModelFromHomology.html | 716 +++++----- docs/html/tasks/parseTaskList.html | 713 +++++----- 18 files changed, 2929 insertions(+), 2288 deletions(-) diff --git a/docs/html/INIT/ftINIT.html b/docs/html/INIT/ftINIT.html index 2a58a646..579d4990 100644 --- a/docs/html/INIT/ftINIT.html +++ b/docs/html/INIT/ftINIT.html @@ -494,7 +494,7 @@

    SOURCE CODE ^%be performed also without manipulating the b vector in the model (which is what is done in the gap-filling). 0449 exchRxns = getExchangeRxns(prepData.refModel); 0450 deletedRxnsInINIT = setdiff(prepData.refModel.rxns,union(union(initModel.rxns, addedRxnsForTasks), exchRxns)); -0451 outModel = removeReactions(prepData.refModel, deletedRxnsInINIT, true); %we skip removing the genes for now, I'm not sure it is desirable +0451 outModel = removeReactions(prepData.refModel, deletedRxnsInINIT, true, true); 0452 0453 % If requested, attempt to remove negative-score genes from the model, 0454 % depending on their role (isozyme or complex subunit) in each grRule. diff --git a/docs/html/INIT/prepINITModel.html b/docs/html/INIT/prepINITModel.html index 6cfd0428..9bc3862c 100644 --- a/docs/html/INIT/prepINITModel.html +++ b/docs/html/INIT/prepINITModel.html @@ -40,6 +40,10 @@

    DESCRIPTION ^SOURCE CODE ^% taskStruct : struct 0013 % the essential tasks. Can be loaded with for example 0014 % taskStruct = parseTaskList('../data/metabolicTasks_Essential.txt'). -0015 % -0016 % Name-Value Arguments -0017 % -------------------- -0018 % spontRxnNames : cell -0019 % the spontaneous rxns (default {}). -0020 % convertGenes : logical -0021 % if true the genes are converted to gene names (from ENSEMBL). This -0022 % relies on translateGrRules, which is distributed with Human-GEM rather -0023 % than with RAVEN, so Human-GEM has to be on the MATLAB path (default -0024 % false). -0025 % customRxnsToIgnore : cell -0026 % these reactions can be ignored in the ignore mask (specifying b7=1) -0027 % (default {}). -0028 % extComp : char -0029 % name of the external compartment, typically 's' or 'e'. This is used for -0030 % identifying exch and import rxns (default 'e'). -0031 % skipScaling : logical -0032 % if true the scaling step is not run on the minimal model. The scaling is -0033 % there to remove large differences between the stoichiometric coefficients -0034 % within a reaction, since such differences creates numerical issues in the -0035 % ftINIT algorithm due to limitations in solver resolution. However, the -0036 % current scaling step is also risky and may lead to that some reactions -0037 % cannot carry flux, since it changes the stoichiometry of the reactions -0038 % with large differences. If you experience problems where the solution is -0039 % infeasible, it may be worth trying to turn off the scaling. Note that it -0040 % is only the minModel that is scaled, the scaling will not be present in -0041 % the final model (default false). -0042 % runParallel : logical -0043 % true to run the task-essentiality check in Step 3 (checkTasks) across -0044 % parallel workers, since each task is independent of the others (see -0045 % parallelWorkersRAVEN). Default false, matching the previous (serial) -0046 % behaviour. -0047 % -0048 % Returns -0049 % ------- -0050 % prepData : struct -0051 % the resulting prepData structure which is used as input to ftINIT. -0052 -0053 -0054 p=parseRAVENargs(varargin, {'spontRxnNames',{}; 'convertGenes',false; 'customRxnsToIgnore',{}; 'extComp','e'; 'skipScaling',false; 'runParallel',false}); -0055 spontRxnNames=p.spontRxnNames; -0056 convertGenes=p.convertGenes; -0057 customRxnsToIgnore=p.customRxnsToIgnore; -0058 extComp=p.extComp; -0059 skipScaling=p.skipScaling; -0060 runParallel=p.runParallel; -0061 disp('Step 1: Gene rules') -0062 [origRefModel.grRules, origRefModel.rxnGeneMat] = standardizeGrRules(origRefModel, true); -0063 -0064 if convertGenes %For mouse we might want to translate in the opposite direction - this has to be done before calling this function in that case. -0065 if ~exist('translateGrRules','file') -0066 EM=['convertGenes requires translateGrRules, which is distributed with ' ... -0067 'Human-GEM and not with RAVEN. Add Human-GEM to the MATLAB path, or ' ... -0068 'convert the gene identifiers before calling prepINITModel.']; -0069 error('RAVEN:badInput', '%s', EM); -0070 end -0071 [origRefModel.grRules, origRefModel.genes, origRefModel.rxnGeneMat] = translateGrRules(origRefModel.grRules, 'Name'); -0072 end -0073 -0074 -0075 % Get list of all dead-end/inacessible/constrained reactions in the model. -0076 % We don't merge linear dependent reactions here, that will happen later. -0077 % We also don't remove reversibility here, we don't want that in the final -0078 % models produced. Therefore, this is done as a separate step. -0079 % This takes quite some time to run -0080 disp('Step 2: First simplification') -0081 [~,deletedDeadEndRxns] = simplifyModel(origRefModel,true,false,true,true,true); -0082 -0083 % get reduced model -0084 cModel = removeReactions(origRefModel,deletedDeadEndRxns,false,true); -0085 -0086 -0087 %Then, run the tasks to find out which reactions are essential. These rxns -0088 %will be forced to carry flux in the optimization later +0015 % The task ids must be character vectors: a non-text id or a missing id +0016 % field raises RAVEN:badInput. Ids may repeat (for example as category +0017 % codes) and may be empty; each task is one row group of the task list, +0018 % and a task that cannot be performed is removed by its position. +0019 % +0020 % Name-Value Arguments +0021 % -------------------- +0022 % spontRxnNames : cell +0023 % the spontaneous rxns (default {}). +0024 % convertGenes : logical +0025 % if true the genes are converted to gene names (from ENSEMBL). This +0026 % relies on translateGrRules, which is distributed with Human-GEM rather +0027 % than with RAVEN, so Human-GEM has to be on the MATLAB path (default +0028 % false). +0029 % customRxnsToIgnore : cell +0030 % these reactions can be ignored in the ignore mask (specifying b7=1) +0031 % (default {}). +0032 % extComp : char +0033 % name of the external compartment, typically 's' or 'e'. This is used for +0034 % identifying exch and import rxns (default 'e'). +0035 % skipScaling : logical +0036 % if true the scaling step is not run on the minimal model. The scaling is +0037 % there to remove large differences between the stoichiometric coefficients +0038 % within a reaction, since such differences creates numerical issues in the +0039 % ftINIT algorithm due to limitations in solver resolution. However, the +0040 % current scaling step is also risky and may lead to that some reactions +0041 % cannot carry flux, since it changes the stoichiometry of the reactions +0042 % with large differences. If you experience problems where the solution is +0043 % infeasible, it may be worth trying to turn off the scaling. Note that it +0044 % is only the minModel that is scaled, the scaling will not be present in +0045 % the final model (default false). +0046 % runParallel : logical +0047 % true to run the task-essentiality check in Step 3 (checkTasks) across +0048 % parallel workers, since each task is independent of the others (see +0049 % parallelWorkersRAVEN). Default false, matching the previous (serial) +0050 % behaviour. +0051 % +0052 % Returns +0053 % ------- +0054 % prepData : struct +0055 % the resulting prepData structure which is used as input to ftINIT. +0056 +0057 +0058 p=parseRAVENargs(varargin, {'spontRxnNames',{}; 'convertGenes',false; 'customRxnsToIgnore',{}; 'extComp','e'; 'skipScaling',false; 'runParallel',false}); +0059 spontRxnNames=p.spontRxnNames; +0060 convertGenes=p.convertGenes; +0061 customRxnsToIgnore=p.customRxnsToIgnore; +0062 extComp=p.extComp; +0063 skipScaling=p.skipScaling; +0064 runParallel=p.runParallel; +0065 +0066 if ~isempty(taskStruct) +0067 if ~isfield(taskStruct,'id') +0068 error('RAVEN:badInput','The task structure must have an id field'); +0069 end +0070 taskIds = {taskStruct.id}; +0071 if ~all(cellfun(@(x) ischar(x) && (isrow(x) || isempty(x)),taskIds)) +0072 error('RAVEN:badInput','The task ids must be character vectors'); +0073 end +0074 end +0075 +0076 disp('Step 1: Gene rules') +0077 [origRefModel.grRules, origRefModel.rxnGeneMat] = standardizeGrRules(origRefModel, true); +0078 +0079 if convertGenes %For mouse we might want to translate in the opposite direction - this has to be done before calling this function in that case. +0080 if ~exist('translateGrRules','file') +0081 EM=['convertGenes requires translateGrRules, which is distributed with ' ... +0082 'Human-GEM and not with RAVEN. Add Human-GEM to the MATLAB path, or ' ... +0083 'convert the gene identifiers before calling prepINITModel.']; +0084 error('RAVEN:badInput', '%s', EM); +0085 end +0086 [origRefModel.grRules, origRefModel.genes, origRefModel.rxnGeneMat] = translateGrRules(origRefModel.grRules, 'Name'); +0087 end +0088 0089 -0090 % determine reactions essential for tasks - takes ~10 min -0091 % need to add boundary mets first - but only to a temporary model, we don't -0092 % want those for further processing -0093 disp('Step 3: Check tasks (~10 min)') -0094 if ~isempty(taskStruct) -0095 bModel = closeModel(cModel); -0096 [taskReport, essentialRxnMat, ~, essentialFluxes] = checkTasks(bModel,[],true,false,true,taskStruct,'runParallel',runParallel); +0090 % Get list of all dead-end/inacessible/constrained reactions in the model. +0091 % We don't merge linear dependent reactions here, that will happen later. +0092 % We also don't remove reversibility here, we don't want that in the final +0093 % models produced. Therefore, this is done as a separate step. +0094 % This takes quite some time to run +0095 disp('Step 2: First simplification') +0096 [~,deletedDeadEndRxns] = simplifyModel(origRefModel,true,false,true,true,true); 0097 -0098 %extract the essential rxns: -0099 sel = sum(essentialRxnMat,2) > 0; -0100 selInd = find(sel); -0101 essentialRxns = bModel.rxns(selInd);%382 rxns for human-GEM -0102 -0103 %Find metabolites present in taskStruct. We want to avoid removing -0104 %these metabolites from the final model (even though they may no longer -0105 %participate in any reacitons) so that the final model is still able to -0106 %complete all of the tasks without any errors. -0107 taskMets = union(vertcat(taskStruct.inputs),vertcat(taskStruct.outputs)); -0108 taskMets = union(taskMets, parseRxnEqu(vertcat(taskStruct.equations))); -0109 modelMets = strcat(cModel.metNames,'[',cModel.comps(cModel.metComps),']'); -0110 [inModel,metInd] = ismember(taskMets,modelMets); -0111 essentialMetsForTasks = cModel.mets(metInd(inModel));%118 mets +0098 % get reduced model +0099 cModel = removeReactions(origRefModel,deletedDeadEndRxns,false,true); +0100 +0101 +0102 %Then, run the tasks to find out which reactions are essential. These rxns +0103 %will be forced to carry flux in the optimization later +0104 +0105 % determine reactions essential for tasks - takes ~10 min +0106 % need to add boundary mets first - but only to a temporary model, we don't +0107 % want those for further processing +0108 disp('Step 3: Check tasks (~10 min)') +0109 if ~isempty(taskStruct) +0110 bModel = closeModel(cModel); +0111 [taskReport, essentialRxnMat, ~, essentialFluxes] = checkTasks(bModel,[],true,false,true,taskStruct,'runParallel',runParallel); 0112 -0113 %Remove tasks that cannot be performed -0114 taskStruct(taskReport.ok == false) = []; -0115 -0116 else -0117 essentialRxns = {}; -0118 essentialMetsForTasks = {}; -0119 taskReport = {}; -0120 end -0121 -0122 -0123 % remove metabolites separately to avoid removing those needed for tasks -0124 unusedMets = cModel.mets(all(cModel.S == 0,2));%1248 in Human-GEM -0125 cModel = removeMets(cModel, setdiff(unusedMets,essentialMetsForTasks)); -0126 -0127 if ~isempty(taskStruct) -0128 -0129 %Here, we decide on a direction in which the essential reactions should be forced. -0130 essentialRevDir = false(length(essentialRxns),1); -0131 pp = zeros(length(essentialRxns),1); -0132 nn = zeros(length(essentialRxns),1); -0133 for i = 1:length(selInd) -0134 pos = sum(essentialFluxes(selInd(i),essentialRxnMat(selInd(i),:)) > 0); -0135 neg = sum(essentialFluxes(selInd(i),essentialRxnMat(selInd(i),:)) < 0); -0136 essentialRevDir(i) = pos < neg; -0137 pp(i) = pos; -0138 nn(i) = neg; -0139 end -0140 -0141 %sum(pp>0 & nn>0) %0, so it is not a big problem that different tasks share essential -0142 %reactions but in different directions, at least no such cases exist now, so we ignore this for now. +0113 %extract the essential rxns: +0114 sel = sum(essentialRxnMat,2) > 0; +0115 selInd = find(sel); +0116 essentialRxns = bModel.rxns(selInd);%382 rxns for human-GEM +0117 +0118 %Find metabolites present in taskStruct. We want to avoid removing +0119 %these metabolites from the final model (even though they may no longer +0120 %participate in any reacitons) so that the final model is still able to +0121 %complete all of the tasks without any errors. +0122 taskMets = union(vertcat(taskStruct.inputs),vertcat(taskStruct.outputs)); +0123 taskMets = union(taskMets, parseRxnEqu(vertcat(taskStruct.equations))); +0124 modelMets = strcat(cModel.metNames,'[',cModel.comps(cModel.metComps),']'); +0125 [inModel,metInd] = ismember(taskMets,modelMets); +0126 essentialMetsForTasks = cModel.mets(metInd(inModel));%118 mets +0127 +0128 %Remove tasks that cannot be performed +0129 taskStruct(taskReport.ok == false) = []; +0130 +0131 else +0132 essentialRxns = {}; +0133 essentialMetsForTasks = {}; +0134 taskReport = {}; +0135 end +0136 +0137 +0138 % remove metabolites separately to avoid removing those needed for tasks +0139 unusedMets = cModel.mets(all(cModel.S == 0,2));%1248 in Human-GEM +0140 cModel = removeMets(cModel, setdiff(unusedMets,essentialMetsForTasks)); +0141 +0142 if ~isempty(taskStruct) 0143 -0144 %Now create a minimal model for the MILP - we want to make this as small as we can, but we -0145 %don't want to use this later when we create the final models - just for the first MILP. -0146 %We don't want boundary metabolites in here either. -0147 -0148 %We do a trick with the essential rxns here: -0149 %What we want is to get rid of the reversibility of the reactions, because reversible reactions require -0150 %an integer each in the MILP, and we can also get the case that it may be cheaper to force flux in the wrong direction, which -0151 %will probably not give the gap-filling of reactions that we want. -0152 %So, we simply change the reversible reactions to irreversible, and flip their direction if the direction -0153 %to force flux in is negative. We don't need to do anything else, runINIT will then handle all essential rxns -0154 %as irreversible (and will not add any integers etc. for them). +0144 %Here, we decide on a direction in which the essential reactions should be forced. +0145 essentialRevDir = false(length(essentialRxns),1); +0146 pp = zeros(length(essentialRxns),1); +0147 nn = zeros(length(essentialRxns),1); +0148 for i = 1:length(selInd) +0149 pos = sum(essentialFluxes(selInd(i),essentialRxnMat(selInd(i),:)) > 0); +0150 neg = sum(essentialFluxes(selInd(i),essentialRxnMat(selInd(i),:)) < 0); +0151 essentialRevDir(i) = pos < neg; +0152 pp(i) = pos; +0153 nn(i) = neg; +0154 end 0155 -0156 minModel1 = cModel; -0157 %for the essential rxns where we should force flux in the positive direction, just make them irreversible -0158 %for the negative direction, we do the same, but also flip the direction of the reaction -0159 -0160 %first flip the reactions with negative direction -0161 %constructEquations(minModel1,minModel1.rxns(selInd(essentialRevDir))) %looks good -0162 minModel1 = reverseRxns(minModel1, minModel1.rxns(selInd(essentialRevDir))); -0163 %constructEquations(minModel2,minModel2.rxns(selInd(essentialRevDir))) %looks good -0164 -0165 %Then make them irreversible -0166 minModel1.rev(selInd) = 0; -0167 minModel1.lb(selInd) = 0; -0168 else -0169 minModel1 = cModel; -0170 end -0171 %Now, remove reversibility on any reactions that can only be run in one direction - that simplifies the problem -0172 %This takes a long time to run (at least an hour or so). -0173 %This is an important step, we go from 5533 to 3734 reversible rxns, which reduces the size of the MILP, -0174 %since irreversible rxns are easier to handle. -0175 disp('Step 4: Second simplification (~1 hour)') -0176 minModel2 = simplifyModel(minModel1,false,false,false,false,false,false,true); -0177 -0178 disp('Step 5: Final work') +0156 %sum(pp>0 & nn>0) %0, so it is not a big problem that different tasks share essential +0157 %reactions but in different directions, at least no such cases exist now, so we ignore this for now. +0158 +0159 %Now create a minimal model for the MILP - we want to make this as small as we can, but we +0160 %don't want to use this later when we create the final models - just for the first MILP. +0161 %We don't want boundary metabolites in here either. +0162 +0163 %We do a trick with the essential rxns here: +0164 %What we want is to get rid of the reversibility of the reactions, because reversible reactions require +0165 %an integer each in the MILP, and we can also get the case that it may be cheaper to force flux in the wrong direction, which +0166 %will probably not give the gap-filling of reactions that we want. +0167 %So, we simply change the reversible reactions to irreversible, and flip their direction if the direction +0168 %to force flux in is negative. We don't need to do anything else, runINIT will then handle all essential rxns +0169 %as irreversible (and will not add any integers etc. for them). +0170 +0171 minModel1 = cModel; +0172 %for the essential rxns where we should force flux in the positive direction, just make them irreversible +0173 %for the negative direction, we do the same, but also flip the direction of the reaction +0174 +0175 %first flip the reactions with negative direction +0176 %constructEquations(minModel1,minModel1.rxns(selInd(essentialRevDir))) %looks good +0177 minModel1 = reverseRxns(minModel1, minModel1.rxns(selInd(essentialRevDir))); +0178 %constructEquations(minModel2,minModel2.rxns(selInd(essentialRevDir))) %looks good 0179 -0180 %Now, merge all linear dependent rxns - this reduces the size of the model a lot. -0181 %Size goes from 11888 rxns to 7922 rxns for human-GEM, so this is an important step -0182 [minModel3,origRxnIds,groupIds]=mergeLinear(minModel2, {}); -0183 -0184 %we now need to figure out what happened to the essential rxns in the merge step above. -0185 %Note that it would be ideal to run checkTasks on the result from mergeLinear, but that doesn't work. -0186 %The exchange rxns have in many cases been merged with other reactions, making it difficult to run -0187 %the tasks. So, instead we run checkTasks before, and figure out which merged reactions the -0188 %essential reactions belong to now -0189 -0190 if ~isempty(taskStruct) -0191 -0192 %find all potential rxns -0193 rxnIndOrig = ismember(origRxnIds, essentialRxns); -0194 ids = unique(groupIds(rxnIndOrig)); -0195 ids = ids(ids > 0);%0 means it has not been merged -0196 rxnCandidates = unique([essentialRxns;origRxnIds(ismember(groupIds, ids))]);%388, essentialRxns is 382 -0197 newEssentialRxns = minModel3.rxns(ismember(minModel3.rxns,rxnCandidates));%196 in Human-GEM -0198 %setdiff(newEssentialRxns, essentialRxns)%only 2, that is very few -0199 %setdiff(essentialRxns, newEssentialRxns)%188, that is very many -0200 %It is probably the case that many essential reactions are linearly dependent on one another, which makes sense. -0201 else -0202 newEssentialRxns = {}; -0203 end -0204 %What is left now is to remove some rxns from the problem, such as exchange rxns and spontaneous rxns. -0205 %We set the rxn scores of these to exactly 0, and make sure no other scores are zero. -0206 %runINIT will then handle this, we do >0 and <0 to get positive and negative scores, the rest -0207 %are not included in the problem. -0208 %But, this has to happen in the scoring - we identify the reactions in the origrxns here, and then fix this -0209 %in the scoring for each sample (the function groupRxnScores handles this and is run for each sample). -0210 -0211 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -0212 %identify the reactions to ignore in the minModel2 -0213 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -0214 -0215 %Identify reactions that should be ignored by tINIT, i.e. tINIT will not remove these regardless of score -0216 -0217 -0218 [~,exchRxnInd] = getExchangeRxns(minModel2); -0219 toIgnoreExch = false(numel(minModel2.rxns),1); -0220 toIgnoreExch(exchRxnInd) = true; -0221 toIgnoreImportRxns = false(numel(minModel2.rxns),1); -0222 toIgnoreSimpleTransp = false(numel(minModel2.rxns),1); -0223 toIgnoreAdvTransp = false(numel(minModel2.rxns),1); -0224 toIgnoreS = false(numel(minModel2.rxns),1); +0180 %Then make them irreversible +0181 minModel1.rev(selInd) = 0; +0182 minModel1.lb(selInd) = 0; +0183 else +0184 minModel1 = cModel; +0185 end +0186 %Now, remove reversibility on any reactions that can only be run in one direction - that simplifies the problem +0187 %This takes a long time to run (at least an hour or so). +0188 %This is an important step, we go from 5533 to 3734 reversible rxns, which reduces the size of the MILP, +0189 %since irreversible rxns are easier to handle. +0190 disp('Step 4: Second simplification (~1 hour)') +0191 minModel2 = simplifyModel(minModel1,false,false,false,false,false,false,true); +0192 +0193 disp('Step 5: Final work') +0194 +0195 %Now, merge all linear dependent rxns - this reduces the size of the model a lot. +0196 %Size goes from 11888 rxns to 7922 rxns for human-GEM, so this is an important step +0197 [minModel3,origRxnIds,groupIds]=mergeLinear(minModel2, {}); +0198 +0199 %we now need to figure out what happened to the essential rxns in the merge step above. +0200 %Note that it would be ideal to run checkTasks on the result from mergeLinear, but that doesn't work. +0201 %The exchange rxns have in many cases been merged with other reactions, making it difficult to run +0202 %the tasks. So, instead we run checkTasks before, and figure out which merged reactions the +0203 %essential reactions belong to now +0204 +0205 if ~isempty(taskStruct) +0206 +0207 %find all potential rxns +0208 rxnIndOrig = ismember(origRxnIds, essentialRxns); +0209 ids = unique(groupIds(rxnIndOrig)); +0210 ids = ids(ids > 0);%0 means it has not been merged +0211 rxnCandidates = unique([essentialRxns;origRxnIds(ismember(groupIds, ids))]);%388, essentialRxns is 382 +0212 newEssentialRxns = minModel3.rxns(ismember(minModel3.rxns,rxnCandidates));%196 in Human-GEM +0213 %setdiff(newEssentialRxns, essentialRxns)%only 2, that is very few +0214 %setdiff(essentialRxns, newEssentialRxns)%188, that is very many +0215 %It is probably the case that many essential reactions are linearly dependent on one another, which makes sense. +0216 else +0217 newEssentialRxns = {}; +0218 end +0219 %What is left now is to remove some rxns from the problem, such as exchange rxns and spontaneous rxns. +0220 %We set the rxn scores of these to exactly 0, and make sure no other scores are zero. +0221 %runINIT will then handle this, we do >0 and <0 to get positive and negative scores, the rest +0222 %are not included in the problem. +0223 %But, this has to happen in the scoring - we identify the reactions in the origrxns here, and then fix this +0224 %in the scoring for each sample (the function groupRxnScores handles this and is run for each sample). 0225 -0226 -0227 -0228 %sum(toIgnore) %1500 for Human-GEM -0229 %find simple transport reactions with no GPRs: -0230 %We really only skip the reactions with two metabolites -0231 %where they are the same but different compartments - only skip the transport into the cell for now -0232 numMets = sum(minModel2.S ~= 0, 1); -0233 scomp = find(strcmp(minModel2.comps,extComp)); -0234 for i = 1:length(minModel2.rxns) -0235 %check that it has no GPR and that the rxn has two metabolites -0236 if strcmp(minModel2.grRules(i),'') && (numMets(i) == 2) -0237 metsComps = minModel2.metComps(minModel2.S(:,i) ~= 0); -0238 metNames = minModel2.metNames(minModel2.S(:,i) ~= 0); -0239 if metsComps(1) ~= metsComps(2) && (metsComps(1) == scomp || metsComps(2) == scomp) %different compartments, one is the extComp -0240 if strcmp(metNames{1}, metNames{2}) %the same metabolite, it is a transport reaction -0241 toIgnoreImportRxns(i) = true; -0242 end -0243 elseif metsComps(1) ~= metsComps(2) %different compartments, no check for extComp -0244 if strcmp(metNames{1}, metNames{2}) %the same metabolite, it is a transport reaction -0245 toIgnoreSimpleTransp(i) = true; -0246 end -0247 end -0248 else %check for advanced -0249 metsInd = find(minModel2.S(:,i) ~= 0); -0250 %check that we have an even number of mets that is larger than 2 and that we don't have a GPR -0251 if rem(length(metsInd),2) == 0 && length(metsInd) > 2 && isempty(minModel2.grRules{i}) -0252 %Now check that we have pairs of metabolites that are transported -0253 SVals = full(minModel2.S(metsInd,i)); -0254 metNames = minModel2.metNames(metsInd); -0255 comps = minModel2.metComps(metsInd); -0256 success = true; -0257 while ~isempty(metNames) -0258 if length(metNames) < 2 %this should not happen -0259 error('We should never arrive here!'); -0260 end -0261 metMatch = find(strcmp(metNames(2:length(metNames)),metNames{1})) + 1; -0262 if length(metMatch) ~= 1 %we want a match, and only one (if there is some other complex transport rxn, we skip that) -0263 success = false; -0264 break; -0265 end -0266 if (SVals(1) + SVals(metMatch)) ~= 0 %the stoichiometry is not right -0267 success = false; -0268 break; -0269 end -0270 if comps(1) == comps(metMatch) %they must be in different compartments -0271 success = false; -0272 break; -0273 end -0274 %now remove the pair and continue with the next -0275 SVals([1;metMatch]) = []; -0276 metNames([1;metMatch]) = []; -0277 comps([1;metMatch]) = []; -0278 end -0279 toIgnoreAdvTransp(i) = success; -0280 end -0281 end -0282 end -0283 -0284 %Spontaneous reactions: -0285 toIgnoreSpont = ismember(minModel2.rxns, spontRxnNames);%only 10 rxns in human-GEM -0286 -0287 %rxns without GPRs in the s compartment (outside the cell) -0288 sCompInd = find(strcmp(minModel2.comps, extComp)); -0289 for i = 1:length(minModel2.rxns) -0290 metsInd = find(minModel2.S(:,i) ~= 0); -0291 %check that it has more than 1 metabolite (not exch rxn) and no GPR -0292 if length(metsInd) > 1 && isempty(minModel2.grRules{i}) -0293 if all(minModel2.metComps(metsInd) == sCompInd) -0294 toIgnoreS(i) = true; -0295 end +0226 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +0227 %identify the reactions to ignore in the minModel2 +0228 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +0229 +0230 %Identify reactions that should be ignored by tINIT, i.e. tINIT will not remove these regardless of score +0231 +0232 +0233 [~,exchRxnInd] = getExchangeRxns(minModel2); +0234 toIgnoreExch = false(numel(minModel2.rxns),1); +0235 toIgnoreExch(exchRxnInd) = true; +0236 toIgnoreImportRxns = false(numel(minModel2.rxns),1); +0237 toIgnoreSimpleTransp = false(numel(minModel2.rxns),1); +0238 toIgnoreAdvTransp = false(numel(minModel2.rxns),1); +0239 toIgnoreS = false(numel(minModel2.rxns),1); +0240 +0241 +0242 +0243 %sum(toIgnore) %1500 for Human-GEM +0244 %find simple transport reactions with no GPRs: +0245 %We really only skip the reactions with two metabolites +0246 %where they are the same but different compartments - only skip the transport into the cell for now +0247 numMets = sum(minModel2.S ~= 0, 1); +0248 scomp = find(strcmp(minModel2.comps,extComp)); +0249 for i = 1:length(minModel2.rxns) +0250 %check that it has no GPR and that the rxn has two metabolites +0251 if strcmp(minModel2.grRules(i),'') && (numMets(i) == 2) +0252 metsComps = minModel2.metComps(minModel2.S(:,i) ~= 0); +0253 metNames = minModel2.metNames(minModel2.S(:,i) ~= 0); +0254 if metsComps(1) ~= metsComps(2) && (metsComps(1) == scomp || metsComps(2) == scomp) %different compartments, one is the extComp +0255 if strcmp(metNames{1}, metNames{2}) %the same metabolite, it is a transport reaction +0256 toIgnoreImportRxns(i) = true; +0257 end +0258 elseif metsComps(1) ~= metsComps(2) %different compartments, no check for extComp +0259 if strcmp(metNames{1}, metNames{2}) %the same metabolite, it is a transport reaction +0260 toIgnoreSimpleTransp(i) = true; +0261 end +0262 end +0263 else %check for advanced +0264 metsInd = find(minModel2.S(:,i) ~= 0); +0265 %check that we have an even number of mets that is larger than 2 and that we don't have a GPR +0266 if rem(length(metsInd),2) == 0 && length(metsInd) > 2 && isempty(minModel2.grRules{i}) +0267 %Now check that we have pairs of metabolites that are transported +0268 SVals = full(minModel2.S(metsInd,i)); +0269 metNames = minModel2.metNames(metsInd); +0270 comps = minModel2.metComps(metsInd); +0271 success = true; +0272 while ~isempty(metNames) +0273 if length(metNames) < 2 %this should not happen +0274 error('We should never arrive here!'); +0275 end +0276 metMatch = find(strcmp(metNames(2:length(metNames)),metNames{1})) + 1; +0277 if length(metMatch) ~= 1 %we want a match, and only one (if there is some other complex transport rxn, we skip that) +0278 success = false; +0279 break; +0280 end +0281 if (SVals(1) + SVals(metMatch)) ~= 0 %the stoichiometry is not right +0282 success = false; +0283 break; +0284 end +0285 if comps(1) == comps(metMatch) %they must be in different compartments +0286 success = false; +0287 break; +0288 end +0289 %now remove the pair and continue with the next +0290 SVals([1;metMatch]) = []; +0291 metNames([1;metMatch]) = []; +0292 comps([1;metMatch]) = []; +0293 end +0294 toIgnoreAdvTransp(i) = success; +0295 end 0296 end 0297 end 0298 -0299 toIgnoreAllWithoutGPRs = cellfun(@isempty,minModel2.grRules); -0300 +0299 %Spontaneous reactions: +0300 toIgnoreSpont = ismember(minModel2.rxns, spontRxnNames);%only 10 rxns in human-GEM 0301 -0302 %sum(toIgnore)%in total 2375 rxns in human-GEM -0303 %sum(toIgnoreAllTransp)%in total 3337 rxns in human-GEM, so 1,000 more -0304 -0305 %Now, try to scale the model to become more favorable for the solver. -0306 %We try to make all fluxes as similar as possible. -0307 % There is room for improvement here, this function is pretty simple. -0308 % It only rescales reactions, while it would be possible to rescale -0309 % metabolites as well (ROS => kROS, albumin => millialbumin, etc., but -0310 % scaled freely with a mathematical method). It could potentially open up -0311 % for using less strict margins in the MILP, and make it run faster. -0312 if (skipScaling) -0313 scaledMinModel = minModel3; -0314 else -0315 scaledMinModel = rescaleModelForINIT(minModel3); -0316 scaledMinModel.ub(scaledMinModel.ub > 0) = 1000; -0317 scaledMinModel.lb(scaledMinModel.lb < 0) = -1000; -0318 end -0319 % create data structure with pre-processing results -0320 prepData.taskReport = taskReport; -0321 prepData.essentialRxns = newEssentialRxns; %essential rxns in the minModel -0322 prepData.taskStruct = taskStruct; -0323 prepData.refModel = cModel; -0324 prepData.minModel = scaledMinModel; -0325 prepData.refModelWithBM = closeModel(cModel); %do this here so we don't have to do this for each sample -0326 prepData.groupIds = groupIds; -0327 prepData.essentialMetsForTasks = essentialMetsForTasks; -0328 -0329 prepData.toIgnoreExch = toIgnoreExch; -0330 prepData.toIgnoreImportRxns = toIgnoreImportRxns; -0331 prepData.toIgnoreSimpleTransp = toIgnoreSimpleTransp; -0332 prepData.toIgnoreAdvTransp = toIgnoreAdvTransp; -0333 prepData.toIgnoreSpont = toIgnoreSpont; -0334 prepData.toIgnoreS = toIgnoreS; -0335 prepData.toIgnoreCustomRxns = ismember(minModel2.rxns, customRxnsToIgnore); -0336 prepData.toIgnoreAllWithoutGPRs = toIgnoreAllWithoutGPRs; -0337 -0338 end +0302 %rxns without GPRs in the s compartment (outside the cell) +0303 sCompInd = find(strcmp(minModel2.comps, extComp)); +0304 for i = 1:length(minModel2.rxns) +0305 metsInd = find(minModel2.S(:,i) ~= 0); +0306 %check that it has more than 1 metabolite (not exch rxn) and no GPR +0307 if length(metsInd) > 1 && isempty(minModel2.grRules{i}) +0308 if all(minModel2.metComps(metsInd) == sCompInd) +0309 toIgnoreS(i) = true; +0310 end +0311 end +0312 end +0313 +0314 toIgnoreAllWithoutGPRs = cellfun(@isempty,minModel2.grRules); +0315 +0316 +0317 %sum(toIgnore)%in total 2375 rxns in human-GEM +0318 %sum(toIgnoreAllTransp)%in total 3337 rxns in human-GEM, so 1,000 more +0319 +0320 %Now, try to scale the model to become more favorable for the solver. +0321 %We try to make all fluxes as similar as possible. +0322 % There is room for improvement here, this function is pretty simple. +0323 % It only rescales reactions, while it would be possible to rescale +0324 % metabolites as well (ROS => kROS, albumin => millialbumin, etc., but +0325 % scaled freely with a mathematical method). It could potentially open up +0326 % for using less strict margins in the MILP, and make it run faster. +0327 if (skipScaling) +0328 scaledMinModel = minModel3; +0329 else +0330 scaledMinModel = rescaleModelForINIT(minModel3); +0331 scaledMinModel.ub(scaledMinModel.ub > 0) = 1000; +0332 scaledMinModel.lb(scaledMinModel.lb < 0) = -1000; +0333 end +0334 % create data structure with pre-processing results +0335 prepData.taskReport = taskReport; +0336 prepData.essentialRxns = newEssentialRxns; %essential rxns in the minModel +0337 prepData.taskStruct = taskStruct; +0338 prepData.refModel = cModel; +0339 prepData.minModel = scaledMinModel; +0340 prepData.refModelWithBM = closeModel(cModel); %do this here so we don't have to do this for each sample +0341 prepData.groupIds = groupIds; +0342 prepData.essentialMetsForTasks = essentialMetsForTasks; +0343 +0344 prepData.toIgnoreExch = toIgnoreExch; +0345 prepData.toIgnoreImportRxns = toIgnoreImportRxns; +0346 prepData.toIgnoreSimpleTransp = toIgnoreSimpleTransp; +0347 prepData.toIgnoreAdvTransp = toIgnoreAdvTransp; +0348 prepData.toIgnoreSpont = toIgnoreSpont; +0349 prepData.toIgnoreS = toIgnoreS; +0350 prepData.toIgnoreCustomRxns = ismember(minModel2.rxns, customRxnsToIgnore); +0351 prepData.toIgnoreAllWithoutGPRs = toIgnoreAllWithoutGPRs; +0352 +0353 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/analysis/reporterMetabolites.html b/docs/html/analysis/reporterMetabolites.html index 4f11bc25..26febd25 100644 --- a/docs/html/analysis/reporterMetabolites.html +++ b/docs/html/analysis/reporterMetabolites.html @@ -39,7 +39,8 @@

    DESCRIPTION ^SOURCE CODE ^% genes : cell 0012 % a cell array of gene names (should match with model.genes). 0013 % genePValues : double -0014 % P-values for differential expression of the genes. -0015 % -0016 % Name-Value Arguments -0017 % -------------------- -0018 % printResults : logical -0019 % true if the top 20 Reporter Metabolites should be printed to the -0020 % screen (default false). -0021 % outputFile : char -0022 % the results are printed to this file (default none). -0023 % geneFoldChanges : double -0024 % log-fold changes for the genes. If supplied, then Reporter -0025 % Metabolites are calculated for only up/down-regulated genes in -0026 % addition to the full test (default none). -0027 % -0028 % Returns -0029 % ------- -0030 % repMets : struct -0031 % an array of structures with the following fields: -0032 % -0033 % - test : a string that describes the genes used to calculate the -0034 % Reporter Metabolites ("all", "only up", or "only down"). The two -0035 % latter structures are only calculated if geneFoldChanges are -0036 % supplied. -0037 % - mets : a cell array of metabolite IDs for the metabolites for which -0038 % a score could be calculated. -0039 % - metZScores : background-corrected Z-scores for differential -0040 % expression around each metabolite in "mets". The entries of all -0041 % fields are sorted by this score, highest first. -0042 % - metPValues : one-sided (upper-tail) P-values of "metZScores". -0043 % - metNGenes : number of neighbouring genes for each metabolite in -0044 % "mets". -0045 % - meanZ : average Z-scores for the genes around each metabolite in -0046 % "mets". -0047 % - stdZ : standard deviations of the Z-scores around each metabolite -0048 % in "mets". -0049 % -0050 % Examples -0051 % -------- -0052 % repMets = reporterMetabolites(model, genes, genePValues, ... -0053 % printResults, outputFile, geneFoldChanges); -0054 % -0055 % Notes -0056 % ----- -0057 % For details about the algorithm, see Patil KR, Nielsen J, Uncovering -0058 % transcriptional regulation of metabolism by using metabolic network -0059 % topology. Proc. Natl Acad. Sci. USA 2005;102:2685-2689. -0060 % -0061 % The background correction uses the closed-form mean and standard -0062 % deviation of the aggregate Z-score of n genes drawn at random from all -0063 % scored genes, so the results are deterministic. -0064 -0065 genes=convertCharArray(genes); -0066 p=parseRAVENargs(varargin, {'printResults',false; 'outputFile',[]; 'geneFoldChanges',[]}); -0067 printResults=p.printResults; -0068 outputFile=p.outputFile; -0069 geneFoldChanges=p.geneFoldChanges; -0070 -0071 %Check some stuff -0072 if numel(genes)~=numel(genePValues) -0073 EM='The number of genes and the number of Z-scores must be the same'; -0074 error('RAVEN:badInput', '%s', EM); -0075 end -0076 if ~isfield(model,'rxnGeneMat') -0077 EM='The model structure must have a rxnGeneMat field'; -0078 error('RAVEN:badInput', '%s', EM); -0079 end -0080 -0081 %Remove the genes which are not in the model -0082 genePValues(~ismember(genes,model.genes))=[]; -0083 if any(geneFoldChanges) -0084 geneFoldChanges(~ismember(genes,model.genes))=[]; -0085 end -0086 genes(~ismember(genes,model.genes))=[]; -0087 -0088 %Remove the genes that has NA P-value -0089 genes(isnan(genePValues))=[]; -0090 if any(geneFoldChanges) -0091 geneFoldChanges(isnan(genePValues))=[]; -0092 end -0093 genePValues(isnan(genePValues))=[]; -0094 -0095 %Convert p-values to Z-scores. Uses base MATLAB erfcinv instead of the -0096 %Statistics Toolbox norminv: -norminv(p) == sqrt(2)*erfcinv(2*p). -0097 geneZScores=sqrt(2)*erfcinv(2*genePValues); -0098 -0099 %This is to prevent errors if the p-values are exactly 0 or 1 -0100 geneZScores(geneZScores==-inf)=-15; -0101 geneZScores(geneZScores==inf)=15; -0102 -0103 %For each metabolite, calculate the aggregate Z-score and keep track of the -0104 %number of neighbouring genes -0105 metZScores=nan(numel(model.mets),1); -0106 metNGenes=nan(numel(model.mets),1); -0107 meanZ=nan(numel(model.mets),1); -0108 stdZ=nan(numel(model.mets),1); -0109 %model.genes -> genes(filtered list) index, computed once rather than -0110 %re-scanning the whole "genes" list (via ismember) for every metabolite, -0111 %which is quadratic in a genome-scale model's mets x genes. -0112 [~,geneIdxInList]=ismember(model.genes,genes); -0113 for i=1:numel(model.mets) -0114 %Get the involved rxns -0115 I=model.S(i,:); -0116 -0117 %Get the involved genes -0118 [~, J]=find(model.rxnGeneMat(I~=0,:)); -0119 -0120 %Find the genes in the gene list -0121 K=unique(geneIdxInList(J)); -0122 K=K(K>0); -0123 -0124 %Calculate the aggregated Z-score for the metabolite -0125 if any(K) -0126 metZScores(i)=sum(geneZScores(K))/sqrt(numel(K)); -0127 meanZ(i)=mean(geneZScores(K)); -0128 stdZ(i)=std(geneZScores(K)); -0129 metNGenes(i)=numel(K); -0130 end -0131 end -0132 -0133 %Remove the metabolites which have no Z-scores -0134 mets=model.mets(~isnan(metZScores)); -0135 metNames=model.metNames(~isnan(metZScores)); -0136 metZScores(isnan(metZScores))=[]; -0137 metNGenes(isnan(metNGenes))=[]; -0138 meanZ(isnan(meanZ))=[]; -0139 stdZ(isnan(stdZ))=[]; -0140 -0141 %Correct for background using the closed-form moments of the sampling -0142 %distribution. For n genes drawn with replacement from the scored-gene pool -0143 %(mean mu, population std sigma), the aggregate Z = sum(z)/sqrt(n) has: -0144 % E[Z] = sqrt(n)*mu and Std[Z] = sigma (independent of n) -0145 %so the corrected score is (metZ - sqrt(n)*mu) / sigma. -0146 mu = mean(geneZScores); -0147 sigma = std(geneZScores,1); -0148 metZScores = (metZScores - sqrt(metNGenes) .* mu) ./ sigma; -0149 -0150 %Calculate the P-values (upper-tail of the standard normal). Uses the base -0151 %MATLAB erfc so this does not depend on the Statistics Toolbox normcdf: -0152 %1 - normcdf(z) == 0.5*erfc(z/sqrt(2)). -0153 metPValues=0.5*erfc(metZScores/sqrt(2)); -0154 -0155 %Sort the results -0156 [metZScores, I]=sort(metZScores,'descend'); -0157 mets=mets(I); -0158 metNames=metNames(I); -0159 metPValues=metPValues(I); -0160 metNGenes=metNGenes(I); -0161 meanZ=meanZ(I); -0162 stdZ=stdZ(I); -0163 -0164 %Prepare the output -0165 repMets.mets=mets; -0166 repMets.metNames=metNames; -0167 repMets.metZScores=metZScores; -0168 repMets.metPValues=metPValues; -0169 repMets.metNGenes=metNGenes; -0170 repMets.meanZ=meanZ; -0171 repMets.stdZ=stdZ; -0172 -0173 %Call this function recursively if geneFoldChanges has been specified -0174 repMets(1).test='all'; -0175 if any(geneFoldChanges) -0176 repMets=[repMets;reporterMetabolites(model,genes(geneFoldChanges>=0),genePValues(geneFoldChanges>=0))]; -0177 repMets=[repMets;reporterMetabolites(model,genes(geneFoldChanges<0),genePValues(geneFoldChanges<0))]; -0178 repMets(2).test='only up'; -0179 repMets(3).test='only down'; -0180 end -0181 -0182 %This is for printing results to the screen. For printing a full list of -0183 %all scores, specify a output file -0184 %Escapes metabolite text before it is spliced into an fprintf template, -0185 %since a literal "%" in e.g. a metabolite name would otherwise be misread -0186 %as a format directive and swallow the remainder of the line. -0187 esc=@(s) strrep(s,'%','%%'); -0188 -0189 if printResults==true -0190 for i=1:numel(repMets) -0191 fprintf(['TOP 20 REPORTER METABOLITES\nTEST TYPE: ' repMets(i).test '\nID\tNAME\tP-VALUE\n']); -0192 for j=1:min(20,numel(repMets(i).mets)) -0193 fprintf([esc(repMets(i).mets{j}) '\t' esc(repMets(i).metNames{j}) '\t' num2str(repMets(i).metPValues(j)) '\n']); -0194 end -0195 fprintf('\n'); -0196 end -0197 end -0198 -0199 %Print results to file -0200 if any(outputFile) -0201 fid=fopen(outputFile,'w'); -0202 for i=1:numel(repMets) -0203 fprintf(fid,['REPORTER METABOLITES USING TEST TYPE: ' repMets(i).test '\n']); -0204 fprintf(fid,'ID\tNAME\tZ-SCORE\tP-VALUE\tNUMBER OF NEIGHBOURS\tAVERAGE Z-SCORE\tSTD Z-SCORE\n'); -0205 for j=1:numel(repMets(i).mets) -0206 fprintf(fid,[esc(repMets(i).mets{j}) '\t' esc(repMets(i).metNames{j}) '\t' num2str(repMets(i).metZScores(j)) '\t' num2str(repMets(i).metPValues(j)) '\t' num2str(repMets(i).metNGenes(j)) '\t' num2str(repMets(i).meanZ(j)) '\t' num2str(repMets(i).stdZ(j)) '\n']); -0207 end -0208 end -0209 fclose(fid); -0210 end -0211 end +0014 % P-values for differential expression of the genes. Values must lie in +0015 % [0, 1]; NaN values are ignored, any other value raises an error. +0016 % +0017 % Name-Value Arguments +0018 % -------------------- +0019 % printResults : logical +0020 % true if the top 20 Reporter Metabolites should be printed to the +0021 % screen (default false). +0022 % outputFile : char +0023 % the results are printed to this file (default none). +0024 % geneFoldChanges : double +0025 % log-fold changes for the genes. If supplied, then Reporter +0026 % Metabolites are calculated for only up/down-regulated genes in +0027 % addition to the full test (default none). +0028 % +0029 % Returns +0030 % ------- +0031 % repMets : struct +0032 % an array of structures with the following fields: +0033 % +0034 % - test : a string that describes the genes used to calculate the +0035 % Reporter Metabolites ("all", "only up", or "only down"). The two +0036 % latter structures are only calculated if geneFoldChanges are +0037 % supplied. +0038 % - mets : a cell array of metabolite IDs for the metabolites for which +0039 % a score could be calculated. +0040 % - metZScores : background-corrected Z-scores for differential +0041 % expression around each metabolite in "mets". The entries of all +0042 % fields are sorted by this score, highest first. +0043 % - metPValues : one-sided (upper-tail) P-values of "metZScores". +0044 % - metNGenes : number of neighbouring genes for each metabolite in +0045 % "mets". +0046 % - meanZ : average Z-scores for the genes around each metabolite in +0047 % "mets". +0048 % - stdZ : standard deviations of the Z-scores around each metabolite +0049 % in "mets". +0050 % +0051 % Examples +0052 % -------- +0053 % repMets = reporterMetabolites(model, genes, genePValues, ... +0054 % printResults, outputFile, geneFoldChanges); +0055 % +0056 % Notes +0057 % ----- +0058 % For details about the algorithm, see Patil KR, Nielsen J, Uncovering +0059 % transcriptional regulation of metabolism by using metabolic network +0060 % topology. Proc. Natl Acad. Sci. USA 2005;102:2685-2689. +0061 % +0062 % The background correction uses the closed-form mean and standard +0063 % deviation of the aggregate Z-score of n genes drawn at random from all +0064 % scored genes, so the results are deterministic. +0065 +0066 genes=convertCharArray(genes); +0067 p=parseRAVENargs(varargin, {'printResults',false; 'outputFile',[]; 'geneFoldChanges',[]}); +0068 printResults=p.printResults; +0069 outputFile=p.outputFile; +0070 geneFoldChanges=p.geneFoldChanges; +0071 +0072 %Check some stuff +0073 if numel(genes)~=numel(genePValues) +0074 EM='The number of genes and the number of Z-scores must be the same'; +0075 error('RAVEN:badInput', '%s', EM); +0076 end +0077 if ~isfield(model,'rxnGeneMat') +0078 EM='The model structure must have a rxnGeneMat field'; +0079 error('RAVEN:badInput', '%s', EM); +0080 end +0081 +0082 %All P-values that are given must lie in [0,1]; NaN means that no P-value +0083 %is available and is removed below +0084 badP=~isnan(genePValues) & (genePValues<0 | genePValues>1); +0085 if any(badP) +0086 firstBad=find(badP,1); +0087 EM=sprintf('The P-values must lie between 0 and 1, but %d of them do not. The first is %g (gene %s)',nnz(badP),genePValues(firstBad),genes{firstBad}); +0088 error('RAVEN:badInput', '%s', EM); +0089 end +0090 +0091 %Remove the genes which are not in the model +0092 genePValues(~ismember(genes,model.genes))=[]; +0093 if any(geneFoldChanges) +0094 geneFoldChanges(~ismember(genes,model.genes))=[]; +0095 end +0096 genes(~ismember(genes,model.genes))=[]; +0097 +0098 %Remove the genes that has NA P-value +0099 genes(isnan(genePValues))=[]; +0100 if any(geneFoldChanges) +0101 geneFoldChanges(isnan(genePValues))=[]; +0102 end +0103 genePValues(isnan(genePValues))=[]; +0104 +0105 %Convert p-values to Z-scores. Uses base MATLAB erfcinv instead of the +0106 %Statistics Toolbox norminv: -norminv(p) == sqrt(2)*erfcinv(2*p). +0107 geneZScores=sqrt(2)*erfcinv(2*genePValues); +0108 +0109 %This is to prevent errors if the p-values are exactly 0 or 1 +0110 geneZScores(geneZScores==-inf)=-15; +0111 geneZScores(geneZScores==inf)=15; +0112 +0113 %For each metabolite, calculate the aggregate Z-score and keep track of the +0114 %number of neighbouring genes +0115 metZScores=nan(numel(model.mets),1); +0116 %Sums are taken on Z-scores relative to their mean (found in two passes, the +0117 %second relative to the first gene's Z-score), which keeps the rounding error +0118 %proportional to the spread of the Z-scores instead of to their size +0119 metZShifted=nan(numel(model.mets),1); +0120 zCentre=0; +0121 if ~isempty(geneZScores) +0122 zCentre=geneZScores(1)+mean(geneZScores-geneZScores(1)); +0123 end +0124 metNGenes=nan(numel(model.mets),1); +0125 meanZ=nan(numel(model.mets),1); +0126 stdZ=nan(numel(model.mets),1); +0127 %model.genes -> genes(filtered list) index, computed once rather than +0128 %re-scanning the whole "genes" list (via ismember) for every metabolite, +0129 %which is quadratic in a genome-scale model's mets x genes. +0130 [~,geneIdxInList]=ismember(model.genes,genes); +0131 for i=1:numel(model.mets) +0132 %Get the involved rxns +0133 I=model.S(i,:); +0134 +0135 %Get the involved genes +0136 [~, J]=find(model.rxnGeneMat(I~=0,:)); +0137 +0138 %Find the genes in the gene list +0139 K=unique(geneIdxInList(J)); +0140 K=K(K>0); +0141 +0142 %Calculate the aggregated Z-score for the metabolite +0143 if any(K) +0144 metZScores(i)=sum(geneZScores(K))/sqrt(numel(K)); +0145 metZShifted(i)=sum(geneZScores(K)-zCentre)/sqrt(numel(K)); +0146 meanZ(i)=mean(geneZScores(K)); +0147 stdZ(i)=std(geneZScores(K)); +0148 metNGenes(i)=numel(K); +0149 end +0150 end +0151 +0152 %Remove the metabolites which have no Z-scores, from all fields alike +0153 noScore=isnan(metZScores); +0154 mets=model.mets(~noScore); +0155 metNames=model.metNames(~noScore); +0156 metZShifted(noScore)=[]; +0157 metZScores(noScore)=[]; +0158 metNGenes(noScore)=[]; +0159 meanZ(noScore)=[]; +0160 stdZ(noScore)=[]; +0161 +0162 %Correct for background using the closed-form moments of the sampling +0163 %distribution. For n genes drawn with replacement from the scored-gene pool +0164 %(mean mu, population std sigma), the aggregate Z = sum(z)/sqrt(n) has: +0165 % E[Z] = sqrt(n)*mu and Std[Z] = sigma (independent of n) +0166 %so the corrected score is (metZ - sqrt(n)*mu) / sigma. When sigma is 0 (all +0167 %scored genes have the same Z-score) no metabolite stands out from the +0168 %background, and the corrected score is 0 (P-value 0.5). The moments are +0169 %taken on Z-scores shifted by their mean, so that mu and the numerator keep +0170 %their precision when the Z-scores are close to each other or the first one is +0171 %an outlier. Identical Z-scores then give exactly 0 for the numerator, and the +0172 %test of the spread of the Z-scores (0 up to 100*eps(max(abs(Z)))) only +0173 %prevents a division of 0 by 0. +0174 zShifted = geneZScores - zCentre; +0175 muShifted = mean(zShifted); +0176 sigma = std(zShifted,1); +0177 if max(geneZScores)-min(geneZScores) > 100*eps(max(abs(geneZScores))) +0178 metZScores = (metZShifted - sqrt(metNGenes) .* muShifted) ./ sigma; +0179 else +0180 metZScores = zeros(size(metZScores)); +0181 end +0182 +0183 %Calculate the P-values (upper-tail of the standard normal). Uses the base +0184 %MATLAB erfc so this does not depend on the Statistics Toolbox normcdf: +0185 %1 - normcdf(z) == 0.5*erfc(z/sqrt(2)). +0186 metPValues=0.5*erfc(metZScores/sqrt(2)); +0187 +0188 %Sort the results +0189 [metZScores, I]=sort(metZScores,'descend'); +0190 mets=mets(I); +0191 metNames=metNames(I); +0192 metPValues=metPValues(I); +0193 metNGenes=metNGenes(I); +0194 meanZ=meanZ(I); +0195 stdZ=stdZ(I); +0196 +0197 %Prepare the output +0198 repMets.mets=mets; +0199 repMets.metNames=metNames; +0200 repMets.metZScores=metZScores; +0201 repMets.metPValues=metPValues; +0202 repMets.metNGenes=metNGenes; +0203 repMets.meanZ=meanZ; +0204 repMets.stdZ=stdZ; +0205 +0206 %Call this function recursively if geneFoldChanges has been specified +0207 repMets(1).test='all'; +0208 if any(geneFoldChanges) +0209 repMets=[repMets;reporterMetabolites(model,genes(geneFoldChanges>=0),genePValues(geneFoldChanges>=0))]; +0210 repMets=[repMets;reporterMetabolites(model,genes(geneFoldChanges<0),genePValues(geneFoldChanges<0))]; +0211 repMets(2).test='only up'; +0212 repMets(3).test='only down'; +0213 end +0214 +0215 %This is for printing results to the screen. For printing a full list of +0216 %all scores, specify a output file +0217 %Escapes metabolite text before it is spliced into an fprintf template, +0218 %since a literal "%" in e.g. a metabolite name would otherwise be misread +0219 %as a format directive and swallow the remainder of the line. +0220 esc=@(s) strrep(s,'%','%%'); +0221 +0222 if printResults==true +0223 for i=1:numel(repMets) +0224 fprintf(['TOP 20 REPORTER METABOLITES\nTEST TYPE: ' repMets(i).test '\nID\tNAME\tP-VALUE\n']); +0225 for j=1:min(20,numel(repMets(i).mets)) +0226 fprintf([esc(repMets(i).mets{j}) '\t' esc(repMets(i).metNames{j}) '\t' num2str(repMets(i).metPValues(j)) '\n']); +0227 end +0228 fprintf('\n'); +0229 end +0230 end +0231 +0232 %Print results to file +0233 if any(outputFile) +0234 fid=fopen(outputFile,'w'); +0235 for i=1:numel(repMets) +0236 fprintf(fid,['REPORTER METABOLITES USING TEST TYPE: ' repMets(i).test '\n']); +0237 fprintf(fid,'ID\tNAME\tZ-SCORE\tP-VALUE\tNUMBER OF NEIGHBOURS\tAVERAGE Z-SCORE\tSTD Z-SCORE\n'); +0238 for j=1:numel(repMets(i).mets) +0239 fprintf(fid,[esc(repMets(i).mets{j}) '\t' esc(repMets(i).metNames{j}) '\t' num2str(repMets(i).metZScores(j)) '\t' num2str(repMets(i).metPValues(j)) '\t' num2str(repMets(i).metNGenes(j)) '\t' num2str(repMets(i).meanZ(j)) '\t' num2str(repMets(i).stdZ(j)) '\n']); +0240 end +0241 end +0242 fclose(fid); +0243 end +0244 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/analysis/sampleACHR.html b/docs/html/analysis/sampleACHR.html index dfc28729..6f280c35 100644 --- a/docs/html/analysis/sampleACHR.html +++ b/docs/html/analysis/sampleACHR.html @@ -33,6 +33,15 @@

    DESCRIPTION ^CROSS-REFERENCE INFORMATION ^
 <li><a href=randomSampling randomSampling Sample the flux solution space (entry point for all samplers). - +

    SUBFUNCTIONS ^

    +

    SOURCE CODE ^

    0001 function solutions = sampleACHR(model, nSamples, thinning, warmup, seed)
    @@ -76,80 +87,146 @@ 

    SOURCE CODE ^% {v : S*v = 0, lb <= v <= ub}, using the ACHR algorithm (Kaufman & Smith 1998). 0006 % No rounding is performed, so on strongly elongated polytopes (e.g. 0007 % enzyme-constrained models) mixing is slower than sampleCHRR; prefer CHRR there. -0008 % -0009 % Parameters -0010 % ---------- -0011 % model : struct -0012 % a RAVEN model structure. -0013 % nSamples : double -0014 % number of flux vectors to return (default 1000). -0015 % thinning : double -0016 % Markov-chain steps between recorded samples (default 100). -0017 % warmup : double -0018 % nPoints-by-nRxns warmup vertices (default: generated by sampleWarmupPoints). -0019 % seed : double -0020 % random seed for reproducibility (default: current RNG state). -0021 % -0022 % Returns -0023 % ------- -0024 % solutions : double -0025 % nRxns-by-nSamples matrix of sampled flux distributions. -0026 % -0027 % See also -0028 % -------- -0029 % randomSampling, sampleCHRR, sampleWarmupPoints -0030 -0031 if nargin < 2 || isempty(nSamples); nSamples = 1000; end -0032 if nargin < 3 || isempty(thinning); thinning = 100; end -0033 if nargin < 4 || isempty(warmup); warmup = sampleWarmupPoints(model); end -0034 if nargin < 5; seed = []; end -0035 if ~isempty(seed) -0036 rng(seed); -0037 end -0038 -0039 nRxns = numel(model.rxns); -0040 lb = model.lb(:); -0041 ub = model.ub(:); -0042 nW = size(warmup, 1); -0043 -0044 center = mean(warmup, 1)'; % nRxns-by-1 -0045 prev = center; -0046 nTotal = 0; -0047 dirTol = 1e-12; -0048 -0049 solutions = zeros(nRxns, nSamples); -0050 rec = 0; -0051 nSteps = thinning*nSamples; +0008 % Reactions whose bounds or warmup values coincide never move. The remaining +0009 % reactions are corrected towards S*v = 0 every few steps and before each +0010 % recorded sample, with a sparse Cholesky factor of the row-normalised S*S' +0011 % (no dense null-space basis), so the rounding error in S*v does not grow with +0012 % the number of steps and samples stay inside the bounds. The guarantee +0013 % S*v = 0 holds for a well-conditioned S and warmup points that are feasible +0014 % for the model. When the largest residual of a recorded sample exceeds 1e-9 +0015 % times max(1, max(abs(v))) (nearly dependent rows of S, a noisy or infeasible +0016 % warmup), sampleACHR issues the warning sampleACHR:steadyStateNotRestored. +0017 % +0018 % Parameters +0019 % ---------- +0020 % model : struct +0021 % a RAVEN model structure. +0022 % nSamples : double +0023 % number of flux vectors to return (default 1000). +0024 % thinning : double +0025 % Markov-chain steps between recorded samples (default 100). +0026 % warmup : double +0027 % nPoints-by-nRxns warmup vertices (default: generated by sampleWarmupPoints). +0028 % seed : double +0029 % random seed for reproducibility (default: current RNG state). +0030 % +0031 % Returns +0032 % ------- +0033 % solutions : double +0034 % nRxns-by-nSamples matrix of sampled flux distributions. +0035 % +0036 % See also +0037 % -------- +0038 % randomSampling, sampleCHRR, sampleWarmupPoints +0039 +0040 if nargin < 2 || isempty(nSamples); nSamples = 1000; end +0041 if nargin < 3 || isempty(thinning); thinning = 100; end +0042 if nargin < 4 || isempty(warmup); warmup = sampleWarmupPoints(model); end +0043 if nargin < 5; seed = []; end +0044 if ~isempty(seed) +0045 rng(seed); +0046 end +0047 +0048 nRxns = numel(model.rxns); +0049 lb = model.lb(:); +0050 ub = model.ub(:); +0051 nW = size(warmup, 1); 0052 -0053 for stepno = 1:nSteps -0054 pidx = randi(nW); -0055 delta = warmup(pidx, :)' - center; -0056 -0057 posD = delta > dirTol; -0058 negD = delta < -dirTol; -0059 ubp = ub - prev; -0060 lbp = lb - prev; -0061 % alpha range keeping lb <= prev + alpha*delta <= ub -0062 upperLims = [ubp(posD)./delta(posD); lbp(negD)./delta(negD)]; -0063 lowerLims = [lbp(posD)./delta(posD); ubp(negD)./delta(negD)]; -0064 if isempty(upperLims); tHi = 0; else; tHi = min(upperLims); end -0065 if isempty(lowerLims); tLo = 0; else; tLo = max(lowerLims); end -0066 -0067 if tHi > tLo -0068 alpha = tLo + rand*(tHi - tLo); -0069 prev = prev + alpha*delta; -0070 end -0071 -0072 % Running centre (quasi-Markovian ACHR: centre converges and steadies). -0073 center = (nTotal*center + prev) / (nTotal + 1); -0074 nTotal = nTotal + 1; +0053 % Reactions that cannot move: fixed bounds, or identical value in all warmup +0054 % points. Directions and corrections act on the other reactions only. +0055 spread = (max(warmup, [], 1) - min(warmup, [], 1))'; +0056 free = (ub - lb > 1e-9) & (spread > 1e-9); +0057 proj = buildSteadyStateCorrection(model.S, free, lb, ub); +0058 corrEvery = min(thinning, 20); +0059 +0060 center = mean(warmup, 1)'; % nRxns-by-1 +0061 prev = restoreSteadyState(center, proj); +0062 center = prev; +0063 nTotal = 0; +0064 +0065 solutions = zeros(nRxns, nSamples); +0066 rec = 0; +0067 worstResidual = 0; +0068 nSteps = thinning*nSamples; +0069 +0070 for stepno = 1:nSteps +0071 pidx = randi(nW); +0072 delta = warmup(pidx, :)' - center; +0073 delta(~free) = 0; +0074 dirTol = 1e-12*max(1, max(abs(delta))); 0075 -0076 if mod(stepno, thinning) == 0 -0077 rec = rec + 1; -0078 solutions(:, rec) = prev; -0079 end -0080 end -0081 end

    +0076 posD = delta > dirTol; +0077 negD = delta < -dirTol; +0078 ubp = ub - prev; +0079 lbp = lb - prev; +0080 % alpha range keeping lb <= prev + alpha*delta <= ub +0081 upperLims = [ubp(posD)./delta(posD); lbp(negD)./delta(negD)]; +0082 lowerLims = [lbp(posD)./delta(posD); ubp(negD)./delta(negD)]; +0083 if isempty(upperLims); tHi = 0; else; tHi = min(upperLims); end +0084 if isempty(lowerLims); tLo = 0; else; tLo = max(lowerLims); end +0085 +0086 if tHi > tLo +0087 alpha = tLo + rand*(tHi - tLo); +0088 prev = min(max(prev + alpha*delta, lb), ub); +0089 end +0090 +0091 % Running centre (quasi-Markovian ACHR: centre converges and steadies). +0092 center = (nTotal*center + prev) / (nTotal + 1); +0093 nTotal = nTotal + 1; +0094 +0095 if mod(stepno, corrEvery) == 0 || mod(stepno, thinning) == 0 +0096 prev = restoreSteadyState(prev, proj); +0097 center = restoreSteadyState(center, proj); +0098 end +0099 +0100 if mod(stepno, thinning) == 0 +0101 rec = rec + 1; +0102 solutions(:, rec) = prev; +0103 worstResidual = max(worstResidual, ... +0104 max(abs(model.S*prev))/max(1, max(abs(prev)))); +0105 end +0106 end +0107 if worstResidual > 1e-9 +0108 warning('sampleACHR:steadyStateNotRestored', ... +0109 ['The largest residual of S*v over the samples is %.3g times max(1, max(abs(v))), ' ... +0110 'above 1e-9. The rows of S are nearly dependent or the warmup points are not ' ... +0111 'feasible for the model.'], worstResidual); +0112 end +0113 end +0114 +0115 function proj = buildSteadyStateCorrection(S, free, lb, ub) +0116 % Sparse factor of the row-normalised S(:,free)*S(:,free)' with a small ridge, +0117 % used to move a flux vector onto S*v = 0 through the free reactions only. +0118 Sf = S(:, free); +0119 rowNorm = sqrt(full(sum(Sf.^2, 2))); +0120 keep = rowNorm > 0; +0121 rowNorm = rowNorm(keep); +0122 Sn = spdiags(1./rowNorm, 0, numel(rowNorm), numel(rowNorm))*Sf(keep, :); +0123 A = Sn*Sn' + 1e-10*speye(size(Sn, 1)); +0124 [R, flag, p] = chol(A, 'vector'); +0125 if flag ~= 0 +0126 error('sampleACHR:factorization', 'The stoichiometric matrix could not be factorized.'); +0127 end +0128 proj = struct('S', S, 'free', free, 'keep', keep, 'rowNorm', rowNorm, ... +0129 'Sn', Sn, 'R', R, 'Rt', R', 'p', p, 'lb', lb, 'ub', ub); +0130 end +0131 +0132 function v = restoreSteadyState(v, proj) +0133 % Removes the residual of S*v through the free reactions (a few ridge-regularised +0134 % projection passes), then clips v to its bounds. +0135 tol = 1e-12*max(1, max(abs(v))); +0136 for it = 1:8 +0137 r = proj.S*v; +0138 if max(abs(r)) <= tol +0139 break +0140 end +0141 rn = r(proj.keep) ./ proj.rowNorm; +0142 x = zeros(size(rn)); +0143 x(proj.p) = proj.R \ (proj.Rt \ rn(proj.p)); +0144 v(proj.free) = v(proj.free) - proj.Sn'*x; +0145 end +0146 v = min(max(v, proj.lb), proj.ub); +0147 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/biomass/scaleBiomassPseudoreaction.html b/docs/html/biomass/scaleBiomassPseudoreaction.html index ef9c3787..2bef595c 100644 --- a/docs/html/biomass/scaleBiomassPseudoreaction.html +++ b/docs/html/biomass/scaleBiomassPseudoreaction.html @@ -128,52 +128,57 @@

    SOURCE CODE ^'No reaction named %s in model.', comp.pseudoreaction_name); 0046 end 0047 -0048 for i = 1:length(model.mets) -0049 S_ir = model.S(i, rxnPos); -0050 isProd = strcmp(model.metNames{i}, componentName); -0051 if S_ir ~= 0 && ~isProd -0052 model.S(i, rxnPos) = factor * S_ir; -0053 end -0054 end -0055 -0056 % Rebalance H+ to keep charge neutrality. -0057 Hc = find(strcmp(model.mets, biomassConfig.proton_met)); -0058 model.S(Hc, rxnPos) = 0; -0059 model.S(Hc, rxnPos) = -chargeResidual(model, rxnPos); +0048 Hc = find(strcmp(model.mets, biomassConfig.proton_met)); +0049 if isempty(Hc) +0050 error('scaleBiomassPseudoreaction:missingProton', ... +0051 'Proton metabolite %s not found in model.', biomassConfig.proton_met); +0052 end +0053 +0054 for i = 1:length(model.mets) +0055 S_ir = model.S(i, rxnPos); +0056 isProd = strcmp(model.metNames{i}, componentName); +0057 if S_ir ~= 0 && ~isProd +0058 model.S(i, rxnPos) = factor * S_ir; +0059 end 0060 end 0061 -0062 function residual = chargeResidual(model, rxnPos) -0063 % Sum of charges over the metabolites that take part in a reaction. Only the -0064 % participants may be summed: S is sparse and 0*NaN is NaN, so any unset -0065 % charge in the model would otherwise poison the result. Summing with -0066 % 'omitnan' instead would treat an unset charge as neutral and silently -0067 % rebalance the reaction to the wrong coefficient. -0068 metIdx = find(model.S(:, rxnPos)); -0069 if ~isfield(model, 'metCharges') -0070 error('scaleBiomassPseudoreaction:noCharges', ... -0071 ['Cannot rebalance charge: the model has no metCharges field. ' ... -0072 'Provide metabolite charges, or rescale without charge balancing.']); -0073 end -0074 unknown = metIdx(isnan(model.metCharges(metIdx))); -0075 if ~isempty(unknown) -0076 error('scaleBiomassPseudoreaction:unknownCharge', ... -0077 ['Cannot rebalance charge of %s: the following metabolites have no ' ... -0078 'charge, so the charge balance is unknown rather than zero:\n\t%s'], ... -0079 model.rxns{rxnPos}, strjoin(model.mets(unknown), ', ')); -0080 end -0081 residual = sum(full(model.S(metIdx, rxnPos)) .* model.metCharges(metIdx)); -0082 end -0083 -0084 function comp = findComponent(cfg, name) -0085 for i = 1:numel(cfg.components) -0086 if strcmp(cfg.components{i}.name, name) -0087 comp = cfg.components{i}; -0088 return; -0089 end -0090 end -0091 error('scaleBiomassPseudoreaction:unknownComponent', ... -0092 'biomassConfig has no component named %s', name); -0093 end +0062 % Rebalance H+ to keep charge neutrality. +0063 model.S(Hc, rxnPos) = 0; +0064 model.S(Hc, rxnPos) = -chargeResidual(model, rxnPos); +0065 end +0066 +0067 function residual = chargeResidual(model, rxnPos) +0068 % Sum of charges over the metabolites that take part in a reaction. Only the +0069 % participants may be summed: S is sparse and 0*NaN is NaN, so any unset +0070 % charge in the model would otherwise poison the result. Summing with +0071 % 'omitnan' instead would treat an unset charge as neutral and silently +0072 % rebalance the reaction to the wrong coefficient. +0073 metIdx = find(model.S(:, rxnPos)); +0074 if ~isfield(model, 'metCharges') +0075 error('scaleBiomassPseudoreaction:noCharges', ... +0076 ['Cannot rebalance charge: the model has no metCharges field. ' ... +0077 'Provide metabolite charges, or rescale without charge balancing.']); +0078 end +0079 unknown = metIdx(isnan(model.metCharges(metIdx))); +0080 if ~isempty(unknown) +0081 error('scaleBiomassPseudoreaction:unknownCharge', ... +0082 ['Cannot rebalance charge of %s: the following metabolites have no ' ... +0083 'charge, so the charge balance is unknown rather than zero:\n\t%s'], ... +0084 model.rxns{rxnPos}, strjoin(model.mets(unknown), ', ')); +0085 end +0086 residual = sum(full(model.S(metIdx, rxnPos)) .* model.metCharges(metIdx)); +0087 end +0088 +0089 function comp = findComponent(cfg, name) +0090 for i = 1:numel(cfg.components) +0091 if strcmp(cfg.components{i}.name, name) +0092 comp = cfg.components{i}; +0093 return; +0094 end +0095 end +0096 error('scaleBiomassPseudoreaction:unknownComponent', ... +0097 'biomassConfig has no component named %s', name); +0098 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/biomass/setGAM.html b/docs/html/biomass/setGAM.html index eb60e2a9..3f03a37d 100644 --- a/docs/html/biomass/setGAM.html +++ b/docs/html/biomass/setGAM.html @@ -36,9 +36,11 @@

    DESCRIPTION ^SOURCE CODE ^% 0009 % For every metabolite in the biomass pseudoreaction whose model.metNames 0010 % entry is in cofactorMetNames, the stoichiometric coefficient is set to -0011 % ±value preserving the sign of the current coefficient. Yeast-GEM scales -0012 % ATP, ADP, H2O, H+ and phosphate (with ATP and H2O on the substrate side, -0013 % ADP / H+ / phosphate on the product side). -0014 % -0015 % Parameters -0016 % ---------- -0017 % model : struct -0018 % RAVEN model struct. -0019 % value : double -0020 % New GAM value (mmol ATP / gDW per growth unit). -0021 % biomassRxn : char -0022 % Reaction id of the biomass pseudoreaction. -0023 % cofactorMetNames : cell -0024 % Cell array of metabolite NAMES (not IDs) to rescale, e.g. -0025 % {'ATP','ADP','H2O','H+','phosphate'}. -0026 % -0027 % Name-Value Arguments -0028 % -------------------- -0029 % ngamRxn : char -0030 % NGAM reaction id. Required when ngamValue is supplied. -0031 % ngamValue : double -0032 % NGAM flux to fix. Sets the NGAM reaction's bounds to (ngamValue, -0033 % ngamValue). -0034 % -0035 % Returns -0036 % ------- -0037 % model : struct -0038 % Modified model. -0039 % -0040 % Examples -0041 % -------- -0042 % model = setGAM(model, 80, 'r_4041', {'ATP','ADP','H2O','H+','phosphate'}); -0043 % -0044 % See also -0045 % -------- -0046 % fitParameters : estimates such a value from measured fluxes instead of -0047 % applying a known one. The two are complementary - fit first, then set. -0048 -0049 if nargin < 4 -0050 error('setGAM:missingArgs', ... -0051 'biomassRxn and cofactorMetNames are required.'); -0052 end -0053 -0054 p=parseRAVENargs(varargin, {'ngamRxn',[]; 'ngamValue',[]}); -0055 ngamRxn=p.ngamRxn; -0056 ngamValue=p.ngamValue; -0057 -0058 bioPos = strcmp(model.rxns, biomassRxn); -0059 if ~any(bioPos) -0060 error('setGAM:missingBiomassRxn', ... -0061 'Reaction %s not found in model.', biomassRxn); -0062 end -0063 -0064 for i = 1:length(model.mets) -0065 S_ix = model.S(i, bioPos); -0066 isCofactor = any(strcmp(cofactorMetNames, model.metNames{i})); -0067 if S_ix ~= 0 && isCofactor -0068 model.S(i, bioPos) = sign(S_ix) * value; -0069 end -0070 end -0071 -0072 if ~isempty(ngamRxn) && ~isempty(ngamValue) -0073 model = setParam(model, 'eq', ngamRxn, ngamValue); -0074 end -0075 end +0011 % ±value preserving the sign of the current coefficient. An error is raised +0012 % before the model is changed if ngamRxn is given and is not in the model, +0013 % and if ngamValue is given without ngamRxn (RAVEN:badInput). +0014 % Yeast-GEM scales ATP, ADP, H2O, H+ and phosphate (with ATP and H2O on the +0015 % substrate side, ADP / H+ / phosphate on the product side). +0016 % +0017 % Parameters +0018 % ---------- +0019 % model : struct +0020 % RAVEN model struct. +0021 % value : double +0022 % New GAM value (mmol ATP / gDW per growth unit). +0023 % biomassRxn : char +0024 % Reaction id of the biomass pseudoreaction. +0025 % cofactorMetNames : cell +0026 % Cell array of metabolite NAMES (not IDs) to rescale, e.g. +0027 % {'ATP','ADP','H2O','H+','phosphate'}. +0028 % +0029 % Name-Value Arguments +0030 % -------------------- +0031 % ngamRxn : char +0032 % NGAM reaction id. Required when ngamValue is supplied. +0033 % ngamValue : double +0034 % NGAM flux to fix. Sets the NGAM reaction's bounds to (ngamValue, +0035 % ngamValue). +0036 % +0037 % Returns +0038 % ------- +0039 % model : struct +0040 % Modified model. +0041 % +0042 % Examples +0043 % -------- +0044 % model = setGAM(model, 80, 'r_4041', {'ATP','ADP','H2O','H+','phosphate'}); +0045 % +0046 % See also +0047 % -------- +0048 % fitParameters : estimates such a value from measured fluxes instead of +0049 % applying a known one. The two are complementary - fit first, then set. +0050 +0051 if nargin < 4 +0052 error('setGAM:missingArgs', ... +0053 'biomassRxn and cofactorMetNames are required.'); +0054 end +0055 +0056 p=parseRAVENargs(varargin, {'ngamRxn',[]; 'ngamValue',[]}); +0057 ngamRxn=p.ngamRxn; +0058 ngamValue=p.ngamValue; +0059 %An empty id counts as not given +0060 if ~isempty(ngamRxn) +0061 ngamRxn=cellstr(ngamRxn); +0062 ngamRxn=ngamRxn(~cellfun(@isempty,ngamRxn)); +0063 end +0064 if ~isempty(ngamValue) && isempty(ngamRxn) +0065 error('RAVEN:badInput', ... +0066 'ngamValue is given without ngamRxn. Give the NGAM reaction id as ''ngamRxn''.'); +0067 end +0068 +0069 bioPos = strcmp(model.rxns, biomassRxn); +0070 if ~any(bioPos) +0071 error('setGAM:missingBiomassRxn', ... +0072 'Reaction %s not found in model.', biomassRxn); +0073 end +0074 +0075 setNgam = ~isempty(ngamRxn) && ~isempty(ngamValue); +0076 if ~isempty(ngamRxn) && ~all(ismember(ngamRxn, model.rxns)) +0077 error('setGAM:missingNgamRxn', ... +0078 'NGAM reaction %s not found in model.', strjoin(ngamRxn, ', ')); +0079 end +0080 +0081 for i = 1:length(model.mets) +0082 S_ix = model.S(i, bioPos); +0083 isCofactor = any(strcmp(cofactorMetNames, model.metNames{i})); +0084 if S_ix ~= 0 && isCofactor +0085 model.S(i, bioPos) = sign(S_ix) * value; +0086 end +0087 end +0088 +0089 if setNgam +0090 model = setParam(model, 'eq', ngamRxn, ngamValue); +0091 end +0092 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/curation/curateModelFromTables.html b/docs/html/curation/curateModelFromTables.html index abf33502..763c8b41 100644 --- a/docs/html/curation/curateModelFromTables.html +++ b/docs/html/curation/curateModelFromTables.html @@ -35,17 +35,22 @@

    DESCRIPTION ^DESCRIPTION ^DESCRIPTION ^CROSS-REFERENCE INFORMATION ^

    @@ -107,7 +161,7 @@

    CROSS-REFERENCE INFORMATION ^
 
 <h2><a name=SUBFUNCTIONS ^

    +
  • function [header,data] = readCurationTable(fileName)
  • function [col,miriamCols] = matchColumns(header,fileName,required,spec)
  • function model = writeExistingField(model,field,modelIdx,values,skip)
  • function newModel = extractAndAddMiriam(model,miriamName,miriamData,inputIndex,modelIndex,type,skipEmpty)
  • function empty = emptyCells(values)
  • function values = parseIndexCells(cells,field,fileLabel)
  • function values = parseNumericCells(cells,field,fileLabel)
  • function v = parseChargeCell(text,fileLabel)
  • function checkCoefficientsGiven(idx,coeffs,rowNo)
  • function keys = indexKeys(idx,names)
  • function checkGenesListedOnce(genes)
  • function rules = standardGrRules(rules,rxnNames,rowNo)
  • function checkRevValues(values,cells)
  • function v = parseNumberCell(text,field,fileLabel)
  • SOURCE CODE ^

    0001 function newModel=curateModelFromTables(model,metsInfo,varargin)
    @@ -119,421 +173,722 @@ 

    SOURCE CODE ^% If the *.tsv files contain metabolites, reactions and/or genes that are 0008 % already present in the model, then information in the model will be 0009 % overwritten. Note that this includes empty annotations in the *.tsv -0010 % files! Metabolites are matched by metaboliteName[comp]; reactions by the -0011 % stoichiometry of its reactants and products; genes by their gene name. -0012 % This function can therefore be used to add new entities in the model, or -0013 % curate those already existing in the model. -0014 % -0015 % Columns are identified by their header (case-insensitive), so their order -0016 % does not matter and optional columns can be left out. Every field that a -0017 % table supplies is written to new and existing entities alike; a field the -0018 % model does not have yet is created, with its default value for the other -0019 % entities, unless all supplied values for it are empty. An empty grRules -0020 % cell leaves the rule of an existing reaction unchanged. -0021 % -0022 % Parameters -0023 % ---------- -0024 % model : struct -0025 % RAVEN model structure to be curated. -0026 % metsInfo : char -0027 % Path to a *.tsv file with metabolite information, or 'none' to skip -0028 % metabolite curation. Columns: metNames, comps (both required), -0029 % formula, charge, inchi, metNotes, and any number of MIRIAM-namespace -0030 % columns. Singular forms and the RAVEN field names (metFormulas, -0031 % metCharges, inchis) are accepted as headers too. -0032 % -0033 % Name-Value Arguments -0034 % -------------------- -0035 % genesInfo : char -0036 % Path to a *.tsv file with gene information, or 'none'. Columns: -0037 % genes (required), geneShortNames (or geneShortName), and MIRIAM. -0038 % rxnsCoeffs : char -0039 % Path to a *.tsv file with reaction stoichiometric coefficients, or -0040 % 'none'. Columns (all required): rxnIdx (or index), rxnNames, -0041 % metNames, comps, coefficient. One row per (reaction, metabolite) -0042 % pair. -0043 % rxnsInfo : char -0044 % Path to a *.tsv file with reaction information, or 'none'. Columns: -0045 % rxnIdx (or index), rxnNames (both required), grRules, lb, ub, rev, -0046 % subSystems, eccodes, rxnNotes, rxnReferences, rxnConfidenceScores, -0047 % and MIRIAM. -0048 % metPrefix : char -0049 % Prefix used to mint fresh metabolite ids (e.g. 's_' for yeast-GEM, -0050 % 'M_' for the BiGG default) (default 'M_'). -0051 % rxnPrefix : char -0052 % Prefix used to mint fresh reaction ids (default 'R_'). -0053 % -0054 % Returns -0055 % ------- -0056 % newModel : struct -0057 % Curated RAVEN model structure. -0058 % -0059 % Examples -0060 % -------- -0061 % newModel = curateModelFromTables(model, metsInfo, genesInfo, ... -0062 % rxnsCoeffs, rxnsInfo, metPrefix, rxnPrefix); -0063 % -0064 % Notes -0065 % ----- -0066 % In all three info tables, any column whose header is not one of the -0067 % listed core fields is treated as a MIRIAM annotation namespace and -0068 % stored on the matching entity. -0069 -0070 p=parseRAVENargs(varargin, {'genesInfo','none'; 'rxnsCoeffs','none'; 'rxnsInfo','none'; 'metPrefix',[]; 'rxnPrefix',[]}); -0071 genesInfo=p.genesInfo; -0072 rxnsCoeffs=p.rxnsCoeffs; -0073 rxnsInfo=p.rxnsInfo; -0074 metPrefix=p.metPrefix; -0075 if isempty(metPrefix) -0076 metPrefix = 'M_'; -0077 end -0078 rxnPrefix=p.rxnPrefix; -0079 if isempty(rxnPrefix) -0080 rxnPrefix = 'R_'; -0081 end -0082 if xor(strcmp(rxnsCoeffs,'none'), strcmp(rxnsInfo,'none')) -0083 error('Provide both a ''rxnsInfo'' and a ''rxnsCoeffs'' file') -0084 end -0085 newModel=model; -0086 -0087 %% Metabolites -0088 if ~strcmp(metsInfo,'none') -0089 [header,data] = readCurationTable(metsInfo); -0090 [col,miriamCols] = matchColumns(header,metsInfo,{'metNames','comps'},{ ... -0091 'metNames', {'metNames','metName'}; ... -0092 'comps', {'comps','comp'}; ... -0093 'metFormulas', {'formula','formulas','metFormula','metFormulas'}; ... -0094 'metCharges', {'charge','charges','metCharge','metCharges'}; ... -0095 'inchis', {'inchi','inchis'}; ... -0096 'metNotes', {'metNotes','metNote'}}); -0097 metsToAdd.metNames = data(:,col.metNames); -0098 metsToAdd.compartments = data(:,col.comps); -0099 textFields = {'metFormulas','inchis','metNotes'}; -0100 for f = textFields(isfield(col,textFields)) -0101 metsToAdd.(f{1}) = data(:,col.(f{1})); -0102 end -0103 if isfield(col,'metCharges') -0104 metsToAdd.metCharges = cellfun(@str2num,data(:,col.metCharges),'UniformOutput',false); -0105 emptyEntry = cellfun(@isempty,metsToAdd.metCharges); -0106 if all(emptyEntry) -0107 metsToAdd = rmfield(metsToAdd,'metCharges'); -0108 else % If some charges are given, assume 0 for those without charges specified -0109 metsToAdd.metCharges(emptyEntry) = {0}; -0110 metsToAdd.metCharges = cell2mat(metsToAdd.metCharges); -0111 end -0112 end -0113 optFields = intersect({'metFormulas','metCharges','inchis','metNotes'},fieldnames(metsToAdd)','stable'); -0114 -0115 % Check if metabolite already exists (check by metName[comp]) -0116 existingMets = []; -0117 existingMetsIdx = []; -0118 newMets = []; -0119 for i=1:numel(metsToAdd.metNames) -0120 metIdx = find(strcmp(newModel.metNames,metsToAdd.metNames{i})); -0121 existMet = strcmp(metsToAdd.compartments{i},newModel.comps(newModel.metComps(metIdx))); -0122 if any(existMet) -0123 existingMets = [existingMets, i]; -0124 existingMetsIdx = [existingMetsIdx, metIdx(existMet)]; -0125 else -0126 newMets = [newMets, i]; -0127 end -0128 end -0129 -0130 % Overwrite annotation of existing entries -0131 if any(existingMets) -0132 for f = optFields -0133 newModel = writeExistingField(newModel,f{1},existingMetsIdx,metsToAdd.(f{1})(existingMets)); -0134 end -0135 newModel = extractAndAddMiriam(newModel,header(miriamCols),data(:,miriamCols),existingMets,existingMetsIdx,'met'); -0136 warning(['The following metabolites are already present in the model, '... -0137 'their annotation will be overwritten to match the metsInfo file. '... -0138 'If you do not particularly want to curate their annotations, it '... -0139 'would be better to remove these metabolites from metsInfo:\n\t\t%s'],... -0140 strjoin(strcat(metsToAdd.metNames(existingMets),'[',metsToAdd.compartments(existingMets),']'),'\n\t\t')); -0141 end -0142 -0143 % Continue with new metabolites -0144 if numel(newMets)>0 -0145 metsToAdd.metNames = metsToAdd.metNames(newMets); -0146 metsToAdd.compartments = metsToAdd.compartments(newMets); -0147 for f = optFields -0148 metsToAdd.(f{1}) = metsToAdd.(f{1})(newMets); -0149 if iscell(metsToAdd.(f{1})) && all(cellfun(@isempty,metsToAdd.(f{1}))) -0150 metsToAdd = rmfield(metsToAdd,f{1}); -0151 end -0152 end -0153 % Add metabolites -0154 newModel = addMets(newModel,metsToAdd,true,metPrefix); -0155 addedIdx = numel(newModel.mets)-numel(newMets)+1:numel(newModel.mets); -0156 newModel = extractAndAddMiriam(newModel,header(miriamCols),data(:,miriamCols),newMets,addedIdx,'met'); -0157 end -0158 end -0159 %% Genes -0160 if ~strcmp(genesInfo,'none') -0161 [header,data] = readCurationTable(genesInfo); -0162 [col,miriamCols] = matchColumns(header,genesInfo,{'genes'},{ ... -0163 'genes', {'genes','gene'}; ... -0164 'geneShortNames', {'geneShortNames','geneShortName'}}); -0165 genesToAdd.genes = data(:,col.genes); -0166 if isfield(col,'geneShortNames') -0167 genesToAdd.geneShortNames = data(:,col.geneShortNames); -0168 end -0169 -0170 [~,notNewGene,existingGene] = intersect(genesToAdd.genes,newModel.genes); -0171 if ~isempty(notNewGene) -0172 warning(['The following genes are already present in the model, their annotation '... -0173 'will be overwritten to match the genesInfo file: \n\t\t%s'],... -0174 strjoin(genesToAdd.genes(notNewGene),'\n\t\t')); -0175 if isfield(genesToAdd,'geneShortNames') -0176 newModel = writeExistingField(newModel,'geneShortNames',existingGene,genesToAdd.geneShortNames(notNewGene)); +0010 % files, unless 'overwriteWithEmpty' is false! Metabolites are matched by +0011 % metaboliteName[comp]; reactions by the stoichiometry of its reactants and +0012 % products; genes by their gene name. +0013 % This function can therefore be used to add new entities in the model, or +0014 % curate those already existing in the model. +0015 % +0016 % Columns are identified by their header (case-insensitive, and spaces around +0017 % a header are ignored, also inside its quotes), so their order +0018 % does not matter and optional columns can be left out. Every field that a +0019 % table supplies is written to new and existing entities alike; a field the +0020 % model does not have yet is created, with its default value for the other +0021 % entities, unless all supplied values for it are empty. An empty grRules +0022 % cell leaves the rule of an existing reaction unchanged, and so does an +0023 % empty lb, ub or rev cell: an empty bound means that no bound is given. +0024 % A new reaction without a lb gets a lower bound of -Inf, and one without an +0025 % ub gets model.annotation.defaultUB (Inf if the model has none). +0026 % +0027 % Parameters +0028 % ---------- +0029 % model : struct +0030 % RAVEN model structure to be curated. +0031 % metsInfo : char +0032 % Path to a *.tsv file with metabolite information, or 'none' to skip +0033 % metabolite curation. Columns: metNames, comps (both required), +0034 % formula, charge, inchi, metNotes, and any number of MIRIAM-namespace +0035 % columns. Singular forms and the RAVEN field names (metFormulas, +0036 % metCharges, inchis) are accepted as headers too. +0037 % +0038 % Name-Value Arguments +0039 % -------------------- +0040 % genesInfo : char +0041 % Path to a *.tsv file with gene information, or 'none'. Columns: +0042 % genes (required), geneShortNames (or geneShortName), and MIRIAM. +0043 % rxnsCoeffs : char +0044 % Path to a *.tsv file with reaction stoichiometric coefficients, or +0045 % 'none'. Columns (all required): rxnIdx (or index), rxnNames, +0046 % metNames, comps, coefficient. One row per (reaction, metabolite) +0047 % pair. +0048 % rxnsInfo : char +0049 % Path to a *.tsv file with reaction information, or 'none'. Columns: +0050 % rxnIdx (or index), rxnNames (both required), grRules, lb, ub, rev, +0051 % subSystems, eccodes, rxnNotes, rxnReferences, rxnConfidenceScores, +0052 % and MIRIAM. Each rxnIdx may be used by one row only (the rxnsCoeffs +0053 % file has one row per metabolite of the reaction and repeats it). +0054 % metPrefix : char +0055 % Prefix used to mint fresh metabolite ids (e.g. 's_' for yeast-GEM, +0056 % 'M_' for the BiGG default) (default 'M_'). +0057 % rxnPrefix : char +0058 % Prefix used to mint fresh reaction ids (default 'R_'). +0059 % overwriteWithEmpty : logical +0060 % if true, an empty cell in a row of an existing metabolite, gene or +0061 % reaction empties the corresponding field of the model. If false, an +0062 % empty cell leaves the existing value unchanged and only the cells with a +0063 % value are written (default true; [] also means the default). Must be a +0064 % scalar logical or 0/1. A cell with only blanks counts as empty in both +0065 % modes. Empty grRules cells never overwrite. An empty or NaN lb, ub or +0066 % rev cell always keeps the existing value. For the charge and the +0067 % confidence score, an empty or NaN cell is treated alike: when +0068 % overwriteWithEmpty is true and at least one other row of the column has +0069 % a value, it becomes 0; a column in which no row has a value is not +0070 % written, and the existing values stay. The same holds for a MIRIAM +0071 % column: a column in which no row has a value does not clear the +0072 % annotations of existing entities. +0073 % +0074 % Returns +0075 % ------- +0076 % newModel : struct +0077 % Curated RAVEN model structure. +0078 % +0079 % Examples +0080 % -------- +0081 % newModel = curateModelFromTables(model, metsInfo, genesInfo, ... +0082 % rxnsCoeffs, rxnsInfo, metPrefix, rxnPrefix); +0083 % +0084 % Notes +0085 % ----- +0086 % In all three info tables, any column whose header is not one of the +0087 % listed core fields is treated as a MIRIAM annotation namespace and +0088 % stored on the matching entity. +0089 % +0090 % Cell values: the charge, confidence score, lb, ub, rev, rxnIdx and +0091 % coefficient cells are read as plain decimal numbers (digits with an +0092 % optional sign, decimal point and exponent), Inf or NaN in any case. Only +0093 % the lb, ub and rev cells also accept true and false in any case (read as +0094 % 1 and 0). Any other text, including text with a comma, hexadecimal numbers +0095 % and expressions, raises RAVEN:badInput; no expression is evaluated. A +0096 % charge must be a finite whole number (or empty or NaN). A rev must be 0, 1, +0097 % true or false (or empty or NaN). The rev column sets the reversibility of +0098 % existing reactions only, independently of lb and ub, which are written as +0099 % given; for new reactions the reversibility is derived from lb and ub. +0100 % +0101 % Gene rules: a grRules cell must be a well-formed gene rule (genes joined by +0102 % and/or in any case, or by &, &&, | and ||, with balanced parentheses); a +0103 % rule with an operator that lacks an operand, empty parentheses or two genes +0104 % without an operator between them raises RAVEN:badInput before the model is +0105 % changed. A valid rule is stored in RAVEN's standard format. Whether the +0106 % genes exist is checked when the rule is applied. +0107 % +0108 % Genes: a gene id that the genesInfo table lists more than once raises +0109 % RAVEN:badInput, whether the model has the gene or not. +0110 % +0111 % Bounds: a lower bound above the upper bound is written as given. solveLP +0112 % rejects such a model when it is run ("Invalid set of bounds for +0113 % reaction(s): ..."). +0114 % +0115 % Reaction tables: a row of the rxnsCoeffs table without a rxnIdx, rxnNames, +0116 % metNames and comps value is ignored; a row with any of them needs a +0117 % rxnIdx and a coefficient that is a finite number other than 0. A row of +0118 % the rxnsInfo table without any value is ignored; a row with a value needs +0119 % a rxnIdx. A rxnIdx is a whole number of at least 1; the indices (and +0120 % names) of the two reaction tables must match exactly, compared by their +0121 % numeric value (1, 1.0 and 1e0 are the same index). Otherwise RAVEN:badInput +0122 % is raised. +0123 +0124 p=parseRAVENargs(varargin, {'genesInfo','none'; 'rxnsCoeffs','none'; 'rxnsInfo','none'; 'metPrefix',[]; 'rxnPrefix',[]; 'overwriteWithEmpty',true}); +0125 overwriteWithEmpty=p.overwriteWithEmpty; +0126 if isempty(overwriteWithEmpty) +0127 overwriteWithEmpty=true; +0128 end +0129 if ~((islogical(overwriteWithEmpty) || isnumeric(overwriteWithEmpty)) && isscalar(overwriteWithEmpty) ... +0130 && isreal(overwriteWithEmpty) && ~isnan(overwriteWithEmpty) && ismember(overwriteWithEmpty,[0 1])) +0131 error('RAVEN:badInput','overwriteWithEmpty must be a scalar logical or 0/1'); +0132 end +0133 overwriteWithEmpty=logical(overwriteWithEmpty); +0134 genesInfo=p.genesInfo; +0135 rxnsCoeffs=p.rxnsCoeffs; +0136 rxnsInfo=p.rxnsInfo; +0137 metPrefix=p.metPrefix; +0138 if isempty(metPrefix) +0139 metPrefix = 'M_'; +0140 end +0141 rxnPrefix=p.rxnPrefix; +0142 if isempty(rxnPrefix) +0143 rxnPrefix = 'R_'; +0144 end +0145 if xor(strcmp(rxnsCoeffs,'none'), strcmp(rxnsInfo,'none')) +0146 error('RAVEN:badInput','Provide both a ''rxnsInfo'' and a ''rxnsCoeffs'' file') +0147 end +0148 newModel=model; +0149 +0150 %% Metabolites +0151 if ~strcmp(metsInfo,'none') +0152 [header,data] = readCurationTable(metsInfo); +0153 [col,miriamCols] = matchColumns(header,metsInfo,{'metNames','comps'},{ ... +0154 'metNames', {'metNames','metName'}; ... +0155 'comps', {'comps','comp'}; ... +0156 'metFormulas', {'formula','formulas','metFormula','metFormulas'}; ... +0157 'metCharges', {'charge','charges','metCharge','metCharges'}; ... +0158 'inchis', {'inchi','inchis'}; ... +0159 'metNotes', {'metNotes','metNote'}}); +0160 metsToAdd.metNames = data(:,col.metNames); +0161 metsToAdd.compartments = data(:,col.comps); +0162 textFields = {'metFormulas','inchis','metNotes'}; +0163 for f = textFields(isfield(col,textFields)) +0164 metsToAdd.(f{1}) = data(:,col.(f{1})); +0165 end +0166 if isfield(col,'metCharges') +0167 metsToAdd.metCharges = cellfun(@(x) parseChargeCell(x,'metsInfo'),data(:,col.metCharges),'UniformOutput',false); +0168 isNanCharge = cellfun(@(x) isscalar(x) && isnan(x),metsToAdd.metCharges); +0169 metsToAdd.metCharges(isNanCharge) = {[]}; % a NaN charge counts as not given +0170 emptyEntry = cellfun(@isempty,metsToAdd.metCharges); +0171 emptyCharge = emptyEntry; +0172 if all(emptyEntry) +0173 metsToAdd = rmfield(metsToAdd,'metCharges'); +0174 else % If some charges are given, assume 0 for those without charges specified +0175 metsToAdd.metCharges(emptyEntry) = {0}; +0176 metsToAdd.metCharges = cell2mat(metsToAdd.metCharges); 0177 end -0178 newModel = extractAndAddMiriam(newModel,header(miriamCols),data(:,miriamCols),notNewGene,existingGene,'gene'); -0179 end +0178 end +0179 optFields = intersect({'metFormulas','metCharges','inchis','metNotes'},fieldnames(metsToAdd)','stable'); 0180 -0181 % Continue with new genes -0182 toAdd = 1:numel(genesToAdd.genes); -0183 toAdd(notNewGene) = []; -0184 if ~isempty(toAdd) -0185 genesToAdd.genes = genesToAdd.genes(toAdd); -0186 if isfield(genesToAdd,'geneShortNames') -0187 genesToAdd.geneShortNames = genesToAdd.geneShortNames(toAdd); -0188 if all(cellfun(@isempty,genesToAdd.geneShortNames)) -0189 genesToAdd = rmfield(genesToAdd,'geneShortNames'); -0190 end -0191 end -0192 % Add genes -0193 newModel = addGenesRaven(newModel,genesToAdd); -0194 addedIdx = numel(newModel.genes)-numel(toAdd)+1:numel(newModel.genes); -0195 newModel = extractAndAddMiriam(newModel,header(miriamCols),data(:,miriamCols),toAdd,addedIdx,'gene'); -0196 end -0197 end -0198 %% Reactions -0199 if ~any(strcmp({rxnsCoeffs,rxnsInfo},'none')) -0200 % Gather all data, first about reaction stoichiometries... -0201 [header,data] = readCurationTable(rxnsCoeffs); -0202 col = matchColumns(header,rxnsCoeffs,{'rxnIdx','rxnNames','metNames','comps','coefficient'},{ ... -0203 'rxnIdx', {'rxnIdx','index'}; ... -0204 'rxnNames', {'rxnNames','rxnName'}; ... -0205 'metNames', {'metNames','metName'}; ... -0206 'comps', {'comps','comp'}; ... -0207 'coefficient', {'coefficient','coefficients','coeffs','stoichCoeffs'}}); -0208 rxnCheck.coeffsIdx = str2double(data(:,col.rxnIdx)); -0209 rxns.rxnNames = data(:,col.rxnNames); -0210 rxns.metNames = data(:,col.metNames); -0211 rxns.comps = data(:,col.comps); -0212 rxns.coeffs = str2double(data(:,col.coefficient)); -0213 rxnCheck.coeffs = strcat(data(:,col.rxnIdx),'***',rxns.rxnNames); -0214 -0215 % ... and then additional reaction-specific data. -0216 [header,data] = readCurationTable(rxnsInfo); -0217 [col,miriamCols] = matchColumns(header,rxnsInfo,{'rxnIdx','rxnNames'},{ ... -0218 'rxnIdx', {'rxnIdx','index'}; ... -0219 'rxnNames', {'rxnNames','rxnName'}; ... -0220 'grRules', {'grRules'}; ... -0221 'lb', {'lb'}; ... -0222 'ub', {'ub'}; ... -0223 'rev', {'rev'}; ... -0224 'subSystems', {'subSystems'}; ... -0225 'eccodes', {'eccodes'}; ... -0226 'rxnNotes', {'rxnNotes'}; ... -0227 'rxnReferences', {'rxnReferences'}; ... -0228 'rxnConfidenceScores', {'rxnConfidenceScores'}}); -0229 rxnCheck.rxnsIdx = str2double(data(:,col.rxnIdx)); -0230 rxnCheck.rxns = strcat(data(:,col.rxnIdx),'***',data(:,col.rxnNames)); -0231 -0232 notMatching = setxor(rxnCheck.coeffs,rxnCheck.rxns); -0233 if numel(notMatching)>1 -0234 error(['The following reactions ánd/or their indices are not matched '... -0235 'between the rxnsInfo and rxnsCoeffs files:\n\t\t%s'],... -0236 strjoin(regexprep(notMatching,'^\d+***',''),'\n\t\t')) -0237 end -0238 -0239 rxnsToAdd.rxnNames = data(:,col.rxnNames); -0240 for f = intersect({'grRules','subSystems','eccodes','rxnNotes','rxnReferences'},fieldnames(col)','stable') -0241 rxnsToAdd.(f{1}) = data(:,col.(f{1})); -0242 end -0243 for f = intersect({'lb','ub','rev'},fieldnames(col)','stable') -0244 rxnsToAdd.(f{1}) = cellfun(@str2num,data(:,col.(f{1}))); -0245 end -0246 if isfield(col,'rxnConfidenceScores') -0247 rxnsToAdd.rxnConfidenceScores = cellfun(@str2num,data(:,col.rxnConfidenceScores),'UniformOutput',false); -0248 emptyEntry = cellfun(@isempty,rxnsToAdd.rxnConfidenceScores); -0249 if all(emptyEntry) -0250 rxnsToAdd = rmfield(rxnsToAdd,'rxnConfidenceScores'); -0251 else % If some rxnConfidenceScores are given, assume 0 for those without rxnConfidenceScores specified -0252 rxnsToAdd.rxnConfidenceScores(emptyEntry) = {0}; -0253 rxnsToAdd.rxnConfidenceScores = cell2mat(rxnsToAdd.rxnConfidenceScores); -0254 end -0255 end -0256 optFields = intersect({'subSystems','eccodes','rxnNotes','rxnReferences','rxnConfidenceScores'},fieldnames(rxnsToAdd)','stable'); -0257 -0258 existingRxn=[]; -0259 notNewRxn=[]; -0260 for i=1:numel(rxnCheck.rxnsIdx) -0261 rxnRows = find(rxnCheck.rxnsIdx(i)==rxnCheck.coeffsIdx); -0262 rxnsToAdd.mets{i,1} = cell(1,1); -0263 rxnsToAdd.stoichCoeffs{i,1} = cell(1,1); -0264 for j=1:numel(rxnRows) -0265 newMetComp = [rxns.metNames{rxnRows(j)}, '[', rxns.comps{rxnRows(j)}, ']']; -0266 try -0267 newMetComp = getIndexes(newModel,newMetComp,'metcomps'); -0268 catch -0269 error(['Not all metabolites in reaction "%s" are present in the '... -0270 'model or in the provided table with new metabolites.'],rxnsToAdd.rxnNames{i}) -0271 end -0272 rxnsToAdd.mets{i}(j) = newModel.mets(newMetComp); -0273 rxnsToAdd.stoichCoeffs{i}{j} = rxns.coeffs(rxnRows(j)); -0274 end -0275 rxnsToAdd.stoichCoeffs{i}=cell2mat(rxnsToAdd.stoichCoeffs{i}); -0276 %Check if the reaction not already exists, by stoichiometry of its -0277 %products and reactants -0278 metIdx = getIndexes(newModel,rxnsToAdd.mets{i},'mets'); -0279 modelCoeffs = transpose(newModel.S(metIdx,:)); -0280 modelCoeffs2 = find(~all(modelCoeffs==0,2)); -0281 % Only reactions built from exactly these metabolites can be -0282 % duplicates. Any model reaction that also involves other -0283 % metabolites projects onto the same row of coefficients, but is a -0284 % different reaction and must not be overwritten. -0285 sameSupport = full(sum(newModel.S(:,modelCoeffs2)~=0,1))' == numel(metIdx); -0286 modelCoeffs2 = modelCoeffs2(sameSupport); -0287 modelCoeffs = modelCoeffs(modelCoeffs2,:); -0288 [~, duplicateRxn] = intersect(modelCoeffs,rxnsToAdd.stoichCoeffs{i},'rows'); -0289 if ~isempty(duplicateRxn) -0290 existingRxn = [existingRxn,modelCoeffs2(duplicateRxn)]; -0291 notNewRxn = [notNewRxn,i]; -0292 end -0293 end -0294 -0295 % If some reactions already existed, then replace its information -0296 if ~isempty(existingRxn) -0297 warning(['The following reactions are the same as existing model '... -0298 'reactions, their annotation will be overwritten to match the '... -0299 'rxnCoeffs file: \n\t\t(Existing reaction ID): New reaction name\n\t\t%s'],... -0300 strjoin(strcat('(',newModel.rxns(existingRxn),{'): '},rxnsToAdd.rxnNames(notNewRxn)),'\n\t\t')); -0301 newModel.rxnNames(existingRxn) = rxnsToAdd.rxnNames(notNewRxn); -0302 for f = intersect({'lb','ub','rev'},fieldnames(rxnsToAdd)','stable') -0303 newModel.(f{1})(existingRxn) = rxnsToAdd.(f{1})(notNewRxn); -0304 end -0305 for f = optFields -0306 values = rxnsToAdd.(f{1})(notNewRxn); -0307 if strcmp(f{1},'subSystems') && isfield(newModel,'subSystems') ... -0308 && any(cellfun(@iscell,newModel.subSystems)) -0309 values = cellfun(@(x) {x}, values, 'UniformOutput', false); -0310 end -0311 newModel = writeExistingField(newModel,f{1},existingRxn,values); -0312 end -0313 if isfield(rxnsToAdd,'grRules') -0314 emptyEntries = cellfun(@isempty,rxnsToAdd.grRules(notNewRxn)); -0315 if ~all(emptyEntries) -0316 newModel = changeGrRules(newModel,newModel.rxns(existingRxn(~emptyEntries)),rxnsToAdd.grRules(notNewRxn(~emptyEntries)),true); -0317 end -0318 end -0319 newModel = extractAndAddMiriam(newModel,header(miriamCols),data(:,miriamCols),notNewRxn,existingRxn,'rxn'); -0320 end -0321 -0322 % Continue with new reactions -0323 toAdd = 1:numel(rxnsToAdd.rxnNames); -0324 toAdd(notNewRxn) = []; -0325 if ~isempty(toAdd) -0326 rxnsToAdd.rxnNames = rxnsToAdd.rxnNames(toAdd); -0327 for f = intersect({'lb','ub','rev','grRules'},fieldnames(rxnsToAdd)','stable') -0328 rxnsToAdd.(f{1}) = rxnsToAdd.(f{1})(toAdd); -0329 end -0330 for f = optFields -0331 rxnsToAdd.(f{1}) = rxnsToAdd.(f{1})(toAdd); -0332 if iscell(rxnsToAdd.(f{1})) && all(cellfun(@isempty,rxnsToAdd.(f{1}))) -0333 rxnsToAdd = rmfield(rxnsToAdd,f{1}); -0334 end -0335 end -0336 rxnsToAdd.mets = rxnsToAdd.mets(toAdd); -0337 rxnsToAdd.stoichCoeffs = rxnsToAdd.stoichCoeffs(toAdd); -0338 rxnsToAdd.rxns = generateNewIds(model,'rxns',rxnPrefix,numel(rxnsToAdd.rxnNames)); +0181 % Check if metabolite already exists (check by metName[comp]) +0182 existingMets = []; +0183 existingMetsIdx = []; +0184 newMets = []; +0185 for i=1:numel(metsToAdd.metNames) +0186 metIdx = find(strcmp(newModel.metNames,metsToAdd.metNames{i})); +0187 existMet = strcmp(metsToAdd.compartments{i},newModel.comps(newModel.metComps(metIdx))); +0188 if any(existMet) +0189 existingMets = [existingMets, i]; +0190 existingMetsIdx = [existingMetsIdx, metIdx(existMet)]; +0191 else +0192 newMets = [newMets, i]; +0193 end +0194 end +0195 +0196 % Overwrite annotation of existing entries +0197 if any(existingMets) +0198 for f = optFields +0199 skip = []; +0200 if ~overwriteWithEmpty +0201 if strcmp(f{1},'metCharges') +0202 skip = emptyCharge(existingMets); +0203 else +0204 skip = emptyCells(metsToAdd.(f{1})(existingMets)); +0205 end +0206 end +0207 newModel = writeExistingField(newModel,f{1},existingMetsIdx,metsToAdd.(f{1})(existingMets),skip); +0208 end +0209 newModel = extractAndAddMiriam(newModel,header(miriamCols),data(:,miriamCols),existingMets,existingMetsIdx,'met',~overwriteWithEmpty); +0210 warning(['The following metabolites are already present in the model, '... +0211 'their annotation will be overwritten to match the metsInfo file. '... +0212 'If you do not particularly want to curate their annotations, it '... +0213 'would be better to remove these metabolites from metsInfo:\n\t\t%s'],... +0214 strjoin(strcat(metsToAdd.metNames(existingMets),'[',metsToAdd.compartments(existingMets),']'),'\n\t\t')); +0215 end +0216 +0217 % Continue with new metabolites +0218 if numel(newMets)>0 +0219 metsToAdd.metNames = metsToAdd.metNames(newMets); +0220 metsToAdd.compartments = metsToAdd.compartments(newMets); +0221 for f = optFields +0222 metsToAdd.(f{1}) = metsToAdd.(f{1})(newMets); +0223 if iscell(metsToAdd.(f{1})) && all(cellfun(@isempty,metsToAdd.(f{1}))) +0224 metsToAdd = rmfield(metsToAdd,f{1}); +0225 end +0226 end +0227 % Add metabolites +0228 newModel = addMets(newModel,metsToAdd,true,metPrefix); +0229 addedIdx = numel(newModel.mets)-numel(newMets)+1:numel(newModel.mets); +0230 newModel = extractAndAddMiriam(newModel,header(miriamCols),data(:,miriamCols),newMets,addedIdx,'met'); +0231 end +0232 end +0233 %% Genes +0234 if ~strcmp(genesInfo,'none') +0235 [header,data] = readCurationTable(genesInfo); +0236 [col,miriamCols] = matchColumns(header,genesInfo,{'genes'},{ ... +0237 'genes', {'genes','gene'}; ... +0238 'geneShortNames', {'geneShortNames','geneShortName'}}); +0239 genesToAdd.genes = data(:,col.genes); +0240 checkGenesListedOnce(genesToAdd.genes); +0241 if isfield(col,'geneShortNames') +0242 genesToAdd.geneShortNames = data(:,col.geneShortNames); +0243 end +0244 +0245 [~,notNewGene,existingGene] = intersect(genesToAdd.genes,newModel.genes); +0246 if ~isempty(notNewGene) +0247 warning(['The following genes are already present in the model, their annotation '... +0248 'will be overwritten to match the genesInfo file: \n\t\t%s'],... +0249 strjoin(genesToAdd.genes(notNewGene),'\n\t\t')); +0250 if isfield(genesToAdd,'geneShortNames') +0251 skip = []; +0252 if ~overwriteWithEmpty +0253 skip = emptyCells(genesToAdd.geneShortNames(notNewGene)); +0254 end +0255 newModel = writeExistingField(newModel,'geneShortNames',existingGene,genesToAdd.geneShortNames(notNewGene),skip); +0256 end +0257 newModel = extractAndAddMiriam(newModel,header(miriamCols),data(:,miriamCols),notNewGene,existingGene,'gene',~overwriteWithEmpty); +0258 end +0259 +0260 % Continue with new genes +0261 toAdd = 1:numel(genesToAdd.genes); +0262 toAdd(notNewGene) = []; +0263 if ~isempty(toAdd) +0264 genesToAdd.genes = genesToAdd.genes(toAdd); +0265 if isfield(genesToAdd,'geneShortNames') +0266 genesToAdd.geneShortNames = genesToAdd.geneShortNames(toAdd); +0267 if all(cellfun(@isempty,genesToAdd.geneShortNames)) +0268 genesToAdd = rmfield(genesToAdd,'geneShortNames'); +0269 end +0270 end +0271 % Add genes +0272 newModel = addGenesRaven(newModel,genesToAdd); +0273 addedIdx = numel(newModel.genes)-numel(toAdd)+1:numel(newModel.genes); +0274 newModel = extractAndAddMiriam(newModel,header(miriamCols),data(:,miriamCols),toAdd,addedIdx,'gene'); +0275 end +0276 end +0277 %% Reactions +0278 if ~any(strcmp({rxnsCoeffs,rxnsInfo},'none')) +0279 % Gather all data, first about reaction stoichiometries... +0280 [header,data] = readCurationTable(rxnsCoeffs); +0281 col = matchColumns(header,rxnsCoeffs,{'rxnIdx','rxnNames','metNames','comps','coefficient'},{ ... +0282 'rxnIdx', {'rxnIdx','index'}; ... +0283 'rxnNames', {'rxnNames','rxnName'}; ... +0284 'metNames', {'metNames','metName'}; ... +0285 'comps', {'comps','comp'}; ... +0286 'coefficient', {'coefficient','coefficients','coeffs','stoichCoeffs'}}); +0287 % A row without a rxnIdx, rxnNames, metNames and comps value is ignored +0288 rowNo = (1:size(data,1))'; +0289 keyCells = data(:,[col.rxnIdx,col.rxnNames,col.metNames,col.comps]); +0290 ignoredRow = all(reshape(emptyCells(keyCells(:)),size(keyCells)),2); +0291 data(ignoredRow,:) = []; +0292 rowNo(ignoredRow) = []; +0293 rxnCheck.coeffsIdx = parseIndexCells(data(:,col.rxnIdx),'rxnIdx','rxnsCoeffs'); +0294 rxns.rxnNames = data(:,col.rxnNames); +0295 rxns.metNames = data(:,col.metNames); +0296 rxns.comps = data(:,col.comps); +0297 rxns.coeffs = parseNumericCells(data(:,col.coefficient),'coefficient','rxnsCoeffs'); +0298 checkCoefficientsGiven(rxnCheck.coeffsIdx,rxns.coeffs,rowNo); +0299 rxnCheck.coeffs = indexKeys(rxnCheck.coeffsIdx,rxns.rxnNames); +0300 +0301 % ... and then additional reaction-specific data. +0302 [header,data] = readCurationTable(rxnsInfo); +0303 [col,miriamCols] = matchColumns(header,rxnsInfo,{'rxnIdx','rxnNames'},{ ... +0304 'rxnIdx', {'rxnIdx','index'}; ... +0305 'rxnNames', {'rxnNames','rxnName'}; ... +0306 'grRules', {'grRules'}; ... +0307 'lb', {'lb'}; ... +0308 'ub', {'ub'}; ... +0309 'rev', {'rev'}; ... +0310 'subSystems', {'subSystems'}; ... +0311 'eccodes', {'eccodes'}; ... +0312 'rxnNotes', {'rxnNotes'}; ... +0313 'rxnReferences', {'rxnReferences'}; ... +0314 'rxnConfidenceScores', {'rxnConfidenceScores'}}); +0315 % A row without any value is ignored; a row with a value needs a rxnIdx +0316 rowNo = (1:size(data,1))'; +0317 ignoredRow = all(reshape(emptyCells(data(:)),size(data)),2); +0318 data(ignoredRow,:) = []; +0319 rowNo(ignoredRow) = []; +0320 rxnCheck.rxnsIdx = parseIndexCells(data(:,col.rxnIdx),'rxnIdx','rxnsInfo'); +0321 noIdx = find(isnan(rxnCheck.rxnsIdx),1); +0322 if ~isempty(noIdx) +0323 error('RAVEN:badInput','Data row %d of the rxnsInfo file has values but its rxnIdx is empty or NaN',rowNo(noIdx)); +0324 end +0325 [uniqueIdx,~,idxGroup] = unique(rxnCheck.rxnsIdx); +0326 repeated = find(accumarray(idxGroup,1)>1,1); +0327 if ~isempty(repeated) +0328 error('RAVEN:badInput','The rxnIdx %s is used by data rows %s of the rxnsInfo file; each reaction needs its own index', ... +0329 num2str(uniqueIdx(repeated)),strjoin(arrayfun(@num2str,rowNo(idxGroup==repeated)','UniformOutput',false),', ')); +0330 end +0331 rxnCheck.rxns = indexKeys(rxnCheck.rxnsIdx,data(:,col.rxnNames)); +0332 +0333 notMatching = setxor(rxnCheck.coeffs,rxnCheck.rxns); +0334 if ~isempty(notMatching) +0335 error('RAVEN:badInput',['The following reactions and/or their indices are not matched '... +0336 'between the rxnsInfo and rxnsCoeffs files:\n\t\t%s'],... +0337 strjoin(regexprep(notMatching,'^(\d+)\*\*\*(.*)$','index $1 ($2)'),'\n\t\t')) +0338 end 0339 -0340 newModel = addRxns(newModel,rxnsToAdd,1,[],false,false); -0341 rxnsModelIdx = numel(newModel.rxns)-numel(toAdd)+1:numel(newModel.rxns); -0342 newModel = extractAndAddMiriam(newModel,header(miriamCols),data(:,miriamCols),toAdd,rxnsModelIdx,'rxn'); +0340 rxnsToAdd.rxnNames = data(:,col.rxnNames); +0341 for f = intersect({'grRules','subSystems','eccodes','rxnNotes','rxnReferences'},fieldnames(col)','stable') +0342 rxnsToAdd.(f{1}) = data(:,col.(f{1})); 0343 end -0344 end -0345 end -0346 -0347 function [header,data] = readCurationTable(fileName) -0348 % Read a tab-delimited curation table into its header row and a cell -0349 % array of text with one column per header field. -0350 fid = fopen(fileName); -0351 if fid==-1 -0352 error('Could not open the file "%s"',fileName); -0353 end -0354 headerLine = fgetl(fid); -0355 if ~ischar(headerLine) -0356 fclose(fid); -0357 error('The file "%s" is empty',fileName); -0358 end -0359 headerLine = strrep(headerLine,char(65279),''); % byte order mark -0360 header = strtrim(strsplit(headerLine,'\t')); -0361 header = regexprep(header,'^"(.*)"$','$1'); -0362 nCols = numel(header); -0363 raw = textscan(fid,repmat('%q',1,nCols),'Delimiter','\t'); -0364 fclose(fid); -0365 nRows = max(cellfun(@numel,raw)); -0366 data = repmat({''},nRows,nCols); -0367 for i=1:nCols -0368 data(1:numel(raw{i}),i) = raw{i}; -0369 end -0370 end -0371 -0372 function [col,miriamCols] = matchColumns(header,fileName,required,spec) -0373 % Locate the core columns of a curation table by header name (case -0374 % insensitive). spec has one row per core field: {field, accepted headers}. -0375 % Every other column is a MIRIAM namespace. -0376 col = struct(); -0377 isCore = false(1,numel(header)); -0378 for i=1:size(spec,1) -0379 match = find(ismember(lower(header),lower(spec{i,2}))); -0380 if ~isempty(match) -0381 col.(spec{i,1}) = match(1); -0382 isCore(match) = true; -0383 end -0384 end -0385 missing = setdiff(required,fieldnames(col)','stable'); -0386 if ~isempty(missing) -0387 error('The file "%s" has no column with header: %s',fileName,strjoin(missing,', ')); -0388 end -0389 miriamCols = find(~isCore & ~cellfun(@isempty,header)); -0390 end -0391 -0392 function model = writeExistingField(model,field,modelIdx,values) -0393 % Write the values of an optional field onto existing entities. A field -0394 % the model does not have is created from its ravenModelFields default, -0395 % unless all supplied values are empty. -0396 if ~isfield(model,field) -0397 if iscell(values) && all(cellfun(@isempty,values)) -0398 return -0399 end -0400 reg = ravenModelFields(); -0401 entry = reg(strcmp({reg.name},field)); -0402 n = numel(model.([entry.type 's'])); -0403 if iscell(values) -0404 model.(field) = repmat({entry.default},n,1); -0405 else -0406 model.(field) = repmat(entry.default,n,1); -0407 end -0408 end -0409 model.(field)(modelIdx) = values; -0410 end -0411 -0412 function newModel = extractAndAddMiriam(model,miriamName,miriamData,inputIndex,modelIndex,type) -0413 newModel=model; -0414 miriamValues = miriamData(inputIndex,:); -0415 emptyMiriam = all(cellfun(@isempty,miriamValues),1); -0416 miriamName(emptyMiriam) = []; -0417 miriamValues(:,emptyMiriam) = []; -0418 if ~isfield(newModel,[type 'Miriams']) -0419 newModel.([type 'Miriams'])=cell(numel(newModel.([type 's'])),1); -0420 end -0421 for i=1:numel(miriamName) -0422 newModel = editMiriam(newModel,type,modelIndex,miriamName{i},miriamValues(:,i),'replace'); -0423 end -0424 end

    +0344 if isfield(col,'grRules') +0345 rxnsToAdd.grRules = standardGrRules(rxnsToAdd.grRules,rxnsToAdd.rxnNames,rowNo); +0346 end +0347 for f = intersect({'lb','ub','rev'},fieldnames(col)','stable') +0348 rxnsToAdd.(f{1}) = parseNumericCells(data(:,col.(f{1})),f{1},'rxnsInfo'); +0349 end +0350 if isfield(rxnsToAdd,'rev') +0351 checkRevValues(rxnsToAdd.rev,data(:,col.rev)); +0352 end +0353 if isfield(col,'rxnConfidenceScores') +0354 rxnsToAdd.rxnConfidenceScores = cellfun(@(x) parseNumberCell(x,'rxnConfidenceScores','rxnsInfo'),data(:,col.rxnConfidenceScores),'UniformOutput',false); +0355 isNanScore = cellfun(@(x) isscalar(x) && isnan(x),rxnsToAdd.rxnConfidenceScores); +0356 rxnsToAdd.rxnConfidenceScores(isNanScore) = {[]}; % a NaN score counts as not given +0357 emptyEntry = cellfun(@isempty,rxnsToAdd.rxnConfidenceScores); +0358 emptyScore = emptyEntry; +0359 if all(emptyEntry) +0360 rxnsToAdd = rmfield(rxnsToAdd,'rxnConfidenceScores'); +0361 else % If some rxnConfidenceScores are given, assume 0 for those without rxnConfidenceScores specified +0362 rxnsToAdd.rxnConfidenceScores(emptyEntry) = {0}; +0363 rxnsToAdd.rxnConfidenceScores = cell2mat(rxnsToAdd.rxnConfidenceScores); +0364 end +0365 end +0366 optFields = intersect({'subSystems','eccodes','rxnNotes','rxnReferences','rxnConfidenceScores'},fieldnames(rxnsToAdd)','stable'); +0367 +0368 existingRxn=[]; +0369 notNewRxn=[]; +0370 for i=1:numel(rxnCheck.rxnsIdx) +0371 rxnRows = find(rxnCheck.rxnsIdx(i)==rxnCheck.coeffsIdx); +0372 rxnsToAdd.mets{i,1} = cell(1,1); +0373 rxnsToAdd.stoichCoeffs{i,1} = cell(1,1); +0374 for j=1:numel(rxnRows) +0375 newMetComp = [rxns.metNames{rxnRows(j)}, '[', rxns.comps{rxnRows(j)}, ']']; +0376 try +0377 newMetComp = getIndexes(newModel,newMetComp,'metcomps'); +0378 catch +0379 error('RAVEN:badInput',['Not all metabolites in reaction "%s" are present in the '... +0380 'model or in the provided table with new metabolites.'],rxnsToAdd.rxnNames{i}) +0381 end +0382 rxnsToAdd.mets{i}(j) = newModel.mets(newMetComp); +0383 rxnsToAdd.stoichCoeffs{i}{j} = rxns.coeffs(rxnRows(j)); +0384 end +0385 rxnsToAdd.stoichCoeffs{i}=cell2mat(rxnsToAdd.stoichCoeffs{i}); +0386 %Check if the reaction not already exists, by stoichiometry of its +0387 %products and reactants +0388 metIdx = getIndexes(newModel,rxnsToAdd.mets{i},'mets'); +0389 modelCoeffs = transpose(newModel.S(metIdx,:)); +0390 modelCoeffs2 = find(~all(modelCoeffs==0,2)); +0391 % Only reactions built from exactly these metabolites can be +0392 % duplicates. Any model reaction that also involves other +0393 % metabolites projects onto the same row of coefficients, but is a +0394 % different reaction and must not be overwritten. +0395 sameSupport = full(sum(newModel.S(:,modelCoeffs2)~=0,1))' == numel(metIdx); +0396 modelCoeffs2 = modelCoeffs2(sameSupport); +0397 modelCoeffs = modelCoeffs(modelCoeffs2,:); +0398 [~, duplicateRxn] = intersect(modelCoeffs,rxnsToAdd.stoichCoeffs{i},'rows'); +0399 if ~isempty(duplicateRxn) +0400 existingRxn = [existingRxn,modelCoeffs2(duplicateRxn)]; +0401 notNewRxn = [notNewRxn,i]; +0402 end +0403 end +0404 +0405 % If some reactions already existed, then replace its information +0406 if ~isempty(existingRxn) +0407 warning(['The following reactions are the same as existing model '... +0408 'reactions, their annotation will be overwritten to match the '... +0409 'rxnCoeffs file: \n\t\t(Existing reaction ID): New reaction name\n\t\t%s'],... +0410 strjoin(strcat('(',newModel.rxns(existingRxn),{'): '},rxnsToAdd.rxnNames(notNewRxn)),'\n\t\t')); +0411 nameValues = rxnsToAdd.rxnNames(notNewRxn); +0412 nameIdx = existingRxn; +0413 if ~overwriteWithEmpty +0414 nameIdx = nameIdx(~emptyCells(nameValues)); +0415 nameValues = nameValues(~emptyCells(nameValues)); +0416 end +0417 newModel.rxnNames(nameIdx) = nameValues; +0418 for f = intersect({'lb','ub','rev'},fieldnames(rxnsToAdd)','stable') +0419 values = rxnsToAdd.(f{1})(notNewRxn); +0420 given = ~isnan(values); +0421 newModel.(f{1})(existingRxn(given)) = values(given); +0422 end +0423 for f = optFields +0424 values = rxnsToAdd.(f{1})(notNewRxn); +0425 skip = []; +0426 if ~overwriteWithEmpty +0427 if strcmp(f{1},'rxnConfidenceScores') +0428 skip = emptyScore(notNewRxn); +0429 else +0430 skip = emptyCells(values); +0431 end +0432 end +0433 if strcmp(f{1},'subSystems') && isfield(newModel,'subSystems') ... +0434 && any(cellfun(@iscell,newModel.subSystems)) +0435 values = cellfun(@(x) {x}, values, 'UniformOutput', false); +0436 end +0437 newModel = writeExistingField(newModel,f{1},existingRxn,values,skip); +0438 end +0439 if isfield(rxnsToAdd,'grRules') +0440 emptyEntries = emptyCells(rxnsToAdd.grRules(notNewRxn)); +0441 if ~all(emptyEntries) +0442 newModel = changeGrRules(newModel,newModel.rxns(existingRxn(~emptyEntries)),rxnsToAdd.grRules(notNewRxn(~emptyEntries)),true); +0443 end +0444 end +0445 newModel = extractAndAddMiriam(newModel,header(miriamCols),data(:,miriamCols),notNewRxn,existingRxn,'rxn',~overwriteWithEmpty); +0446 end +0447 +0448 % Continue with new reactions +0449 toAdd = 1:numel(rxnsToAdd.rxnNames); +0450 toAdd(notNewRxn) = []; +0451 if ~isempty(toAdd) +0452 rxnsToAdd.rxnNames = rxnsToAdd.rxnNames(toAdd); +0453 for f = intersect({'lb','ub','rev','grRules'},fieldnames(rxnsToAdd)','stable') +0454 rxnsToAdd.(f{1}) = rxnsToAdd.(f{1})(toAdd); +0455 end +0456 if isfield(rxnsToAdd,'lb') +0457 rxnsToAdd.lb(isnan(rxnsToAdd.lb)) = -inf; +0458 end +0459 if isfield(rxnsToAdd,'ub') +0460 defaultUB = inf; +0461 if isfield(newModel,'annotation') && isfield(newModel.annotation,'defaultUB') +0462 defaultUB = newModel.annotation.defaultUB; +0463 end +0464 rxnsToAdd.ub(isnan(rxnsToAdd.ub)) = defaultUB; +0465 end +0466 for f = optFields +0467 rxnsToAdd.(f{1}) = rxnsToAdd.(f{1})(toAdd); +0468 if iscell(rxnsToAdd.(f{1})) && all(cellfun(@isempty,rxnsToAdd.(f{1}))) +0469 rxnsToAdd = rmfield(rxnsToAdd,f{1}); +0470 end +0471 end +0472 rxnsToAdd.mets = rxnsToAdd.mets(toAdd); +0473 rxnsToAdd.stoichCoeffs = rxnsToAdd.stoichCoeffs(toAdd); +0474 rxnsToAdd.rxns = generateNewIds(model,'rxns',rxnPrefix,numel(rxnsToAdd.rxnNames)); +0475 +0476 newModel = addRxns(newModel,rxnsToAdd,1,[],false,false); +0477 rxnsModelIdx = numel(newModel.rxns)-numel(toAdd)+1:numel(newModel.rxns); +0478 newModel = extractAndAddMiriam(newModel,header(miriamCols),data(:,miriamCols),toAdd,rxnsModelIdx,'rxn'); +0479 end +0480 end +0481 end +0482 +0483 function [header,data] = readCurationTable(fileName) +0484 % Read a tab-delimited curation table into its header row and a cell +0485 % array of text with one column per header field. +0486 fid = fopen(fileName); +0487 if fid==-1 +0488 error('RAVEN:badInput','Could not open the file "%s"',fileName); +0489 end +0490 headerLine = fgetl(fid); +0491 if ~ischar(headerLine) +0492 fclose(fid); +0493 error('RAVEN:badInput','The file "%s" is empty',fileName); +0494 end +0495 headerLine = strrep(headerLine,char(65279),''); % byte order mark +0496 header = strtrim(strsplit(headerLine,'\t')); +0497 header = strtrim(regexprep(header,'^"(.*)"$','$1')); +0498 nCols = numel(header); +0499 raw = textscan(fid,repmat('%q',1,nCols),'Delimiter','\t'); +0500 fclose(fid); +0501 nRows = max(cellfun(@numel,raw)); +0502 data = repmat({''},nRows,nCols); +0503 for i=1:nCols +0504 data(1:numel(raw{i}),i) = raw{i}; +0505 end +0506 blank = cellfun(@(x) ischar(x) && isempty(strtrim(x)),data); +0507 data(blank) = {''}; +0508 end +0509 +0510 function [col,miriamCols] = matchColumns(header,fileName,required,spec) +0511 % Locate the core columns of a curation table by header name (case +0512 % insensitive). spec has one row per core field: {field, accepted headers}. +0513 % Every other column is a MIRIAM namespace. +0514 col = struct(); +0515 isCore = false(1,numel(header)); +0516 for i=1:size(spec,1) +0517 match = find(ismember(lower(header),lower(spec{i,2}))); +0518 if ~isempty(match) +0519 col.(spec{i,1}) = match(1); +0520 isCore(match) = true; +0521 end +0522 end +0523 missing = setdiff(required,fieldnames(col)','stable'); +0524 if ~isempty(missing) +0525 error('RAVEN:badInput','The file "%s" has no column with header: %s',fileName,strjoin(missing,', ')); +0526 end +0527 miriamCols = find(~isCore & ~cellfun(@isempty,header)); +0528 end +0529 +0530 function model = writeExistingField(model,field,modelIdx,values,skip) +0531 % Write the values of an optional field onto existing entities. A field +0532 % the model does not have is created from its ravenModelFields default, +0533 % unless all supplied values are empty. Entries flagged in the logical +0534 % vector skip are not written. +0535 if nargin>4 && ~isempty(skip) +0536 modelIdx = modelIdx(~skip(:)'); +0537 values = values(~skip(:)); +0538 if isempty(modelIdx) +0539 return +0540 end +0541 end +0542 if ~isfield(model,field) +0543 if iscell(values) && all(cellfun(@isempty,values)) +0544 return +0545 end +0546 reg = ravenModelFields(); +0547 entry = reg(strcmp({reg.name},field)); +0548 n = numel(model.([entry.type 's'])); +0549 if iscell(values) +0550 model.(field) = repmat({entry.default},n,1); +0551 else +0552 model.(field) = repmat(entry.default,n,1); +0553 end +0554 end +0555 model.(field)(modelIdx) = values; +0556 end +0557 +0558 function newModel = extractAndAddMiriam(model,miriamName,miriamData,inputIndex,modelIndex,type,skipEmpty) +0559 % Replace the MIRIAM annotations of the entities in modelIndex with the +0560 % values of the table rows in inputIndex. With skipEmpty, an empty cell +0561 % leaves the annotation of that entity unchanged. +0562 newModel=model; +0563 miriamValues = miriamData(inputIndex,:); +0564 if nargin>6 && skipEmpty +0565 for i=1:numel(miriamName) +0566 given = ~emptyCells(miriamValues(:,i)); +0567 if any(given) +0568 if ~isfield(newModel,[type 'Miriams']) +0569 newModel.([type 'Miriams'])=cell(numel(newModel.([type 's'])),1); +0570 end +0571 idx = modelIndex(:); +0572 newModel = editMiriam(newModel,type,idx(given),miriamName{i},miriamValues(given,i),'replace'); +0573 end +0574 end +0575 return +0576 end +0577 emptyMiriam = all(cellfun(@isempty,miriamValues),1); +0578 miriamName(emptyMiriam) = []; +0579 miriamValues(:,emptyMiriam) = []; +0580 if ~isfield(newModel,[type 'Miriams']) +0581 newModel.([type 'Miriams'])=cell(numel(newModel.([type 's'])),1); +0582 end +0583 for i=1:numel(miriamName) +0584 newModel = editMiriam(newModel,type,modelIndex,miriamName{i},miriamValues(:,i),'replace'); +0585 end +0586 end +0587 +0588 function empty = emptyCells(values) +0589 % Logical column vector that is true for the empty entries of a cell array +0590 % of text (a cell with only blanks counts as empty) or of a numeric cell. +0591 empty = cellfun(@(x) isempty(x) || (ischar(x) && isempty(strtrim(x))),values(:)); +0592 end +0593 +0594 function values = parseIndexCells(cells,field,fileLabel) +0595 % Reaction indices from table cells: NaN where the cell is empty or says NaN +0596 % (the row is ignored), otherwise a finite whole number of at least 1. +0597 values = parseNumericCells(cells,field,fileLabel); +0598 bad = find(~isnan(values) & (~isfinite(values) | values~=round(values) | values<1),1); +0599 if ~isempty(bad) +0600 error('RAVEN:badInput','The %s value "%s" in the %s file is not a whole number of at least 1',field,strtrim(cells{bad}),fileLabel); +0601 end +0602 end +0603 +0604 function values = parseNumericCells(cells,field,fileLabel) +0605 % Numbers from table cells, NaN where the cell is empty or says NaN. A cell +0606 % that is not a number (or true/false) raises an error. +0607 values = nan(numel(cells),1); +0608 for i=1:numel(cells) +0609 v = parseNumberCell(cells{i},field,fileLabel); +0610 if ~isempty(v) +0611 values(i) = v; +0612 end +0613 end +0614 end +0615 +0616 function v = parseChargeCell(text,fileLabel) +0617 % Charge from a table cell: [] for an empty cell, NaN for NaN, otherwise a +0618 % finite whole number. A fractional or infinite charge raises an error. +0619 v = parseNumberCell(text,'charge',fileLabel); +0620 if ~isempty(v) && ~isnan(v) && (~isfinite(v) || v~=round(v)) +0621 error('RAVEN:badInput','The charge value "%s" in the %s file is not a finite whole number',strtrim(text),fileLabel); +0622 end +0623 end +0624 +0625 function checkCoefficientsGiven(idx,coeffs,rowNo) +0626 % Every row of the rxnsCoeffs table that is kept has a rxnIdx and a +0627 % coefficient that is a finite number other than 0. +0628 noIdx = find(isnan(idx),1); +0629 if ~isempty(noIdx) +0630 error('RAVEN:badInput',['Data row %d of the rxnsCoeffs file has values but its rxnIdx is empty or NaN'],rowNo(noIdx)); +0631 end +0632 missing = find(isnan(coeffs),1); +0633 if ~isempty(missing) +0634 error('RAVEN:badInput',['The coefficient in data row %d of the rxnsCoeffs file is empty or NaN; ' ... +0635 'every row with a reaction, metabolite or compartment needs a coefficient'],rowNo(missing)); +0636 end +0637 bad = find(~isfinite(coeffs) | coeffs==0,1); +0638 if ~isempty(bad) +0639 error('RAVEN:badInput','The coefficient in data row %d of the rxnsCoeffs file must be a finite non-zero number',rowNo(bad)); +0640 end +0641 end +0642 +0643 function keys = indexKeys(idx,names) +0644 % One key per table row from the numeric reaction index and the name, so that +0645 % the indices of two tables are compared by value. +0646 keys = cell(numel(idx),1); +0647 for i=1:numel(idx) +0648 keys{i} = sprintf('%.0f***%s',idx(i),names{i}); +0649 end +0650 end +0651 +0652 function checkGenesListedOnce(genes) +0653 % Raise RAVEN:badInput for a gene id that the genesInfo table lists more than +0654 % once, whether the model has the gene or not: two rows for one gene would +0655 % say two things about it, and the second row would add a second gene with +0656 % the same id. +0657 [ids,~,j] = unique(genes); +0658 repeated = ids(accumarray(j,1) > 1); +0659 if ~isempty(repeated) +0660 error('RAVEN:badInput',['The following genes are listed more than once '... +0661 'in the genesInfo file:\n\t\t%s'],strjoin(repeated,'\n\t\t')); +0662 end +0663 end +0664 +0665 +0666 function rules = standardGrRules(rules,rxnNames,rowNo) +0667 % Raise RAVEN:badInput for a grRules cell that is not a well-formed gene rule +0668 % (unbalanced parentheses, an operator without two operands, empty +0669 % parentheses, two genes without an operator between them) and write each +0670 % other rule in RAVEN's standard format (" and " / " or ", parentheses only +0671 % where needed), so that addRxns and changeGrRules read every spelling that +0672 % parseGrRule reads. Empty cells are left as they are. +0673 for i = find(~emptyCells(rules))' +0674 try +0675 rules{i} = grRuleToString(parseGrRule(rules{i})); +0676 catch err +0677 if strcmp(err.identifier,'RAVEN:badGrRule') +0678 error('RAVEN:badInput','The grRules value "%s" of reaction "%s" (data row %d of the rxnsInfo file) is not a valid gene rule: %s',... +0679 rules{i},rxnNames{i},rowNo(i),err.message); +0680 end +0681 rethrow(err); +0682 end +0683 end +0684 end +0685 +0686 +0687 function checkRevValues(values,cells) +0688 % A rev cell is 0, 1, true or false; an empty or NaN cell is not given. +0689 bad = find(~isnan(values) & values~=0 & values~=1,1); +0690 if ~isempty(bad) +0691 error('RAVEN:badInput','The rev value "%s" in the rxnsInfo file is not 0, 1, true or false',strtrim(cells{bad})); +0692 end +0693 end +0694 +0695 function v = parseNumberCell(text,field,fileLabel) +0696 % Real number from a table cell: [] for an empty cell, 1 or 0 for true or +0697 % false in any case (lb, ub and rev cells only), NaN or +/-Inf for those words in any case, otherwise a +0698 % plain decimal number (digits with an optional sign, decimal point and +0699 % exponent). Any other text raises an error; nothing is evaluated. +0700 text = strtrim(text); +0701 if isempty(text) +0702 v = []; +0703 return +0704 end +0705 word = lower(text); +0706 if any(strcmp(word,{'true','false'})) && ~ismember(field,{'lb','ub','rev'}) +0707 error('RAVEN:badInput','The %s value "%s" in the %s file is not a number',field,text,fileLabel); +0708 end +0709 if strcmp(word,'true') +0710 v = 1; +0711 elseif strcmp(word,'false') +0712 v = 0; +0713 elseif ~isempty(regexp(word,'^[+-]?nan$','once')) +0714 v = NaN; +0715 elseif ~isempty(regexp(word,'^[+-]?inf$','once')) +0716 v = Inf; +0717 if word(1)=='-' +0718 v = -Inf; +0719 end +0720 elseif ~isempty(regexp(text,'^[+-]?([0-9]+\.?[0-9]*|\.[0-9]+)([eE][+-]?[0-9]+)?$','once')) +0721 v = str2double(text); +0722 else +0723 error('RAVEN:badInput','The %s value "%s" in the %s file is not a number',field,text,fileLabel); +0724 end +0725 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/gapfilling/gapFillMILP.html b/docs/html/gapfilling/gapFillMILP.html index c851a3e6..4b867b58 100644 --- a/docs/html/gapfilling/gapFillMILP.html +++ b/docs/html/gapfilling/gapFillMILP.html @@ -55,8 +55,8 @@

    DESCRIPTION ^SOURCE CODE ^% minimum objective value to achieve. If empty, runs FBA on the merged 0028 % model and uses 10% of the maximum as the floor. 0029 % 'bigM' (default 1000) -0030 % Big-M constant for coupling constraints. Must be larger than any -0031 % expected flux magnitude. +0030 % Big-M constant for the coupling constraints of universal reactions +0031 % with an infinite bound. A finite bound is used as it is. 0032 % 'params' (default []) 0033 % solver parameter struct passed to optimizeProb. Fields such as 0034 % params.TimeLimit and params.intTol can be used to tune the MILP. @@ -257,96 +257,98 @@

    SOURCE CODE ^% (3) Universal upper coupling: v_k - ub_k * y_db_k <= 0 [nUniv rows, 'L'] 0162 % (forces v_k <= 0 when y_db_k = 0) -0163 % Coupling coefficients are capped at the Big-M constant: an unbounded -0164 % universal reaction (ub = Inf, RAVEN's own default when no upper-bound -0165 % annotation is set) would otherwise put an Inf coefficient into the -0166 % constraint matrix, and Inf*0 (y_db_k = 0) is NaN, not 0. +0163 % Coupling coefficients are the reaction bounds. An infinite bound is replaced +0164 % by the Big-M constant: an unbounded universal reaction (ub = Inf, RAVEN's own +0165 % default when no upper-bound annotation is set) would otherwise put an Inf +0166 % coefficient into the constraint matrix, and Inf*0 (y_db_k = 0) is NaN, not 0. 0167 yDbCols = nMergedRxns + (1:nUniv); -0168 uniUb = min(mergedModel.ub(univIdx), M); -0169 uniLb = max(mergedModel.lb(univIdx), -M); -0170 A_uniUp = sparse(... -0171 [1:nUniv, 1:nUniv], ... -0172 [univIdx(:)', yDbCols], ... -0173 [ones(1, nUniv), -uniUb'], ... -0174 nUniv, nVar); -0175 -0176 % (4) Universal lower coupling: v_k - lb_k * y_db_k >= 0 [nUniv rows, 'G'] -0177 % (forces v_k >= 0 when y_db_k = 0; together with (3): v_k = 0 when y_db_k = 0) -0178 A_uniLo = sparse(... -0179 [1:nUniv, 1:nUniv], ... -0180 [univIdx(:)', yDbCols], ... -0181 [ones(1, nUniv), -uniLb'], ... -0182 nUniv, nVar); -0183 -0184 % --- Assemble --- -0185 prob.A = [A_SS; A_growth; A_uniUp; A_uniLo]; -0186 prob.a = prob.A; -0187 prob.b = [zeros(nMergedMets, 1); minGrowth; zeros(2 * nUniv, 1)]; -0188 prob.csense = [repmat('E', 1, nMergedMets), 'G', ... -0189 repmat('L', 1, nUniv), ... -0190 repmat('G', 1, nUniv)]; -0191 prob.c = c; -0192 prob.osense = 1; % minimise the number of added reactions -0193 prob.lb = lb; -0194 prob.ub = ub; -0195 prob.vartype = repmat('C', 1, nVar); -0196 prob.vartype(nMergedRxns + 1 : end) = 'B'; % y_db is binary -0197 -0198 if isempty(params) -0199 params.intTol = 1e-9; -0200 params.TimeLimit = 600; -0201 end -0202 -0203 % ---- Solve ---- -0204 if verbose -0205 fprintf('gapFillMILP: solving MILP (%d vars, %d binary, %d constraints)...\n', ... -0206 nVar, nUniv, size(prob.A, 1)); -0207 end -0208 sol = optimizeProb(prob, params, verbose); -0209 -0210 if ~checkSolution(sol) -0211 if verbose -0212 fprintf('gapFillMILP: MILP infeasible or timed out.\n'); -0213 end -0214 exitFlag = -1; -0215 return; -0216 end -0217 -0218 % ---- Extract solution ---- -0219 binThreshold = 1e-3; -0220 yDb = sol.full(nMergedRxns + 1 : end); -0221 dbSelected = univIdx(yDb > binThreshold); -0222 -0223 % Map back to original IDs. dbSelected is already restricted to universal -0224 % reactions via univIdx/isDraft above (an id-membership test against -0225 % model.rxns, not a name it could have been renamed away from), so no -0226 % further filtering against universalModel.rxns is needed here -- doing so -0227 % would silently drop a universal reaction that mergeModels renamed -0228 % because its id collided with one already in the draft model. -0229 addedRxns = mergedModel.rxns(dbSelected); -0230 -0231 if verbose -0232 fprintf('gapFillMILP: added %d universal reaction(s).\n', numel(addedRxns)); -0233 end -0234 -0235 % ---- Build new model ---- -0236 newModel = model; -0237 -0238 % Add universal reactions. Extracted from mergedModel by the dbSelected -0239 % indices directly, not by looking addedRxns' names back up in -0240 % universalModel: that lookup fails whenever mergeModels renamed one of -0241 % them because its id collided with one already in the draft model, in -0242 % which case setdiff would keep the (unrenamed) original in universalModel -0243 % marked for removal, silently dropping the very reaction just selected. -0244 if ~isempty(dbSelected) -0245 toRemove = true(nMergedRxns, 1); -0246 toRemove(dbSelected) = false; -0247 trimmedUniv = removeReactions(mergedModel, toRemove); -0248 newModel = mergeModels({newModel; trimmedUniv}); -0249 end -0250 -0251 exitFlag = 1; -0252 end +0168 uniUb = mergedModel.ub(univIdx); +0169 uniUb(isinf(uniUb)) = M; +0170 uniLb = mergedModel.lb(univIdx); +0171 uniLb(isinf(uniLb)) = -M; +0172 A_uniUp = sparse(... +0173 [1:nUniv, 1:nUniv], ... +0174 [univIdx(:)', yDbCols], ... +0175 [ones(1, nUniv), -uniUb'], ... +0176 nUniv, nVar); +0177 +0178 % (4) Universal lower coupling: v_k - lb_k * y_db_k >= 0 [nUniv rows, 'G'] +0179 % (forces v_k >= 0 when y_db_k = 0; together with (3): v_k = 0 when y_db_k = 0) +0180 A_uniLo = sparse(... +0181 [1:nUniv, 1:nUniv], ... +0182 [univIdx(:)', yDbCols], ... +0183 [ones(1, nUniv), -uniLb'], ... +0184 nUniv, nVar); +0185 +0186 % --- Assemble --- +0187 prob.A = [A_SS; A_growth; A_uniUp; A_uniLo]; +0188 prob.a = prob.A; +0189 prob.b = [zeros(nMergedMets, 1); minGrowth; zeros(2 * nUniv, 1)]; +0190 prob.csense = [repmat('E', 1, nMergedMets), 'G', ... +0191 repmat('L', 1, nUniv), ... +0192 repmat('G', 1, nUniv)]; +0193 prob.c = c; +0194 prob.osense = 1; % minimise the number of added reactions +0195 prob.lb = lb; +0196 prob.ub = ub; +0197 prob.vartype = repmat('C', 1, nVar); +0198 prob.vartype(nMergedRxns + 1 : end) = 'B'; % y_db is binary +0199 +0200 if isempty(params) +0201 params.intTol = 1e-9; +0202 params.TimeLimit = 600; +0203 end +0204 +0205 % ---- Solve ---- +0206 if verbose +0207 fprintf('gapFillMILP: solving MILP (%d vars, %d binary, %d constraints)...\n', ... +0208 nVar, nUniv, size(prob.A, 1)); +0209 end +0210 sol = optimizeProb(prob, params, verbose); +0211 +0212 if ~checkSolution(sol) +0213 if verbose +0214 fprintf('gapFillMILP: MILP infeasible or timed out.\n'); +0215 end +0216 exitFlag = -1; +0217 return; +0218 end +0219 +0220 % ---- Extract solution ---- +0221 binThreshold = 1e-3; +0222 yDb = sol.full(nMergedRxns + 1 : end); +0223 dbSelected = univIdx(yDb > binThreshold); +0224 +0225 % Map back to original IDs. dbSelected is already restricted to universal +0226 % reactions via univIdx/isDraft above (an id-membership test against +0227 % model.rxns, not a name it could have been renamed away from), so no +0228 % further filtering against universalModel.rxns is needed here -- doing so +0229 % would silently drop a universal reaction that mergeModels renamed +0230 % because its id collided with one already in the draft model. +0231 addedRxns = mergedModel.rxns(dbSelected); +0232 +0233 if verbose +0234 fprintf('gapFillMILP: added %d universal reaction(s).\n', numel(addedRxns)); +0235 end +0236 +0237 % ---- Build new model ---- +0238 newModel = model; +0239 +0240 % Add universal reactions. Extracted from mergedModel by the dbSelected +0241 % indices directly, not by looking addedRxns' names back up in +0242 % universalModel: that lookup fails whenever mergeModels renamed one of +0243 % them because its id collided with one already in the draft model, in +0244 % which case setdiff would keep the (unrenamed) original in universalModel +0245 % marked for removal, silently dropping the very reaction just selected. +0246 if ~isempty(dbSelected) +0247 toRemove = true(nMergedRxns, 1); +0248 toRemove(dbSelected) = false; +0249 trimmedUniv = removeReactions(mergedModel, toRemove); +0250 newModel = mergeModels({newModel; trimmedUniv}); +0251 end +0252 +0253 exitFlag = 1; +0254 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/io/exportForGit.html b/docs/html/io/exportForGit.html index a97b3e93..f864faf7 100644 --- a/docs/html/io/exportForGit.html +++ b/docs/html/io/exportForGit.html @@ -34,6 +34,10 @@

    DESCRIPTION ^DESCRIPTION ^CROSS-REFERENCE INFORMATION ^
 
 <h2><a name=SUBFUNCTIONS ^

    +
  • function s = shortestNumber(x)
  • function version = getToolboxVersion(toolbox,fileID,mainBranchFlag)
  • function slashPos = getSlashPos(path)
  • function flag = checkFlag(flag, name, default)
  • SOURCE CODE ^

    0001 function out=exportForGit(model,varargin)
    @@ -95,54 +100,54 @@ 

    SOURCE CODE ^% model as SBML L3V1 FBCv2 (both XML and YAML), COBRA text, Matlab MAT-file 0007 % and Microsoft Excel formats. 0008 % -0009 % Parameters -0010 % ---------- -0011 % model : struct -0012 % model structure in RAVEN format that should be exported. -0013 % -0014 % Name-Value Arguments -0015 % -------------------- -0016 % prefix : char -0017 % prefix for all filenames (default 'model'). -0018 % path : char -0019 % path where the directory structure should be generated and populated -0020 % with all files (default current working directory). -0021 % formats : cell -0022 % cell array of strings specifying in what file formats the model -0023 % should be exported (default all formats as {'mat', 'txt', 'xlsx', -0024 % 'xml', 'yml'}). -0025 % mainBranchFlag : logical -0026 % if true, function will error if RAVEN (and GECKO for enzyme- -0027 % constrained models) is/are not on the main branch (default false). -0028 % subDirs : logical -0029 % whether model files for each file format should be written in their -0030 % own subdirectory, with 'model' as parent directory, in accordance to -0031 % the standard-GEM repository format. If false, all files are stored in -0032 % the same folder (default true). -0033 % COBRAtext : logical -0034 % whether the txt file should be in COBRA Toolbox format using -0035 % metabolite IDs, instead of metabolite names and compartments -0036 % (default false). -0037 % neverPrefixIDs : logical -0038 % true if prefixes are never added to identifiers, even if they start -0039 % with e.g. digits. This might result in invalid SBML files (default -0040 % false). -0041 % -0042 % Examples -0043 % -------- -0044 % exportForGit(model, prefix, path, formats, mainBranchFlag, subDirs, ... -0045 % COBRAtext, neverPrefixIDs); -0046 p=parseRAVENargs(varargin, {'prefix',[]; 'path',[]; 'formats',[]; 'mainBranchFlag',[]; 'subDirs',[]; 'COBRAtext',[]; 'neverPrefixIDs',false}); -0047 prefix=p.prefix; path=p.path; formats=p.formats; mainBranchFlag=p.mainBranchFlag; subDirs=p.subDirs; COBRAtext=p.COBRAtext; neverPrefixIDs=p.neverPrefixIDs; -0048 if isempty(COBRAtext) -0049 COBRAtext=false; -0050 end -0051 if isempty(subDirs) -0052 subDirs=true; -0053 end -0054 if isempty(mainBranchFlag) -0055 mainBranchFlag=false; -0056 end +0009 % The flags mainBranchFlag, subDirs, COBRAtext and neverPrefixIDs accept a +0010 % scalar logical or 0/1, or an empty value for the default. Text and other +0011 % types raise RAVEN:badInput. +0012 % +0013 % Parameters +0014 % ---------- +0015 % model : struct +0016 % model structure in RAVEN format that should be exported. +0017 % +0018 % Name-Value Arguments +0019 % -------------------- +0020 % prefix : char +0021 % prefix for all filenames (default 'model'). +0022 % path : char +0023 % path where the directory structure should be generated and populated +0024 % with all files (default current working directory). The folder, with +0025 % any missing parent folders, is created if it does not exist. +0026 % formats : cell +0027 % cell array of strings specifying in what file formats the model +0028 % should be exported (default all formats as {'mat', 'txt', 'xlsx', +0029 % 'xml', 'yml'}). +0030 % mainBranchFlag : logical +0031 % if true, function will error if RAVEN (and GECKO for enzyme- +0032 % constrained models) is/are not on the main branch (default false). +0033 % subDirs : logical +0034 % whether model files for each file format should be written in their +0035 % own subdirectory, with 'model' as parent directory, in accordance to +0036 % the standard-GEM repository format. If false, all files are stored in +0037 % the same folder (default true). +0038 % COBRAtext : logical +0039 % whether the txt file should be in COBRA Toolbox format using +0040 % metabolite IDs, instead of metabolite names and compartments +0041 % (default false). +0042 % neverPrefixIDs : logical +0043 % true if prefixes are never added to identifiers, even if they start +0044 % with e.g. digits. This might result in invalid SBML files (default +0045 % false). +0046 % +0047 % Examples +0048 % -------- +0049 % exportForGit(model, prefix, path, formats, mainBranchFlag, subDirs, ... +0050 % COBRAtext, neverPrefixIDs); +0051 p=parseRAVENargs(varargin, {'prefix',[]; 'path',[]; 'formats',[]; 'mainBranchFlag',[]; 'subDirs',[]; 'COBRAtext',[]; 'neverPrefixIDs',false}); +0052 prefix=p.prefix; path=p.path; formats=p.formats; mainBranchFlag=p.mainBranchFlag; subDirs=p.subDirs; COBRAtext=p.COBRAtext; neverPrefixIDs=p.neverPrefixIDs; +0053 COBRAtext=checkFlag(COBRAtext,'COBRAtext',false); +0054 subDirs=checkFlag(subDirs,'subDirs',true); +0055 mainBranchFlag=checkFlag(mainBranchFlag,'mainBranchFlag',false); +0056 neverPrefixIDs=checkFlag(neverPrefixIDs,'neverPrefixIDs',false); 0057 if isempty(formats) 0058 formats={'mat', 'txt', 'xlsx', 'xml', 'yml'}; 0059 else @@ -168,9 +173,9 @@

    SOURCE CODE ^%Get versions or commits of toolboxes. GECKO is only relevant for 0081 %enzyme-constrained models (those with an "ec" field). -0082 RAVENver = getToolboxVersion('RAVEN','ravenCobraWrapper.m',mainBranchFlag); +0082 RAVENver = getToolboxVersion('RAVEN','ravenCobraWrapper.m',mainBranchFlag); 0083 if isfield(model,'ec') -0084 GECKOver = getToolboxVersion('GECKO','GECKOInstaller.m',mainBranchFlag); +0084 GECKOver = getToolboxVersion('GECKO','GECKOInstaller.m',mainBranchFlag); 0085 else 0086 GECKOver = []; 0087 end @@ -187,7 +192,7 @@

    SOURCE CODE ^'tempModelForLibSBMLversion.xml'); 0099 end 0100 -0101 % Make models folder, no warnings if folder already exists +0101 % Make the folders, no warnings if they already exist 0102 if subDirs 0103 path=fullfile(path,'model'); 0104 filePath=strcat(path,filesep,{'txt','yml','mat','xlsx','xml'}); @@ -196,131 +201,169 @@

    SOURCE CODE ^end 0109 else -0110 filePath=cell(1,5); filePath(:)={path}; -0111 end -0112 +0110 [~,~,~]=mkdir(path); +0111 filePath=cell(1,5); filePath(:)={path}; +0112 end 0113 -0114 % Write TXT format -0115 if ismember('txt', formats) -0116 fid=fopen(fullfile(filePath{1},strcat(prefix,'.txt')),'w'); -0117 if COBRAtext==true -0118 eqns=constructEquations(model,model.rxns,false,false,false); -0119 eqns=strrep(eqns,' => ',' -> '); -0120 eqns=strrep(eqns,' <=> ',' <=> '); -0121 eqns=regexprep(eqns,'> $','>'); -0122 grRules=regexprep(model.grRules,'\((?!\()','( '); -0123 grRules=regexprep(grRules,'(?<!\))\)',' )'); -0124 else -0125 eqns=constructEquations(model,model.rxns); -0126 grRules=model.grRules; -0127 end -0128 fprintf(fid, 'Rxn name\tFormula\tGene-reaction association\tLB\tUB\tObjective\n'); -0129 for i = 1:numel(model.rxns) -0130 fprintf(fid, '%s\t', model.rxns{i}); -0131 fprintf(fid, '%s \t', eqns{i}); -0132 fprintf(fid, '%s\t', grRules{i}); -0133 fprintf(fid, '%6.2f\t%6.2f\t%6.2f\n', model.lb(i), model.ub(i), model.c(i)); -0134 end -0135 fclose(fid); -0136 end -0137 -0138 % Write YML format -0139 if ismember('yml', formats) -0140 writeYAMLmodel(model,fullfile(filePath{2},strcat(prefix,'.yml'))); -0141 end -0142 -0143 % Write MAT format -0144 if ismember('mat', formats) -0145 save(fullfile(filePath{3},strcat(prefix,'.mat')),'model'); -0146 end -0147 -0148 % Write XLSX format -0149 if ismember('xlsx', formats) -0150 exportToExcelFormat(model,fullfile(filePath{4},strcat(prefix,'.xlsx'))); -0151 end -0152 -0153 % Write XML format -0154 if ismember('xml', formats) -0155 exportModel(model,fullfile(filePath{5},strcat(prefix,'.xml')),neverPrefixIDs); -0156 end -0157 -0158 %Save file with versions: -0159 fid = fopen(fullfile(path,'dependencies.txt'),'wt'); -0160 fprintf(fid,['MATLAB\t' version '\n']); -0161 fprintf(fid,['libSBML\t' libSBMLver '\n']); -0162 fprintf(fid,['RAVEN_toolbox\t' RAVENver '\n']); -0163 if ~isempty(GECKOver) -0164 fprintf(fid,['GECKO_toolbox\t' GECKOver '\n']); -0165 end -0166 if isfield(model,'modelVersion') -0167 fields = fieldnames(model.modelVersion); -0168 for i = 1:length(fields) -0169 value = model.modelVersion.(fields{i}); -0170 fprintf(fid,[fields{i} '\t' num2str(value) '\n']); -0171 end -0172 end -0173 fclose(fid); -0174 end -0175 -0176 function version = getToolboxVersion(toolbox,fileID,mainBranchFlag) -0177 % getToolboxVersion Return the version of RAVEN or GECKO. -0178 % -0179 % Returns the version of the toolbox as stated in its version.txt, or -0180 % 'unknown' if that file cannot be found. -0181 % -0182 % toolbox name of the toolbox (e.g. 'RAVEN' or 'GECKO'). -0183 % fileID a file only found in that toolbox, used to locate its root -0184 % (e.g. 'ravenCobraWrapper.m' or 'GECKOInstaller.m'). -0185 % mainBranchFlag if true, error if the toolbox is not on the main branch. -0186 currentPath = pwd; -0187 version = ''; -0188 -0189 %Try to find root of toolbox: -0190 try -0191 toolboxPath = which(fileID); %full file path -0192 slashPos = getSlashPos(toolboxPath); -0193 toolboxPath = toolboxPath(1:slashPos(end)); %folder path -0194 %Go up until the root is found: -0195 D = dir(toolboxPath); -0196 while ~ismember({'.git'},{D.name}) -0197 slashPos = getSlashPos(toolboxPath); -0198 toolboxPath = toolboxPath(1:slashPos(end-1)); -0199 D = dir(toolboxPath); -0200 end -0201 cd(toolboxPath); -0202 catch -0203 disp([toolbox ' toolbox cannot be found']) -0204 version = 'unknown'; -0205 end -0206 %Check if in main: -0207 if mainBranchFlag -0208 [~,currentBranch] = system('git rev-parse --abbrev-ref HEAD'); -0209 currentBranch = strtrim(currentBranch); -0210 if ~any([strcmp(currentBranch, "main"), strcmp(currentBranch, "master")]) -0211 cd(currentPath); -0212 error(['ERROR: ' toolbox ' not in main (or master) branch. Check-out this branch of ' toolbox ' before submitting model for Git.']) -0213 end -0214 end -0215 %Read the version from the toolbox version.txt; report 'unknown' otherwise -0216 if isempty(version) -0217 fid = fopen([toolboxPath 'version.txt'],'r'); -0218 if fid ~= -1 -0219 version = fscanf(fid,'%s'); -0220 fclose(fid); -0221 end -0222 if isempty(version) -0223 version = 'unknown'; -0224 end -0225 end -0226 cd(currentPath); -0227 end -0228 -0229 function slashPos = getSlashPos(path) -0230 slashPos = strfind(path,'\'); %Windows -0231 if isempty(slashPos) -0232 slashPos = strfind(path,'/'); %MAC/Linux -0233 end -0234 end

    +0114 +0115 % Write TXT format +0116 if ismember('txt', formats) +0117 fid=fopen(fullfile(filePath{1},strcat(prefix,'.txt')),'w'); +0118 if COBRAtext==true +0119 eqns=constructEquations(model,model.rxns,false,false,false); +0120 eqns=strrep(eqns,' => ',' -> '); +0121 eqns=strrep(eqns,' <=> ',' <=> '); +0122 eqns=regexprep(eqns,'> $','>'); +0123 grRules=regexprep(model.grRules,'\((?!\()','( '); +0124 grRules=regexprep(grRules,'(?<!\))\)',' )'); +0125 else +0126 eqns=constructEquations(model,model.rxns); +0127 grRules=model.grRules; +0128 end +0129 fprintf(fid, 'Rxn id\tFormula\tGene-reaction association\tLB\tUB\tObjective\n'); +0130 for i = 1:numel(model.rxns) +0131 fprintf(fid, '%s\t', model.rxns{i}); +0132 fprintf(fid, '%s\t', strtrim(eqns{i})); +0133 fprintf(fid, '%s\t', grRules{i}); +0134 fprintf(fid, '%s\t%s\t%s\n', shortestNumber(model.lb(i)), shortestNumber(model.ub(i)), shortestNumber(model.c(i))); +0135 end +0136 fclose(fid); +0137 end +0138 +0139 % Write YML format +0140 if ismember('yml', formats) +0141 writeYAMLmodel(model,fullfile(filePath{2},strcat(prefix,'.yml'))); +0142 end +0143 +0144 % Write MAT format +0145 if ismember('mat', formats) +0146 save(fullfile(filePath{3},strcat(prefix,'.mat')),'model'); +0147 end +0148 +0149 % Write XLSX format +0150 if ismember('xlsx', formats) +0151 exportToExcelFormat(model,fullfile(filePath{4},strcat(prefix,'.xlsx'))); +0152 end +0153 +0154 % Write XML format +0155 if ismember('xml', formats) +0156 exportModel(model,fullfile(filePath{5},strcat(prefix,'.xml')),neverPrefixIDs); +0157 end +0158 +0159 %Save file with versions: +0160 fid = fopen(fullfile(path,'dependencies.txt'),'wt'); +0161 fprintf(fid,['MATLAB\t' version '\n']); +0162 fprintf(fid,['libSBML\t' libSBMLver '\n']); +0163 fprintf(fid,['RAVEN_toolbox\t' RAVENver '\n']); +0164 if ~isempty(GECKOver) +0165 fprintf(fid,['GECKO_toolbox\t' GECKOver '\n']); +0166 end +0167 if isfield(model,'modelVersion') +0168 fields = fieldnames(model.modelVersion); +0169 for i = 1:length(fields) +0170 value = model.modelVersion.(fields{i}); +0171 fprintf(fid,[fields{i} '\t' num2str(value) '\n']); +0172 end +0173 end +0174 fclose(fid); +0175 end +0176 +0177 function s = shortestNumber(x) +0178 % shortestNumber The shortest text that reads back as exactly x, so a bound +0179 % is never rounded on the way out (1/3 is 0.3333333333333333, 1000 is 1000). +0180 if isinf(x) +0181 if x > 0 +0182 s = 'Inf'; +0183 else +0184 s = '-Inf'; +0185 end +0186 return +0187 elseif isnan(x) +0188 s = 'NaN'; +0189 return +0190 end +0191 if x == floor(x) && abs(x) < 1e15 +0192 s = sprintf('%d', x); +0193 return +0194 end +0195 for digits = 1:17 +0196 s = sprintf(['%.' num2str(digits) 'g'], x); +0197 if str2double(s) == x +0198 break +0199 end +0200 end +0201 end +0202 +0203 function version = getToolboxVersion(toolbox,fileID,mainBranchFlag) +0204 % getToolboxVersion Return the version of RAVEN or GECKO. +0205 % +0206 % Returns the version of the toolbox as stated in its version.txt, or +0207 % 'unknown' if that file cannot be found. +0208 % +0209 % toolbox name of the toolbox (e.g. 'RAVEN' or 'GECKO'). +0210 % fileID a file only found in that toolbox, used to locate its root +0211 % (e.g. 'ravenCobraWrapper.m' or 'GECKOInstaller.m'). +0212 % mainBranchFlag if true, error if the toolbox is not on the main branch. +0213 currentPath = pwd; +0214 version = ''; +0215 +0216 %Try to find root of toolbox: +0217 try +0218 toolboxPath = which(fileID); %full file path +0219 slashPos = getSlashPos(toolboxPath); +0220 toolboxPath = toolboxPath(1:slashPos(end)); %folder path +0221 %Go up until the root is found: +0222 D = dir(toolboxPath); +0223 while ~ismember({'.git'},{D.name}) +0224 slashPos = getSlashPos(toolboxPath); +0225 toolboxPath = toolboxPath(1:slashPos(end-1)); +0226 D = dir(toolboxPath); +0227 end +0228 cd(toolboxPath); +0229 catch +0230 disp([toolbox ' toolbox cannot be found']) +0231 version = 'unknown'; +0232 end +0233 %Check if in main: +0234 if mainBranchFlag +0235 [~,currentBranch] = system('git rev-parse --abbrev-ref HEAD'); +0236 currentBranch = strtrim(currentBranch); +0237 if ~any([strcmp(currentBranch, "main"), strcmp(currentBranch, "master")]) +0238 cd(currentPath); +0239 error('RAVEN:notMainBranch',['ERROR: ' toolbox ' not in main (or master) branch. Check-out this branch of ' toolbox ' before submitting model for Git.']) +0240 end +0241 end +0242 %Read the version from the toolbox version.txt; report 'unknown' otherwise +0243 if isempty(version) +0244 fid = fopen([toolboxPath 'version.txt'],'r'); +0245 if fid ~= -1 +0246 version = fscanf(fid,'%s'); +0247 fclose(fid); +0248 end +0249 if isempty(version) +0250 version = 'unknown'; +0251 end +0252 end +0253 cd(currentPath); +0254 end +0255 +0256 function slashPos = getSlashPos(path) +0257 slashPos = strfind(path,'\'); %Windows +0258 if isempty(slashPos) +0259 slashPos = strfind(path,'/'); %MAC/Linux +0260 end +0261 end +0262 +0263 function flag = checkFlag(flag, name, default) +0264 %An empty value gives the default; otherwise a scalar logical or 0/1 is required +0265 if isempty(flag) +0266 flag = default; +0267 elseif (islogical(flag) || isnumeric(flag)) && isscalar(flag) && isreal(flag) && (flag == 0 || flag == 1) +0268 flag = logical(flag); +0269 else +0270 error('RAVEN:badInput', '%s must be a scalar logical or 0/1.', name); +0271 end +0272 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/io/readYAMLmodel.html b/docs/html/io/readYAMLmodel.html index d565424d..3f5bb2dd 100644 --- a/docs/html/io/readYAMLmodel.html +++ b/docs/html/io/readYAMLmodel.html @@ -879,146 +879,149 @@

    SOURCE CODE ^% end 0814 0815 if isfield(model,'subSystems') -0816 % If all entries are 1x1, then flatten -0817 if all(cellfun(@(x) numel(x) <= 1, model.subSystems)) -0818 model.subSystems = transpose([model.subSystems{:}]); -0819 end -0820 end -0821 -0822 % Make rxnGeneMat fields and map to the existing model.genes field -0823 if isfield(model,'grRules') -0824 [genes, rxnGeneMat] = getGenesFromGrRules(model.grRules); -0825 model.rxnGeneMat = sparse(numel(model.rxns),numel(model.genes)); -0826 [~,geneOrder] = ismember(genes,model.genes); -0827 if any(geneOrder == 0) -0828 error(['The grRules includes the following gene(s), that are not in '... -0829 'the list of model genes: ', genes{~geneOrder}]) -0830 end -0831 model.rxnGeneMat(:,geneOrder) = rxnGeneMat; -0832 else -0833 % Gene-less model: keep rxnGeneMat dimensionally consistent with the -0834 % number of reactions and genes, rather than leaving it empty. -0835 model.rxnGeneMat = sparse(numel(model.rxns),numel(model.genes)); -0836 end -0837 -0838 % Finalize GECKO model -0839 if isGECKO -0840 % Fill in empty fields and empty entries -0841 for i={'kcat','source','notes','eccodes'} % Even keep empty -0842 model.ec = emptyOrFill(model.ec,i{1},{''},'rxns',true); -0843 end -0844 for i={'enzymes','mw','sequence'} -0845 model.ec = emptyOrFill(model.ec,i{1},{''},'genes',true); +0816 % A reaction without subsystem holds [] or '' here: give it an empty entry, +0817 % so every reaction keeps its place when the entries are flattened +0818 model.subSystems(cellfun(@isempty, model.subSystems)) = {{''}}; +0819 % If all entries are 1x1, then flatten +0820 if all(cellfun(@(x) numel(x) <= 1, model.subSystems)) +0821 model.subSystems = cellfun(@(x) x{1}, model.subSystems, 'UniformOutput', false); +0822 end +0823 end +0824 +0825 % Make rxnGeneMat fields and map to the existing model.genes field +0826 if isfield(model,'grRules') +0827 [genes, rxnGeneMat] = getGenesFromGrRules(model.grRules); +0828 model.rxnGeneMat = sparse(numel(model.rxns),numel(model.genes)); +0829 [~,geneOrder] = ismember(genes,model.genes); +0830 if any(geneOrder == 0) +0831 error(['The grRules includes the following gene(s), that are not in '... +0832 'the list of model genes: ', genes{~geneOrder}]) +0833 end +0834 model.rxnGeneMat(:,geneOrder) = rxnGeneMat; +0835 else +0836 % Gene-less model: keep rxnGeneMat dimensionally consistent with the +0837 % number of reactions and genes, rather than leaving it empty. +0838 model.rxnGeneMat = sparse(numel(model.rxns),numel(model.genes)); +0839 end +0840 +0841 % Finalize GECKO model +0842 if isGECKO +0843 % Fill in empty fields and empty entries +0844 for i={'kcat','source','notes','eccodes'} % Even keep empty +0845 model.ec = emptyOrFill(model.ec,i{1},{''},'rxns',true); 0846 end -0847 model.ec = emptyOrFill(model.ec,'concs',{'NaN'},'genes',true); -0848 model.ec = emptyOrFill(model.ec,'kcat',{'0'},'genes',true); -0849 % Change string to double -0850 for i={'kcat','mw','concs'} -0851 if isfield(model.ec,i{1}) -0852 model.ec.(i{1}) = str2double(model.ec.(i{1})); -0853 end -0854 end -0855 % Fill rxnEnzMat -0856 rxnIdx = cellfun('isempty', enzStoich(:,1)); -0857 enzStoich(rxnIdx,:) = ''; -0858 rxnIdx = cell2mat(enzStoich(:,1)); -0859 [~,enzIdx] = ismember(enzStoich(:,2),model.ec.enzymes); -0860 coeffs = cell2mat(enzStoich(:,3)); -0861 model.ec.rxnEnzMat = zeros(numel(model.ec.rxns), numel(model.ec.genes)); -0862 linearIndices = sub2ind([numel(model.ec.rxns), numel(model.ec.genes)], rxnIdx, enzIdx); -0863 model.ec.rxnEnzMat(linearIndices) = coeffs; -0864 %Parse ec-codes -0865 if ~isempty(ecGecko) -0866 locs = cell2mat(ecGecko(:,1)); -0867 for i=unique(locs)' -0868 ecGeckoCat=strjoin(ecGecko(locs==i,2),';'); -0869 model.ec.eccodes{i,1}=ecGeckoCat; -0870 end -0871 emptyEc=cellfun('isempty',model.ec.eccodes); -0872 model.ec.eccodes(emptyEc)={''}; -0873 end -0874 end -0875 -0876 if verbose -0877 fprintf(' Done!\n'); -0878 end -0879 end -0880 -0881 function model = emptyOrFill(model,field,emptyEntry,type,keepEmpty) -0882 if nargin<5 -0883 keepEmpty=false; -0884 end -0885 if isnumeric(emptyEntry) -0886 emptyCells=isempty(model.(field)); -0887 else -0888 emptyCells=cellfun('isempty',model.(field)); -0889 end -0890 if all(emptyCells) && ~keepEmpty -0891 model = rmfield(model, field); -0892 elseif numel(model.(field))<numel(model.(type)) -0893 model.(field)(end+1:numel(model.(type)),1)=emptyEntry; -0894 end -0895 end -0896 -0897 function model = readFieldValue(model, fieldName, value, pos) -0898 if numel(model.(fieldName))<pos-1 -0899 model.(fieldName)(end+1:pos,1) = {''}; -0900 end -0901 model.(fieldName)(pos,1) = {value}; -0902 end -0903 -0904 function model = appendNote(model, fieldName, raw, key, value, pos) -0905 % One line of a notes map: a 'note' entry adds its value (RAVEN's own note -0906 % text), another entry adds 'key: value', a list item adds its value to the -0907 % entry above it. Entries are separated by '; ', list items by ', '. A -0908 % quoted list item is read whole, as the key detection would split it at -0909 % a ': ' inside the quotes. -0910 item = regexp(raw, '^ +- ([''"])(.*)\1$', 'tokens', 'once'); -0911 if ~isempty(item) -0912 key = ''; -0913 value = item{2}; -0914 end -0915 if strcmp(value, '!!omap') -0916 value = ''; +0847 for i={'enzymes','mw','sequence'} +0848 model.ec = emptyOrFill(model.ec,i{1},{''},'genes',true); +0849 end +0850 model.ec = emptyOrFill(model.ec,'concs',{'NaN'},'genes',true); +0851 model.ec = emptyOrFill(model.ec,'kcat',{'0'},'genes',true); +0852 % Change string to double +0853 for i={'kcat','mw','concs'} +0854 if isfield(model.ec,i{1}) +0855 model.ec.(i{1}) = str2double(model.ec.(i{1})); +0856 end +0857 end +0858 % Fill rxnEnzMat +0859 rxnIdx = cellfun('isempty', enzStoich(:,1)); +0860 enzStoich(rxnIdx,:) = ''; +0861 rxnIdx = cell2mat(enzStoich(:,1)); +0862 [~,enzIdx] = ismember(enzStoich(:,2),model.ec.enzymes); +0863 coeffs = cell2mat(enzStoich(:,3)); +0864 model.ec.rxnEnzMat = zeros(numel(model.ec.rxns), numel(model.ec.genes)); +0865 linearIndices = sub2ind([numel(model.ec.rxns), numel(model.ec.genes)], rxnIdx, enzIdx); +0866 model.ec.rxnEnzMat(linearIndices) = coeffs; +0867 %Parse ec-codes +0868 if ~isempty(ecGecko) +0869 locs = cell2mat(ecGecko(:,1)); +0870 for i=unique(locs)' +0871 ecGeckoCat=strjoin(ecGecko(locs==i,2),';'); +0872 model.ec.eccodes{i,1}=ecGeckoCat; +0873 end +0874 emptyEc=cellfun('isempty',model.ec.eccodes); +0875 model.ec.eccodes(emptyEc)={''}; +0876 end +0877 end +0878 +0879 if verbose +0880 fprintf(' Done!\n'); +0881 end +0882 end +0883 +0884 function model = emptyOrFill(model,field,emptyEntry,type,keepEmpty) +0885 if nargin<5 +0886 keepEmpty=false; +0887 end +0888 if isnumeric(emptyEntry) +0889 emptyCells=isempty(model.(field)); +0890 else +0891 emptyCells=cellfun('isempty',model.(field)); +0892 end +0893 if all(emptyCells) && ~keepEmpty +0894 model = rmfield(model, field); +0895 elseif numel(model.(field))<numel(model.(type)) +0896 model.(field)(end+1:numel(model.(type)),1)=emptyEntry; +0897 end +0898 end +0899 +0900 function model = readFieldValue(model, fieldName, value, pos) +0901 if numel(model.(fieldName))<pos-1 +0902 model.(fieldName)(end+1:pos,1) = {''}; +0903 end +0904 model.(fieldName)(pos,1) = {value}; +0905 end +0906 +0907 function model = appendNote(model, fieldName, raw, key, value, pos) +0908 % One line of a notes map: a 'note' entry adds its value (RAVEN's own note +0909 % text), another entry adds 'key: value', a list item adds its value to the +0910 % entry above it. Entries are separated by '; ', list items by ', '. A +0911 % quoted list item is read whole, as the key detection would split it at +0912 % a ': ' inside the quotes. +0913 item = regexp(raw, '^ +- ([''"])(.*)\1$', 'tokens', 'once'); +0914 if ~isempty(item) +0915 key = ''; +0916 value = item{2}; 0917 end -0918 text = ''; -0919 if numel(model.(fieldName)) >= pos -0920 text = model.(fieldName){pos}; -0921 end -0922 if isempty(key) -0923 if isempty(text) || endsWith(text, ':') -0924 text = strtrim([text ' ' value]); -0925 else -0926 text = [text ', ' value]; -0927 end -0928 else -0929 if isempty(value) -0930 entry = [key ':']; -0931 elseif strcmp(key, 'note') -0932 entry = value; -0933 else -0934 entry = [key ': ' value]; -0935 end -0936 if isempty(text) -0937 text = entry; -0938 else -0939 text = [text '; ' entry]; -0940 end -0941 end -0942 model = readFieldValue(model, fieldName, text, pos); -0943 end -0944 -0945 function [miriams,miriamKey,entryNumber] = gatherAnnotation(pos,miriams,key,value,miriamKey,entryNumber) -0946 if isempty(key) -0947 key=miriamKey; -0948 else -0949 miriamKey=key; -0950 end -0951 if ~isempty(value) -0952 miriams(entryNumber,1:3) = {pos, key, strip(value)}; -0953 entryNumber = entryNumber + 1; -0954 end -0955 end +0918 if strcmp(value, '!!omap') +0919 value = ''; +0920 end +0921 text = ''; +0922 if numel(model.(fieldName)) >= pos +0923 text = model.(fieldName){pos}; +0924 end +0925 if isempty(key) +0926 if isempty(text) || endsWith(text, ':') +0927 text = strtrim([text ' ' value]); +0928 else +0929 text = [text ', ' value]; +0930 end +0931 else +0932 if isempty(value) +0933 entry = [key ':']; +0934 elseif strcmp(key, 'note') +0935 entry = value; +0936 else +0937 entry = [key ': ' value]; +0938 end +0939 if isempty(text) +0940 text = entry; +0941 else +0942 text = [text '; ' entry]; +0943 end +0944 end +0945 model = readFieldValue(model, fieldName, text, pos); +0946 end +0947 +0948 function [miriams,miriamKey,entryNumber] = gatherAnnotation(pos,miriams,key,value,miriamKey,entryNumber) +0949 if isempty(key) +0950 key=miriamKey; +0951 else +0952 miriamKey=key; +0953 end +0954 if ~isempty(value) +0955 miriams(entryNumber,1:3) = {pos, key, strip(value)}; +0956 entryNumber = entryNumber + 1; +0957 end +0958 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/manipulation/contractModel.html b/docs/html/manipulation/contractModel.html index 5b56454c..0e35eda2 100644 --- a/docs/html/manipulation/contractModel.html +++ b/docs/html/manipulation/contractModel.html @@ -76,7 +76,7 @@

    DESCRIPTION ^CROSS-REFERENCE INFORMATION ^

    This function calls:
      -
    • removeReactions removeReactions Delete a set of reactions from a model.
    • sortModel sortModel Standardize a model's internal ordering: by default orients
    • standardizeGrRules standardizeGrRules Standardize gene-reaction rules in a model.
    +
  • removeReactions removeReactions Delete a set of reactions from a model.
  • sortModel sortModel Standardize a model's internal ordering: can orient
  • standardizeGrRules standardizeGrRules Standardize gene-reaction rules in a model.
  • This function is called by:
    • mergeCompartments mergeCompartments Merge all compartments in a model.
    • replaceMets replaceMets Replace a metabolite with another already in the model.
    • simplifyModel simplifyModel Simplify a model by deleting reactions and metabolites.
    @@ -139,7 +139,7 @@

    SOURCE CODE ^%First sort the model so that reversible reactions are in the same 0052 %direction -0053 modelS=sortModel(model); +0053 modelS=sortModel(model,true); 0054 0055 %Get a list of duplicate reactions 0056 if distReverse diff --git a/docs/html/manipulation/generateNewIds.html b/docs/html/manipulation/generateNewIds.html index 763a130c..67af47e2 100644 --- a/docs/html/manipulation/generateNewIds.html +++ b/docs/html/manipulation/generateNewIds.html @@ -30,7 +30,10 @@

    DESCRIPTION ^
     generateNewIds  Generate a list of new metabolite or reaction ids.
     
      The ids are sequentially numbered with a defined prefix. The model is
    - queried for the highest existing number of that type of id.
    + queried for the highest existing number of that type of id. Suffixes are
    + read with str2double. A suffix counts as a number when the result is a
    + finite integer value below 2^52; text, Inf, NaN, fractions (r_2.5) and
    + numbers of 2^52 or more (r_1e300, r_1e400) do not count.
     
      Parameters
      ----------
    @@ -77,77 +80,89 @@ 

    SOURCE CODE ^% generateNewIds Generate a list of new metabolite or reaction ids. 0003 % 0004 % The ids are sequentially numbered with a defined prefix. The model is -0005 % queried for the highest existing number of that type of id. -0006 % -0007 % Parameters -0008 % ---------- -0009 % model : struct -0010 % model structure. -0011 % type : char -0012 % type of id, 'rxns' or 'mets'. -0013 % prefix : char -0014 % prefix to be used in all ids, e.g. 's_' or 'r_'. -0015 % -0016 % Name-Value Arguments -0017 % -------------------- -0018 % quantity : double -0019 % number of new ids that should be generated (default 1). -0020 % numLength : double -0021 % length of the numerical part of the id. E.g. 4 gives ids like r_0001 -0022 % and 6 gives ids like r_000001. If the prefix is already used in the -0023 % model, then the model-defined length will be used instead -0024 % (default 4). -0025 % -0026 % Returns -0027 % ------- -0028 % newIds : cell -0029 % cell array with the generated ids. -0030 % -0031 % Examples -0032 % -------- -0033 % newIds = generateNewIds(model, type, prefix, quantity, numLength); -0034 type=char(type); -0035 prefix=char(prefix); -0036 -0037 if type=='rxns' -0038 existingIds=model.rxns; -0039 elseif type=='mets' -0040 existingIds=model.mets; -0041 else -0042 error('type should be either ''rxns'' or ''mets''.') -0043 end -0044 p=parseRAVENargs(varargin, {'quantity',1; 'numLength',4}); -0045 quantity=p.quantity; -0046 numLength=p.numLength; -0047 -0048 % Subset only existingIds that have the prefix. Escaped: prefix is -0049 % arbitrary caller-supplied text, not a regex pattern. -0050 escapedPrefix=regexptranslate('escape',prefix); -0051 existingIds=existingIds(~cellfun(@isempty,regexp(existingIds,['^' escapedPrefix]))); -0052 -0053 if ~isempty(existingIds) -0054 existingIds=regexprep(existingIds,['^' escapedPrefix],''); -0055 existingIds=sort(existingIds); -0056 lastId=existingIds{end}; -0057 numLength=length(lastId); -0058 lastId=str2double(lastId); -0059 else -0060 lastId=0; -0061 fprintf(['No ' type ' ids with prefix "' prefix ... -0062 '" currently exist in the model. The first new id will be "' ... -0063 [prefix,num2str(1,['%0' num2str(numLength) 'd'])] '"\n'],'%s') -0064 end -0065 -0066 if isnan(lastId) -0067 lastId = 0; -0068 end -0069 -0070 newIds=cell(quantity,1); -0071 -0072 for k=1:quantity -0073 newIds{k}=[prefix,num2str(k+lastId,['%0' num2str(numLength) 'd'])]; -0074 end -0075 end

    +0005 % queried for the highest existing number of that type of id. Suffixes are +0006 % read with str2double. A suffix counts as a number when the result is a +0007 % finite integer value below 2^52; text, Inf, NaN, fractions (r_2.5) and +0008 % numbers of 2^52 or more (r_1e300, r_1e400) do not count. +0009 % +0010 % Parameters +0011 % ---------- +0012 % model : struct +0013 % model structure. +0014 % type : char +0015 % type of id, 'rxns' or 'mets'. +0016 % prefix : char +0017 % prefix to be used in all ids, e.g. 's_' or 'r_'. +0018 % +0019 % Name-Value Arguments +0020 % -------------------- +0021 % quantity : double +0022 % number of new ids that should be generated (default 1). +0023 % numLength : double +0024 % length of the numerical part of the id. E.g. 4 gives ids like r_0001 +0025 % and 6 gives ids like r_000001. If the prefix is already used in the +0026 % model, then the model-defined length will be used instead +0027 % (default 4). +0028 % +0029 % Returns +0030 % ------- +0031 % newIds : cell +0032 % cell array with the generated ids. +0033 % +0034 % Examples +0035 % -------- +0036 % newIds = generateNewIds(model, type, prefix, quantity, numLength); +0037 type=char(type); +0038 prefix=char(prefix); +0039 +0040 if type=='rxns' +0041 existingIds=model.rxns; +0042 elseif type=='mets' +0043 existingIds=model.mets; +0044 else +0045 error('type should be either ''rxns'' or ''mets''.') +0046 end +0047 p=parseRAVENargs(varargin, {'quantity',1; 'numLength',4}); +0048 quantity=p.quantity; +0049 numLength=p.numLength; +0050 +0051 % Subset only existingIds that have the prefix. Escaped: prefix is +0052 % arbitrary caller-supplied text, not a regex pattern. +0053 escapedPrefix=regexptranslate('escape',prefix); +0054 existingIds=existingIds(~cellfun(@isempty,regexp(existingIds,['^' escapedPrefix]))); +0055 +0056 if ~isempty(existingIds) +0057 existingIds=regexprep(existingIds,['^' escapedPrefix],''); +0058 numbers=str2double(existingIds); +0059 notInteger=~isfinite(numbers) | imag(numbers)~=0 | real(numbers)~=floor(real(numbers)) | abs(real(numbers))>=flintmax/2; +0060 numbers=real(numbers); +0061 numbers(notInteger)=NaN; +0062 if all(isnan(numbers)) +0063 [~,I]=sort(existingIds); +0064 lastId=existingIds{I(end)}; +0065 numLength=length(lastId); +0066 lastId=NaN; +0067 else +0068 [lastId,I]=max(numbers); +0069 numLength=length(existingIds{I}); +0070 end +0071 else +0072 lastId=0; +0073 fprintf(['No ' type ' ids with prefix "' prefix ... +0074 '" currently exist in the model. The first new id will be "' ... +0075 [prefix,num2str(1,['%0' num2str(numLength) 'd'])] '"\n'],'%s') +0076 end +0077 +0078 if isnan(lastId) +0079 lastId = 0; +0080 end +0081 +0082 newIds=cell(quantity,1); +0083 +0084 for k=1:quantity +0085 newIds{k}=[prefix,num2str(k+lastId,['%0' num2str(numLength) 'd'])]; +0086 end +0087 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/manipulation/index.html b/docs/html/manipulation/index.html index 60619481..2787ef16 100644 --- a/docs/html/manipulation/index.html +++ b/docs/html/manipulation/index.html @@ -19,7 +19,7 @@

    Index for manipulation

    Matlab files in this directory:

    -
     addExchangeRxnsaddExchangeRxns Add exchange reactions for some metabolites.
     addGenesRavenaddGenesRaven Add genes to a model.
     addMetsaddMets Add metabolites to a model.
     addRxnsaddRxns Add reactions to a model.
     addRxnsGenesMetsaddRxnsGenesMets Copy reactions from a source model into another model.
     addTransportaddTransport Add transport reactions between compartments.
     changeGrRuleschangeGrRules Change multiple grRules at the same time.
     changeRxnschangeRxns Modify the equations of reactions in a model.
     closeModelcloseModel Add boundary metabolites and their exchange reactions.
     contractModelcontractModel Contract a model by grouping all identical reactions.
     convertToIrrevconvertToIrrev Convert a model to irreversible form.
     copyToCompscopyToComps Copy reactions to new compartment(s).
     deleteUnusedGenesdeleteUnusedGenes Delete all genes not associated to any reaction.
     expandModelexpandModel Expand reactions that use several gene associations.
     findDuplicateRxnsfindDuplicateRxns Find reactions that share identical stoichiometry.
     findPotentialErrorsfindPotentialErrors Identify reactions with non-DNF GPR rules.
     generateNewIdsgenerateNewIds Generate a list of new metabolite or reaction ids.
     mergeCompartmentsmergeCompartments Merge all compartments in a model.
     mergeModelsmergeModels Merge models into one model structure.
     permuteModelpermuteModel Change the order of the reactions or metabolites in a model.
     removeBadRxnsremoveBadRxns Remove reactions that enable production/consumption from nothing.
     removeGenesremoveGenes Delete a set of genes from a model.
     removeLowScoreGenesremoveLowScoreGenes Remove low-scoring genes from model.
     removeMetsremoveMets Delete a set of metabolites from a model.
     removeReactionsremoveReactions Delete a set of reactions from a model.
     replaceMetsreplaceMets Replace a metabolite with another already in the model.
     reverseRxnsreverseRxns
     setExchangeBoundssetExchangeBounds Define exchange flux bounds for a set of metabolites.
     setParamsetParam Set parameters for reactions.
     simplifyModelsimplifyModel Simplify a model by deleting reactions and metabolites.
     sortIdentifierssortIdentifiers Sort model identifiers alphabetically.
     sortModelsortModel Standardize a model's internal ordering: by default orients
     standardizeGrRulesstandardizeGrRules Standardize gene-reaction rules in a model.
    + addExchangeRxnsaddExchangeRxns Add exchange reactions for some metabolites.  addGenesRavenaddGenesRaven Add genes to a model.  addMetsaddMets Add metabolites to a model.  addRxnsaddRxns Add reactions to a model.  addRxnsGenesMetsaddRxnsGenesMets Copy reactions from a source model into another model.  addTransportaddTransport Add transport reactions between compartments.  changeGrRuleschangeGrRules Change multiple grRules at the same time.  changeRxnschangeRxns Modify the equations of reactions in a model.  closeModelcloseModel Add boundary metabolites and their exchange reactions.  contractModelcontractModel Contract a model by grouping all identical reactions.  convertToIrrevconvertToIrrev Convert a model to irreversible form.  copyToCompscopyToComps Copy reactions to new compartment(s).  deleteUnusedGenesdeleteUnusedGenes Delete all genes not associated to any reaction.  expandModelexpandModel Expand reactions that use several gene associations.  findDuplicateRxnsfindDuplicateRxns Find reactions that share identical stoichiometry.  findPotentialErrorsfindPotentialErrors Identify reactions with non-DNF GPR rules.  generateNewIdsgenerateNewIds Generate a list of new metabolite or reaction ids.  mergeCompartmentsmergeCompartments Merge all compartments in a model.  mergeModelsmergeModels Merge models into one model structure.  permuteModelpermuteModel Change the order of the reactions or metabolites in a model.  removeBadRxnsremoveBadRxns Remove reactions that enable production/consumption from nothing.  removeGenesremoveGenes Delete a set of genes from a model.  removeLowScoreGenesremoveLowScoreGenes Remove low-scoring genes from model.  removeMetsremoveMets Delete a set of metabolites from a model.  removeReactionsremoveReactions Delete a set of reactions from a model.  replaceMetsreplaceMets Replace a metabolite with another already in the model.  reverseRxnsreverseRxns  setExchangeBoundssetExchangeBounds Define exchange flux bounds for a set of metabolites.  setParamsetParam Set parameters for reactions.  simplifyModelsimplifyModel Simplify a model by deleting reactions and metabolites.  sortIdentifierssortIdentifiers Sort model identifiers alphabetically.  sortModelsortModel Standardize a model's internal ordering: can orient  standardizeGrRulesstandardizeGrRules Standardize gene-reaction rules in a model. diff --git a/docs/html/manipulation/permuteModel.html b/docs/html/manipulation/permuteModel.html index 716ee440..24723ecc 100644 --- a/docs/html/manipulation/permuteModel.html +++ b/docs/html/manipulation/permuteModel.html @@ -56,7 +56,7 @@

    CROSS-REFERENCE INFORMATION ^
 </ul>
 This function is called by:
 <ul style= -
  • changeRxns changeRxns Modify the equations of reactions in a model.
  • sortIdentifiers sortIdentifiers Sort model identifiers alphabetically.
  • sortModel sortModel Standardize a model's internal ordering: by default orients
  • +
  • changeRxns changeRxns Modify the equations of reactions in a model.
  • sortIdentifiers sortIdentifiers Sort model identifiers alphabetically.
  • sortModel sortModel Standardize a model's internal ordering: can orient
  • diff --git a/docs/html/manipulation/sortModel.html b/docs/html/manipulation/sortModel.html index 90e1a67a..42401098 100644 --- a/docs/html/manipulation/sortModel.html +++ b/docs/html/manipulation/sortModel.html @@ -4,7 +4,7 @@ Description of sortModel - + @@ -21,13 +21,13 @@

    sortModel

    PURPOSE ^

    -
    sortModel Standardize a model's internal ordering: by default orients
    +
    sortModel Standardize a model's internal ordering: can orient

    SYNOPSIS ^

    function model=sortModel(model,varargin)

    DESCRIPTION ^

    -
     sortModel  Standardize a model's internal ordering: by default orients
    +
     sortModel  Standardize a model's internal ordering: can orient
      reversible reactions consistently; can also reorder metabolites within
      reaction equations and/or reactions within each subsystem, depending on
      which options are enabled.
    @@ -41,7 +41,7 @@ 

    DESCRIPTION ^CROSS-REFERENCE INFORMATION ^
 
 <h2><a name=SOURCE CODE ^

    0001 function model=sortModel(model,varargin)
    -0002 % sortModel  Standardize a model's internal ordering: by default orients
    +0002 % sortModel  Standardize a model's internal ordering: can orient
     0003 % reversible reactions consistently; can also reorder metabolites within
     0004 % reaction equations and/or reactions within each subsystem, depending on
     0005 % which options are enabled.
    @@ -93,7 +93,7 @@ 

    SOURCE CODE ^% -------------------- 0014 % sortReversible : logical 0015 % sorts the reversible reactions so that the metabolite that is first -0016 % in lexicographical order is a reactant (default true). +0016 % in lexicographical order is a reactant (default false). 0017 % sortMetName : logical 0018 % sort the metabolite names in the equation, also uses compartment 0019 % abbreviation (default false). @@ -117,7 +117,7 @@

    SOURCE CODE ^% -------- 0038 % model = sortModel(model, sortReversible, sortMetName, sortReactionOrder); 0039 -0040 p=parseRAVENargs(varargin, {'sortReversible',true; 'sortMetName',false; 'sortReactionOrder',false; 'seed',[]}); +0040 p=parseRAVENargs(varargin, {'sortReversible',false; 'sortMetName',false; 'sortReactionOrder',false; 'seed',[]}); 0041 if ~isempty(p.seed) 0042 rng(p.seed); 0043 end diff --git a/docs/html/queries/constructEquations.html b/docs/html/queries/constructEquations.html index 1e0994eb..e407d806 100644 --- a/docs/html/queries/constructEquations.html +++ b/docs/html/queries/constructEquations.html @@ -149,7 +149,7 @@

    SOURCE CODE ^%Sort reversible equations 0063 if sortRevRxns==true -0064 model=sortModel(model); +0064 model=sortModel(model,true); 0065 end 0066 0067 %Sort metabolite names, including compartment diff --git a/docs/html/reconstruction/homology/getModelFromHomology.html b/docs/html/reconstruction/homology/getModelFromHomology.html index d0a4b1da..bc19f0a5 100644 --- a/docs/html/reconstruction/homology/getModelFromHomology.html +++ b/docs/html/reconstruction/homology/getModelFromHomology.html @@ -655,364 +655,366 @@

    SOURCE CODE ^%Remove all the genes that were already found and add the other 0495 %ones to allUsedGenes -0496 models{useOrderIndexes(i)}=removeGenes(models{useOrderIndexes(i)},allGenes{i+1}(genesToDelete),true,true,false); -0497 allUsedGenes(usedGenes)=true; -0498 -0499 %Remove the deleted genes from finalMappings and allGenes. -0500 finalMappings{i}(:,genesToDelete)=[]; -0501 allGenes{i+1}(genesToDelete)=[]; -0502 end -0503 end -0504 -0505 %Now loop through the models and update the gene associations. Genes not -0506 %belonging to the new organism will be renamed as "OLD_MODELID_gene" -0507 for i=1:numel(models) -0508 %Find all the new genes that should be used for this model -0509 [newGenes, oldGenes]=find(finalMappings{i}); -0510 -0511 %Create a new gene list with the genes from the new organism and those -0512 %genes that could not be removed -0513 replaceableGenes=allGenes{i+1}(unique(oldGenes)); -0514 nonReplaceableGenes=setdiff(models{useOrderIndexes(i)}.genes,replaceableGenes); -0515 fullGeneList=[allGenes{1}(unique(newGenes));nonReplaceableGenes]; -0516 -0517 %Just to save some indexing later. This is the LAST index of -0518 %replaceable ones -0519 nonRepStartIndex=numel(unique(newGenes)); -0520 -0521 %Construct a new rxnGeneMat -0522 newRxnGeneMat=sparse(numel(models{useOrderIndexes(i)}.rxns),numel(fullGeneList)); -0523 -0524 %Now update the rxnGeneMat. This is a little tricky and could -0525 %probably be done in a more efficient way, but I just loop through the -0526 %reactions and add them one by one -0527 for j=1:numel(models{useOrderIndexes(i)}.rxns) -0528 %Get the old genes encoding for this reaction -0529 [~, oldGeneIds]=find(models{useOrderIndexes(i)}.rxnGeneMat(j,:)); -0530 -0531 %Tokenize the grRule once; all gene substitutions operate on the -0532 %token list so that no gene ID can partially match another. -0533 tokens=gprTokenize(models{useOrderIndexes(i)}.grRules{j}); -0534 -0535 %Update the matrix for each gene. This includes replacing one gene -0536 %with several new ones if there were several matches -0537 for k=1:numel(oldGeneIds) -0538 %Match the gene to one in the gene list. This is done as a text -0539 %match. Could probably be done better, but the indexing is -0540 %complex -0541 -0542 geneName=models{useOrderIndexes(i)}.genes(oldGeneIds(k)); +0496 if ~isempty(genesToDelete) +0497 models{useOrderIndexes(i)}=removeGenes(models{useOrderIndexes(i)},allGenes{i+1}(genesToDelete),true,true,false); +0498 end +0499 allUsedGenes(usedGenes)=true; +0500 +0501 %Remove the deleted genes from finalMappings and allGenes. +0502 finalMappings{i}(:,genesToDelete)=[]; +0503 allGenes{i+1}(genesToDelete)=[]; +0504 end +0505 end +0506 +0507 %Now loop through the models and update the gene associations. Genes not +0508 %belonging to the new organism will be renamed as "OLD_MODELID_gene" +0509 for i=1:numel(models) +0510 %Find all the new genes that should be used for this model +0511 [newGenes, oldGenes]=find(finalMappings{i}); +0512 +0513 %Create a new gene list with the genes from the new organism and those +0514 %genes that could not be removed +0515 replaceableGenes=allGenes{i+1}(unique(oldGenes)); +0516 nonReplaceableGenes=setdiff(models{useOrderIndexes(i)}.genes,replaceableGenes); +0517 fullGeneList=[allGenes{1}(unique(newGenes));nonReplaceableGenes]; +0518 +0519 %Just to save some indexing later. This is the LAST index of +0520 %replaceable ones +0521 nonRepStartIndex=numel(unique(newGenes)); +0522 +0523 %Construct a new rxnGeneMat +0524 newRxnGeneMat=sparse(numel(models{useOrderIndexes(i)}.rxns),numel(fullGeneList)); +0525 +0526 %Now update the rxnGeneMat. This is a little tricky and could +0527 %probably be done in a more efficient way, but I just loop through the +0528 %reactions and add them one by one +0529 for j=1:numel(models{useOrderIndexes(i)}.rxns) +0530 %Get the old genes encoding for this reaction +0531 [~, oldGeneIds]=find(models{useOrderIndexes(i)}.rxnGeneMat(j,:)); +0532 +0533 %Tokenize the grRule once; all gene substitutions operate on the +0534 %token list so that no gene ID can partially match another. +0535 tokens=gprTokenize(models{useOrderIndexes(i)}.grRules{j}); +0536 +0537 %Update the matrix for each gene. This includes replacing one gene +0538 %with several new ones if there were several matches +0539 for k=1:numel(oldGeneIds) +0540 %Match the gene to one in the gene list. This is done as a text +0541 %match. Could probably be done better, but the indexing is +0542 %complex 0543 -0544 %First search in the mappable genes -0545 mapIndex=find(ismember(allGenes{i+1},geneName)); -0546 -0547 if ~isempty(mapIndex) -0548 % add the old genes -0549 hitGenes.oldGenes = [hitGenes.oldGenes, {geneName}]; -0550 -0551 %Get the new genes for that gene -0552 a=find(finalMappings{i}(:,mapIndex)); -0553 -0554 %Find the positions of these genes in the final gene list -0555 [~, b]=ismember(allGenes{1}(a),fullGeneList); -0556 -0557 %Update the matrix -0558 newRxnGeneMat(j,b)=1; -0559 -0560 %Build repString for hitGenes (display only) -0561 repString=fullGeneList{b(1)}; -0562 if numel(b)>1 -0563 for l=2:numel(b) -0564 repString=[repString ' or ' fullGeneList{b(l)}]; %#ok<AGROW> -0565 end -0566 repString=['(' repString ')']; -0567 end -0568 hitGenes.newGenes = [hitGenes.newGenes, {repString}]; -0569 -0570 %Replace the old gene token with the new gene(s) -0571 tokens=gprReplaceToken(tokens,geneName{1},fullGeneList(b)); -0572 else -0573 %Then search in the non-replaceable genes. There could only -0574 %be one match here -0575 index=find(ismember(nonReplaceableGenes,geneName)); -0576 -0577 %Update the matrix -0578 newRxnGeneMat(j,nonRepStartIndex+index)=1; -0579 -0580 oldName=strcat('OLD_',models{useOrderIndexes(i)}.id,'_',geneName{1}); -0581 tokens=gprReplaceToken(tokens,geneName{1},{oldName}); -0582 end -0583 end -0584 -0585 models{useOrderIndexes(i)}.grRules{j}=gprBuildRule(tokens); -0586 end -0587 -0588 %Add the new list of genes -0589 models{useOrderIndexes(i)}.rxnGeneMat=newRxnGeneMat; -0590 if ~isempty(nonReplaceableGenes) -0591 models{useOrderIndexes(i)}.genes=[allGenes{1}(unique(newGenes));strcat('OLD_',models{useOrderIndexes(i)}.id,'_',nonReplaceableGenes)]; -0592 else -0593 models{useOrderIndexes(i)}.genes=allGenes{1}(unique(newGenes)); -0594 end -0595 if isfield(models{useOrderIndexes(i)},'geneComps') -0596 geneComps=models{useOrderIndexes(i)}.geneComps(1); -0597 models{useOrderIndexes(i)}.geneComps=zeros(numel(models{useOrderIndexes(i)}.genes),1); -0598 %Assume that all genes are in the same compartment, and this -0599 %compartment is specified for the first gene -0600 models{useOrderIndexes(i)}.geneComps(:)=geneComps; -0601 end -0602 end -0603 -0604 %Now merge the models. All information should be correct except for "or" -0605 %complexes -0606 draftModel=mergeModels(models,'metNames'); -0607 -0608 %Remove OLD_ genes that appear only in OR branches (they were added for -0609 %AND-complex genes with no orthologs; in OR branches they are redundant). -0610 %Uses token-level removal to avoid partial-match hazards. -0611 for oldI=1:numel(draftModel.grRules) -0612 if ~contains(draftModel.grRules{oldI},'OLD_') -0613 continue -0614 end -0615 tkns=gprTokenize(draftModel.grRules{oldI}); -0616 again=true; -0617 while again -0618 again=false; -0619 for kk=1:numel(tkns) -0620 if startsWith(tkns{kk},'OLD_') -0621 if kk>1 && strcmp(tkns{kk-1},'or') -0622 tkns(kk-1:kk)=[]; -0623 again=true; break -0624 elseif kk<numel(tkns) && strcmp(tkns{kk+1},'or') -0625 tkns(kk:kk+1)=[]; -0626 again=true; break -0627 end -0628 end -0629 end -0630 end -0631 draftModel.grRules{oldI}=gprBuildRule(tkns); -0632 end -0633 -0634 %Apply the complex policy to the OLD_ genes left in "and" relations, and -0635 %write each grRule without repeated genes or redundant brackets -0636 toRemove=false(numel(draftModel.rxns),1); -0637 for j=1:numel(draftModel.grRules) -0638 rule=draftModel.grRules{j}; -0639 if isempty(rule) -0640 continue -0641 end -0642 if contains(rule,'OLD_') && strcmp(complexPolicy,'drop') -0643 toRemove(j)=true; -0644 elseif contains(rule,'OLD_') && strcmp(complexPolicy,'keep') -0645 draftModel.grRules{j}=gprSimplify(rule,'OLD_'); -0646 toRemove(j)=isempty(draftModel.grRules{j}); -0647 else -0648 draftModel.grRules{j}=gprSimplify(rule,''); -0649 end -0650 end -0651 if any(toRemove) -0652 draftModel=removeReactions(draftModel,draftModel.rxns(toRemove),true,true,true); -0653 end -0654 -0655 %Change name of the resulting model -0656 draftModel.id=getModelFor; -0657 name='Generated by getModelFromHomology using '; -0658 for i=1:numel(models) -0659 if i<numel(models) -0660 name=[name models{i}.id ', ']; -0661 else -0662 name=[name models{i}.id]; -0663 end -0664 end -0665 draftModel.name=name; -0666 %The draft is a new model: the templates' version, date and description are -0667 %not its own -0668 draftModel=rmfield(draftModel,intersect({'version','date','description'},fieldnames(draftModel))); -0669 if isfield(draftModel,'annotation') -0670 descriptive={'taxonomy','sourceUrl','note','givenName','familyName','authors','email','organization'}; -0671 draftModel.annotation=rmfield(draftModel.annotation,intersect(descriptive,fieldnames(draftModel.annotation))); -0672 end -0673 if ~preserveNotes || ~isfield(draftModel,'rxnNotes') -0674 draftModel.rxnNotes=cell(length(draftModel.rxns),1); -0675 draftModel.rxnNotes(:)={'Included by getModelFromHomology'}; -0676 end -0677 if ~preserveNotes || ~isfield(draftModel,'rxnConfidenceScores') -0678 draftModel.rxnConfidenceScores=NaN(length(draftModel.rxns),1); -0679 draftModel.rxnConfidenceScores(:)=2; -0680 end -0681 draftModel=deleteUnusedGenes(draftModel,0); -0682 %Standardize grRules and notify if problematic grRules are found -0683 [draftModel.grRules,draftModel.rxnGeneMat]=standardizeGrRules(draftModel,false); -0684 draftModel=deleteUnusedGenes(draftModel,false); -0685 end -0686 -0687 -0688 function tokens=gprTokenize(rule) -0689 % Split a GPR rule into tokens: gene IDs, "and", "or", "(", ")". -0690 % Brackets are separated from adjacent text by whitespace before splitting. -0691 if isempty(rule) -0692 tokens={}; -0693 return -0694 end -0695 r=regexprep(rule,'([\(\)])',' $1 '); -0696 r=regexprep(r,'\s+',' '); -0697 tokens=strsplit(strtrim(r),' '); -0698 tokens=tokens(~cellfun(@isempty,tokens)); -0699 end -0700 -0701 -0702 function rule=gprBuildRule(tokens) -0703 % Reconstruct a GPR rule string from a token list. -0704 if isempty(tokens) -0705 rule=''; -0706 return -0707 end -0708 rule=strjoin(tokens,' '); -0709 rule=strrep(rule,'( ','('); -0710 rule=strrep(rule,' )',')'); -0711 end -0712 -0713 -0714 function tokens=gprReplaceToken(tokens,oldGene,newGenes) -0715 % Replace each occurrence of token oldGene with the gene(s) in newGenes. -0716 % newGenes is a cell array; multiple genes are wrapped in parentheses and -0717 % joined with " or ". -0718 k=1; -0719 while k<=numel(tokens) -0720 if strcmp(tokens{k},oldGene) -0721 if numel(newGenes)==1 -0722 tokens{k}=newGenes{1}; -0723 k=k+1; -0724 else -0725 seq={'('}; -0726 for g=1:numel(newGenes) -0727 seq{end+1}=newGenes{g}; %#ok<AGROW> -0728 if g<numel(newGenes) -0729 seq{end+1}='or'; %#ok<AGROW> -0730 end -0731 end -0732 seq{end+1}=')'; -0733 tokens=[tokens(1:k-1),seq,tokens(k+1:end)]; -0734 k=k+numel(seq); -0735 end -0736 else -0737 k=k+1; -0738 end -0739 end -0740 end -0741 -0742 -0743 function rule=gprSimplify(rule,removePrefix) -0744 % Rewrite a grRule as a tree: genes starting with removePrefix (if any) are -0745 % dropped from their group, groups of the same operator are flattened, -0746 % repeated genes or groups are removed, and brackets appear only around a -0747 % group of the other operator. A rule with no gene left becomes ''. -0748 tokens=gprTokenize(rule); -0749 if isempty(tokens) -0750 rule=''; -0751 return -0752 end -0753 [node,~]=gprParseOr(tokens,1); -0754 node=gprPrune(node,removePrefix); -0755 if isempty(node) -0756 rule=''; -0757 else -0758 rule=gprRender(node,''); -0759 end -0760 end -0761 -0762 -0763 function [node,pos]=gprParseOr(tokens,pos) -0764 [node,pos]=gprParseAnd(tokens,pos); -0765 kids={node}; -0766 while pos<=numel(tokens) && strcmpi(tokens{pos},'or') -0767 [node,pos]=gprParseAnd(tokens,pos+1); -0768 kids{end+1}=node; %#ok<AGROW> -0769 end -0770 if numel(kids)>1 -0771 node=struct('op','or','gene','','kids',{kids}); -0772 end -0773 end -0774 -0775 -0776 function [node,pos]=gprParseAnd(tokens,pos) -0777 [node,pos]=gprParseAtom(tokens,pos); -0778 kids={node}; -0779 while pos<=numel(tokens) && strcmpi(tokens{pos},'and') -0780 [node,pos]=gprParseAtom(tokens,pos+1); -0781 kids{end+1}=node; %#ok<AGROW> -0782 end -0783 if numel(kids)>1 -0784 node=struct('op','and','gene','','kids',{kids}); -0785 end -0786 end -0787 -0788 -0789 function [node,pos]=gprParseAtom(tokens,pos) -0790 if pos>numel(tokens) -0791 error('Malformed grRule: it ends with an operator'); -0792 end -0793 if strcmp(tokens{pos},'(') -0794 [node,pos]=gprParseOr(tokens,pos+1); -0795 if pos>numel(tokens) || ~strcmp(tokens{pos},')') -0796 error('Malformed grRule: unbalanced brackets'); -0797 end -0798 pos=pos+1; -0799 else -0800 node=struct('op','gene','gene',tokens{pos},'kids',{{}}); -0801 pos=pos+1; -0802 end -0803 end -0804 -0805 -0806 function node=gprPrune(node,removePrefix) -0807 if strcmp(node.op,'gene') -0808 if ~isempty(removePrefix) && startsWith(node.gene,removePrefix) -0809 node=[]; -0810 end -0811 return -0812 end -0813 kids={}; -0814 seen={}; -0815 for k=1:numel(node.kids) -0816 kid=gprPrune(node.kids{k},removePrefix); -0817 if isempty(kid) -0818 continue -0819 end -0820 if strcmp(kid.op,node.op) -0821 parts=kid.kids; -0822 else -0823 parts={kid}; -0824 end -0825 for m=1:numel(parts) -0826 key=gprRender(parts{m},node.op); -0827 if ~ismember(key,seen) -0828 seen{end+1}=key; %#ok<AGROW> -0829 kids{end+1}=parts{m}; %#ok<AGROW> -0830 end -0831 end -0832 end -0833 if isempty(kids) -0834 node=[]; -0835 elseif numel(kids)==1 -0836 node=kids{1}; -0837 else -0838 node.kids=kids; -0839 end -0840 end -0841 -0842 -0843 function rule=gprRender(node,parentOp) -0844 if strcmp(node.op,'gene') -0845 rule=node.gene; -0846 return -0847 end -0848 parts=cellfun(@(k) gprRender(k,node.op),node.kids,'UniformOutput',false); -0849 rule=strjoin(parts,[' ' node.op ' ']); -0850 if ~isempty(parentOp) && ~strcmp(parentOp,node.op) -0851 rule=['(' rule ')']; -0852 end -0853 end

    +0544 geneName=models{useOrderIndexes(i)}.genes(oldGeneIds(k)); +0545 +0546 %First search in the mappable genes +0547 mapIndex=find(ismember(allGenes{i+1},geneName)); +0548 +0549 if ~isempty(mapIndex) +0550 % add the old genes +0551 hitGenes.oldGenes = [hitGenes.oldGenes, {geneName}]; +0552 +0553 %Get the new genes for that gene +0554 a=find(finalMappings{i}(:,mapIndex)); +0555 +0556 %Find the positions of these genes in the final gene list +0557 [~, b]=ismember(allGenes{1}(a),fullGeneList); +0558 +0559 %Update the matrix +0560 newRxnGeneMat(j,b)=1; +0561 +0562 %Build repString for hitGenes (display only) +0563 repString=fullGeneList{b(1)}; +0564 if numel(b)>1 +0565 for l=2:numel(b) +0566 repString=[repString ' or ' fullGeneList{b(l)}]; %#ok<AGROW> +0567 end +0568 repString=['(' repString ')']; +0569 end +0570 hitGenes.newGenes = [hitGenes.newGenes, {repString}]; +0571 +0572 %Replace the old gene token with the new gene(s) +0573 tokens=gprReplaceToken(tokens,geneName{1},fullGeneList(b)); +0574 else +0575 %Then search in the non-replaceable genes. There could only +0576 %be one match here +0577 index=find(ismember(nonReplaceableGenes,geneName)); +0578 +0579 %Update the matrix +0580 newRxnGeneMat(j,nonRepStartIndex+index)=1; +0581 +0582 oldName=strcat('OLD_',models{useOrderIndexes(i)}.id,'_',geneName{1}); +0583 tokens=gprReplaceToken(tokens,geneName{1},{oldName}); +0584 end +0585 end +0586 +0587 models{useOrderIndexes(i)}.grRules{j}=gprBuildRule(tokens); +0588 end +0589 +0590 %Add the new list of genes +0591 models{useOrderIndexes(i)}.rxnGeneMat=newRxnGeneMat; +0592 if ~isempty(nonReplaceableGenes) +0593 models{useOrderIndexes(i)}.genes=[allGenes{1}(unique(newGenes));strcat('OLD_',models{useOrderIndexes(i)}.id,'_',nonReplaceableGenes)]; +0594 else +0595 models{useOrderIndexes(i)}.genes=allGenes{1}(unique(newGenes)); +0596 end +0597 if isfield(models{useOrderIndexes(i)},'geneComps') +0598 geneComps=models{useOrderIndexes(i)}.geneComps(1); +0599 models{useOrderIndexes(i)}.geneComps=zeros(numel(models{useOrderIndexes(i)}.genes),1); +0600 %Assume that all genes are in the same compartment, and this +0601 %compartment is specified for the first gene +0602 models{useOrderIndexes(i)}.geneComps(:)=geneComps; +0603 end +0604 end +0605 +0606 %Now merge the models. All information should be correct except for "or" +0607 %complexes +0608 draftModel=mergeModels(models,'metNames'); +0609 +0610 %Remove OLD_ genes that appear only in OR branches (they were added for +0611 %AND-complex genes with no orthologs; in OR branches they are redundant). +0612 %Uses token-level removal to avoid partial-match hazards. +0613 for oldI=1:numel(draftModel.grRules) +0614 if ~contains(draftModel.grRules{oldI},'OLD_') +0615 continue +0616 end +0617 tkns=gprTokenize(draftModel.grRules{oldI}); +0618 again=true; +0619 while again +0620 again=false; +0621 for kk=1:numel(tkns) +0622 if startsWith(tkns{kk},'OLD_') +0623 if kk>1 && strcmp(tkns{kk-1},'or') +0624 tkns(kk-1:kk)=[]; +0625 again=true; break +0626 elseif kk<numel(tkns) && strcmp(tkns{kk+1},'or') +0627 tkns(kk:kk+1)=[]; +0628 again=true; break +0629 end +0630 end +0631 end +0632 end +0633 draftModel.grRules{oldI}=gprBuildRule(tkns); +0634 end +0635 +0636 %Apply the complex policy to the OLD_ genes left in "and" relations, and +0637 %write each grRule without repeated genes or redundant brackets +0638 toRemove=false(numel(draftModel.rxns),1); +0639 for j=1:numel(draftModel.grRules) +0640 rule=draftModel.grRules{j}; +0641 if isempty(rule) +0642 continue +0643 end +0644 if contains(rule,'OLD_') && strcmp(complexPolicy,'drop') +0645 toRemove(j)=true; +0646 elseif contains(rule,'OLD_') && strcmp(complexPolicy,'keep') +0647 draftModel.grRules{j}=gprSimplify(rule,'OLD_'); +0648 toRemove(j)=isempty(draftModel.grRules{j}); +0649 else +0650 draftModel.grRules{j}=gprSimplify(rule,''); +0651 end +0652 end +0653 if any(toRemove) +0654 draftModel=removeReactions(draftModel,draftModel.rxns(toRemove),true,true,true); +0655 end +0656 +0657 %Change name of the resulting model +0658 draftModel.id=getModelFor; +0659 name='Generated by getModelFromHomology using '; +0660 for i=1:numel(models) +0661 if i<numel(models) +0662 name=[name models{i}.id ', ']; +0663 else +0664 name=[name models{i}.id]; +0665 end +0666 end +0667 draftModel.name=name; +0668 %The draft is a new model: the templates' version, date and description are +0669 %not its own +0670 draftModel=rmfield(draftModel,intersect({'version','date','description'},fieldnames(draftModel))); +0671 if isfield(draftModel,'annotation') +0672 descriptive={'taxonomy','sourceUrl','note','givenName','familyName','authors','email','organization'}; +0673 draftModel.annotation=rmfield(draftModel.annotation,intersect(descriptive,fieldnames(draftModel.annotation))); +0674 end +0675 if ~preserveNotes || ~isfield(draftModel,'rxnNotes') +0676 draftModel.rxnNotes=cell(length(draftModel.rxns),1); +0677 draftModel.rxnNotes(:)={'Included by getModelFromHomology'}; +0678 end +0679 if ~preserveNotes || ~isfield(draftModel,'rxnConfidenceScores') +0680 draftModel.rxnConfidenceScores=NaN(length(draftModel.rxns),1); +0681 draftModel.rxnConfidenceScores(:)=2; +0682 end +0683 draftModel=deleteUnusedGenes(draftModel,0); +0684 %Standardize grRules and notify if problematic grRules are found +0685 [draftModel.grRules,draftModel.rxnGeneMat]=standardizeGrRules(draftModel,false); +0686 draftModel=deleteUnusedGenes(draftModel,false); +0687 end +0688 +0689 +0690 function tokens=gprTokenize(rule) +0691 % Split a GPR rule into tokens: gene IDs, "and", "or", "(", ")". +0692 % Brackets are separated from adjacent text by whitespace before splitting. +0693 if isempty(rule) +0694 tokens={}; +0695 return +0696 end +0697 r=regexprep(rule,'([\(\)])',' $1 '); +0698 r=regexprep(r,'\s+',' '); +0699 tokens=strsplit(strtrim(r),' '); +0700 tokens=tokens(~cellfun(@isempty,tokens)); +0701 end +0702 +0703 +0704 function rule=gprBuildRule(tokens) +0705 % Reconstruct a GPR rule string from a token list. +0706 if isempty(tokens) +0707 rule=''; +0708 return +0709 end +0710 rule=strjoin(tokens,' '); +0711 rule=strrep(rule,'( ','('); +0712 rule=strrep(rule,' )',')'); +0713 end +0714 +0715 +0716 function tokens=gprReplaceToken(tokens,oldGene,newGenes) +0717 % Replace each occurrence of token oldGene with the gene(s) in newGenes. +0718 % newGenes is a cell array; multiple genes are wrapped in parentheses and +0719 % joined with " or ". +0720 k=1; +0721 while k<=numel(tokens) +0722 if strcmp(tokens{k},oldGene) +0723 if numel(newGenes)==1 +0724 tokens{k}=newGenes{1}; +0725 k=k+1; +0726 else +0727 seq={'('}; +0728 for g=1:numel(newGenes) +0729 seq{end+1}=newGenes{g}; %#ok<AGROW> +0730 if g<numel(newGenes) +0731 seq{end+1}='or'; %#ok<AGROW> +0732 end +0733 end +0734 seq{end+1}=')'; +0735 tokens=[tokens(1:k-1),seq,tokens(k+1:end)]; +0736 k=k+numel(seq); +0737 end +0738 else +0739 k=k+1; +0740 end +0741 end +0742 end +0743 +0744 +0745 function rule=gprSimplify(rule,removePrefix) +0746 % Rewrite a grRule as a tree: genes starting with removePrefix (if any) are +0747 % dropped from their group, groups of the same operator are flattened, +0748 % repeated genes or groups are removed, and brackets appear only around a +0749 % group of the other operator. A rule with no gene left becomes ''. +0750 tokens=gprTokenize(rule); +0751 if isempty(tokens) +0752 rule=''; +0753 return +0754 end +0755 [node,~]=gprParseOr(tokens,1); +0756 node=gprPrune(node,removePrefix); +0757 if isempty(node) +0758 rule=''; +0759 else +0760 rule=gprRender(node,''); +0761 end +0762 end +0763 +0764 +0765 function [node,pos]=gprParseOr(tokens,pos) +0766 [node,pos]=gprParseAnd(tokens,pos); +0767 kids={node}; +0768 while pos<=numel(tokens) && strcmpi(tokens{pos},'or') +0769 [node,pos]=gprParseAnd(tokens,pos+1); +0770 kids{end+1}=node; %#ok<AGROW> +0771 end +0772 if numel(kids)>1 +0773 node=struct('op','or','gene','','kids',{kids}); +0774 end +0775 end +0776 +0777 +0778 function [node,pos]=gprParseAnd(tokens,pos) +0779 [node,pos]=gprParseAtom(tokens,pos); +0780 kids={node}; +0781 while pos<=numel(tokens) && strcmpi(tokens{pos},'and') +0782 [node,pos]=gprParseAtom(tokens,pos+1); +0783 kids{end+1}=node; %#ok<AGROW> +0784 end +0785 if numel(kids)>1 +0786 node=struct('op','and','gene','','kids',{kids}); +0787 end +0788 end +0789 +0790 +0791 function [node,pos]=gprParseAtom(tokens,pos) +0792 if pos>numel(tokens) +0793 error('Malformed grRule: it ends with an operator'); +0794 end +0795 if strcmp(tokens{pos},'(') +0796 [node,pos]=gprParseOr(tokens,pos+1); +0797 if pos>numel(tokens) || ~strcmp(tokens{pos},')') +0798 error('Malformed grRule: unbalanced brackets'); +0799 end +0800 pos=pos+1; +0801 else +0802 node=struct('op','gene','gene',tokens{pos},'kids',{{}}); +0803 pos=pos+1; +0804 end +0805 end +0806 +0807 +0808 function node=gprPrune(node,removePrefix) +0809 if strcmp(node.op,'gene') +0810 if ~isempty(removePrefix) && startsWith(node.gene,removePrefix) +0811 node=[]; +0812 end +0813 return +0814 end +0815 kids={}; +0816 seen={}; +0817 for k=1:numel(node.kids) +0818 kid=gprPrune(node.kids{k},removePrefix); +0819 if isempty(kid) +0820 continue +0821 end +0822 if strcmp(kid.op,node.op) +0823 parts=kid.kids; +0824 else +0825 parts={kid}; +0826 end +0827 for m=1:numel(parts) +0828 key=gprRender(parts{m},node.op); +0829 if ~ismember(key,seen) +0830 seen{end+1}=key; %#ok<AGROW> +0831 kids{end+1}=parts{m}; %#ok<AGROW> +0832 end +0833 end +0834 end +0835 if isempty(kids) +0836 node=[]; +0837 elseif numel(kids)==1 +0838 node=kids{1}; +0839 else +0840 node.kids=kids; +0841 end +0842 end +0843 +0844 +0845 function rule=gprRender(node,parentOp) +0846 if strcmp(node.op,'gene') +0847 rule=node.gene; +0848 return +0849 end +0850 parts=cellfun(@(k) gprRender(k,node.op),node.kids,'UniformOutput',false); +0851 rule=strjoin(parts,[' ' node.op ' ']); +0852 if ~isempty(parentOp) && ~strcmp(parentOp,node.op) +0853 rule=['(' rule ')']; +0854 end +0855 end

    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/tasks/parseTaskList.html b/docs/html/tasks/parseTaskList.html index 6627f16b..3b104358 100644 --- a/docs/html/tasks/parseTaskList.html +++ b/docs/html/tasks/parseTaskList.html @@ -33,14 +33,16 @@

    DESCRIPTION ^SOURCE CODE ^% ---------- 0006 % inputFile : char 0007 % a task list in either Excel (*.xlsx, with a sheet named TASKS with -0008 % all relevant content) or tab-delimited (*.txt) format. The file may -0009 % contain the following column headers (note, all rows starting with a -0010 % non-empty cell are removed; the first row after that is considered -0011 % the headers): -0012 % -0013 % - ID : the only required header. Each task must have a unique id -0014 % (string or numeric). Tasks can span multiple rows, only the first -0015 % row in each task should have an id -0016 % - DESCRIPTION : description of the task -0017 % - IN : allowed input(s) for the task. Metabolite names should be on -0018 % the form "model.metName[model.comps]". Several inputs can be -0019 % delimited by ";". If so, then the same bounds are used for all -0020 % inputs. If that is not wanted, then use several rows for the task -0021 % - IN LB : lower bound for the uptake of the metabolites in the row -0022 % (optional, default 0 which corresponds to a minimal uptake of 0 -0023 % units) -0024 % - IN UB : upper bound for the uptake of the metabolites in the row -0025 % (optional, default 1000 which corresponds to a maximal uptake of -0026 % 1000 units) -0027 % - OUT : allowed output(s) for the task (see IN) -0028 % - OUT LB : lower bound for the production of the metabolites in the -0029 % row (optional, default 0 which corresponds to a minimal production -0030 % of 0 units) -0031 % - OUT UB : upper bound for the production of the metabolites in the -0032 % row (optional, default 1000 which corresponds to a maximal -0033 % production of 1000 units) -0034 % - EQU : equation to add. The equation should be on the form -0035 % "0.4 A + 2 B <=> (or =>) C" and the metabolites should be on the -0036 % form "model.metName[model.comps]" (optional) -0037 % - EQU LB : lower bound for the equation (optional, default -1000 for -0038 % reversible and 0 for irreversible) -0039 % - EQU UB : upper bound for the equation (optional, default 1000) -0040 % - CHANGED RXN : reaction ID for which to change the bounds for. -0041 % Several IDs can be delimited by ";". If so, then the same bounds -0042 % are used for all reactions. If that is not wanted, then use several -0043 % rows for the task -0044 % - CHANGED LB : lower bound for the reaction. If empty, the lower bound -0045 % of the reaction is left as it is -0046 % - CHANGED UB : upper bound for the reaction. If empty, the upper bound +0008 % all relevant content) or tab-delimited (*.txt) format. The old binary +0009 % *.xls format is not supported. The file may contain the following +0010 % column headers (note, all rows starting with a non-empty cell are +0011 % removed; the first row after that is considered the headers; the +0012 % header row itself must therefore start with an empty cell): +0013 % +0014 % - ID : the only required header (string or numeric). Ids may repeat, +0015 % for example as category codes. A task starts at a row with a +0016 % non-empty id and can span multiple rows; only the first row of a task +0017 % should have an id +0018 % - DESCRIPTION : description of the task +0019 % - IN : allowed input(s) for the task. Metabolite names should be on +0020 % the form "model.metName[model.comps]". Several inputs can be +0021 % delimited by ";". If so, then the same bounds are used for all +0022 % inputs. If that is not wanted, then use several rows for the task +0023 % - IN LB : lower bound for the uptake of the metabolites in the row +0024 % (optional, default 0 which corresponds to a minimal uptake of 0 +0025 % units) +0026 % - IN UB : upper bound for the uptake of the metabolites in the row +0027 % (optional, default 1000 which corresponds to a maximal uptake of +0028 % 1000 units) +0029 % - OUT : allowed output(s) for the task (see IN) +0030 % - OUT LB : lower bound for the production of the metabolites in the +0031 % row (optional, default 0 which corresponds to a minimal production +0032 % of 0 units) +0033 % - OUT UB : upper bound for the production of the metabolites in the +0034 % row (optional, default 1000 which corresponds to a maximal +0035 % production of 1000 units) +0036 % - EQU : equation to add. The equation should be on the form +0037 % "0.4 A + 2 B <=> (or =>) C" and the metabolites should be on the +0038 % form "model.metName[model.comps]" (optional) +0039 % - EQU LB : lower bound for the equation (optional, default -1000 for +0040 % reversible and 0 for irreversible) +0041 % - EQU UB : upper bound for the equation (optional, default 1000) +0042 % - CHANGED RXN : reaction ID for which to change the bounds for. +0043 % Several IDs can be delimited by ";". If so, then the same bounds +0044 % are used for all reactions. If that is not wanted, then use several +0045 % rows for the task +0046 % - CHANGED LB : lower bound for the reaction. If empty, the lower bound 0047 % of the reaction is left as it is -0048 % - SHOULD FAIL : true if the correct behavior of the model is to not -0049 % have a feasible solution given the constraints. Any non-empty value -0050 % except 0, false and no (any case) counts as true (optional, default -0051 % false) -0052 % - PRINT FLUX : true if the function should print the corresponding -0053 % flux distribution for a task. Can be useful for testing. Read in -0054 % the same way as SHOULD FAIL (optional, default false) -0055 % -0056 % Headers are matched ignoring case and surrounding whitespace. In the -0057 % ";"-delimited cells, items are trimmed and empty items are dropped. -0058 % -0059 % Returns -0060 % ------- -0061 % taskStruct : struct -0062 % array of structures with the following fields: -0063 % -0064 % - id : the id of the task -0065 % - description : the description of the task -0066 % - shouldFail : true if the task should fail -0067 % - printFluxes : true if the fluxes should be printed -0068 % - comments : string with comments -0069 % - inputs : cell array with input metabolites (in the form metName[comps]) -0070 % - LBin : array with lower bounds on inputs (default, 0) -0071 % - UBin : array with upper bounds on inputs (default, 1000) -0072 % - outputs : cell array with output metabolites (in the form metName[comps]) -0073 % - LBout : array with lower bounds on outputs (default, 0) -0074 % - UBout : array with upper bounds on outputs (default, 1000) -0075 % - equations : cell array with equations (with mets in the form metName[comps]) -0076 % - LBequ : array with lower bounds on equations (default, -1000 for -0077 % reversible and 0 for irreversible) -0078 % - UBequ : array with upper bounds on equations (default, 1000) -0079 % - changed : cell array with reactions to change bounds for -0080 % - LBrxn : array with lower bounds on changed reactions (NaN if empty, -0081 % meaning the bound is left unchanged) -0082 % - UBrxn : array with upper bounds on changed reactions (NaN if empty, +0048 % - CHANGED UB : upper bound for the reaction. If empty, the upper bound +0049 % of the reaction is left as it is +0050 % - SHOULD FAIL : true if the correct behavior of the model is to not +0051 % have a feasible solution given the constraints. Any non-empty value +0052 % except 0, false and no (any case) counts as true (optional, default +0053 % false) +0054 % - PRINT FLUX : true if the function should print the corresponding +0055 % flux distribution for a task. Can be useful for testing. Read in +0056 % the same way as SHOULD FAIL (optional, default false) +0057 % +0058 % Headers are matched ignoring case and surrounding whitespace. In the +0059 % ";"-delimited cells, items are trimmed and empty items are dropped. +0060 % +0061 % Returns +0062 % ------- +0063 % taskStruct : struct +0064 % array of structures with the following fields: +0065 % +0066 % - id : the id of the task +0067 % - description : the description of the task +0068 % - shouldFail : true if the task should fail +0069 % - printFluxes : true if the fluxes should be printed +0070 % - comments : string with comments +0071 % - inputs : cell array with input metabolites (in the form metName[comps]) +0072 % - LBin : array with lower bounds on inputs (default, 0) +0073 % - UBin : array with upper bounds on inputs (default, 1000) +0074 % - outputs : cell array with output metabolites (in the form metName[comps]) +0075 % - LBout : array with lower bounds on outputs (default, 0) +0076 % - UBout : array with upper bounds on outputs (default, 1000) +0077 % - equations : cell array with equations (with mets in the form metName[comps]) +0078 % - LBequ : array with lower bounds on equations (default, -1000 for +0079 % reversible and 0 for irreversible) +0080 % - UBequ : array with upper bounds on equations (default, 1000) +0081 % - changed : cell array with reactions to change bounds for +0082 % - LBrxn : array with lower bounds on changed reactions (NaN if empty, 0083 % meaning the bound is left unchanged) -0084 % -0085 % Examples -0086 % -------- -0087 % taskStruct = parseTaskList(inputFile); -0088 % -0089 % Notes -0090 % ----- -0091 % This function is used for defining a set of tasks for a model to perform. -0092 % The tasks are defined by defining constraints on the model, and if the -0093 % problem is feasible, then the task is considered successful. In general, -0094 % each row can contain one constraint on uptakes, one constraint on -0095 % outputs, one new equation, and one change of reaction bounds. If more -0096 % bounds are needed to define the task, then several rows can be used for -0097 % each task. To perform the task use checkTasks or fitTasks. -0098 % -0099 % The general metabolites "ALLMETS" and "ALLMETSIN[comps]" can be used as -0100 % inputs or outputs in the similar manner to normal metabolites. This is a -0101 % convenient way to, for example, allow excretion of all metabolites to -0102 % check whether it is the synthesis of some metabolite that is limiting or -0103 % whether it is the degradation of some byproduct. One important difference -0104 % is that only the upper bounds are used for these general metabolites. -0105 % That is, you can only say that uptake or excretion is allowed, not that -0106 % it is required. This is to avoid conflicts where the constraints for the -0107 % general metabolites overwrite those of the real ones. -0108 % -0109 % See also -0110 % -------- -0111 % checkTasks, fitTasks -0112 -0113 if ~isfile(inputFile) -0114 error('Task list %s cannot be found',string(inputFile)); -0115 end -0116 -0117 %Load the tasks file -0118 convNumeric = false; -0119 if strcmp(extractAfter(inputFile,strlength(inputFile) - 4), '.txt') -0120 %load from tab delimited text file -0121 fid = fopen(inputFile); -0122 %Need to read numeric columns as strings, this is converted further -0123 %down. If not, the titles would be lost. -0124 convNumeric = true; +0084 % - UBrxn : array with upper bounds on changed reactions (NaN if empty, +0085 % meaning the bound is left unchanged) +0086 % +0087 % Examples +0088 % -------- +0089 % taskStruct = parseTaskList(inputFile); +0090 % +0091 % Notes +0092 % ----- +0093 % This function is used for defining a set of tasks for a model to perform. +0094 % The tasks are defined by defining constraints on the model, and if the +0095 % problem is feasible, then the task is considered successful. In general, +0096 % each row can contain one constraint on uptakes, one constraint on +0097 % outputs, one new equation, and one change of reaction bounds. If more +0098 % bounds are needed to define the task, then several rows can be used for +0099 % each task. To perform the task use checkTasks or fitTasks. +0100 % +0101 % The general metabolites "ALLMETS" and "ALLMETSIN[comps]" can be used as +0102 % inputs or outputs in the similar manner to normal metabolites. This is a +0103 % convenient way to, for example, allow excretion of all metabolites to +0104 % check whether it is the synthesis of some metabolite that is limiting or +0105 % whether it is the degradation of some byproduct. One important difference +0106 % is that only the upper bounds are used for these general metabolites. +0107 % That is, you can only say that uptake or excretion is allowed, not that +0108 % it is required. This is to avoid conflicts where the constraints for the +0109 % general metabolites overwrite those of the real ones. +0110 % +0111 % See also +0112 % -------- +0113 % checkTasks, fitTasks +0114 +0115 if ~isfile(inputFile) +0116 error('Task list %s cannot be found',string(inputFile)); +0117 end +0118 +0119 %Load the tasks file +0120 if strcmp(extractAfter(inputFile,strlength(inputFile) - 4), '.txt') +0121 %load from tab delimited text file +0122 fid = fopen(inputFile); +0123 %Need to read numeric columns as strings, this is converted further +0124 %down. If not, the titles would be lost. 0125 C = textscan(fid,'%q%q%q%q%q%q%q%q%q%q%q%q%q%q%q%q%q%q%q%q%*[^\n]', 'Delimiter', '\t'); 0126 fclose(fid); 0127 raw = [C{:}];%unnest the cell array of cell arrays into a 2-dim cell array 0128 else -0129 %Read the TASKS sheet from an Excel file (readcell handles both .xls -0130 %and .xlsx). -0131 try -0132 raw=readcell(inputFile,'Sheet','TASKS'); -0133 catch -0134 error('RAVEN:badInput', '%s', ['Could not load sheet "TASKS" from ' inputFile]); -0135 end -0136 %Normalise blank cells (which readcell returns as "missing") to [], as -0137 %expected by the downstream cleanSheet/parsing. -0138 raw(cellfun(@(x) all(ismissing(x)),raw))={[]}; -0139 end -0140 -0141 %Remove all lines starting with "#" (or actually any character) and all -0142 %empty columns -0143 raw=cleanSheet(raw); -0144 -0145 %Captions -0146 columns={'ID';'DESCRIPTION';'IN';'IN LB';'IN UB';'OUT';'OUT LB';'OUT UB';'EQU';'EQU LB';'EQU UB';'CHANGED RXN';'CHANGED LB';'CHANGED UB';'SHOULD FAIL';'PRINT FLUX';'COMMENTS'}; -0147 %Match the columns. Headers are matched ignoring case and surrounding -0148 %whitespace, so "id" and " IN LB " are accepted. -0149 header=raw(1,:); -0150 isText=cellfun(@ischar,header); -0151 header(isText)=upper(strtrim(header(isText))); -0152 [I, colI]=ismember(columns,header); -0153 -0154 %Only ID is required. Columns that are absent get index 0 from ismember, which -0155 %cannot be used to index raw, so point them at a spare empty column instead: -0156 %every optional field then reads as empty and its default applies. -0157 raw(:,end+1)={[]}; -0158 colI(colI==0)=size(raw,2); -0159 -0160 %If read from a text file, the numbers will be strings - fix that -0161 if convNumeric % safeguard; a no-op if the columns are already numeric -0162 numericColumns = [0 0 0 1 1 0 1 1 0 1 1 0 1 1 0 0 0] == 1; -0163 cols = colI(numericColumns); -0164 numeric = cellfun(@isnumeric,raw(:,cols)); -0165 %trick to avoid messing up the title row: -0166 numeric(1,:) = 1; -0167 for colind = 1:numel(cols) -0168 col = cols(colind); -0169 raw(~numeric(:,colind),col) = cellfun(@str2num, raw(~numeric(:,colind),col), 'UniformOutput', false); -0170 end -0171 end -0172 -0173 %Check that the ID field is present -0174 if I(1)==0 -0175 EM='The TASKS sheet must have a column named ID'; -0176 error('RAVEN:badInput', '%s', EM); -0177 end -0178 -0179 %make sure numerical fields are converted from strings -0180 -0181 %Add default bounds where needed -0182 for i=[4 5 7 8] -0183 I=cellfun(@isempty,raw(:,colI(i))); -0184 if i==5 || i==8 -0185 raw(I,colI(i))={1000}; -0186 else -0187 raw(I,colI(i))={0}; -0188 end -0189 end -0190 -0191 %Create an empty task structure -0192 eTask.id=''; -0193 eTask.description=''; -0194 eTask.shouldFail=false; -0195 eTask.printFluxes=false; -0196 eTask.comments=''; -0197 eTask.inputs={}; -0198 eTask.LBin=[]; -0199 eTask.UBin=[]; -0200 eTask.outputs={}; -0201 eTask.LBout=[]; -0202 eTask.UBout=[]; -0203 eTask.equations={}; -0204 eTask.LBequ=[]; -0205 eTask.UBequ=[]; -0206 eTask.changed={}; -0207 eTask.LBrxn=[]; -0208 eTask.UBrxn=[]; +0129 if endsWith(lower(char(inputFile)),'.xls') +0130 error('RAVEN:badInput', '%s', [inputFile ': the old binary .xls format is not supported. Save the task list as .xlsx (sheet ''TASKS'') or as a tab-delimited .txt file.']); +0131 end +0132 %Read the TASKS sheet from an Excel file +0133 try +0134 raw=readcell(inputFile,'Sheet','TASKS'); +0135 catch +0136 error('RAVEN:badInput', '%s', ['Could not load sheet "TASKS" from ' inputFile]); +0137 end +0138 %Normalise blank cells (which readcell returns as "missing") to [], as +0139 %expected by the downstream cleanSheet/parsing. +0140 raw(cellfun(@(x) all(ismissing(x)),raw))={[]}; +0141 end +0142 +0143 %Rows without any content carry no information (a header must not be +0144 %mistaken for one of them) +0145 if iscell(raw) +0146 emptyRow=all(cellfun(@(x) isempty(x) || (ischar(x) && isempty(strtrim(x))),raw),2); +0147 raw=raw(~emptyRow,:); +0148 end +0149 +0150 %Leading rows that start with a non-empty cell are comments; the first row +0151 %with an empty first cell is the header row +0152 if iscell(raw) +0153 if isempty(raw) +0154 error('RAVEN:badInput', '%s', 'The TASKS sheet must have a column named ID'); +0155 end +0156 firstBlank=cellfun(@(x) isempty(x) || (ischar(x) && isempty(strtrim(x))),raw(:,1)); +0157 first=find(firstBlank,1); +0158 if isempty(first) +0159 error('RAVEN:badInput', '%s', 'The TASKS sheet must have a column named ID'); +0160 end +0161 raw=raw(first:end,:); +0162 end +0163 +0164 %Remove all lines starting with "#" (or actually any character) and all +0165 %empty columns +0166 raw=cleanSheet(raw); +0167 +0168 %Captions +0169 columns={'ID';'DESCRIPTION';'IN';'IN LB';'IN UB';'OUT';'OUT LB';'OUT UB';'EQU';'EQU LB';'EQU UB';'CHANGED RXN';'CHANGED LB';'CHANGED UB';'SHOULD FAIL';'PRINT FLUX';'COMMENTS'}; +0170 %Match the columns. Headers are matched ignoring case and surrounding +0171 %whitespace, so "id" and " IN LB " are accepted. +0172 header=raw(1,:); +0173 isText=cellfun(@ischar,header); +0174 header(isText)=upper(strtrim(header(isText))); +0175 [I, colI]=ismember(columns,header); +0176 +0177 %Only ID is required. Columns that are absent get index 0 from ismember, which +0178 %cannot be used to index raw, so point each of them at its own spare empty +0179 %column instead: every optional field then reads as empty and its default +0180 %applies. +0181 absent=find(colI==0); +0182 nOld=size(raw,2); +0183 raw(:,nOld+(1:numel(absent)))={[]}; +0184 colI(absent)=nOld+(1:numel(absent)); +0185 +0186 %Text cells (every cell of a text file, and numbers typed as text in Excel) +0187 %are converted to numbers; text that is not a number stays and is refused +0188 %below +0189 numericColumns = [0 0 0 1 1 0 1 1 0 1 1 0 1 1 0 0 0] == 1; +0190 cols = colI(numericColumns); +0191 numeric = cellfun(@isnumeric,raw(:,cols)); +0192 %trick to avoid messing up the title row: +0193 numeric(1,:) = 1; +0194 for colind = 1:numel(cols) +0195 col = cols(colind); +0196 for r = find(~numeric(:,colind))' +0197 x = raw{r,col}; +0198 if ischar(x) && isempty(strtrim(x)) +0199 raw{r,col} = []; +0200 elseif ischar(x) +0201 %Only plain numbers are accepted; str2double alone would also read +0202 %'1,5' as 15, '1,000' as 1000 and '2i' as a complex number +0203 if ~isempty(regexpi(strtrim(x),'^([+-]?(\d+\.?\d*|\.\d+)(e[+-]?\d+)?|[+-]?inf|nan)$','once')) +0204 raw{r,col} = str2double(x); +0205 end +0206 end +0207 end +0208 end 0209 -0210 %Main loop -0211 taskStruct=[]; -0212 task=eTask; -0213 if isnumeric(raw{2,colI(1)}) -0214 task.id=num2str(raw{2,colI(1)}); -0215 else -0216 task.id=raw{2,colI(1)}; -0217 end -0218 task.description=raw{2,colI(2)}; -0219 if isTruthy(raw{2,colI(15)}) -0220 task.shouldFail=true; -0221 end -0222 if isTruthy(raw{2,colI(16)}) -0223 task.printFluxes=true; +0210 %Every bound that is given must be a number +0211 numericNames = columns([4 5 7 8 10 11 13 14]); +0212 numericCols = colI([4 5 7 8 10 11 13 14]); +0213 for k = 1:numel(numericCols) +0214 for r = 2:size(raw,1) +0215 x = raw{r,numericCols(k)}; +0216 if ~isempty(x) && ~(isnumeric(x) && isscalar(x)) +0217 error('RAVEN:badInput', 'The value in column %s of task list data row %d is not a number (rows are counted after the header, comment rows and empty rows are removed)', numericNames{k}, r-1); +0218 end +0219 %NaN means "leave unchanged" only in the bounds of changed reactions +0220 if k <= 6 && isnumeric(x) && isscalar(x) && isnan(x) +0221 error('RAVEN:badInput', 'The value in column %s of task list data row %d is NaN, which is only accepted in the bounds of changed reactions (rows are counted after the header, comment rows and empty rows are removed)', numericNames{k}, r-1); +0222 end +0223 end 0224 end -0225 if ~isempty(raw{2,colI(17)}) -0226 task.comments=raw{2,colI(17)}; -0227 end -0228 -0229 for i=2:size(raw,1) -0230 %Set the inputs -0231 if ischar(raw{i,colI(3)}) -0232 inputs=splitList(raw{i,colI(3)}); -0233 task.inputs=[task.inputs;inputs(:)]; -0234 task.LBin=[task.LBin;ones(numel(inputs),1)*raw{i,colI(4)}]; -0235 task.UBin=[task.UBin;ones(numel(inputs),1)*raw{i,colI(5)}]; -0236 end -0237 %Set the outputs -0238 if ischar(raw{i,colI(6)}) -0239 outputs=splitList(raw{i,colI(6)}); -0240 task.outputs=[task.outputs;outputs(:)]; -0241 task.LBout=[task.LBout;ones(numel(outputs),1)*raw{i,colI(7)}]; -0242 task.UBout=[task.UBout;ones(numel(outputs),1)*raw{i,colI(8)}]; -0243 end -0244 %Add new rxns -0245 if ischar(raw{i,colI(9)}) -0246 task.equations=[task.equations;raw{i,colI(9)}]; -0247 if ~isempty(raw{i,colI(10)}) -0248 task.LBequ=[task.LBequ;raw{i,colI(10)}]; -0249 else -0250 if any(strfind(raw{i,colI(9)},'<=>')) -0251 task.LBequ=[task.LBequ;-1000]; -0252 else -0253 task.LBequ=[task.LBequ;0]; -0254 end -0255 end -0256 if ~isempty(raw{i,colI(11)}) -0257 task.UBequ=[task.UBequ;raw{i,colI(11)}]; -0258 else -0259 task.UBequ=[task.UBequ;1000]; -0260 end -0261 end -0262 %Add changed bounds -0263 if ischar(raw{i,colI(12)}) -0264 changed=splitList(raw{i,colI(12)}); -0265 task.changed=[task.changed;changed(:)]; -0266 %An empty bound means "leave this bound as it is", stored as NaN -0267 task.LBrxn=[task.LBrxn;ones(numel(changed),1)*boundOrNaN(raw{i,colI(13)})]; -0268 task.UBrxn=[task.UBrxn;ones(numel(changed),1)*boundOrNaN(raw{i,colI(14)})]; -0269 end -0270 -0271 %Check if it should add more constraints -0272 if i<size(raw,1) -0273 if isempty(raw{i+1,colI(1)}) -0274 continue; -0275 end -0276 end -0277 -0278 taskStruct=[taskStruct;task]; -0279 task=eTask; -0280 if i<size(raw,1) -0281 if isnumeric(raw{i+1,colI(1)}) -0282 task.id=num2str(raw{i+1,colI(1)}); -0283 else -0284 task.id=raw{i+1,colI(1)}; -0285 end -0286 task.description=raw{i+1,colI(2)}; -0287 if isTruthy(raw{i+1,colI(15)}) -0288 task.shouldFail=true; -0289 end -0290 if isTruthy(raw{i+1,colI(16)}) -0291 task.printFluxes=true; -0292 end -0293 if ~isempty(raw{i+1,colI(17)}) -0294 task.comments=raw{i+1,colI(17)}; -0295 end -0296 end -0297 end -0298 -0299 %Should add more checks, such as unique IDs and missing headers -0300 -0301 end -0302 -0303 function tf = isTruthy(x) -0304 % A flag cell is true unless it is empty, 0, or one of the texts "0", "false" -0305 % and "no" (any case). -0306 if isempty(x) -0307 tf = false; -0308 elseif ischar(x) || isstring(x) -0309 tf = ~ismember(lower(strtrim(char(x))),{'','0','false','no'}); -0310 elseif isnumeric(x) || islogical(x) -0311 tf = any(x(:) ~= 0); -0312 else -0313 tf = true; -0314 end -0315 end -0316 -0317 function items = splitList(str) -0318 % Split a ";"-delimited cell on ";", trim each item and drop empty ones, so -0319 % that "A; B;" gives {'A';'B'}. -0320 items = strtrim(regexp(str,';','split')); -0321 items = items(~cellfun(@isempty,items)); -0322 end -0323 -0324 function b = boundOrNaN(x) -0325 % Bound for a changed reaction: NaN (leave unchanged) when the cell is empty. -0326 if isempty(x) -0327 b = NaN; -0328 else -0329 b = x; -0330 end -0331 end

    +0225 +0226 %Check that the ID field is present +0227 if I(1)==0 +0228 EM='The TASKS sheet must have a column named ID'; +0229 error('RAVEN:badInput', '%s', EM); +0230 end +0231 +0232 %Rows before the first task id belong to no task +0233 firstId=find(~cellfun(@isempty,raw(2:end,colI(1))),1)+1; +0234 if isempty(firstId) +0235 firstId=size(raw,1)+1; +0236 end +0237 if firstId>2 +0238 warning('RAVEN:warning', 'Skipping %d row(s) before the first task id', firstId-2); +0239 raw(2:firstId-1,:)=[]; +0240 end +0241 if size(raw,1)<2 +0242 taskStruct=[]; +0243 return; +0244 end +0245 +0246 %Add default bounds where needed +0247 for i=[4 5 7 8] +0248 I=cellfun(@isempty,raw(:,colI(i))); +0249 if i==5 || i==8 +0250 raw(I,colI(i))={1000}; +0251 else +0252 raw(I,colI(i))={0}; +0253 end +0254 end +0255 +0256 %Create an empty task structure +0257 eTask.id=''; +0258 eTask.description=''; +0259 eTask.shouldFail=false; +0260 eTask.printFluxes=false; +0261 eTask.comments=''; +0262 eTask.inputs={}; +0263 eTask.LBin=[]; +0264 eTask.UBin=[]; +0265 eTask.outputs={}; +0266 eTask.LBout=[]; +0267 eTask.UBout=[]; +0268 eTask.equations={}; +0269 eTask.LBequ=[]; +0270 eTask.UBequ=[]; +0271 eTask.changed={}; +0272 eTask.LBrxn=[]; +0273 eTask.UBrxn=[]; +0274 +0275 %Main loop +0276 taskStruct=[]; +0277 task=eTask; +0278 if isnumeric(raw{2,colI(1)}) +0279 task.id=num2str(raw{2,colI(1)}); +0280 else +0281 task.id=raw{2,colI(1)}; +0282 end +0283 task.description=raw{2,colI(2)}; +0284 if isTruthy(raw{2,colI(15)}) +0285 task.shouldFail=true; +0286 end +0287 if isTruthy(raw{2,colI(16)}) +0288 task.printFluxes=true; +0289 end +0290 if ~isempty(raw{2,colI(17)}) +0291 task.comments=raw{2,colI(17)}; +0292 end +0293 +0294 for i=2:size(raw,1) +0295 %Set the inputs +0296 if ischar(raw{i,colI(3)}) +0297 inputs=splitList(raw{i,colI(3)}); +0298 task.inputs=[task.inputs;inputs(:)]; +0299 task.LBin=[task.LBin;ones(numel(inputs),1)*raw{i,colI(4)}]; +0300 task.UBin=[task.UBin;ones(numel(inputs),1)*raw{i,colI(5)}]; +0301 end +0302 %Set the outputs +0303 if ischar(raw{i,colI(6)}) +0304 outputs=splitList(raw{i,colI(6)}); +0305 task.outputs=[task.outputs;outputs(:)]; +0306 task.LBout=[task.LBout;ones(numel(outputs),1)*raw{i,colI(7)}]; +0307 task.UBout=[task.UBout;ones(numel(outputs),1)*raw{i,colI(8)}]; +0308 end +0309 %Add new rxns +0310 if ischar(raw{i,colI(9)}) +0311 task.equations=[task.equations;raw{i,colI(9)}]; +0312 if ~isempty(raw{i,colI(10)}) +0313 task.LBequ=[task.LBequ;raw{i,colI(10)}]; +0314 else +0315 if any(strfind(raw{i,colI(9)},'<=>')) +0316 task.LBequ=[task.LBequ;-1000]; +0317 else +0318 task.LBequ=[task.LBequ;0]; +0319 end +0320 end +0321 if ~isempty(raw{i,colI(11)}) +0322 task.UBequ=[task.UBequ;raw{i,colI(11)}]; +0323 else +0324 task.UBequ=[task.UBequ;1000]; +0325 end +0326 end +0327 %Add changed bounds +0328 if ischar(raw{i,colI(12)}) +0329 changed=splitList(raw{i,colI(12)}); +0330 task.changed=[task.changed;changed(:)]; +0331 %An empty bound means "leave this bound as it is", stored as NaN +0332 task.LBrxn=[task.LBrxn;ones(numel(changed),1)*boundOrNaN(raw{i,colI(13)})]; +0333 task.UBrxn=[task.UBrxn;ones(numel(changed),1)*boundOrNaN(raw{i,colI(14)})]; +0334 end +0335 +0336 %Check if it should add more constraints +0337 if i<size(raw,1) +0338 if isempty(raw{i+1,colI(1)}) +0339 continue; +0340 end +0341 end +0342 +0343 taskStruct=[taskStruct;task]; +0344 task=eTask; +0345 if i<size(raw,1) +0346 if isnumeric(raw{i+1,colI(1)}) +0347 task.id=num2str(raw{i+1,colI(1)}); +0348 else +0349 task.id=raw{i+1,colI(1)}; +0350 end +0351 task.description=raw{i+1,colI(2)}; +0352 if isTruthy(raw{i+1,colI(15)}) +0353 task.shouldFail=true; +0354 end +0355 if isTruthy(raw{i+1,colI(16)}) +0356 task.printFluxes=true; +0357 end +0358 if ~isempty(raw{i+1,colI(17)}) +0359 task.comments=raw{i+1,colI(17)}; +0360 end +0361 end +0362 end +0363 +0364 %Should add more checks, such as unique IDs and missing headers +0365 +0366 end +0367 +0368 function tf = isTruthy(x) +0369 % A flag cell is true unless it is empty, 0, or one of the texts "0", "false" +0370 % and "no" (any case). +0371 if isempty(x) +0372 tf = false; +0373 elseif ischar(x) || isstring(x) +0374 tf = ~ismember(lower(strtrim(char(x))),{'','0','false','no'}); +0375 elseif isnumeric(x) || islogical(x) +0376 tf = any(x(:) ~= 0); +0377 else +0378 tf = true; +0379 end +0380 end +0381 +0382 function items = splitList(str) +0383 % Split a ";"-delimited cell on ";", trim each item and drop empty ones, so +0384 % that "A; B;" gives {'A';'B'}. +0385 items = strtrim(regexp(str,';','split')); +0386 items = items(~cellfun(@isempty,items)); +0387 end +0388 +0389 function b = boundOrNaN(x) +0390 % Bound for a changed reaction: NaN (leave unchanged) when the cell is empty. +0391 if isempty(x) +0392 b = NaN; +0393 else +0394 b = x; +0395 end +0396 end
    Generated by m2html © 2005
    \ No newline at end of file From c1c8bb70d4e59321721eb6cf94b278d7d7e42fa1 Mon Sep 17 00:00:00 2001 From: Eduard Kerkhoven Date: Fri, 30 Oct 2026 11:20:12 +0100 Subject: [PATCH 36/48] fix: align RAVEN with raven-toolbox (parity review, round 3) (#15) * ftINIT: resolveTies 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 resolveTies minimal-flux phase stops at a 1e-6 relative or absolute gap * ftINIT: resolveTies fixes equal-total-flux directions by id rank and ranks a merged reaction by its smallest original id * ftINIT: the resolveTies direction phase starts from the minimal-flux solution * mergeCompartments: keep the id of the copy in s or the one that sorts first The merged metabolite was the first copy in model order, so its id depended on the metabolite order of the model. It is now the copy already in compartment s, if any, otherwise the copy whose id sorts first, the rule raven-toolbox's merge_compartments applies. * mergeLinear: number groups by their first reaction Group numbers followed the order in which the reactions were merged. They are now 1, 2, ... in the order of the first reaction of each group in origRxnIds, the numbering raven-toolbox's merge_linear uses. * grRuleToDNF: help states the parseGrRule grammar and the RAVEN:badGrRule error; tUtils tests operator spellings and malformed rules * gapFillTopological: help states that the default targets are every metabolite of the objective reaction; tGapfilling tests the default targets * gapFillTopological: the no-objective error is RAVEN:badInput Without targets and without an objective reaction the function raised an error without identifier, which tGapfilling could only test as any MException. It now raises RAVEN:badInput, which the help states and tGapfilling tests. * tUtils: grRuleToDNF splits words at the characters isspace counts char(28) and char(8195) separate words; char(133), char(160), char(8199) and char(8239), for which isspace is false, are part of a word. * CHANGELOG: Unreleased section for mergeCompartments and mergeLinear * CHANGELOG: Unreleased section for grRuleToDNF and gapFillTopological * mergeLinear: a reaction flipped in a later pass is marked reversed by its id flipRxn indexed groupIds and reversedRxns, which follow origRxnIds, with the index of the reduced model, so after the first pass it marked the reaction at the same original index instead of the flipped one. * removeGenes: help states that genes no remaining reaction uses are removed * removeGenes: help states that nothing changes when no requested gene is in the model * tINIT: ftINIT ranks a merged reaction by the smallest original id of its group * tINIT: the paths of the rank-key test model are reversible, as prepINITModel needs * ftINITInternalAlg: resolveTies skips the tie-break after a time-limited primary solve and gives phases 2a and 2b half of TimeLimit * tINIT: resolveTies runs no tie-break phase after a time-limited primary solve * prepINITModel: prepData carries reversedRxns of mergeLinear * ftINIT: resolveTies orients a merged reaction as its smallest member and skips the flux phases in the last step * tINIT: resolveTies follows rxnOrientation, pinFluxes skips the flux phases, prepData carries reversedRxns * simplifyModel: constrainReversible changes nothing on a model without a reversible reaction * tINIT: prepINITModel runs on a model without a reversible reaction * ftINITInternalAlg: help and comments of pinFluxes name the phases it skips * ftINIT: resolveTies keeps the orientation of each merged reaction as written Phase 2d of ftINITInternalAlg weights the backward flux by the rank key only, a turned-on reaction with zero flux is forced forward in the next step, and prepData has no reversedRxns field. ftINITInternalAlg takes pinFluxes as its 15th argument. tINIT checks that ftINIT passes pinFluxes false in the last step only. * CHANGELOG: ftINITInternalAlg arguments rankKeys and pinFluxes, phase names of the tie-resolution warning --- CHANGELOG.md | 57 +++++ src/INIT/ftINIT.m | 38 ++- src/INIT/ftINITInternalAlg.m | 146 ++++++++++- src/INIT/mergeLinear.m | 21 +- src/gapfilling/gapFillTopological.m | 11 +- src/manipulation/mergeCompartments.m | 31 ++- src/manipulation/removeGenes.m | 10 +- src/manipulation/simplifyModel.m | 41 ++-- src/utils/grRuleToDNF.m | 9 +- tests/function_tests/tGapfilling.m | 15 ++ tests/function_tests/tINIT.m | 355 +++++++++++++++++++++++++++ tests/function_tests/tManipulation.m | 34 +++ tests/function_tests/tUtils.m | 26 ++ 13 files changed, 741 insertions(+), 53 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 41cdf996..f197f7a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,62 @@ # Changelog +## Unreleased + +### Manipulation + +* `mergeCompartments`: a merged metabolite is the copy already in compartment `s`, if any, + otherwise the copy whose id sorts first, with every field of that copy (was: the first copy + in model order), so `ACCOA_c` and `ACCOA_m` become `ACCOA_c` whatever their order. +* `simplifyModel` with `constrainReversible`: on a model without a reversible reaction this step + changes nothing (was: an error "logical indices ... outside of the array bounds", as + `getAllowedBounds` took the empty reaction list for all reactions). `prepINITModel` therefore + runs on a model whose reactions are all irreversible after the first simplification, with the + result of raven-toolbox's `prep_init_model`. +* `removeGenes`: the help states that, when at least one of the requested genes is in the model, + genes no remaining reaction uses are removed as well; when none is, no gene, reaction or bound + changes. + +### Gap-filling + +* `gapFillTopological`: without `targets` and without an objective reaction it raises + `RAVEN:badInput` (was: an error without identifier). Help states the default seeds and + targets. + +### Context-specific models (ftINIT / tINIT) + +* `ftINIT` with `resolveTies`: each main-extraction step takes its fluxes from the + least-total-flux solution for the tie-broken on/off state, so the direction and magnitude + at which the next step forces a reaction no longer depend on the solver's vertex. + That phase stops at a relative or absolute gap of 1e-6. +* `ftINIT` with `resolveTies`: among the least-total-flux solutions, each main-extraction + step takes its fluxes from the one with the least id-rank-weighted backward flux, so the + direction of a flux that carries the same total either way no longer depends on the + solver. The id-rank phases rank a merged reaction by the smallest original reaction id in + its group, so they no longer depend on which member names it. +* `ftINITInternalAlg` with `resolveTies`: when the primary solve stops at the time limit, the + tie-break phases are skipped and the primary solution is kept, as in raven-toolbox (was: the + phases ran on the unproven incumbent). The count and id-rank phases get half of + `params.TimeLimit` each, rounded down and at least 1 s (was: the full limit). +* `ftINIT` with `resolveTies`: the last step skips the minimal-flux and direction phases, as + no step reads its fluxes (new `ftINITInternalAlg` argument `pinFluxes`). The reaction set + does not change; the fluxes and `metProduction` of the last step are those of the id-rank + phase. +* `ftINITInternalAlg`: new optional positional arguments `rankKeys` (14th, the key each + reaction is sorted by in the id-rank phases, default `model.rxns`) and `pinFluxes` (15th, + default `true`). The "tie resolution did not converge" warning of `resolveTies` also names + `phase2c-minflux` and `phase2d-direction` when the minimal-flux or direction phase does not + prove optimal. +* `mergeLinear`: groups are numbered 1, 2, ... in the order of the first reaction of each + group in `origRxnIds` (was: the order in which the reactions were merged). +* `mergeLinear`: a reaction flipped after the first merge pass is the one marked in + `reversedRxns` (was: the reaction at the same index of the original model). + +### Utilities + +* `grRuleToDNF`: help states the grammar it reads, that of `parseGrRule` (`and`/`or` in any + case, `&`, `&&`, `|`, `||`, operators as whole words, so `G1&G2` is one gene id), and that a + malformed rule raises `RAVEN:badGrRule`. + ## 3.0.0b1 — 2026-09-07 First beta toward 3.0. Changes below are relative to RAVEN 2.9.3, the last 2.x release: diff --git a/src/INIT/ftINIT.m b/src/INIT/ftINIT.m index 1c66ba91..61de0623 100644 --- a/src/INIT/ftINIT.m +++ b/src/INIT/ftINIT.m @@ -71,10 +71,21 @@ % if true, pin each step's degenerate MILP to a canonical answer instead % of relying on the solver Seed alone: hold the score objective at its % optimum, then minimise the count of removable ("on") reactions, then, -% among the sparsest, minimise their summed reaction-id rank. Applies to -% every main-extraction step (ftINITInternalAlg) and to the final -% gap-filling step (fitTasks' resolveTies) -- see raven-gecko-parity#104 -% and raven-toolbox's resolve_ties (default false). +% among the sparsest, minimise their summed reaction-id rank. This +% applies to every main-extraction step and to the final gap-filling +% step (fitTasks' resolveTies) -- see raven-gecko-parity#104 and +% raven-toolbox's resolve_ties. In each main-extraction step but the +% last (ftINITInternalAlg) the fluxes, which set the direction and +% magnitude a reaction is forced at in the next step, then come from the +% least-total-flux solution for that on/off state and, among those, from +% the one with the least id-rank-weighted backward flux (a merged +% reaction ranked by the smallest original id in its group). The last +% step skips these flux phases, as no step reads its fluxes. A step +% whose primary solve stops at the time limit keeps its primary +% solution without any of these phases; the count and id-rank phases +% get half of the step's TimeLimit each, and a warning +% names each phase that does not prove optimal or finds no solution +% (default false). % proveAbsGap : double % if set, prove each step to this fixed *absolute* MILP gap in a single % solve, instead of the relative-gap escalation below (stp.MILPParams). @@ -191,6 +202,18 @@ %essential rxn. fluxes = ones(length(prepData.minModel.rxns), 1).*0.1; +%resolveTies ranks a merged reaction by its group's smallest original id, +%which does not depend on which member names the merged reaction. +rankKeys = prepData.minModel.rxns; +[inRef, refInd] = ismember(rankKeys, prepData.refModel.rxns); +for i = find(inRef)' + g = prepData.groupIds(refInd(i)); + if g ~= 0 + members = sort(prepData.refModel.rxns(prepData.groupIds == g)); + rankKeys{i} = members{1}; + end +end + for initStep = 1:length(INITSteps) disp(['ftINIT: Running step ' num2str(initStep)]) stp = INITSteps{initStep}; @@ -199,6 +222,9 @@ error('RAVEN:badInput', '%s', 'RxnsToIgnoreMask may not cover rxns not covered in previous steps, but the other way around is fine.'); end rxnsToIgnoreLastStep = stp.RxnsToIgnoreMask; + %The fluxes of a step set the forced directions of the next one, so the + %last step skips the flux phases of resolveTies. + pinFluxes = initStep < numel(INITSteps); mm = prepData.minModel; @@ -256,7 +282,7 @@ params.MIPGap = 0; params.MIPGapAbs = proveAbsGap; try - [deletedRxnsInINIT1, metProduction,fullMipRes,rxnsTurnedOn1,fluxes1] = ftINITInternalAlg(mm,rxnScores,metData,essentialRxns,5,stp.AllowMetSecr,stp.PosRevOff,params, [], fluxes, verbose, 0.1, resolveTies); + [deletedRxnsInINIT1, metProduction,fullMipRes,rxnsTurnedOn1,fluxes1] = ftINITInternalAlg(mm,rxnScores,metData,essentialRxns,5,stp.AllowMetSecr,stp.PosRevOff,params, [], fluxes, verbose, 0.1, resolveTies, rankKeys, pinFluxes); fluxes1(toRev) = -fluxes1(toRev); success = true; if fullMipRes.stat ~= 1 @@ -310,7 +336,7 @@ if ~isempty(fullMipRes) startVals = fullMipRes.full; end - [deletedRxnsInINIT1, metProduction,fullMipRes,rxnsTurnedOn1,fluxes1] = ftINITInternalAlg(mm,rxnScores,metData,essentialRxns,5,stp.AllowMetSecr,stp.PosRevOff,params, startVals, fluxes, verbose, 0.1, resolveTies); + [deletedRxnsInINIT1, metProduction,fullMipRes,rxnsTurnedOn1,fluxes1] = ftINITInternalAlg(mm,rxnScores,metData,essentialRxns,5,stp.AllowMetSecr,stp.PosRevOff,params, startVals, fluxes, verbose, 0.1, resolveTies, rankKeys, pinFluxes); %This is a bit tricky - since we reversed some reactions, those fluxes also need to be reversed fluxes1(toRev) = -fluxes1(toRev); diff --git a/src/INIT/ftINITInternalAlg.m b/src/INIT/ftINITInternalAlg.m index 9e8ebc84..af29c682 100644 --- a/src/INIT/ftINITInternalAlg.m +++ b/src/INIT/ftINITInternalAlg.m @@ -1,4 +1,4 @@ -function [deletedRxns,metProduction,res,turnedOnRxns,fluxes]=ftINITInternalAlg(model,rxnScores,metData,essentialRxns,prodWeight,allowExcretion,remPosRev,params,startVals,fluxes,verbose,forceOn,resolveTies) +function [deletedRxns,metProduction,res,turnedOnRxns,fluxes]=ftINITInternalAlg(model,rxnScores,metData,essentialRxns,prodWeight,allowExcretion,remPosRev,params,startVals,fluxes,verbose,forceOn,resolveTies,rankKeys,pinFluxes) % ftINITInternalAlg % This function runs the MILP for a step in ftINIT. % @@ -44,9 +44,30 @@ % rank. Positive-score reactions carry a continuous, not a % free binary, on/off variable (pinned near 1 by the score % objective), so only the negative-score ones are a genuine -% tie to resolve -- matches raven-toolbox's resolve_ties / -% _resolve_ties (raven-gecko-parity#104) (optional, default -% false) +% tie to resolve. The fluxes are then taken from the +% least-total-flux solution for that on/off state and, +% among those, from the one with the least id-rank-weighted +% backward flux, so the directions and forced magnitudes +% the next ftINIT step reads from them are pinned too -- +% matches raven-toolbox's resolve_ties +% (raven-gecko-parity#104). The phases are skipped when +% the primary solve stops at params.TimeLimit, and the +% primary solution is kept. Phases 2a and 2b get half of +% params.TimeLimit each (rounded down, at least 1 s), the +% flux phases the full limit. A warning names each phase +% that does not prove optimal or finds no solution; when a +% flux phase finds none, the fluxes of the phase before it +% are kept (optional, default false) +% rankKeys cell array with, for each reaction in model.rxns, the key +% that the id-rank phases of resolveTies sort it by +% (optional, default model.rxns). ftINIT passes the smallest +% original reaction id of each merge group, so the order +% does not depend on which member names the merged reaction +% pinFluxes if false, resolveTies skips phases 2c and 2d and the +% fluxes and metProduction are those of phase 2b +% (optional, default true). +% ftINIT passes false in its last step, whose fluxes no +% later step reads % % deletedRxns reactions which were deleted by the algorithm (only % rxns included in the problem) @@ -70,9 +91,15 @@ if nargin < 12 || isempty(forceOn) forceOn = 0.1; end +if nargin < 14 || isempty(rankKeys) + rankKeys = model.rxns; +end if nargin < 13 || isempty(resolveTies) resolveTies = false; end +if nargin < 15 || isempty(pinFluxes) + pinFluxes = true; +end if isempty(essentialRxns) essentialRxns={}; end @@ -453,7 +480,10 @@ error('RAVEN:badInput', '%s', EM); end -if resolveTies && (nNegIrrev + nNegRev) > 0 +if resolveTies && (nNegIrrev + nNegRev) > 0 && ~res.hitTimeLimit + %Skipped when the primary solve stopped at the time limit: the floor + %constraint would pin an unproven incumbent, so the phases cannot select + %among true optima. %This MILP is highly degenerate: many reaction subsets reach the same %score optimum, and the solver returns an arbitrary one -- reproducible %for a fixed solver build + Seed, but fragile to a solver version bump @@ -483,6 +513,10 @@ tieParams = params; tieParams.MIPGap = 0; tieParams.MIPGapAbs = 0.4; + %Phases 2a and 2b get half of the time limit each, as in raven-toolbox. + if isfield(tieParams,'TimeLimit') + tieParams.TimeLimit = max(1,floor(tieParams.TimeLimit*0.5)); + end unproven = {}; @@ -508,7 +542,7 @@ tieProb2b.b = [tieProb.b; kmin+0.5]; tieProb2b.csense = [tieProb.csense 'L']; - [~, sortOrder] = sort(model.rxns(negRxnIdx)); + [~, sortOrder] = sort(rankKeys(negRxnIdx)); ranks = zeros(numel(negRxnIdx),1); ranks(sortOrder) = (1:numel(negRxnIdx))'; idObj = zeros(size(prob.c)); @@ -522,6 +556,106 @@ if isFeasible2b %Adopt the tie-resolved solution for the on/off read-out below. res = res2b; + + %Phases 2c and 2d change the fluxes, not the on/off state; the next + %ftINIT step reads the fluxes, and pinFluxes is false when no step + %follows. + if pinFluxes + %Phase 2c: the least total absolute flux for this on/off state. + %The next step forces each turned-on reaction in the direction + %of its flux, at min(0.99*|flux|,0.1), so the flux read-out + %comes from a minimal-flux solution instead of an arbitrary + %vertex. A binary on/off variable is fixed at its value; a + %continuous one that is on keeps at least its value, one that + %is off at most its value. Directions of reversible scored + %reactions stay free, so this is still a MILP. + %It stops at a relative or absolute gap of 1e-6, as raven-toolbox's + %phase 2c. + nCols = numel(prob.c); + minProb = tieProb2b; + if isfield(minProb,'start') + minProb = rmfield(minProb,'start'); + end + x = res2b.full; + negVal = round(x(negCols)); + minProb.lb(negCols) = negVal; + minProb.ub(negCols) = negVal; + posCols = [onoffPosIrrev, onoffPosRev]; + posVal = min(max(x(posCols),0),1); + posOn = posVal >= 0.5; + minProb.lb(posCols(posOn)) = posVal(posOn); + minProb.ub(posCols(~posOn)) = posVal(~posOn); + %|v| is v for a forward-only flux, -v for a backward-only one, + %and an added t >= v, t >= -v otherwise. + fwdOnly = minProb.lb(1:nRxns) >= 0; + bwdOnly = ~fwdOnly & minProb.ub(1:nRxns) <= 0; + tRxns = find(~fwdOnly & ~bwdOnly); + nT = numel(tRxns); + selV = sparse(1:nT, tRxns, 1, nT, nCols); + minProb.A = [minProb.A sparse(size(minProb.A,1),nT); -selV speye(nT); selV speye(nT)]; + minProb.a = minProb.A; + minProb.b = [minProb.b; zeros(2*nT,1)]; + minProb.csense = [minProb.csense repmat('G',1,2*nT)]; + minProb.lb = [minProb.lb; zeros(nT,1)]; + minProb.ub = [minProb.ub; inf(nT,1)]; + minProb.vartype = [minProb.vartype repmat('C',1,nT)]; + minProb.c = [zeros(nCols,1); ones(nT,1)]; + minProb.c(fwdOnly) = 1; + minProb.c(bwdOnly) = -1; + minParams = params; + minParams.MIPGap = 1e-6; + minParams.MIPGapAbs = 1e-6; + res2c = optimizeProb(minProb,minParams,verbose); + [isFeasible2c, isOptimal2c] = checkSolution(res2c); + if ~isOptimal2c + unproven{end+1} = 'phase2c-minflux'; + end + if isFeasible2c + %Only the fluxes change; the status stays that of phase 2b. + res.full = res2c.full(1:nCols); + + %Phase 2d: the least total flux can leave a direction open + %when two directions carry the same total flux, for example + %a pair of reversible reactions closing a loop either way. + %Keep the on/off state of phase 2c, hold the total absolute + %flux at most at its phase-2c value plus + %max(1e-7*total, 1e-6), and minimise the backward flux + %max(0,-v) of the reactions whose flux can take either sign, + %weighted by id rank from the end: sorted by rankKeys, the + %first of n reactions gets weight n and the last 1, so a + %reaction with a lower id prefers its forward direction. + if nT > 0 + total = minProb.c' * res2c.full; + dirProb = minProb; + nCols2c = numel(minProb.c); + [~, ord] = sort(rankKeys(tRxns)); + w = zeros(nT,1); + w(ord) = (nT:-1:1)'; + dirProb.A = [minProb.A sparse(size(minProb.A,1),nT); ... + minProb.c' sparse(1,nT); ... + selV sparse(nT,nT) speye(nT)]; + dirProb.a = dirProb.A; + dirProb.b = [minProb.b; total + max(abs(total)*1e-7, 1e-6); zeros(nT,1)]; + dirProb.csense = [minProb.csense 'L' repmat('G',1,nT)]; + dirProb.lb = [minProb.lb; zeros(nT,1)]; + dirProb.ub = [minProb.ub; inf(nT,1)]; + dirProb.vartype = [minProb.vartype repmat('C',1,nT)]; + dirProb.c = [zeros(nCols2c,1); w]; + %The phase-2c solution is the MIP start: the tight cap on + %the total flux leaves the solver's heuristics little room + %to find a first solution. + dirProb.start = [res2c.full; max(0, -res2c.full(tRxns))]; + res2d = optimizeProb(dirProb,minParams,verbose); + [isFeasible2d, isOptimal2d] = checkSolution(res2d); + if ~isOptimal2d + unproven{end+1} = 'phase2d-direction'; + end + if isFeasible2d + res.full = res2d.full(1:nCols); + end + end + end + end end end if ~isempty(unproven) diff --git a/src/INIT/mergeLinear.m b/src/INIT/mergeLinear.m index 068fab65..66847e98 100644 --- a/src/INIT/mergeLinear.m +++ b/src/INIT/mergeLinear.m @@ -13,7 +13,8 @@ % reducedModel an updated model structure % origRxnIds Cell array: original rxn ids, used together with the groupIds variable % groupIds Vector of ids: merged rxns have the same id, same length as origRxnIds -% 0 means a reaction was not merged. +% 0 means a reaction was not merged. The groups are numbered 1, 2, ... +% in the order of their first reaction in origRxnIds. % reversedRxns Vector of booleans, saying for each rxn if it is reversed in the combined rxns. % % Usage: [reducedModel,origRxnIds,groupIds]=mergeLinear(model,noMergeRxns) @@ -196,7 +197,12 @@ notInUse=sum(reducedModel.S~=0,2)==0; reducedModel=removeMets(reducedModel,notInUse); end - + +%Number the groups in the order of their first reaction in origRxnIds, so +%the numbers do not depend on the order in which the reactions were merged +merged=groupIds~=0; +[~,~,groupIds(merged)]=unique(groupIds(merged),'stable'); + function [model1,reversedRxns1] = flipRxn(model1, rxnInd, groupIds1, reversedRxns1) model1.S(:,rxnInd) = model1.S(:,rxnInd)*-1; %swap the bounds @@ -208,10 +214,13 @@ %now take care of the reversedRxns - if this is a group, reverse all of the %reactions in the group in the reversedRxns index - they will all be reversed at the - %same time since they are the same rxn. - rxnIndices = rxnInd; - if groupIds1(rxnInd) > 0 - rxnIndices = find(groupIds1 == groupIds1(rxnInd)); + %same time since they are the same rxn. rxnInd indexes model1, which loses + %reactions after each pass, while groupIds1 and reversedRxns1 follow + %origRxnIds, so the reaction is looked up by its id. + origInd = origRxnIdMap(model1.rxns{rxnInd}); + rxnIndices = origInd; + if groupIds1(origInd) > 0 + rxnIndices = find(groupIds1 == groupIds1(origInd)); end reversedRxns1(rxnIndices) = ~reversedRxns1(rxnIndices); end diff --git a/src/gapfilling/gapFillTopological.m b/src/gapfilling/gapFillTopological.m index fe6e335e..bf832c02 100644 --- a/src/gapfilling/gapFillTopological.m +++ b/src/gapfilling/gapFillTopological.m @@ -34,8 +34,11 @@ % metabolites of every exchange reaction whose bounds let it supply the % metabolite, i.e. getExchangeRxns 'uptake' plus 'reverse'. % 'targets' (default []) -% Metabolite IDs (cell array) that should be produced. Default: -% substrates of the objective (biomass) reaction. +% Metabolite IDs (cell array) that should be produced. Default: every +% metabolite of the objective (biomass) reaction, substrates and +% products; the first one when there are several objective reactions. +% Without targets and without an objective reaction the function raises +% RAVEN:badInput. % 'verbose' (default true) % print a summary of the results. % @@ -99,10 +102,10 @@ % ---- Identify target metabolites ---- if isempty(targets) - % Default: substrates of the objective (biomass) reaction + % Default: every metabolite of the objective (biomass) reaction objRxns = find(model.c ~= 0); if isempty(objRxns) - error(['gapFillTopological: no objective function defined. ' ... + error('RAVEN:badInput', ['gapFillTopological: no objective function defined. ' ... 'Provide targets manually via the ''targets'' option.']); end biomassIdx = objRxns(1); diff --git a/src/manipulation/mergeCompartments.m b/src/manipulation/mergeCompartments.m index 80c7982e..ce4e80e9 100755 --- a/src/manipulation/mergeCompartments.m +++ b/src/manipulation/mergeCompartments.m @@ -42,8 +42,13 @@ % be useful for example to ensure that there are metabolic capabilities to % synthesize all metabolites. % -% If the metabolite IDs reflect the compartment that they are in the IDs may -% no longer be representative. +% Metabolites with the same name are merged into one. The merged metabolite +% keeps the id and the other fields of one of the copies: the copy that is +% already in compartment "s" if there is one, otherwise the copy whose id +% comes first in character code order (for example ACCOA_c before ACCOA_m). +% With keepUnconstrained only the copies that are not unconstrained are +% candidates. If the metabolite IDs reflect the compartment that they are in +% the IDs may no longer be representative. p=parseRAVENargs(varargin, {'keepUnconstrained',false; 'deleteRxnsWithOneMet',false; 'distReverse',true}); keepUnconstrained=p.keepUnconstrained; @@ -66,9 +71,8 @@ end end -%Loop through each metabolite, and if it is not unconstrained then change -%the S matrix to use the metabolite with the lowest index in model.comps -%instead +%Loop through each metabolite name, and if a copy is not unconstrained then +%change the S matrix to use the copy chosen as described in the Notes %Group metabolite indices by name once, rather than re-scanning all of %model.metNames (via ismember) for every unique name, which is quadratic %in a genome-scale model's (unique names x mets) count. @@ -79,19 +83,26 @@ I=false(numel(model.metNames),1); I(sameNameIdx{i})=true; - %Find the first of those that is not unconstrained. This is the one - %that the other "un-unconstrained" should be changed to. + %Choose the copy that the other "un-unconstrained" copies are merged + %into: one already in compartment "s" if there is one, otherwise the + %one whose id sorts first. if keepUnconstrained==true - mergeTo=find(I & model.unconstrained==false,1); + candidates=find(I & model.unconstrained==false); %This could happen if there is only one metabolite and it is %unconstrained - if isempty(mergeTo) + if isempty(candidates) continue; end else - mergeTo=find(I,1); + candidates=find(I); end + inMerged=candidates(strcmp(model.comps(model.metComps(candidates)),'s')); + if ~isempty(inMerged) + candidates=inMerged; + end + [~,first]=sort(model.mets(candidates)); + mergeTo=candidates(first(1)); I(mergeTo)=false; %Do not do anything for itself I=find(I); diff --git a/src/manipulation/removeGenes.m b/src/manipulation/removeGenes.m index e2a50f32..00f3e30f 100755 --- a/src/manipulation/removeGenes.m +++ b/src/manipulation/removeGenes.m @@ -23,7 +23,15 @@ % Returns % ------- % reducedModel : struct -% an updated model structure. +% an updated model structure. Genes in genesToRemove that are not in the +% model are ignored. When none of them is in the model, no gene, reaction +% or bound changes (with standardizeRules true the gene rules are still +% written in the standard format). Otherwise, besides genesToRemove, every +% gene that no remaining reaction used before the removal is deleted as +% well, also when it was not requested (for example a gene that no +% reaction uses). A gene whose only reaction is kept but blocked +% (removeBlockedRxns false), such as the other subunit of a complex that +% lost one, stays. % % Examples % -------- diff --git a/src/manipulation/simplifyModel.m b/src/manipulation/simplifyModel.m index e5dd214f..d579230d 100755 --- a/src/manipulation/simplifyModel.m +++ b/src/manipulation/simplifyModel.m @@ -185,30 +185,33 @@ if constrainReversible==true revs=find(reducedModel.rev); - [I,J]=getAllowedBounds(reducedModel,revs); + %getAllowedBounds treats an empty reaction list as all reactions + if ~isempty(revs) + [I,J]=getAllowedBounds(reducedModel,revs); - I=abs(I); - J=abs(J); + I=abs(I); + J=abs(J); - %Get the "small" values - K=I<10^-10; - L=J<10^-10; + %Get the "small" values + K=I<10^-10; + L=J<10^-10; - %Keep the values where only one direction is zero (small) - I(K==L)=[]; - J(K==L)=[]; - revs(K==L)=[]; + %Keep the values where only one direction is zero (small) + I(K==L)=[]; + J(K==L)=[]; + revs(K==L)=[]; - %Change the reversibility of the remaining reactions - reducedModel.rev(revs(J>10^-10))=0; %Ignore reverse direction - reducedModel.lb(revs(J>10^-10))=0; + %Change the reversibility of the remaining reactions + reducedModel.rev(revs(J>10^-10))=0; %Ignore reverse direction + reducedModel.lb(revs(J>10^-10))=0; - toSwitch=revs(I>10^-10); - reducedModel.rev(toSwitch)=0; %Change directionality - reducedModel.ub(toSwitch)=reducedModel.lb(toSwitch)*-1; - reducedModel.lb(toSwitch)=0; - reducedModel.S(:,toSwitch)=reducedModel.S(:,toSwitch).*-1; - reducedModel.c(toSwitch)=reducedModel.c(toSwitch)*-1; + toSwitch=revs(I>10^-10); + reducedModel.rev(toSwitch)=0; %Change directionality + reducedModel.ub(toSwitch)=reducedModel.lb(toSwitch)*-1; + reducedModel.lb(toSwitch)=0; + reducedModel.S(:,toSwitch)=reducedModel.S(:,toSwitch).*-1; + reducedModel.c(toSwitch)=reducedModel.c(toSwitch)*-1; + end end if groupLinear==true if suppressWarnings==false diff --git a/src/utils/grRuleToDNF.m b/src/utils/grRuleToDNF.m index 34da5a64..f2ea4465 100644 --- a/src/utils/grRuleToDNF.m +++ b/src/utils/grRuleToDNF.m @@ -8,7 +8,10 @@ % Parameters % ---------- % rule : char or string or struct -% a grRule, or a tree from parseGrRule. +% a grRule, or a tree from parseGrRule. A grRule is read by parseGrRule: +% genes joined by "and"/"or" in any case or by "&", "&&", "|", "||", +% each operator separated from the gene ids by whitespace or a bracket, +% with AND binding tighter than OR. % % Returns % ------- @@ -18,6 +21,10 @@ % % Notes % ----- +% A malformed rule (an operator without an operand, unbalanced or empty +% parentheses, two genes without an operator between them) raises +% RAVEN:badGrRule. +% % Gene order within a clause follows the order the genes appear in the rule. % Duplicates are not removed: "G1 and G1" stays a two-element clause, since % collapsing it would silently rewrite the user's rule. diff --git a/tests/function_tests/tGapfilling.m b/tests/function_tests/tGapfilling.m index acd68455..b4492fa6 100644 --- a/tests/function_tests/tGapfilling.m +++ b/tests/function_tests/tGapfilling.m @@ -350,6 +350,21 @@ function gapFillTopologicalBlocksWithoutExchange(testCase) testCase.verifyNumElements(result.blockedMets, 3); end + function gapFillTopologicalDefaultTargetsAreObjectiveMets(testCase) + % Objective R2 (B -> C): the default targets are its substrate B + % and its product C. Without an exchange both are blocked. + m = testCase.chainModel(); + evalc('result = gapFillTopological(m, m, ''verbose'', false);'); + testCase.verifyEqual(sort(result.blockedMets(:)), {'B';'C'}); + end + + function gapFillTopologicalDefaultTargetsNeedObjective(testCase) + m = testCase.chainModel(); + m.c = [0;0]; + testCase.verifyError(@() gapFillTopological(m, m, 'seeds', {'A'}, ... + 'verbose', false), 'RAVEN:badInput'); + end + function gapFillMILPRepairsGrowth(testCase) testCase.assumeMILPSolver(); modelDB = testCase.model; modelDB.id = 'DB'; diff --git a/tests/function_tests/tINIT.m b/tests/function_tests/tINIT.m index 4564e29d..dccb1630 100644 --- a/tests/function_tests/tINIT.m +++ b/tests/function_tests/tINIT.m @@ -179,6 +179,75 @@ function mergeLinearGroupsAndScores(testCase) testCase.verifyTrue(all(find(reversedRxns) == [6;9])); end + function mergeLinearNumbersGroupsByFirstRxn(testCase) + % Group numbers follow the first reaction of each group in + % model.rxns, not the merge order. The metabolites are visited + % in model.mets order, so c (R5, R6) merges first. + model = struct(); + model.id = 'numbering'; + model.rxns = {'R1';'R2';'R3';'R4';'R5';'R6'}; + model.rxnNames = model.rxns; + model.mets = {'z';'c';'w';'x';'a';'m';'b';'y'}; + model.metNames = model.mets; + model.comps = {'c'}; + model.compNames = {'c'}; + model.metComps = ones(8,1); + subs = {'x';'a';'m';'b';'z';'c'}; + prods = {'a';'m';'b';'y';'c';'w'}; + model.S = sparse(8,6); + for i = 1:6 + model.S(strcmp(model.mets,subs{i}),i) = -1; + model.S(strcmp(model.mets,prods{i}),i) = 1; + end + model.lb = zeros(6,1); + model.ub = 1000*ones(6,1); + model.rev = zeros(6,1); + model.c = zeros(6,1); + model.b = zeros(8,1); + model.genes = cell(0,1); + model.grRules = repmat({''},6,1); + model.rxnGeneMat = sparse(6,0); + evalc('[reducedModel,~,groupIds] = mergeLinear(model, {});'); + testCase.verifyEqual(groupIds(:), [1;1;1;1;2;2]); + testCase.verifyEqual(sort(reducedModel.rxns), {'R1';'R5'}); + end + + function mergeLinearFlipsRxnInLaterPass(testCase) + % R1 and R2 merge through A in the first pass and R2 is removed, + % so R4 sits at index 3 of the reduced model. The second pass + % merges R1 and R4 through Y and flips R4, which must be the + % reaction marked as reversed (not R3, the third original one). + model = struct(); + model.id = 'lateflip'; + model.rxns = {'R1';'R2';'R3';'R4';'R5';'R6'}; + model.rxnNames = model.rxns; + model.mets = {'A';'Y';'Q';'W'}; + model.metNames = model.mets; + model.comps = {'c'}; + model.compNames = {'c'}; + model.metComps = ones(4,1); + S = zeros(4,6); + S([1 2],1) = [1;1]; % R1: -> A + Y + S([1 2],2) = [-1;1]; % R2: A -> Y + S(3,3) = -1; % R3: Q -> + S([4 2],4) = [-1;1]; % R4: W <=> Y + S(4,5) = 1; % R5: -> W + S(4,6) = -1; % R6: W -> + model.S = sparse(S); + model.lb = [0;0;0;-1000;0;0]; + model.ub = 1000*ones(6,1); + model.rev = double(model.lb < 0); + model.c = zeros(6,1); + model.b = zeros(4,1); + model.genes = cell(0,1); + model.grRules = repmat({''},6,1); + model.rxnGeneMat = sparse(6,0); + evalc('[reducedModel,origRxnIds,groupIds,reversedRxns] = mergeLinear(model, {});'); + testCase.verifyEqual(groupIds(:), [1;1;0;1;0;0]); + testCase.verifyEqual(origRxnIds(reversedRxns), {'R4'}); + testCase.verifyEqual(sort(reducedModel.rxns), {'R1';'R3';'R5';'R6'}); + end + function scoreModelRuns(testCase) arrayData.genes = testCase.model.genes; arrayData.tissues = {'t1'}; @@ -384,6 +453,165 @@ function ftINITResolveTiesAndProveAbsGapStillRunCorrectly(testCase) testCase.verifyTrue(all(strcmp(resBoth.rxns, expected))); end + function ftINITResolveTiesRanksMergedRxnBySmallestOriginalId(testCase) + % a[c] reaches m[c] by path P (Rm, Rc) or path Q (Rn, Rb), each + % path one linear merge group with equal negative scores. Model + % order and group numbers put P first, and the merged reactions + % are named by their first members Rm and Rn, but the smallest + % original id of Q (Rb) sorts before that of P (Rc). resolveTies + % keeps Q; ranking by the merged id, the model order or the group + % number would keep P. + testCase.assumeMILPSolver(); + testModel = getTstModelTwoPaths(); + evalc('prepData = prepINITModel(testModel, getTstModelTasks(), {}, false, {}, ''s'');'); + g = @(r) prepData.groupIds(strcmp(prepData.refModel.rxns, r)); + testCase.verifyEqual(g('Rm'), g('Rc')); + testCase.verifyEqual(g('Rn'), g('Rb')); + testCase.verifyLessThan(g('Rm'), g('Rn')); + testCase.verifyTrue(all(ismember({'Rm';'Rn'}, prepData.minModel.rxns))); + + arrayData.genes = testModel.genes; + arrayData.tissues = {'a'}; + arrayData.levels = getExprForRxnScore([-1;-1;-1;-1;5;5]); + arrayData.threshold = 1; + evalc(['resModel = ftINIT(prepData,arrayData.tissues{1},[],[],' ... + 'arrayData,[],getINITSteps(),true,true,struct(),false,' ... + '''resolveTies'',true);']); + testCase.verifyTrue(all(ismember({'Rn';'Rb'}, resModel.rxns))); + testCase.verifyFalse(any(ismember({'Rm';'Rc'}, resModel.rxns))); + end + + function ftINITPinsFluxesInEveryStepButTheLast(testCase) + % ftINIT passes pinFluxes true to ftINITInternalAlg in each step + % whose fluxes a later step reads and false in the last step. A + % wrapper of ftINITInternalAlg earlier on the path records the + % argument. + testCase.assumeMILPSolver(); + global TINIT_INTERNALALG TINIT_PINFLUXES + TINIT_INTERNALALG = @ftINITInternalAlg; + TINIT_PINFLUXES = false(1,0); + d = tempname; mkdir(d); + fid = fopen(fullfile(d,'ftINITInternalAlg.m'),'w'); + fprintf(fid,'%s\n', ... + 'function varargout = ftINITInternalAlg(varargin)', ... + 'global TINIT_INTERNALALG TINIT_PINFLUXES', ... + 'TINIT_PINFLUXES(end+1) = varargin{15};', ... + '[varargout{1:nargout}] = TINIT_INTERNALALG(varargin{:});', ... + 'end'); + fclose(fid); + testCase.addTeardown(@() rmdir(d,'s')); + addpath(d,'-begin'); + testCase.addTeardown(@() rmpath(d)); + testCase.addTeardown(@() clear('global','TINIT_INTERNALALG','TINIT_PINFLUXES')); + + testModel = getTstModelTwoPaths(); + evalc('prepData = prepINITModel(testModel, getTstModelTasks(), {}, false, {}, ''s'');'); + arrayData.genes = testModel.genes; + arrayData.tissues = {'a'}; + arrayData.levels = getExprForRxnScore([-1;-1;-1;-1;5;5]); + arrayData.threshold = 1; + steps = getINITSteps(); + evalc(['resModel = ftINIT(prepData,arrayData.tissues{1},[],[],' ... + 'arrayData,[],steps,true,true,struct(),false,' ... + '''resolveTies'',true);']); + testCase.verifyEqual(TINIT_PINFLUXES, [true(1,numel(steps)-1) false]); + testCase.verifyTrue(all(ismember({'Rn';'Rb'}, resModel.rxns))); + end + + function ftINITInternalAlgResolveTiesGivesMinimalFluxes(testCase) + % With resolveTies the fluxes come from the least-total-flux + % solution for the tie-broken on/off state. The positive + % reversible R3 (m <=> q) can be on in either direction: backward + % (import q, feed the essential E) carries 0.4 in total, forward + % (a via the free R1 into R3 and E) 0.8. The free loop L1/L2 + % carries nothing and the negative-score dead end R2 stays off. + testCase.assumeMILPSolver(); + m.rxns = {'R1';'R2';'R3';'E';'L1';'L2';'EX_a';'EX_p';'EX_q'}; + m.mets = {'a';'m';'p';'q';'n';'z'}; + % R1 R2 R3 E L1 L2 EXa EXp EXq + m.S = sparse([-1 -1 0 0 0 0 -1 0 0; % a + 1 0 -1 -1 -1 1 0 0 0; % m + 0 0 0 1 0 0 0 -1 0; % p + 0 0 1 0 0 0 0 0 -1; % q + 0 0 0 0 1 -1 0 0 0; % n + 0 1 0 0 0 0 0 0 0]); % z + m.lb = [0;0;-1000;0;0;0;-1000;-1000;-1000]; + m.ub = 1000*ones(9,1); + m.rev = double(m.lb < 0); + scores = [0;-1;1;0;0;0;0;0;0]; + fluxIn = 0.1*ones(9,1); + [deleted,~,~,turnedOn,fluxes] = ftINITInternalAlg(m,scores,[],{'E'}, ... + [],false,false,struct(),[],fluxIn,false,0.1,true); + testCase.verifyTrue(turnedOn(3)); + testCase.verifyTrue(deleted(2)); + % E is forced at min(0.99*0.1,0.1) and needs the 0.1 of R3. + expected = [0;0;-0.1;0.1;0;0;0;0.1;-0.1]; + testCase.verifyEqual(fluxes, expected, 'AbsTol', 1e-6); + end + + function ftINITInternalAlgResolveTiesFixesEqualFluxDirections(testCase) + % The essential E consumes y, which the reversible R1 (y <=> x) + % or R2 (y <=> z) can supply when run backward from the imports + % IN_x or IN_z. Both routes carry the same total flux; with + % resolveTies the higher-ranked reaction carries the backward + % flux: R2 when ranked by id, R1 when rankKeys rank it last. N is + % a negative-score dead end. + testCase.assumeMILPSolver(); + [m, scores] = getTstModelTwoRoutes(); + fluxIn = ones(7,1); + [~,~,~,~,fluxes] = ftINITInternalAlg(m,scores,[],{'E'}, ... + [],false,false,struct(),[],fluxIn,false,0.1,true); + testCase.verifyEqual(fluxes(3:4), [0;-0.1], 'AbsTol', 1e-6); + keys = {'IN_x';'IN_z';'R9';'R2';'E';'OUT_p';'N'}; + [~,~,~,~,fluxes] = ftINITInternalAlg(m,scores,[],{'E'}, ... + [],false,false,struct(),[],fluxIn,false,0.1,true,keys); + testCase.verifyEqual(fluxes(3:4), [-0.1;0], 'AbsTol', 1e-6); + end + + function ftINITInternalAlgResolveTiesSkippedAfterTimeLimit(testCase) + % When the primary solve stops at the time limit, resolveTies + % keeps the primary solution and runs no tie-break phase. A + % wrapper of optimizeProb counts the solves and reports each as + % stopped at the time limit. + testCase.assumeMILPSolver(); + installOptimizeProbCounter(testCase, true); + global TINIT_OPTIMIZEPROB_CALLS + m = getTstModelTwoSources(); + [~,~,res,turnedOn] = ftINITInternalAlg(m,[-1;-1;0;0;0],[],{'E'}, ... + [],false,false,struct(),[],0.1*ones(5,1),false,0.1,true); + testCase.verifyEqual(TINIT_OPTIMIZEPROB_CALLS, 1); + testCase.verifyTrue(res.hitTimeLimit); + testCase.verifyEqual(nnz(turnedOn(1:2)), 1); + end + + function ftINITInternalAlgResolveTiesWithoutPinFluxesSkipsFluxPhases(testCase) + % With pinFluxes false, resolveTies runs the primary solve and + % phases 2a and 2b, not 2c and 2d, and still keeps the lower-id R1. + testCase.assumeMILPSolver(); + installOptimizeProbCounter(testCase, false); + global TINIT_OPTIMIZEPROB_CALLS + m = getTstModelTwoSources(); + [~,~,~,turnedOn] = ftINITInternalAlg(m,[-1;-1;0;0;0],[],{'E'}, ... + [],false,false,struct(),[],0.1*ones(5,1),false,0.1,true,[],false); + testCase.verifyEqual(TINIT_OPTIMIZEPROB_CALLS, 3); + testCase.verifyEqual(turnedOn(1:2), [true false]); + TINIT_OPTIMIZEPROB_CALLS = 0; + ftINITInternalAlg(m,[-1;-1;0;0;0],[],{'E'}, ... + [],false,false,struct(),[],0.1*ones(5,1),false,0.1,true); + testCase.verifyEqual(TINIT_OPTIMIZEPROB_CALLS, 5); + end + + function prepINITModelHandlesModelWithoutReversibleRxns(testCase) + % Every reaction is irreversible, so the second simplification + % has no reversible reaction to constrain. The merge gives the + % same reactions and groups as raven-toolbox's prep_init_model. + testModel = getTstModelIrreversible(); + evalc('prepData = prepINITModel(testModel, [], {}, false, {}, ''s'');'); + testCase.verifyEqual(prepData.minModel.rxns, {'EX_a';'R1';'R2';'T2'}); + testCase.verifyEqual(prepData.groupIds, [1;1;0;2;2;3;3]); + testCase.verifyEqual(prepData.refModel.rxns, testModel.rxns); + end + function ftINITWithTaskRuns(testCase) % A task requiring e[s] from a[s] forces R2 and R7 to be essential. testCase.assumeMILPSolver(); @@ -816,6 +1044,133 @@ function scoreModelRejectsUnknownDataPrecedence(testCase) testModel.b = repmat(0,8,1); end +function installOptimizeProbCounter(testCase, fakeTimeLimit) +%Put a wrapper of optimizeProb first on the path for the rest of the test. It +%counts the solves in the global TINIT_OPTIMIZEPROB_CALLS and, if +%fakeTimeLimit, reports each solve as stopped at the time limit. +global TINIT_OPTIMIZEPROB TINIT_OPTIMIZEPROB_CALLS TINIT_OPTIMIZEPROB_FAKETL +TINIT_OPTIMIZEPROB = @optimizeProb; +TINIT_OPTIMIZEPROB_CALLS = 0; +TINIT_OPTIMIZEPROB_FAKETL = fakeTimeLimit; +d = tempname; mkdir(d); +fid = fopen(fullfile(d,'optimizeProb.m'),'w'); +fprintf(fid,'%s\n', ... + 'function res = optimizeProb(varargin)', ... + 'global TINIT_OPTIMIZEPROB TINIT_OPTIMIZEPROB_CALLS TINIT_OPTIMIZEPROB_FAKETL', ... + 'res = TINIT_OPTIMIZEPROB(varargin{:});', ... + 'TINIT_OPTIMIZEPROB_CALLS = TINIT_OPTIMIZEPROB_CALLS + 1;', ... + 'if TINIT_OPTIMIZEPROB_FAKETL, res.hitTimeLimit = true; end', ... + 'end'); +fclose(fid); +testCase.addTeardown(@() rmdir(d,'s')); +addpath(d,'-begin'); +testCase.addTeardown(@() rmpath(d)); +testCase.addTeardown(@() clear('global','TINIT_OPTIMIZEPROB','TINIT_OPTIMIZEPROB_CALLS','TINIT_OPTIMIZEPROB_FAKETL')); +end + +function m = getTstModelTwoSources() +%R1 and R2 (a => m) can each feed the essential E (m => p): a tie for +%resolveTies. +m.rxns = {'R1';'R2';'E';'EX_a';'EX_p'}; +m.mets = {'a';'m';'p'}; +m.S = sparse([-1 -1 0 -1 0; + 1 1 -1 0 0; + 0 0 1 0 -1]); +m.lb = [0;0;0;-1000;-1000]; +m.ub = 1000*ones(5,1); +m.rev = double(m.lb < 0); +end + +function [m, scores] = getTstModelTwoRoutes() +%The essential E consumes y, which the reversible R1 (y <=> x) or R2 +%(y <=> z) can supply when run backward from the imports IN_x or IN_z. N is a +%negative-score dead end. +m.rxns = {'IN_x';'IN_z';'R1';'R2';'E';'OUT_p';'N'}; +m.mets = {'x';'y';'z';'p';'d'}; +% INx INz R1 R2 E OUTp N +m.S = sparse([1 0 1 0 0 0 -1; % x + 0 0 -1 -1 -1 0 0; % y + 0 1 0 1 0 0 0; % z + 0 0 0 0 1 -1 0; % p + 0 0 0 0 0 0 1]); % d +m.lb = [0;0;-1000;-1000;0;0;0]; +m.ub = 1000*ones(7,1); +m.rev = double(m.lb < 0); +scores = [0;0;0;0;0;0;-1]; +end + +function testModel = getTstModelIrreversible() +%a[s] is imported and reaches b[c] by R1 or by R2, R3; b[c] is exported. +%Every reaction is irreversible. +testModel = struct(); +testModel.id = 'irreversible'; +testModel.rxns = {}; +testModel.S = []; +testModel.rev = []; +testModel.mets = {'as';'ac';'bc';'cc';'bs'}; +testModel.metNames = {'a';'a';'b';'c';'b'}; +testModel.comps = {'s';'c'}; +testModel.compNames = testModel.comps; +testModel.metComps = [1;2;2;2;1]; +testModel.genes = {'G1';'G2';'G3'}; +testModel.grRules = {}; +testModel.rxnGeneMat = []; + +rxnsToAdd = struct(); +rxnsToAdd.rxns = {'EX_a';'T1';'R1';'R2';'R3';'T2';'EX_b'}; +rxnsToAdd.equations = {'=> a[s]';... + 'a[s] => a[c]';... + 'a[c] => b[c]';... + 'a[c] => c[c]';... + 'c[c] => b[c]';... + 'b[c] => b[s]';... + 'b[s] =>'}; +rxnsToAdd.grRules = {'';'';'G1';'G2';'G3';'';''}; +evalc('testModel = addRxns(testModel,rxnsToAdd, 3);'); +testModel.c = [0;0;0;0;0;0;1]; +testModel.ub = repmat(1000,7,1); +testModel.lb = zeros(7,1); +testModel.rxnNames = testModel.rxns; +testModel.b = zeros(5,1); +end + +function testModel = getTstModelTwoPaths() +%a[c] reaches m[c] by Rm, Rc or by Rn, Rb; E, R7 and R8 export e[s]. The +%paths are reversible. +testModel = struct(); +testModel.id = 'twoPaths'; +testModel.rxns = {}; +testModel.S = []; +testModel.rev = []; +testModel.mets = {'as';'ac';'xc';'yc';'mc';'ec';'es'}; +testModel.metNames = {'a';'a';'x';'y';'m';'e';'e'}; +testModel.comps = {'s';'c'}; +testModel.compNames = testModel.comps; +testModel.metComps = [1;2;2;2;2;2;1]; +testModel.genes = {'Gm';'Gc';'Gn';'Gb';'GE';'G7'}; +testModel.grRules = {}; +testModel.rxnGeneMat = []; + +rxnsToAdd = struct(); +rxnsToAdd.rxns = {'R1';'R2';'Rm';'Rc';'Rn';'Rb';'E';'R7';'R8'}; +rxnsToAdd.equations = {'=> a[s]';... + 'a[s] <=> a[c]';... + 'a[c] <=> x[c]';... + 'x[c] <=> m[c]';... + 'a[c] <=> y[c]';... + 'y[c] <=> m[c]';... + 'm[c] => e[c]';... + 'e[c] => e[s]';... + 'e[s] =>'}; +rxnsToAdd.grRules = {'';'';'Gm';'Gc';'Gn';'Gb';'GE';'G7';''}; +evalc('testModel = addRxns(testModel,rxnsToAdd, 3);'); +testModel.c = [0;0;0;0;0;0;0;0;1]; +testModel.ub = repmat(1000,9,1); +testModel.lb = [0;-1000;-1000;-1000;-1000;-1000;0;0;0]; +testModel.rxnNames = testModel.rxns; +testModel.b = zeros(7,1); +end + function testModelRxnScores = getTstModelRxnScores() testModelRxnScores = [-2;-2;-1;7;0.5;0.5;-1;-2;-3;3.5]; end diff --git a/tests/function_tests/tManipulation.m b/tests/function_tests/tManipulation.m index e8cfa3b7..aa9ea83f 100644 --- a/tests/function_tests/tManipulation.m +++ b/tests/function_tests/tManipulation.m @@ -510,6 +510,23 @@ function mergeCompartmentsDropsStaleCompMiriams(testCase) end end + function mergeCompartmentsKeepsFirstSortedId(testCase) + % The merged metabolite keeps the id that sorts first, not the + % id of the copy that comes first in the model. + m = tManipulation.mergeToyModel({'ACCOA_m';'ACCOA_c';'X_c'}, {'c';'m'}, [2;1;1]); + evalc('m2 = mergeCompartments(m);'); + testCase.verifyEqual(sort(m2.mets), {'ACCOA_c';'X_c'}); + testCase.verifyEqual(m2.comps, {'s'}); + end + + function mergeCompartmentsPrefersCopyInMergedComp(testCase) + % A copy that is already in compartment s keeps its id, even when + % another copy has an id that sorts before it. + m = tManipulation.mergeToyModel({'ACCOA_c';'ACCOA_z';'X_c'}, {'c';'s'}, [1;2;1]); + evalc('m2 = mergeCompartments(m);'); + testCase.verifyEqual(sort(m2.mets), {'ACCOA_z';'X_c'}); + end + function mergeModelsReturnsStruct(testCase) modelB = removeReactions(testCase.model, testCase.model.rxns(1:5), true, true, true); evalc('merged = mergeModels({testCase.model; modelB});'); @@ -986,6 +1003,23 @@ function expandModelLeavesDnfAlone(testCase) methods (Static, Access = private) + function m = mergeToyModel(mets, comps, metComps) + % Two copies of acetyl-CoA (mets 1 and 2) and X (met 3): + % R1 X => copy 1, R2 copy 2 => X. + m = struct(); + m.id = 'merge'; + m.rxns = {'R1';'R2'}; + m.rxnNames = m.rxns; + m.mets = mets; + m.metNames = {'acetyl-CoA';'acetyl-CoA';'X'}; + m.metComps = metComps; + m.comps = comps; + m.compNames = comps; + m.S = sparse([1 0; 0 -1; -1 1]); + m.lb = [0;0]; m.ub = [1000;1000]; m.rev = [0;0]; m.c = [0;0]; m.b = zeros(3,1); + m.genes = {}; m.grRules = {'';''}; m.rxnGeneMat = sparse(2,0); + end + function m = duplicateModel() % a and b are named oxygen and o2; r1 (a => x) and r2 (b => x) become % duplicates once they are one metabolite. unrelated1 and unrelated2 diff --git a/tests/function_tests/tUtils.m b/tests/function_tests/tUtils.m index 858ed0b2..b85024ca 100644 --- a/tests/function_tests/tUtils.m +++ b/tests/function_tests/tUtils.m @@ -181,6 +181,32 @@ function grRuleToDNFDistributes(testCase) testCase.verifyEqual(numel(grRuleToDNF('(g1 or g2) and (g3 or g4)')), 4); end + function grRuleToDNFTakesTheParseGrRuleGrammar(testCase) + % Doubled symbols and operators in any case are operators; an + % operator glued to a gene id is part of that id. + testCase.verifyEqual(grRuleToDNF('G1 && (G2 || G3)'), {{'G1','G2'},{'G1','G3'}}); + testCase.verifyEqual(grRuleToDNF('G1 Or G2 aNd G3'), {{'G1'},{'G2','G3'}}); + testCase.verifyEqual(grRuleToDNF('G1&G2 or G3'), {{'G1&G2'},{'G3'}}); + end + + function grRuleToDNFWhitespaceIsIsspace(testCase) + % isspace is true for char(28) and char(8195) and false for + % the no-break space char(160) and char(8239), which are then + % part of a word. + testCase.verifyEqual(grRuleToDNF(['G1' char(28) 'or' char(8195) 'G2']), {{'G1'},{'G2'}}); + for c = [133 160 8199 8239] + testCase.verifyError(@() grRuleToDNF(['G1' char(c) 'and G2']), 'RAVEN:badGrRule'); + testCase.verifyEqual(grRuleToDNF(['G1' char(c) 'G2']), {{['G1' char(c) 'G2']}}); + end + end + + function grRuleToDNFRejectsMalformed(testCase) + rules = {'G1 and', '(G1 or G2', 'G1 G2', '()', 'G1 or or G2', 'G1 xor G2'}; + for k = 1:numel(rules) + testCase.verifyError(@() grRuleToDNF(rules{k}), 'RAVEN:badGrRule', rules{k}); + end + end + function grRuleToDNFSimpleCases(testCase) testCase.verifyEqual(grRuleToDNF(''), {}); testCase.verifyEqual(grRuleToDNF('G1'), {{'G1'}}); From 2d0630e3adf62b4b8af6584ea70754f478428a3e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 30 Oct 2026 10:21:41 +0000 Subject: [PATCH 37/48] doc: regenerate HTML documentation for c1c8bb7 [skip ci] --- docs/html/INIT/ftINIT.html | 846 ++++++------ docs/html/INIT/ftINITInternalAlg.html | 1197 ++++++++++------- docs/html/INIT/mergeLinear.html | 414 +++--- docs/html/gapfilling/gapFillTopological.html | 484 +++---- docs/html/manipulation/mergeCompartments.html | 228 ++-- docs/html/manipulation/removeGenes.html | 308 +++-- docs/html/manipulation/simplifyModel.html | 269 ++-- docs/html/utils/grRuleToDNF.html | 146 +- 8 files changed, 2069 insertions(+), 1823 deletions(-) diff --git a/docs/html/INIT/ftINIT.html b/docs/html/INIT/ftINIT.html index 579d4990..e05da6af 100644 --- a/docs/html/INIT/ftINIT.html +++ b/docs/html/INIT/ftINIT.html @@ -33,7 +33,7 @@

    DESCRIPTION ^CROSS-REFERENCE INFORMATION ^

    This function calls: +
  • ftINIT
  • ftINITInternalAlg ftINITInternalAlg
  • getINITSteps getINITSteps Define the steps used as input to ftINIT.
  • groupRxnScores groupRxnScores
  • scoreModel scoreModel Score reactions and genes in a model with complex gene rules.
  • This function is called by: @@ -117,423 +117,449 @@

    SOURCE CODE ^% if true, pin each step's degenerate MILP to a canonical answer instead 0072 % of relying on the solver Seed alone: hold the score objective at its 0073 % optimum, then minimise the count of removable ("on") reactions, then, -0074 % among the sparsest, minimise their summed reaction-id rank. Applies to -0075 % every main-extraction step (ftINITInternalAlg) and to the final -0076 % gap-filling step (fitTasks' resolveTies) -- see raven-gecko-parity#104 -0077 % and raven-toolbox's resolve_ties (default false). -0078 % proveAbsGap : double -0079 % if set, prove each step to this fixed *absolute* MILP gap in a single -0080 % solve, instead of the relative-gap escalation below (stp.MILPParams). -0081 % The escalation's final near-zero-objective run otherwise accepts an -0082 % arbitrary within-gap incumbent -- measured on genome-scale Human-GEM to -0083 % return a solution 2.0-4.0 objective units below the true optimum (see -0084 % raven-toolbox's prove_abs_gap and the ftINIT reproducibility study on -0085 % raven-docs). 1.0 is the recommended value: tighter (down to 0.05) -0086 % proves nothing further and costs substantially more runtime. Pairs -0087 % naturally with resolveTies (default [], i.e. use the escalation). -0088 % -0089 % Returns -0090 % ------- -0091 % model : struct -0092 % the resulting model structure. -0093 % metProduction : double -0094 % array that indicates which of the metabolites in metabolomicsData that -0095 % could be produced. Note that this is before the gap-filling process to -0096 % enable defined tasks. To see which metabolites that can be produced in -0097 % the final model, use canExchange: -0098 % -0099 % - -2 : metabolite name not found in model. -0100 % - -1 : metabolite found, but it could not be produced. -0101 % - 1 : metabolite could be produced. -0102 % addedRxnsForTasks : cell -0103 % cell array of the reactions which were added in order to perform the -0104 % tasks. -0105 % deletedRxnsInINIT : cell -0106 % cell array of reactions deleted because they could not carry flux (INIT -0107 % requires a functional input model). -0108 % fullMipRes : struct -0109 % the solver results from the last MILP step run. -0110 % -0111 % Notes -0112 % ----- -0113 % Exchange metabolites should normally not be removed from the model when using -0114 % this approach, since checkTasks/fitTasks rely on putting specific constraints -0115 % for each task. The INIT algorithm will remove exchange metabolites if any are -0116 % present. Use importModel(file,false) to import a model with exchange -0117 % metabolites remaining. -0118 % -0119 -0120 p=parseRAVENargs(varargin, {'transcrData',[]; 'metabolomicsData',[]; 'INITSteps',[]; 'removeGenes',[]; 'useScoresForTasks',[]; 'paramsFT',[]; 'verbose',false; 'resolveTies',false; 'proveAbsGap',[]}); -0121 transcrData=p.transcrData; -0122 metabolomicsData=p.metabolomicsData; -0123 INITSteps=p.INITSteps; -0124 removeGenes=p.removeGenes; -0125 useScoresForTasks=p.useScoresForTasks; -0126 paramsFT=p.paramsFT; -0127 verbose=p.verbose; -0128 resolveTies=p.resolveTies; -0129 proveAbsGap=p.proveAbsGap; -0130 if isempty(INITSteps) -0131 INITSteps = getINITSteps([],'1+1'); -0132 end -0133 if isempty(removeGenes) -0134 removeGenes = true; -0135 end -0136 if isempty(useScoresForTasks) -0137 useScoresForTasks = true; -0138 end -0139 %Handle detected mets: -0140 %Giving a bonus for secreting these metabolites does not work, since the -0141 %metabolite secretion and uptake can be lost when we merge linearly dependent -0142 %reactions. Instead, we need to figure out which reactions either produce or -0143 %take up the mets, and give a bonus if any of them carry flux. To simplify -0144 %things, we focus on reactions that produce the metabolite (since there must -0145 %be one such reaction). We further reason that the direction does not matter - -0146 %we can force one of these reactions in any direction - if it becomes a consumer, it will -0147 %automatically force another producer on as well (otherwise we will have a net consumption). -0148 -0149 if (~isempty(metabolomicsData)) -0150 if length(unique(upper(metabolomicsData))) ~= length(metabolomicsData) -0151 error('RAVEN:badInput', '%s', 'Metabolomics contains the same metabolite multiple times'); -0152 end -0153 metData = false(numel(metabolomicsData), length(prepData.minModel.rxns)); %one row per metabolite that is a boolean vector -0154 for i=1:numel(metabolomicsData) -0155 %Get the matching mets -0156 metSel = ismember(upper(prepData.refModel.metNames),upper(metabolomicsData{i})); -0157 prodRxnsSel = any(prepData.refModel.S(metSel,:) > 0,1) | ... %direct producers -0158 (any(prepData.refModel.S(metSel,:) < 0,1) & prepData.refModel.rev.'); %reversible reactions that are consumers -0159 %convert the production rxns from refModel to minModel -0160 prepData.groupIds; -0161 [~,ia,ib] = intersect(prepData.minModel.rxns,prepData.refModel.rxns); -0162 grpIdsMerged = nan(length(prepData.minModel.rxns),1); -0163 grpIdsMerged(ia) = prepData.groupIds(ib); -0164 -0165 groupIdsPos = unique(prepData.groupIds(prodRxnsSel));%gets all group ids which includes a production rxn -0166 groupIdsPos = groupIdsPos(groupIdsPos ~= 0);%remove the 0 id, it means there is no group -0167 %the other option is that there is a direct match between the rxn id in minModel and refModel: -0168 posRxns = prepData.refModel.rxns(prodRxnsSel); -0169 directMatch = ismember(prepData.minModel.rxns, posRxns).'; -0170 -0171 metData(i,:) = ismember(grpIdsMerged, groupIdsPos).' | directMatch; -0172 end -0173 metData = sparse(metData); -0174 else -0175 metData = []; -0176 end -0177 -0178 % Get rxn scores and adapt them to the minimized model -0179 origRxnScores = scoreModel(prepData.refModel,hpaData,transcrData,tissue,celltype); -0180 origRxnScores(origRxnScores > -0.1 & origRxnScores <= 0) = -0.1;%we don't want reaction scores that are exactly 0 (or close), this causes problems in the milp -0181 origRxnScores(origRxnScores < 0.1 & origRxnScores > 0) = 0.1; -0182 -0183 rxnsTurnedOn = false(length(prepData.minModel.rxns),1); -0184 fluxes = zeros(length(prepData.minModel.rxns),1); -0185 -0186 rxnsToIgnoreLastStep = [1;1;1;1;1;1;1;1]; -0187 -0188 %We assume that all essential rxns are irrev - this is taken care of in -0189 %prepINITModel. We then use an initial flux "from last run" of 0.1 for all -0190 %reactions. This is used for knowing what flux should be forced through an -0191 %essential rxn. -0192 fluxes = ones(length(prepData.minModel.rxns), 1).*0.1; +0074 % among the sparsest, minimise their summed reaction-id rank. This +0075 % applies to every main-extraction step and to the final gap-filling +0076 % step (fitTasks' resolveTies) -- see raven-gecko-parity#104 and +0077 % raven-toolbox's resolve_ties. In each main-extraction step but the +0078 % last (ftINITInternalAlg) the fluxes, which set the direction and +0079 % magnitude a reaction is forced at in the next step, then come from the +0080 % least-total-flux solution for that on/off state and, among those, from +0081 % the one with the least id-rank-weighted backward flux (a merged +0082 % reaction ranked by the smallest original id in its group). The last +0083 % step skips these flux phases, as no step reads its fluxes. A step +0084 % whose primary solve stops at the time limit keeps its primary +0085 % solution without any of these phases; the count and id-rank phases +0086 % get half of the step's TimeLimit each, and a warning +0087 % names each phase that does not prove optimal or finds no solution +0088 % (default false). +0089 % proveAbsGap : double +0090 % if set, prove each step to this fixed *absolute* MILP gap in a single +0091 % solve, instead of the relative-gap escalation below (stp.MILPParams). +0092 % The escalation's final near-zero-objective run otherwise accepts an +0093 % arbitrary within-gap incumbent -- measured on genome-scale Human-GEM to +0094 % return a solution 2.0-4.0 objective units below the true optimum (see +0095 % raven-toolbox's prove_abs_gap and the ftINIT reproducibility study on +0096 % raven-docs). 1.0 is the recommended value: tighter (down to 0.05) +0097 % proves nothing further and costs substantially more runtime. Pairs +0098 % naturally with resolveTies (default [], i.e. use the escalation). +0099 % +0100 % Returns +0101 % ------- +0102 % model : struct +0103 % the resulting model structure. +0104 % metProduction : double +0105 % array that indicates which of the metabolites in metabolomicsData that +0106 % could be produced. Note that this is before the gap-filling process to +0107 % enable defined tasks. To see which metabolites that can be produced in +0108 % the final model, use canExchange: +0109 % +0110 % - -2 : metabolite name not found in model. +0111 % - -1 : metabolite found, but it could not be produced. +0112 % - 1 : metabolite could be produced. +0113 % addedRxnsForTasks : cell +0114 % cell array of the reactions which were added in order to perform the +0115 % tasks. +0116 % deletedRxnsInINIT : cell +0117 % cell array of reactions deleted because they could not carry flux (INIT +0118 % requires a functional input model). +0119 % fullMipRes : struct +0120 % the solver results from the last MILP step run. +0121 % +0122 % Notes +0123 % ----- +0124 % Exchange metabolites should normally not be removed from the model when using +0125 % this approach, since checkTasks/fitTasks rely on putting specific constraints +0126 % for each task. The INIT algorithm will remove exchange metabolites if any are +0127 % present. Use importModel(file,false) to import a model with exchange +0128 % metabolites remaining. +0129 % +0130 +0131 p=parseRAVENargs(varargin, {'transcrData',[]; 'metabolomicsData',[]; 'INITSteps',[]; 'removeGenes',[]; 'useScoresForTasks',[]; 'paramsFT',[]; 'verbose',false; 'resolveTies',false; 'proveAbsGap',[]}); +0132 transcrData=p.transcrData; +0133 metabolomicsData=p.metabolomicsData; +0134 INITSteps=p.INITSteps; +0135 removeGenes=p.removeGenes; +0136 useScoresForTasks=p.useScoresForTasks; +0137 paramsFT=p.paramsFT; +0138 verbose=p.verbose; +0139 resolveTies=p.resolveTies; +0140 proveAbsGap=p.proveAbsGap; +0141 if isempty(INITSteps) +0142 INITSteps = getINITSteps([],'1+1'); +0143 end +0144 if isempty(removeGenes) +0145 removeGenes = true; +0146 end +0147 if isempty(useScoresForTasks) +0148 useScoresForTasks = true; +0149 end +0150 %Handle detected mets: +0151 %Giving a bonus for secreting these metabolites does not work, since the +0152 %metabolite secretion and uptake can be lost when we merge linearly dependent +0153 %reactions. Instead, we need to figure out which reactions either produce or +0154 %take up the mets, and give a bonus if any of them carry flux. To simplify +0155 %things, we focus on reactions that produce the metabolite (since there must +0156 %be one such reaction). We further reason that the direction does not matter - +0157 %we can force one of these reactions in any direction - if it becomes a consumer, it will +0158 %automatically force another producer on as well (otherwise we will have a net consumption). +0159 +0160 if (~isempty(metabolomicsData)) +0161 if length(unique(upper(metabolomicsData))) ~= length(metabolomicsData) +0162 error('RAVEN:badInput', '%s', 'Metabolomics contains the same metabolite multiple times'); +0163 end +0164 metData = false(numel(metabolomicsData), length(prepData.minModel.rxns)); %one row per metabolite that is a boolean vector +0165 for i=1:numel(metabolomicsData) +0166 %Get the matching mets +0167 metSel = ismember(upper(prepData.refModel.metNames),upper(metabolomicsData{i})); +0168 prodRxnsSel = any(prepData.refModel.S(metSel,:) > 0,1) | ... %direct producers +0169 (any(prepData.refModel.S(metSel,:) < 0,1) & prepData.refModel.rev.'); %reversible reactions that are consumers +0170 %convert the production rxns from refModel to minModel +0171 prepData.groupIds; +0172 [~,ia,ib] = intersect(prepData.minModel.rxns,prepData.refModel.rxns); +0173 grpIdsMerged = nan(length(prepData.minModel.rxns),1); +0174 grpIdsMerged(ia) = prepData.groupIds(ib); +0175 +0176 groupIdsPos = unique(prepData.groupIds(prodRxnsSel));%gets all group ids which includes a production rxn +0177 groupIdsPos = groupIdsPos(groupIdsPos ~= 0);%remove the 0 id, it means there is no group +0178 %the other option is that there is a direct match between the rxn id in minModel and refModel: +0179 posRxns = prepData.refModel.rxns(prodRxnsSel); +0180 directMatch = ismember(prepData.minModel.rxns, posRxns).'; +0181 +0182 metData(i,:) = ismember(grpIdsMerged, groupIdsPos).' | directMatch; +0183 end +0184 metData = sparse(metData); +0185 else +0186 metData = []; +0187 end +0188 +0189 % Get rxn scores and adapt them to the minimized model +0190 origRxnScores = scoreModel(prepData.refModel,hpaData,transcrData,tissue,celltype); +0191 origRxnScores(origRxnScores > -0.1 & origRxnScores <= 0) = -0.1;%we don't want reaction scores that are exactly 0 (or close), this causes problems in the milp +0192 origRxnScores(origRxnScores < 0.1 & origRxnScores > 0) = 0.1; 0193 -0194 for initStep = 1:length(INITSteps) -0195 disp(['ftINIT: Running step ' num2str(initStep)]) -0196 stp = INITSteps{initStep}; -0197 -0198 if any ((rxnsToIgnoreLastStep - stp.RxnsToIgnoreMask) < 0) -0199 error('RAVEN:badInput', '%s', 'RxnsToIgnoreMask may not cover rxns not covered in previous steps, but the other way around is fine.'); -0200 end -0201 rxnsToIgnoreLastStep = stp.RxnsToIgnoreMask; -0202 -0203 mm = prepData.minModel; -0204 -0205 if (~isempty(stp.MetsToIgnore)) -0206 if (~isempty(stp.MetsToIgnore.simpleMets)) -0207 %Here, we remove simple metabolites that will not really affect the milp but -0208 %are very common in the S matrix. For example H2O, H+, etc. -0209 %It is also possible to leave compartments untouched, for example the i compartment in the mitochondria (for H+). -0210 metsToRem = ismember(mm.metNames,stp.MetsToIgnore.simpleMets.mets); -0211 compsToKeep = find(ismember(mm.comps, stp.MetsToIgnore.simpleMets.compsToKeep)); -0212 metsToRem = metsToRem & ~ismember(mm.metComps, compsToKeep); -0213 mm.S(metsToRem,:) = 0; -0214 end -0215 end +0194 rxnsTurnedOn = false(length(prepData.minModel.rxns),1); +0195 fluxes = zeros(length(prepData.minModel.rxns),1); +0196 +0197 rxnsToIgnoreLastStep = [1;1;1;1;1;1;1;1]; +0198 +0199 %We assume that all essential rxns are irrev - this is taken care of in +0200 %prepINITModel. We then use an initial flux "from last run" of 0.1 for all +0201 %reactions. This is used for knowing what flux should be forced through an +0202 %essential rxn. +0203 fluxes = ones(length(prepData.minModel.rxns), 1).*0.1; +0204 +0205 %resolveTies ranks a merged reaction by its group's smallest original id, +0206 %which does not depend on which member names the merged reaction. +0207 rankKeys = prepData.minModel.rxns; +0208 [inRef, refInd] = ismember(rankKeys, prepData.refModel.rxns); +0209 for i = find(inRef)' +0210 g = prepData.groupIds(refInd(i)); +0211 if g ~= 0 +0212 members = sort(prepData.refModel.rxns(prepData.groupIds == g)); +0213 rankKeys{i} = members{1}; +0214 end +0215 end 0216 -0217 %Set up the reaction scores and essential rxns -0218 rxnsToIgnore = getRxnsFromPattern(stp.RxnsToIgnoreMask, prepData); -0219 rxnScores = groupRxnScores(prepData.minModel, origRxnScores, prepData.refModel.rxns, prepData.groupIds, rxnsToIgnore); -0220 -0221 essentialRxns = prepData.essentialRxns; -0222 toRev = false(numel(mm.rxns),1); -0223 %Handle the results from previous steps ("ignore", "exclude", "essential") -0224 if strcmp(stp.HowToUsePrevResults, 'exclude') -0225 rxnScores(rxnsTurnedOn) = 0; %This is not used anymore in any step setup. -0226 elseif strcmp(stp.HowToUsePrevResults, 'essential') -0227 %Make all reversible reactions turned on in previous steps reversible -0228 %in the direction that they were previously carrying flux -0229 -0230 %first reverse the reactions that need to be reversed -0231 rev = mm.rev == 1; -0232 toRev = rxnsTurnedOn & rev & fluxes < 0; -0233 mm = reverseRxns(mm, mm.rxns(toRev)); -0234 -0235 %Then make them irreversible -0236 mm.rev(rxnsTurnedOn) = 0; -0237 mm.lb(rxnsTurnedOn) = 0; -0238 -0239 essentialRxns = unique([prepData.essentialRxns;mm.rxns(rxnsTurnedOn)]); -0240 end -0241 -0242 -0243 fullMipRes = []; -0244 lastError = []; -0245 if ~isempty(proveAbsGap) -0246 %One solve proven to this fixed absolute gap, replacing the relative-gap -0247 %escalation below. The escalation's final near-zero-objective run otherwise -0248 %accepts an arbitrary within-gap incumbent -- measured on genome-scale -0249 %Human-GEM to cost 2.0-4.0 objective units (raven-toolbox's prove_abs_gap, -0250 %raven-gecko-parity#104). Pairs naturally with resolveTies. -0251 if ~isempty(stp.MILPParams) && isfield(stp.MILPParams{1}, 'TimeLimit') -0252 params = struct('TimeLimit', stp.MILPParams{1}.TimeLimit); -0253 else -0254 params = struct('TimeLimit', 5000); -0255 end -0256 params.MIPGap = 0; -0257 params.MIPGapAbs = proveAbsGap; -0258 try -0259 [deletedRxnsInINIT1, metProduction,fullMipRes,rxnsTurnedOn1,fluxes1] = ftINITInternalAlg(mm,rxnScores,metData,essentialRxns,5,stp.AllowMetSecr,stp.PosRevOff,params, [], fluxes, verbose, 0.1, resolveTies); -0260 fluxes1(toRev) = -fluxes1(toRev); -0261 success = true; -0262 if fullMipRes.stat ~= 1 -0263 %stat==2 (checkSolution.m): feasible but not proven within the time -0264 %limit -- an unproven incumbent, same caveat as the escalation path -0265 %below, but disclosed since proveAbsGap explicitly asks for a proof. -0266 EM=['ftINIT step ' num2str(initStep) ' hit the time limit before proving proveAbsGap; its kept reactions are an unproven incumbent. Raise TimeLimit, or loosen proveAbsGap.']; -0267 warning('RAVEN:warning', '%s', EM); -0268 end -0269 catch e -0270 success = false; -0271 lastError = e; -0272 end -0273 else -0274 mipGap = 1; -0275 first = true; -0276 success = false; -0277 for rn = 1:length(stp.MILPParams) -0278 params = stp.MILPParams{rn}; -0279 if ~isfield(params, 'MIPGap') -0280 params.MIPGap = 0.0004; -0281 end -0282 -0283 if ~isfield(params, 'TimeLimit') -0284 params.TimeLimit = 5000; -0285 end -0286 -0287 if ~first -0288 %There is sometimes a problem with that the objective function becomes close to zero, -0289 %which leads to that a small percentage of that (which is the MIPGap sent in) is very small -0290 %and the MILP hence takes a lot of time to finish. We also therefore use an absolute MIP gap, -0291 %converted to a percentage using the last value of the objective function. -0292 params.MIPGap = min(max(params.MIPGap, stp.AbsMIPGaps{rn}/abs(lastObjVal)),1); -0293 params.seed = 1234;%use another seed, may work better -0294 -0295 if mipGap <= params.MIPGap -0296 success = true; -0297 break; %we're done - this will not happen the first time -0298 else -0299 disp(['MipGap too high, trying with a different run. MipGap = ' num2str(mipGap) ' New MipGap Limit = ' num2str(params.MIPGap)]) -0300 end -0301 end -0302 -0303 first = false; -0304 -0305 %now run the MILP -0306 try -0307 %The prodweight for metabolomics is set to 5. This value has not -0308 %been evaluated, but is assumed in the test cases - if changed, update the test case -0309 startVals = []; -0310 if ~isempty(fullMipRes) -0311 startVals = fullMipRes.full; -0312 end -0313 [deletedRxnsInINIT1, metProduction,fullMipRes,rxnsTurnedOn1,fluxes1] = ftINITInternalAlg(mm,rxnScores,metData,essentialRxns,5,stp.AllowMetSecr,stp.PosRevOff,params, startVals, fluxes, verbose, 0.1, resolveTies); -0314 %This is a bit tricky - since we reversed some reactions, those fluxes also need to be reversed -0315 fluxes1(toRev) = -fluxes1(toRev); -0316 -0317 mipGap = fullMipRes.mipgap; -0318 lastObjVal = fullMipRes.obj; -0319 catch e -0320 mipGap = Inf; -0321 lastObjVal = Inf; %we need to set something here, Inf leads to that this doesn't come into play -0322 lastError = e; %kept so a throw is not reported as a time-limit miss -0323 end -0324 -0325 success = mipGap <= params.MIPGap; -0326 end -0327 end +0217 for initStep = 1:length(INITSteps) +0218 disp(['ftINIT: Running step ' num2str(initStep)]) +0219 stp = INITSteps{initStep}; +0220 +0221 if any ((rxnsToIgnoreLastStep - stp.RxnsToIgnoreMask) < 0) +0222 error('RAVEN:badInput', '%s', 'RxnsToIgnoreMask may not cover rxns not covered in previous steps, but the other way around is fine.'); +0223 end +0224 rxnsToIgnoreLastStep = stp.RxnsToIgnoreMask; +0225 %The fluxes of a step set the forced directions of the next one, so the +0226 %last step skips the flux phases of resolveTies. +0227 pinFluxes = initStep < numel(INITSteps); +0228 +0229 mm = prepData.minModel; +0230 +0231 if (~isempty(stp.MetsToIgnore)) +0232 if (~isempty(stp.MetsToIgnore.simpleMets)) +0233 %Here, we remove simple metabolites that will not really affect the milp but +0234 %are very common in the S matrix. For example H2O, H+, etc. +0235 %It is also possible to leave compartments untouched, for example the i compartment in the mitochondria (for H+). +0236 metsToRem = ismember(mm.metNames,stp.MetsToIgnore.simpleMets.mets); +0237 compsToKeep = find(ismember(mm.comps, stp.MetsToIgnore.simpleMets.compsToKeep)); +0238 metsToRem = metsToRem & ~ismember(mm.metComps, compsToKeep); +0239 mm.S(metsToRem,:) = 0; +0240 end +0241 end +0242 +0243 %Set up the reaction scores and essential rxns +0244 rxnsToIgnore = getRxnsFromPattern(stp.RxnsToIgnoreMask, prepData); +0245 rxnScores = groupRxnScores(prepData.minModel, origRxnScores, prepData.refModel.rxns, prepData.groupIds, rxnsToIgnore); +0246 +0247 essentialRxns = prepData.essentialRxns; +0248 toRev = false(numel(mm.rxns),1); +0249 %Handle the results from previous steps ("ignore", "exclude", "essential") +0250 if strcmp(stp.HowToUsePrevResults, 'exclude') +0251 rxnScores(rxnsTurnedOn) = 0; %This is not used anymore in any step setup. +0252 elseif strcmp(stp.HowToUsePrevResults, 'essential') +0253 %Make all reversible reactions turned on in previous steps reversible +0254 %in the direction that they were previously carrying flux +0255 +0256 %first reverse the reactions that need to be reversed +0257 rev = mm.rev == 1; +0258 toRev = rxnsTurnedOn & rev & fluxes < 0; +0259 mm = reverseRxns(mm, mm.rxns(toRev)); +0260 +0261 %Then make them irreversible +0262 mm.rev(rxnsTurnedOn) = 0; +0263 mm.lb(rxnsTurnedOn) = 0; +0264 +0265 essentialRxns = unique([prepData.essentialRxns;mm.rxns(rxnsTurnedOn)]); +0266 end +0267 +0268 +0269 fullMipRes = []; +0270 lastError = []; +0271 if ~isempty(proveAbsGap) +0272 %One solve proven to this fixed absolute gap, replacing the relative-gap +0273 %escalation below. The escalation's final near-zero-objective run otherwise +0274 %accepts an arbitrary within-gap incumbent -- measured on genome-scale +0275 %Human-GEM to cost 2.0-4.0 objective units (raven-toolbox's prove_abs_gap, +0276 %raven-gecko-parity#104). Pairs naturally with resolveTies. +0277 if ~isempty(stp.MILPParams) && isfield(stp.MILPParams{1}, 'TimeLimit') +0278 params = struct('TimeLimit', stp.MILPParams{1}.TimeLimit); +0279 else +0280 params = struct('TimeLimit', 5000); +0281 end +0282 params.MIPGap = 0; +0283 params.MIPGapAbs = proveAbsGap; +0284 try +0285 [deletedRxnsInINIT1, metProduction,fullMipRes,rxnsTurnedOn1,fluxes1] = ftINITInternalAlg(mm,rxnScores,metData,essentialRxns,5,stp.AllowMetSecr,stp.PosRevOff,params, [], fluxes, verbose, 0.1, resolveTies, rankKeys, pinFluxes); +0286 fluxes1(toRev) = -fluxes1(toRev); +0287 success = true; +0288 if fullMipRes.stat ~= 1 +0289 %stat==2 (checkSolution.m): feasible but not proven within the time +0290 %limit -- an unproven incumbent, same caveat as the escalation path +0291 %below, but disclosed since proveAbsGap explicitly asks for a proof. +0292 EM=['ftINIT step ' num2str(initStep) ' hit the time limit before proving proveAbsGap; its kept reactions are an unproven incumbent. Raise TimeLimit, or loosen proveAbsGap.']; +0293 warning('RAVEN:warning', '%s', EM); +0294 end +0295 catch e +0296 success = false; +0297 lastError = e; +0298 end +0299 else +0300 mipGap = 1; +0301 first = true; +0302 success = false; +0303 for rn = 1:length(stp.MILPParams) +0304 params = stp.MILPParams{rn}; +0305 if ~isfield(params, 'MIPGap') +0306 params.MIPGap = 0.0004; +0307 end +0308 +0309 if ~isfield(params, 'TimeLimit') +0310 params.TimeLimit = 5000; +0311 end +0312 +0313 if ~first +0314 %There is sometimes a problem with that the objective function becomes close to zero, +0315 %which leads to that a small percentage of that (which is the MIPGap sent in) is very small +0316 %and the MILP hence takes a lot of time to finish. We also therefore use an absolute MIP gap, +0317 %converted to a percentage using the last value of the objective function. +0318 params.MIPGap = min(max(params.MIPGap, stp.AbsMIPGaps{rn}/abs(lastObjVal)),1); +0319 params.seed = 1234;%use another seed, may work better +0320 +0321 if mipGap <= params.MIPGap +0322 success = true; +0323 break; %we're done - this will not happen the first time +0324 else +0325 disp(['MipGap too high, trying with a different run. MipGap = ' num2str(mipGap) ' New MipGap Limit = ' num2str(params.MIPGap)]) +0326 end +0327 end 0328 -0329 if ~success -0330 if ~isempty(lastError) -0331 %The MILP threw rather than returning a poor gap; reporting this -0332 %as a time-limit miss sends users off to raise params.TimeLimit -0333 %for what is often a solver or input problem. -0334 error('RAVEN:badInput', '%s', ['Failed to find good enough solution within the time frame. The last MILP attempt failed with: ' lastError.message]); -0335 else -0336 error('RAVEN:badInput', '%s', ['Failed to find good enough solution within the time frame. MIPGap: ' num2str(mipGap)]); -0337 end -0338 end -0339 -0340 %save the reactions turned on and their fluxes for the next step -0341 rxnsTurnedOn = rxnsTurnedOn | rxnsTurnedOn1.'; -0342 %The fluxes are a bit tricky - what if they change direction between the steps? -0343 %The fluxes are used to determine the direction in which reactions are forced on -0344 %(to simplify the problem it is good if they are unidirectional). -0345 %We use the following strategy: -0346 %1. Use the fluxes from the most recent step. -0347 %2. If any flux is very low there (i.e. basically zero), use the flux from the previous steps -0348 %This could in theory cause problems, but seems to work well practically -0349 fluxesOld = fluxes; -0350 fluxes = fluxes1; -0351 end -0352 -0353 -0354 %get the essential rxns -0355 essential = ismember(prepData.minModel.rxns,prepData.essentialRxns); -0356 %So, we only add reactions where the linearly merged scores are zero for all linearly dependent reactions -0357 % (this cannot happen by chance, taken care of in the function groupRxnScores) -0358 rxnsToIgn = rxnScores == 0; -0359 deletedRxnsInINITSel = ~(rxnsTurnedOn | rxnsToIgn | essential); -0360 deletedRxnsInINIT = prepData.minModel.rxns(deletedRxnsInINITSel); -0361 -0362 %Here we need to figure out which original reactions (before the linear merge) -0363 %that were removed. These are all reactions with the same group ids as the removed reactions -0364 groupIdsRemoved = prepData.groupIds(ismember(prepData.refModel.rxns, deletedRxnsInINIT)); %can improve this slightly, use sel above -0365 groupIdsRemoved = groupIdsRemoved(groupIdsRemoved ~= 0);%zero means that the reaction was not grouped, all with zeros are not a group! -0366 rxnsToRem = union(prepData.refModel.rxns(ismember(prepData.groupIds,groupIdsRemoved)), deletedRxnsInINIT);%make a union here to include the ungrouped (unmerged) as well -0367 -0368 initModel = removeReactions(prepData.refModel,rxnsToRem,false,true); -0369 -0370 % remove metabolites separately to avoid removing those needed for tasks -0371 unusedMets = initModel.mets(all(initModel.S == 0,2)); -0372 initModel = removeMets(initModel, setdiff(unusedMets, prepData.essentialMetsForTasks)); -0373 -0374 %if printReport == true -0375 % printScores(initModel,"INIT model statistics",hpaData,transcrData,tissue,celltype); -0376 % printScores(removeReactions(cModel,setdiff(cModel.rxns,rxnsToRem),true,true),"Reactions deleted by INIT",hpaData,transcrData,tissue,celltype); -0377 %end +0329 first = false; +0330 +0331 %now run the MILP +0332 try +0333 %The prodweight for metabolomics is set to 5. This value has not +0334 %been evaluated, but is assumed in the test cases - if changed, update the test case +0335 startVals = []; +0336 if ~isempty(fullMipRes) +0337 startVals = fullMipRes.full; +0338 end +0339 [deletedRxnsInINIT1, metProduction,fullMipRes,rxnsTurnedOn1,fluxes1] = ftINITInternalAlg(mm,rxnScores,metData,essentialRxns,5,stp.AllowMetSecr,stp.PosRevOff,params, startVals, fluxes, verbose, 0.1, resolveTies, rankKeys, pinFluxes); +0340 %This is a bit tricky - since we reversed some reactions, those fluxes also need to be reversed +0341 fluxes1(toRev) = -fluxes1(toRev); +0342 +0343 mipGap = fullMipRes.mipgap; +0344 lastObjVal = fullMipRes.obj; +0345 catch e +0346 mipGap = Inf; +0347 lastObjVal = Inf; %we need to set something here, Inf leads to that this doesn't come into play +0348 lastError = e; %kept so a throw is not reported as a time-limit miss +0349 end +0350 +0351 success = mipGap <= params.MIPGap; +0352 end +0353 end +0354 +0355 if ~success +0356 if ~isempty(lastError) +0357 %The MILP threw rather than returning a poor gap; reporting this +0358 %as a time-limit miss sends users off to raise params.TimeLimit +0359 %for what is often a solver or input problem. +0360 error('RAVEN:badInput', '%s', ['Failed to find good enough solution within the time frame. The last MILP attempt failed with: ' lastError.message]); +0361 else +0362 error('RAVEN:badInput', '%s', ['Failed to find good enough solution within the time frame. MIPGap: ' num2str(mipGap)]); +0363 end +0364 end +0365 +0366 %save the reactions turned on and their fluxes for the next step +0367 rxnsTurnedOn = rxnsTurnedOn | rxnsTurnedOn1.'; +0368 %The fluxes are a bit tricky - what if they change direction between the steps? +0369 %The fluxes are used to determine the direction in which reactions are forced on +0370 %(to simplify the problem it is good if they are unidirectional). +0371 %We use the following strategy: +0372 %1. Use the fluxes from the most recent step. +0373 %2. If any flux is very low there (i.e. basically zero), use the flux from the previous steps +0374 %This could in theory cause problems, but seems to work well practically +0375 fluxesOld = fluxes; +0376 fluxes = fluxes1; +0377 end 0378 -0379 %The full model has exchange reactions in it. fitTasks with gapFillMode -0380 %'preMerged' calls ftINITFillGaps, which automatically removes exchange -0381 %metabolites (because it -0382 %assumes that the reactions are constrained when appropriate). In this case the -0383 %uptakes/outputs are retrieved from the task sheet instead. To prevent -0384 %exchange reactions being used to fill gaps, they are deleted from the -0385 %reference model here. -0386 initModel.id = 'INITModel'; +0379 +0380 %get the essential rxns +0381 essential = ismember(prepData.minModel.rxns,prepData.essentialRxns); +0382 %So, we only add reactions where the linearly merged scores are zero for all linearly dependent reactions +0383 % (this cannot happen by chance, taken care of in the function groupRxnScores) +0384 rxnsToIgn = rxnScores == 0; +0385 deletedRxnsInINITSel = ~(rxnsTurnedOn | rxnsToIgn | essential); +0386 deletedRxnsInINIT = prepData.minModel.rxns(deletedRxnsInINITSel); 0387 -0388 %If gaps in the model should be filled using a task list -0389 if ~isempty(prepData.taskStruct) -0390 %Remove exchange reactions and reactions already included in the INIT -0391 %model. Exchange rxns that are essential are kept. -0392 -0393 %At this stage the model is fully connected and most of the genes with -0394 %good scores should have been included. The final gap-filling should -0395 %take the scores of the genes into account, so that "rather bad" -0396 %reactions are preferred to "very bad" reactions. However, reactions -0397 %with positive scores will be included even if they are not connected -0398 %in the current formulation. Therefore, such reactions will have to be -0399 %assigned a small negative score instead. -0400 exchRxns = getExchangeRxns(prepData.refModel); -0401 refModelNoExc = removeReactions(prepData.refModelWithBM,exchRxns,false,true); -0402 exchRxns = getExchangeRxns(initModel); -0403 initModelNoExc = removeReactions(closeModel(initModel),exchRxns,false,true); -0404 -0405 if useScoresForTasks == true -0406 %map the rxn scores to the model without exchange rxns -0407 [~,ia,ib] = intersect(refModelNoExc.rxns,prepData.refModel.rxns); -0408 rxnScores2nd = NaN(length(refModelNoExc.rxns),1); -0409 rxnScores2nd(ia) = origRxnScores(ib); -0410 %all(rxnScores2nd == refRxnScores);%should be the same, ok! -0411 [outModel,addedRxnMat] = fitTasks(initModelNoExc,refModelNoExc,[], ... -0412 'printOutput',true,'rxnScores',min(rxnScores2nd,-0.1), ... -0413 'taskStructure',prepData.taskStruct,'gapFillMode','preMerged', ... -0414 'params',paramsFT,'verbose',verbose,'resolveTies',resolveTies); -0415 else -0416 [outModel,addedRxnMat] = fitTasks(initModelNoExc,refModelNoExc,[], ... -0417 'printOutput',true,'rxnScores',[], ... -0418 'taskStructure',prepData.taskStruct,'gapFillMode','preMerged', ... -0419 'params',paramsFT,'verbose',verbose,'resolveTies',resolveTies); -0420 end -0421 %if printReport == true -0422 % printScores(outModel,"Functional model statistics",hpaData,transcrData,tissue,celltype); -0423 % printScores(removeReactions(outModel,intersect(outModel.rxns,initModel.rxns),true,true),"Reactions added to perform the tasks",hpaData,transcrData,tissue,celltype); -0424 %end -0425 -0426 addedRxnsForTasks = refModelNoExc.rxns(any(addedRxnMat,2)); -0427 else -0428 outModel = initModel; -0429 addedRxnMat = []; -0430 addedRxnsForTasks = {}; -0431 end -0432 -0433 % The model can now perform all the tasks defined in the task list. -0434 model = outModel; -0435 -0436 -0437 % At this stage the model will contain some exchange reactions but probably -0438 % not all (and maybe zero). This can be inconvenient, so all exchange -0439 % reactions from the reference model are added, except for those which -0440 % involve metabolites that are not in the model. -0441 -0442 %Start from the original model, and just remove the reactions that are no longer there (and keep exchange rxns). The model we got out -0443 %from the problem is not complete, it does not have GRPs etc. -0444 %The logic below is a bit complicated. We identify the reactions that should be removed from the full model as -0445 %reactions that have been removed in the init model except the ones that were added back. In addition, we make -0446 %sure that no exchange rxns are removed - they can be removed in the init model if they were linearly merged with other -0447 %reactions that were decided to be removed from the model. We want to keep all exchange rxns to make sure the tasks can -0448 %be performed also without manipulating the b vector in the model (which is what is done in the gap-filling). -0449 exchRxns = getExchangeRxns(prepData.refModel); -0450 deletedRxnsInINIT = setdiff(prepData.refModel.rxns,union(union(initModel.rxns, addedRxnsForTasks), exchRxns)); -0451 outModel = removeReactions(prepData.refModel, deletedRxnsInINIT, true, true); -0452 -0453 % If requested, attempt to remove negative-score genes from the model, -0454 % depending on their role (isozyme or complex subunit) in each grRule. -0455 % See the "removeLowScoreGenes" function more more details, and to adjust -0456 % any default parameters therein. -0457 if ( removeGenes ) -0458 [~, geneScores] = scoreModel(outModel,hpaData,transcrData,tissue,celltype); -0459 outModel = removeLowScoreGenes(outModel,geneScores); -0460 end +0388 %Here we need to figure out which original reactions (before the linear merge) +0389 %that were removed. These are all reactions with the same group ids as the removed reactions +0390 groupIdsRemoved = prepData.groupIds(ismember(prepData.refModel.rxns, deletedRxnsInINIT)); %can improve this slightly, use sel above +0391 groupIdsRemoved = groupIdsRemoved(groupIdsRemoved ~= 0);%zero means that the reaction was not grouped, all with zeros are not a group! +0392 rxnsToRem = union(prepData.refModel.rxns(ismember(prepData.groupIds,groupIdsRemoved)), deletedRxnsInINIT);%make a union here to include the ungrouped (unmerged) as well +0393 +0394 initModel = removeReactions(prepData.refModel,rxnsToRem,false,true); +0395 +0396 % remove metabolites separately to avoid removing those needed for tasks +0397 unusedMets = initModel.mets(all(initModel.S == 0,2)); +0398 initModel = removeMets(initModel, setdiff(unusedMets, prepData.essentialMetsForTasks)); +0399 +0400 %if printReport == true +0401 % printScores(initModel,"INIT model statistics",hpaData,transcrData,tissue,celltype); +0402 % printScores(removeReactions(cModel,setdiff(cModel.rxns,rxnsToRem),true,true),"Reactions deleted by INIT",hpaData,transcrData,tissue,celltype); +0403 %end +0404 +0405 %The full model has exchange reactions in it. fitTasks with gapFillMode +0406 %'preMerged' calls ftINITFillGaps, which automatically removes exchange +0407 %metabolites (because it +0408 %assumes that the reactions are constrained when appropriate). In this case the +0409 %uptakes/outputs are retrieved from the task sheet instead. To prevent +0410 %exchange reactions being used to fill gaps, they are deleted from the +0411 %reference model here. +0412 initModel.id = 'INITModel'; +0413 +0414 %If gaps in the model should be filled using a task list +0415 if ~isempty(prepData.taskStruct) +0416 %Remove exchange reactions and reactions already included in the INIT +0417 %model. Exchange rxns that are essential are kept. +0418 +0419 %At this stage the model is fully connected and most of the genes with +0420 %good scores should have been included. The final gap-filling should +0421 %take the scores of the genes into account, so that "rather bad" +0422 %reactions are preferred to "very bad" reactions. However, reactions +0423 %with positive scores will be included even if they are not connected +0424 %in the current formulation. Therefore, such reactions will have to be +0425 %assigned a small negative score instead. +0426 exchRxns = getExchangeRxns(prepData.refModel); +0427 refModelNoExc = removeReactions(prepData.refModelWithBM,exchRxns,false,true); +0428 exchRxns = getExchangeRxns(initModel); +0429 initModelNoExc = removeReactions(closeModel(initModel),exchRxns,false,true); +0430 +0431 if useScoresForTasks == true +0432 %map the rxn scores to the model without exchange rxns +0433 [~,ia,ib] = intersect(refModelNoExc.rxns,prepData.refModel.rxns); +0434 rxnScores2nd = NaN(length(refModelNoExc.rxns),1); +0435 rxnScores2nd(ia) = origRxnScores(ib); +0436 %all(rxnScores2nd == refRxnScores);%should be the same, ok! +0437 [outModel,addedRxnMat] = fitTasks(initModelNoExc,refModelNoExc,[], ... +0438 'printOutput',true,'rxnScores',min(rxnScores2nd,-0.1), ... +0439 'taskStructure',prepData.taskStruct,'gapFillMode','preMerged', ... +0440 'params',paramsFT,'verbose',verbose,'resolveTies',resolveTies); +0441 else +0442 [outModel,addedRxnMat] = fitTasks(initModelNoExc,refModelNoExc,[], ... +0443 'printOutput',true,'rxnScores',[], ... +0444 'taskStructure',prepData.taskStruct,'gapFillMode','preMerged', ... +0445 'params',paramsFT,'verbose',verbose,'resolveTies',resolveTies); +0446 end +0447 %if printReport == true +0448 % printScores(outModel,"Functional model statistics",hpaData,transcrData,tissue,celltype); +0449 % printScores(removeReactions(outModel,intersect(outModel.rxns,initModel.rxns),true,true),"Reactions added to perform the tasks",hpaData,transcrData,tissue,celltype); +0450 %end +0451 +0452 addedRxnsForTasks = refModelNoExc.rxns(any(addedRxnMat,2)); +0453 else +0454 outModel = initModel; +0455 addedRxnMat = []; +0456 addedRxnsForTasks = {}; +0457 end +0458 +0459 % The model can now perform all the tasks defined in the task list. +0460 model = outModel; 0461 0462 -0463 model = outModel; -0464 -0465 end -0466 -0467 %This is for printing a summary of a model -0468 function [rxnS, geneS] = printScores(model,name,hpaData,transcrData,tissue,celltype) -0469 [a, b] = scoreModel(model,hpaData,transcrData,tissue,celltype); -0470 rxnS = mean(a); -0471 geneS = mean(b,'omitnan'); -0472 fprintf([name ':\n']); -0473 fprintf(['\t' num2str(numel(model.rxns)) ' reactions, ' num2str(numel(model.genes)) ' genes\n']); -0474 fprintf(['\tMean reaction score: ' num2str(rxnS) '\n']); -0475 fprintf(['\tMean gene score: ' num2str(geneS) '\n']); -0476 fprintf(['\tReactions with positive scores: ' num2str(100*sum(a>0)/numel(a)) '%%\n\n']); -0477 end +0463 % At this stage the model will contain some exchange reactions but probably +0464 % not all (and maybe zero). This can be inconvenient, so all exchange +0465 % reactions from the reference model are added, except for those which +0466 % involve metabolites that are not in the model. +0467 +0468 %Start from the original model, and just remove the reactions that are no longer there (and keep exchange rxns). The model we got out +0469 %from the problem is not complete, it does not have GRPs etc. +0470 %The logic below is a bit complicated. We identify the reactions that should be removed from the full model as +0471 %reactions that have been removed in the init model except the ones that were added back. In addition, we make +0472 %sure that no exchange rxns are removed - they can be removed in the init model if they were linearly merged with other +0473 %reactions that were decided to be removed from the model. We want to keep all exchange rxns to make sure the tasks can +0474 %be performed also without manipulating the b vector in the model (which is what is done in the gap-filling). +0475 exchRxns = getExchangeRxns(prepData.refModel); +0476 deletedRxnsInINIT = setdiff(prepData.refModel.rxns,union(union(initModel.rxns, addedRxnsForTasks), exchRxns)); +0477 outModel = removeReactions(prepData.refModel, deletedRxnsInINIT, true, true); 0478 -0479 function rxnsToIgnore = getRxnsFromPattern(rxnsToIgnorePattern, prepData) -0480 rxnsToIgnore = false(length(prepData.toIgnoreExch),1); -0481 if rxnsToIgnorePattern(1) rxnsToIgnore = rxnsToIgnore | prepData.toIgnoreExch; end; -0482 if rxnsToIgnorePattern(2) rxnsToIgnore = rxnsToIgnore | prepData.toIgnoreImportRxns; end; -0483 if rxnsToIgnorePattern(3) rxnsToIgnore = rxnsToIgnore | prepData.toIgnoreSimpleTransp; end; -0484 if rxnsToIgnorePattern(4) rxnsToIgnore = rxnsToIgnore | prepData.toIgnoreAdvTransp; end; -0485 if rxnsToIgnorePattern(5) rxnsToIgnore = rxnsToIgnore | prepData.toIgnoreSpont; end; -0486 if rxnsToIgnorePattern(6) rxnsToIgnore = rxnsToIgnore | prepData.toIgnoreS; end; -0487 if rxnsToIgnorePattern(7) rxnsToIgnore = rxnsToIgnore | prepData.toIgnoreCustomRxns; end; -0488 if rxnsToIgnorePattern(8) rxnsToIgnore = rxnsToIgnore | prepData.toIgnoreAllWithoutGPRs; end; -0489 end -0490 +0479 % If requested, attempt to remove negative-score genes from the model, +0480 % depending on their role (isozyme or complex subunit) in each grRule. +0481 % See the "removeLowScoreGenes" function more more details, and to adjust +0482 % any default parameters therein. +0483 if ( removeGenes ) +0484 [~, geneScores] = scoreModel(outModel,hpaData,transcrData,tissue,celltype); +0485 outModel = removeLowScoreGenes(outModel,geneScores); +0486 end +0487 +0488 +0489 model = outModel; +0490 +0491 end +0492 +0493 %This is for printing a summary of a model +0494 function [rxnS, geneS] = printScores(model,name,hpaData,transcrData,tissue,celltype) +0495 [a, b] = scoreModel(model,hpaData,transcrData,tissue,celltype); +0496 rxnS = mean(a); +0497 geneS = mean(b,'omitnan'); +0498 fprintf([name ':\n']); +0499 fprintf(['\t' num2str(numel(model.rxns)) ' reactions, ' num2str(numel(model.genes)) ' genes\n']); +0500 fprintf(['\tMean reaction score: ' num2str(rxnS) '\n']); +0501 fprintf(['\tMean gene score: ' num2str(geneS) '\n']); +0502 fprintf(['\tReactions with positive scores: ' num2str(100*sum(a>0)/numel(a)) '%%\n\n']); +0503 end +0504 +0505 function rxnsToIgnore = getRxnsFromPattern(rxnsToIgnorePattern, prepData) +0506 rxnsToIgnore = false(length(prepData.toIgnoreExch),1); +0507 if rxnsToIgnorePattern(1) rxnsToIgnore = rxnsToIgnore | prepData.toIgnoreExch; end; +0508 if rxnsToIgnorePattern(2) rxnsToIgnore = rxnsToIgnore | prepData.toIgnoreImportRxns; end; +0509 if rxnsToIgnorePattern(3) rxnsToIgnore = rxnsToIgnore | prepData.toIgnoreSimpleTransp; end; +0510 if rxnsToIgnorePattern(4) rxnsToIgnore = rxnsToIgnore | prepData.toIgnoreAdvTransp; end; +0511 if rxnsToIgnorePattern(5) rxnsToIgnore = rxnsToIgnore | prepData.toIgnoreSpont; end; +0512 if rxnsToIgnorePattern(6) rxnsToIgnore = rxnsToIgnore | prepData.toIgnoreS; end; +0513 if rxnsToIgnorePattern(7) rxnsToIgnore = rxnsToIgnore | prepData.toIgnoreCustomRxns; end; +0514 if rxnsToIgnorePattern(8) rxnsToIgnore = rxnsToIgnore | prepData.toIgnoreAllWithoutGPRs; end; +0515 end +0516
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/INIT/ftINITInternalAlg.html b/docs/html/INIT/ftINITInternalAlg.html index 2481310e..467463be 100644 --- a/docs/html/INIT/ftINITInternalAlg.html +++ b/docs/html/INIT/ftINITInternalAlg.html @@ -24,7 +24,7 @@

    PURPOSE ^ftINITInternalAlg

    SYNOPSIS ^

    -
    function [deletedRxns,metProduction,res,turnedOnRxns,fluxes]=ftINITInternalAlg(model,rxnScores,metData,essentialRxns,prodWeight,allowExcretion,remPosRev,params,startVals,fluxes,verbose,forceOn,resolveTies)
    +
    function [deletedRxns,metProduction,res,turnedOnRxns,fluxes]=ftINITInternalAlg(model,rxnScores,metData,essentialRxns,prodWeight,allowExcretion,remPosRev,params,startVals,fluxes,verbose,forceOn,resolveTies,rankKeys,pinFluxes)

    DESCRIPTION ^

     ftINITInternalAlg
    @@ -72,9 +72,30 @@ 

    DESCRIPTION ^CROSS-REFERENCE INFORMATION ^
 
 
 <h2><a name=SOURCE CODE ^

    -
    0001 function [deletedRxns,metProduction,res,turnedOnRxns,fluxes]=ftINITInternalAlg(model,rxnScores,metData,essentialRxns,prodWeight,allowExcretion,remPosRev,params,startVals,fluxes,verbose,forceOn,resolveTies)
    +
    0001 function [deletedRxns,metProduction,res,turnedOnRxns,fluxes]=ftINITInternalAlg(model,rxnScores,metData,essentialRxns,prodWeight,allowExcretion,remPosRev,params,startVals,fluxes,verbose,forceOn,resolveTies,rankKeys,pinFluxes)
     0002 % ftINITInternalAlg
     0003 %    This function runs the MILP for a step in ftINIT.
     0004 %
    @@ -154,531 +175,665 @@ 

    SOURCE CODE ^% rank. Positive-score reactions carry a continuous, not a 0045 % free binary, on/off variable (pinned near 1 by the score 0046 % objective), so only the negative-score ones are a genuine -0047 % tie to resolve -- matches raven-toolbox's resolve_ties / -0048 % _resolve_ties (raven-gecko-parity#104) (optional, default -0049 % false) -0050 % -0051 % deletedRxns reactions which were deleted by the algorithm (only -0052 % rxns included in the problem) -0053 % metProduction array that indicates which of the -0054 % metabolites in presentMets that could be -0055 % produced -0056 % 0: metabolite could not be produced -0057 % 1: metabolite could be produced -0058 % res The result from the MILP -0059 % turnedOnRxns The reactions determined to be present (only -0060 % rxns included in the problem) -0061 % fluxes The fluxes from the MILP -0062 % -0063 % This function is the actual implementation of the algorithm. See -0064 % ftINIT for a higher-level function for model reconstruction. -0065 % -0066 % Usage: [deletedRxns,metProduction,res,turnedOnRxns,fluxes]=runINIT9(model,... -0067 % rxnScores,presentMets,essentialRxns,prodWeight,allowExcretion,... -0068 % remPosRev,params) -0069 -0070 if nargin < 12 || isempty(forceOn) -0071 forceOn = 0.1; -0072 end -0073 if nargin < 13 || isempty(resolveTies) -0074 resolveTies = false; -0075 end -0076 if isempty(essentialRxns) -0077 essentialRxns={}; -0078 end -0079 essentialRxns=essentialRxns(:); -0080 if isempty(prodWeight) -0081 prodWeight=0.5; -0082 end -0083 -0084 -0085 %The model should be in the reversible format and all relevant exchange -0086 %reactions should be open -0087 if isfield(model,'unconstrained') -0088 EM='Exchange metabolites are still present in the model. Use simplifyModel if this is not intended'; -0089 warning('RAVEN:warning', '%s', EM); -0090 end -0091 -0092 -0093 essential = ismember(model.rxns,essentialRxns); -0094 -0095 %Some nice to have numbers -0096 nMets=numel(model.mets); -0097 nRxns=numel(model.rxns); -0098 nRxnsWithOnOff = nRxns; -0099 -0100 %Reactions with score 0 will just be left in the model, and will not be part of the problem (but can carry flux). -0101 %It is possible to set score = 0 for e.g. spontaneous reactions, exchange rxns, etc., which may not be that interesting -0102 %to remove. -0103 -0104 %if makeIrrev is on, we can just as well skip all positive reversible rxns - they will be turned on without carrying any flux -0105 %since they can form a loop within themself (fwd-rev) -0106 if remPosRev -0107 rxnScores(rxnScores > 0 & model.rev~=0) = 0; -0108 end -0109 -0110 %Handle metabolomics -0111 %A problem with the metabolomics is that some of the producer reactions -0112 %for a metabolite could be excluded from the problem. We solve this by -0113 %adding them with score 0. They can still be seen as positive, since they -0114 %either do not matter (there is another producer) or they can contribute to an -0115 %increased score. -0116 revRxns=model.rev~=0; -0117 essRevRxns = find(revRxns & essential); -0118 essIrrevRxns = find(~revRxns & essential); +0047 % tie to resolve. The fluxes are then taken from the +0048 % least-total-flux solution for that on/off state and, +0049 % among those, from the one with the least id-rank-weighted +0050 % backward flux, so the directions and forced magnitudes +0051 % the next ftINIT step reads from them are pinned too -- +0052 % matches raven-toolbox's resolve_ties +0053 % (raven-gecko-parity#104). The phases are skipped when +0054 % the primary solve stops at params.TimeLimit, and the +0055 % primary solution is kept. Phases 2a and 2b get half of +0056 % params.TimeLimit each (rounded down, at least 1 s), the +0057 % flux phases the full limit. A warning names each phase +0058 % that does not prove optimal or finds no solution; when a +0059 % flux phase finds none, the fluxes of the phase before it +0060 % are kept (optional, default false) +0061 % rankKeys cell array with, for each reaction in model.rxns, the key +0062 % that the id-rank phases of resolveTies sort it by +0063 % (optional, default model.rxns). ftINIT passes the smallest +0064 % original reaction id of each merge group, so the order +0065 % does not depend on which member names the merged reaction +0066 % pinFluxes if false, resolveTies skips phases 2c and 2d and the +0067 % fluxes and metProduction are those of phase 2b +0068 % (optional, default true). +0069 % ftINIT passes false in its last step, whose fluxes no +0070 % later step reads +0071 % +0072 % deletedRxns reactions which were deleted by the algorithm (only +0073 % rxns included in the problem) +0074 % metProduction array that indicates which of the +0075 % metabolites in presentMets that could be +0076 % produced +0077 % 0: metabolite could not be produced +0078 % 1: metabolite could be produced +0079 % res The result from the MILP +0080 % turnedOnRxns The reactions determined to be present (only +0081 % rxns included in the problem) +0082 % fluxes The fluxes from the MILP +0083 % +0084 % This function is the actual implementation of the algorithm. See +0085 % ftINIT for a higher-level function for model reconstruction. +0086 % +0087 % Usage: [deletedRxns,metProduction,res,turnedOnRxns,fluxes]=runINIT9(model,... +0088 % rxnScores,presentMets,essentialRxns,prodWeight,allowExcretion,... +0089 % remPosRev,params) +0090 +0091 if nargin < 12 || isempty(forceOn) +0092 forceOn = 0.1; +0093 end +0094 if nargin < 14 || isempty(rankKeys) +0095 rankKeys = model.rxns; +0096 end +0097 if nargin < 13 || isempty(resolveTies) +0098 resolveTies = false; +0099 end +0100 if nargin < 15 || isempty(pinFluxes) +0101 pinFluxes = true; +0102 end +0103 if isempty(essentialRxns) +0104 essentialRxns={}; +0105 end +0106 essentialRxns=essentialRxns(:); +0107 if isempty(prodWeight) +0108 prodWeight=0.5; +0109 end +0110 +0111 +0112 %The model should be in the reversible format and all relevant exchange +0113 %reactions should be open +0114 if isfield(model,'unconstrained') +0115 EM='Exchange metabolites are still present in the model. Use simplifyModel if this is not intended'; +0116 warning('RAVEN:warning', '%s', EM); +0117 end +0118 0119 -0120 if ~isempty(metData) -0121 %remove any metData rows that is connected to an essential rxn - -0122 %these will be on regardless and will cause problems below. -0123 containsEssential = any(metData(:,essential),2); -0124 metData = metData(~containsEssential,:); -0125 end +0120 essential = ismember(model.rxns,essentialRxns); +0121 +0122 %Some nice to have numbers +0123 nMets=numel(model.mets); +0124 nRxns=numel(model.rxns); +0125 nRxnsWithOnOff = nRxns; 0126 -0127 if ~isempty(metData) -0128 metRxns = any(metData,1).'; -0129 posRxns = rxnScores > 0 | ((rxnScores == 0) & metRxns); -0130 else -0131 posRxns = rxnScores > 0; -0132 end -0133 negRxns = rxnScores < 0; -0134 -0135 posRevRxns = find(posRxns & revRxns & ~essential); -0136 negRevRxns = find(negRxns & revRxns & ~essential); -0137 posIrrevRxns = find(posRxns & ~revRxns & ~essential); -0138 negIrrevRxns = find(negRxns & ~revRxns & ~essential); -0139 nPosRev = numel(posRevRxns); -0140 nNegRev = numel(negRevRxns); -0141 nPosIrrev = numel(posIrrevRxns); -0142 nNegIrrev = numel(negIrrevRxns); -0143 nEssRev = numel(essRevRxns); -0144 nEssIrrev = numel(essIrrevRxns); %not used, but left for symmetry -0145 nMetabolMets = size(metData,1); +0127 %Reactions with score 0 will just be left in the model, and will not be part of the problem (but can carry flux). +0128 %It is possible to set score = 0 for e.g. spontaneous reactions, exchange rxns, etc., which may not be that interesting +0129 %to remove. +0130 +0131 %if makeIrrev is on, we can just as well skip all positive reversible rxns - they will be turned on without carrying any flux +0132 %since they can form a loop within themself (fwd-rev) +0133 if remPosRev +0134 rxnScores(rxnScores > 0 & model.rev~=0) = 0; +0135 end +0136 +0137 %Handle metabolomics +0138 %A problem with the metabolomics is that some of the producer reactions +0139 %for a metabolite could be excluded from the problem. We solve this by +0140 %adding them with score 0. They can still be seen as positive, since they +0141 %either do not matter (there is another producer) or they can contribute to an +0142 %increased score. +0143 revRxns=model.rev~=0; +0144 essRevRxns = find(revRxns & essential); +0145 essIrrevRxns = find(~revRxns & essential); 0146 0147 if ~isempty(metData) -0148 metNegRev = negRxns & revRxns & ~essential & metRxns; -0149 metNegIrrev = negRxns & ~revRxns & ~essential & metRxns; -0150 nMetNegRev = sum(metNegRev); -0151 nMetNegIrrev = sum(metNegIrrev); +0148 %remove any metData rows that is connected to an essential rxn - +0149 %these will be on regardless and will cause problems below. +0150 containsEssential = any(metData(:,essential),2); +0151 metData = metData(~containsEssential,:); 0152 end 0153 -0154 -0155 milpModel = model; -0156 -0157 %These six categories need to be handled separately: -0158 % -0159 %PosIrrev (positive reaction score, irreversible): -0160 %flux >= 0.1*Yi, flux - 0.1*Yi - VPI == 0, 0 <= Yi(onoff) <= 1, 0 <= VPI <= Inf -0161 %The nice thing with rxns with positive score is that they do not require a boolean. The optimizer will -0162 %strive for maximizing the Yi here, so it can be a continuous variable, where we just force a flux if -0163 %the variable is on. -0164 %PosRev: -0165 % 1: Split up the flux into positive and negative: flux - vnrp + vnrn == 0, 0 <= vprp,vprn <= Inf. -0166 % 2. Force one of the fluxes on: vprp + vprn >= 0.1*Yi, vprp + vprn - 0.1*Yi - vprv1 == 0. 0 <= Yi(onoff) <= 1, vprv1 >= 0 -0167 % 3. We need a bool to make sure that one of vprp and vprn are always zero: -0168 % vprb E {0,1}: vprp <= 100*vprb (if bool is 0, vprp is zero): vprp - 100*vprb + vprv2 == 0, vprv2 >= 0 -0169 % vprn <= (1-vprb)*100 (if bool is one, vprn is zero): vprn + 100*vprb + vprv3 == 0, -100 <= vprv3 <= inf -0170 %Neg Irrev: -0171 %For negative scores, we need to use an integer, I see no way around that. -0172 % flux < 100*Yi, Yi E {0,1}. Flux - 100*Yi + vni == 0 -0173 %Neg Rev: -0174 %Two cases: -0175 %A: Without irrev model -0176 % 1: Split up the flux into positive and negative: flux - vnrp + vnrn == 0, 0 <= vprp,vprn <= Inf. -0177 % 2: Force the Yi (on/off) var on if the flux is on: vnrp + vnrn <= 0.1 * Yi, Yi E {0,1}: vnrp + vnrn - 0.1 * Yi + vnrv1 == 0, vnrv1 >= 0. -0178 %B: Irrev model (Not used for now) -0179 % 1. We now have two reactions, but still just boolean variable. We know that -0180 % the flux can only be positive, so we just say that flux1 + flux2 <= 0.1 * Yi, Yi E {0,1}: flux1 + flux2 - 0.1 * Yi + vnrv1 == 0, vnrv1 >= 0. -0181 % There is no need to prevent loops - there is no benefit for the objective to generate one, and it does not matter. -0182 %Ess Irrev (essential rxn, i.e. forced to carry flux, irreversible): -0183 %flux >= 0.1 - solved with lb = 0.1. -0184 %Ess Rev (these are not really used): -0185 % 1: Split up the flux into positive and negative: flux - verp + vern == 0, 0 <= verp,vern <= Inf. -0186 % 2. Force one of the fluxes on: verp + vern >= 0.1, verp + vern - verv1 == 0. verv1 >= 0.1 -0187 % 3. We need a bool to make sure that one of verp and vern are always zero: -0188 % verb E {0,1}: verp <= 100*verb (if bool is 0, verp is zero): verp - 100*verb + verv2 == 0, verv2 >= 0 -0189 % vern <= (1-verb)*100 (if bool is one, vern is zero): vern + 100*verb + verv3 == 0, -100 <= verv3 <= inf -0190 -0191 % Now metabolomics -0192 % The basic idea is that the bonus is gotten if any of the producer reactions are on. We can -0193 % therefore use the "on" variables directly for the reactions that are included. We therefore add one variable per met, mon, which can be -0194 % continuous and between 0 and 1. We then say that mon <= v1 + v2 + ... + vn, i.e. that -0195 % mon + mv1 - v1 - v2 ... - vn == 0, 0 <= mon <= 1, 0 <= mv1 <= inf -0196 % mon gets -prodWeight in the c vector (i.e. objective function) -0197 % A tricky part is that some of the reactions producing a metabolite are left outside the problem -0198 % This is solved by moving them into the problem, but with the score 0. They can still be treated as positive -0199 % reactions. In the end, we are not interested if they are on though, but they may -0200 % allow for production of a metabolite, giving no benefit of turning on another producer reaction. -0201 % Another tricky thing is that some metabolite producers have negative score. There is -0202 % no automatic mechanism for forcing flux on if the variable is on - this is simply -0203 % not needed for negative variables - unless they are a metabolite producer. We therefore need -0204 % to add an extra constraint there, similar to the case of positive. It gets a bit complicated. -0205 % For reversible, we need a bool to make sure that one of vnrp and vnrn stays zero: -0206 % vnrbm E {0,1}: vnrp <= 100*vnrbm (if bool is 0, vnrp is zero): vnrp - 100*vnrbm + vnrvm1 == 0, vnrvm1 >= 0 -0207 % vnrn <= (1-vnrbm)*100 (if bool is one, vnrn is zero): vnrn + 100*vnrbm + vnrvm2 == 0, -100 <= vnrvm2 <= inf -0208 % We then also say that vnrp + vnrn >= 0.1*Yi, vnrp + vnrn - 0.1*Yi - vnrvm3 == 0, vnrvm3 >= 0 -0209 % For irreversible, it is fairly straight-forward. We just say that flux >= 0.1*Yi, i.e. flux - 0.1*Yi - vnim == 0, 0 <= vnim <= Inf. -0210 -0211 -0212 %The total matrix then looks like this. Note that we have ordered the categories, two reactions each, -0213 %in the S matrix to make it easier to follow the figure. In practice, they come in random order, which -0214 %is why the SEye variable is used further down. -0215 %all categories below have two columns - the two column starts where label starts -0216 %since the label cannot fit in two chars, they are on multiple lines -0217 % -0218 %The met variables and constraints are not in this figure for practical reasons. -0219 % -0220 % vprb vprv3 vnrn vern verv2 mv1 -0221 % pi ni ei Ypi Yni vpi vprn vprv2 vnrp verp verb mon -0222 % pr nr er Ypr Ynr vprp vprv1 vni vnrv1 verv1 verv3 -0223 % SSSSSSSSSSSS0000000000000000000000000000000000000000000000 -0224 % SSSSSSSSSSSS0000000000000000000000000000000000000000000000 S block -0225 % SSSSSSSSSSSS0000000000000000000000000000000000000000000000 -0226 % 1 N - Pos irrev block -0227 % 1 N - -0228 % 1 - 1 Pos rev block 1 -0229 % 1 - 1 -0230 % N 1 1 - Pos rev block 2 -0231 % N 1 1 - -0232 % 1 M 1 Pos rev block 3 -0233 % 1 M 1 -0234 % 1 C 1 Pos rev block 4 -0235 % 1 C 1 -0236 % 1 M 1 Neg irrev block -0237 % 1 M 1 -0238 % 1 - 1 Neg rev block 1 -0239 % 1 - 1 -0240 % M 1 1 1 Neg rev block 2 -0241 % M 1 1 1 -0242 % 1 - 1 Ess rev block 1 -0243 % 1 - 1 -0244 % 1 1 - Ess rev block 2 -0245 % 1 1 - -0246 % 1 M 1 Ess rev block 3 -0247 % 1 M 1 -0248 % 1 C 1 Ess rev block 4 -0249 % 1 C 1 -0250 % -------- 1 1 Met block - Here, we assume that all variables support each met. -0251 % -------- 1 1 In practice, fewer of the "on" variables with -1 should be included -0252 % -0253 % -0254 %M = -100 -0255 %N = -0.1 -0256 %D = 0.1 -0257 %C = 100 -0258 %- = -1 -0259 % -0260 %It looks a bit different for the irrev model, but still quite similar -0261 -0262 %build the A matrix -0263 %S row -0264 %When forcing on essential rxns, use the flux value of the previous run (set to 0.1 the first time) -0265 %Do not set it above 0.1, may starve something else out. Leave a margin of 1% from the last run. -0266 forceOnLim = forceOn; -0267 forceOnLimEss=min(abs(fluxes)*0.99,0.1); -0268 forceOnLimEss(essIrrevRxns) = min(forceOnLimEss(essIrrevRxns), milpModel.ub(essIrrevRxns)); -0269 -0270 % Per-reaction big-M: use each reaction own flux capacity (capped at 1000). -0271 % This replaces the fixed big-M=100, which underestimates capacity for standard -0272 % RAVEN models where ub=1000. Must be at least forceOnLim so the constraints are feasible. -0273 bigMCap = 1000; -0274 ubSafe = min(milpModel.ub, bigMCap); ubSafe(~isfinite(ubSafe)) = bigMCap; -0275 lbSafe = min(-milpModel.lb, bigMCap); lbSafe(~isfinite(lbSafe)) = bigMCap; lbSafe = max(lbSafe, 0); -0276 bigMPosRev = max(max(ubSafe(posRevRxns), lbSafe(posRevRxns)), forceOnLim); -0277 bigMNegIrrev = max(ubSafe(negIrrevRxns), forceOnLim); -0278 bigMNegRev = max(max(ubSafe(negRevRxns), lbSafe(negRevRxns)), forceOnLim); -0279 bigMEssRev = max(max(ubSafe(essRevRxns), lbSafe(essRevRxns)), forceOnLim); -0280 -0281 varsPerNegRev = 3; -0282 -0283 -0284 %Figure out the number of variables needed for metabolomics -0285 if ~isempty(metData) -0286 nMetVars = 2*size(metData,1) + 4*nMetNegRev + nMetNegIrrev; %mon,mv1; vnrbm,vnrvm1,vnrvm2,vnrvm3; vnim -0287 else -0288 nMetVars = 0; -0289 end -0290 -0291 -0292 sRow = [milpModel.S sparse(nMets, nPosIrrev*2+nPosRev*7+nNegIrrev*2+nNegRev*(1+varsPerNegRev)+nEssRev*6 + nMetVars)]; -0293 sEye = speye(nRxns); -0294 nYBlock = nPosIrrev+nPosRev+nNegIrrev+nNegRev; -0295 piRows = [sEye(posIrrevRxns,:) speye(nPosIrrev)*-forceOnLim sparse(nPosIrrev,nPosRev+nNegIrrev+nNegRev) speye(nPosIrrev)*-1 sparse(nPosIrrev,nPosRev*6+nNegIrrev+nNegRev*varsPerNegRev+nEssRev*6+nMetVars)]; +0154 if ~isempty(metData) +0155 metRxns = any(metData,1).'; +0156 posRxns = rxnScores > 0 | ((rxnScores == 0) & metRxns); +0157 else +0158 posRxns = rxnScores > 0; +0159 end +0160 negRxns = rxnScores < 0; +0161 +0162 posRevRxns = find(posRxns & revRxns & ~essential); +0163 negRevRxns = find(negRxns & revRxns & ~essential); +0164 posIrrevRxns = find(posRxns & ~revRxns & ~essential); +0165 negIrrevRxns = find(negRxns & ~revRxns & ~essential); +0166 nPosRev = numel(posRevRxns); +0167 nNegRev = numel(negRevRxns); +0168 nPosIrrev = numel(posIrrevRxns); +0169 nNegIrrev = numel(negIrrevRxns); +0170 nEssRev = numel(essRevRxns); +0171 nEssIrrev = numel(essIrrevRxns); %not used, but left for symmetry +0172 nMetabolMets = size(metData,1); +0173 +0174 if ~isempty(metData) +0175 metNegRev = negRxns & revRxns & ~essential & metRxns; +0176 metNegIrrev = negRxns & ~revRxns & ~essential & metRxns; +0177 nMetNegRev = sum(metNegRev); +0178 nMetNegIrrev = sum(metNegIrrev); +0179 end +0180 +0181 +0182 milpModel = model; +0183 +0184 %These six categories need to be handled separately: +0185 % +0186 %PosIrrev (positive reaction score, irreversible): +0187 %flux >= 0.1*Yi, flux - 0.1*Yi - VPI == 0, 0 <= Yi(onoff) <= 1, 0 <= VPI <= Inf +0188 %The nice thing with rxns with positive score is that they do not require a boolean. The optimizer will +0189 %strive for maximizing the Yi here, so it can be a continuous variable, where we just force a flux if +0190 %the variable is on. +0191 %PosRev: +0192 % 1: Split up the flux into positive and negative: flux - vnrp + vnrn == 0, 0 <= vprp,vprn <= Inf. +0193 % 2. Force one of the fluxes on: vprp + vprn >= 0.1*Yi, vprp + vprn - 0.1*Yi - vprv1 == 0. 0 <= Yi(onoff) <= 1, vprv1 >= 0 +0194 % 3. We need a bool to make sure that one of vprp and vprn are always zero: +0195 % vprb E {0,1}: vprp <= 100*vprb (if bool is 0, vprp is zero): vprp - 100*vprb + vprv2 == 0, vprv2 >= 0 +0196 % vprn <= (1-vprb)*100 (if bool is one, vprn is zero): vprn + 100*vprb + vprv3 == 0, -100 <= vprv3 <= inf +0197 %Neg Irrev: +0198 %For negative scores, we need to use an integer, I see no way around that. +0199 % flux < 100*Yi, Yi E {0,1}. Flux - 100*Yi + vni == 0 +0200 %Neg Rev: +0201 %Two cases: +0202 %A: Without irrev model +0203 % 1: Split up the flux into positive and negative: flux - vnrp + vnrn == 0, 0 <= vprp,vprn <= Inf. +0204 % 2: Force the Yi (on/off) var on if the flux is on: vnrp + vnrn <= 0.1 * Yi, Yi E {0,1}: vnrp + vnrn - 0.1 * Yi + vnrv1 == 0, vnrv1 >= 0. +0205 %B: Irrev model (Not used for now) +0206 % 1. We now have two reactions, but still just boolean variable. We know that +0207 % the flux can only be positive, so we just say that flux1 + flux2 <= 0.1 * Yi, Yi E {0,1}: flux1 + flux2 - 0.1 * Yi + vnrv1 == 0, vnrv1 >= 0. +0208 % There is no need to prevent loops - there is no benefit for the objective to generate one, and it does not matter. +0209 %Ess Irrev (essential rxn, i.e. forced to carry flux, irreversible): +0210 %flux >= 0.1 - solved with lb = 0.1. +0211 %Ess Rev (these are not really used): +0212 % 1: Split up the flux into positive and negative: flux - verp + vern == 0, 0 <= verp,vern <= Inf. +0213 % 2. Force one of the fluxes on: verp + vern >= 0.1, verp + vern - verv1 == 0. verv1 >= 0.1 +0214 % 3. We need a bool to make sure that one of verp and vern are always zero: +0215 % verb E {0,1}: verp <= 100*verb (if bool is 0, verp is zero): verp - 100*verb + verv2 == 0, verv2 >= 0 +0216 % vern <= (1-verb)*100 (if bool is one, vern is zero): vern + 100*verb + verv3 == 0, -100 <= verv3 <= inf +0217 +0218 % Now metabolomics +0219 % The basic idea is that the bonus is gotten if any of the producer reactions are on. We can +0220 % therefore use the "on" variables directly for the reactions that are included. We therefore add one variable per met, mon, which can be +0221 % continuous and between 0 and 1. We then say that mon <= v1 + v2 + ... + vn, i.e. that +0222 % mon + mv1 - v1 - v2 ... - vn == 0, 0 <= mon <= 1, 0 <= mv1 <= inf +0223 % mon gets -prodWeight in the c vector (i.e. objective function) +0224 % A tricky part is that some of the reactions producing a metabolite are left outside the problem +0225 % This is solved by moving them into the problem, but with the score 0. They can still be treated as positive +0226 % reactions. In the end, we are not interested if they are on though, but they may +0227 % allow for production of a metabolite, giving no benefit of turning on another producer reaction. +0228 % Another tricky thing is that some metabolite producers have negative score. There is +0229 % no automatic mechanism for forcing flux on if the variable is on - this is simply +0230 % not needed for negative variables - unless they are a metabolite producer. We therefore need +0231 % to add an extra constraint there, similar to the case of positive. It gets a bit complicated. +0232 % For reversible, we need a bool to make sure that one of vnrp and vnrn stays zero: +0233 % vnrbm E {0,1}: vnrp <= 100*vnrbm (if bool is 0, vnrp is zero): vnrp - 100*vnrbm + vnrvm1 == 0, vnrvm1 >= 0 +0234 % vnrn <= (1-vnrbm)*100 (if bool is one, vnrn is zero): vnrn + 100*vnrbm + vnrvm2 == 0, -100 <= vnrvm2 <= inf +0235 % We then also say that vnrp + vnrn >= 0.1*Yi, vnrp + vnrn - 0.1*Yi - vnrvm3 == 0, vnrvm3 >= 0 +0236 % For irreversible, it is fairly straight-forward. We just say that flux >= 0.1*Yi, i.e. flux - 0.1*Yi - vnim == 0, 0 <= vnim <= Inf. +0237 +0238 +0239 %The total matrix then looks like this. Note that we have ordered the categories, two reactions each, +0240 %in the S matrix to make it easier to follow the figure. In practice, they come in random order, which +0241 %is why the SEye variable is used further down. +0242 %all categories below have two columns - the two column starts where label starts +0243 %since the label cannot fit in two chars, they are on multiple lines +0244 % +0245 %The met variables and constraints are not in this figure for practical reasons. +0246 % +0247 % vprb vprv3 vnrn vern verv2 mv1 +0248 % pi ni ei Ypi Yni vpi vprn vprv2 vnrp verp verb mon +0249 % pr nr er Ypr Ynr vprp vprv1 vni vnrv1 verv1 verv3 +0250 % SSSSSSSSSSSS0000000000000000000000000000000000000000000000 +0251 % SSSSSSSSSSSS0000000000000000000000000000000000000000000000 S block +0252 % SSSSSSSSSSSS0000000000000000000000000000000000000000000000 +0253 % 1 N - Pos irrev block +0254 % 1 N - +0255 % 1 - 1 Pos rev block 1 +0256 % 1 - 1 +0257 % N 1 1 - Pos rev block 2 +0258 % N 1 1 - +0259 % 1 M 1 Pos rev block 3 +0260 % 1 M 1 +0261 % 1 C 1 Pos rev block 4 +0262 % 1 C 1 +0263 % 1 M 1 Neg irrev block +0264 % 1 M 1 +0265 % 1 - 1 Neg rev block 1 +0266 % 1 - 1 +0267 % M 1 1 1 Neg rev block 2 +0268 % M 1 1 1 +0269 % 1 - 1 Ess rev block 1 +0270 % 1 - 1 +0271 % 1 1 - Ess rev block 2 +0272 % 1 1 - +0273 % 1 M 1 Ess rev block 3 +0274 % 1 M 1 +0275 % 1 C 1 Ess rev block 4 +0276 % 1 C 1 +0277 % -------- 1 1 Met block - Here, we assume that all variables support each met. +0278 % -------- 1 1 In practice, fewer of the "on" variables with -1 should be included +0279 % +0280 % +0281 %M = -100 +0282 %N = -0.1 +0283 %D = 0.1 +0284 %C = 100 +0285 %- = -1 +0286 % +0287 %It looks a bit different for the irrev model, but still quite similar +0288 +0289 %build the A matrix +0290 %S row +0291 %When forcing on essential rxns, use the flux value of the previous run (set to 0.1 the first time) +0292 %Do not set it above 0.1, may starve something else out. Leave a margin of 1% from the last run. +0293 forceOnLim = forceOn; +0294 forceOnLimEss=min(abs(fluxes)*0.99,0.1); +0295 forceOnLimEss(essIrrevRxns) = min(forceOnLimEss(essIrrevRxns), milpModel.ub(essIrrevRxns)); 0296 -0297 prRows1 = [sEye(posRevRxns,:) sparse(nPosRev,nYBlock + nPosIrrev) speye(nPosRev)*-1 speye(nPosRev) sparse(nPosRev,nPosRev*4+nNegIrrev+nNegRev*varsPerNegRev+nEssRev*6+nMetVars)]; -0298 prRows2 = [sparse(nPosRev,nRxns + nPosIrrev) speye(nPosRev)*-forceOnLim sparse(nPosRev,nNegIrrev + nNegRev + nPosIrrev) speye(nPosRev) speye(nPosRev) sparse(nPosRev,nPosRev) speye(nPosRev)*-1 sparse(nPosRev,nPosRev*2+nNegIrrev+nNegRev*varsPerNegRev+nEssRev*6+nMetVars)]; -0299 prRows3 = [sparse(nPosRev,nRxns + nYBlock + nPosIrrev) speye(nPosRev) sparse(nPosRev,nPosRev) spdiags(-bigMPosRev,0,nPosRev,nPosRev) sparse(nPosRev,nPosRev) speye(nPosRev) sparse(nPosRev,nPosRev+nNegIrrev+nNegRev*varsPerNegRev+nEssRev*6+nMetVars)]; -0300 prRows4 = [sparse(nPosRev,nRxns + nYBlock + nPosIrrev + nPosRev) speye(nPosRev) spdiags(bigMPosRev,0,nPosRev,nPosRev) sparse(nPosRev,nPosRev*2) speye(nPosRev) sparse(nPosRev, nNegIrrev+nNegRev*varsPerNegRev+nEssRev*6+nMetVars)]; -0301 -0302 niRows = [sEye(negIrrevRxns,:) sparse(nNegIrrev,nPosIrrev + nPosRev) spdiags(-bigMNegIrrev,0,nNegIrrev,nNegIrrev) sparse(nNegIrrev,nNegRev + nPosIrrev + nPosRev*6) speye(nNegIrrev) sparse(nNegIrrev,nNegRev*varsPerNegRev + nEssRev*6+nMetVars)]; -0303 -0304 nrRows1 = [sEye(negRevRxns,:) sparse(nNegRev,nYBlock + nPosIrrev + nPosRev*6 + nNegIrrev) speye(nNegRev)*-1 speye(nNegRev) sparse(nNegRev,nNegRev + nEssRev*6+nMetVars)]; -0305 nrRows2 = [sparse(nNegRev,nRxns + nPosIrrev + nPosRev + nNegIrrev) spdiags(-bigMNegRev,0,nNegRev,nNegRev) sparse(nNegRev,nPosIrrev + nPosRev*6 + nNegIrrev) speye(nNegRev) speye(nNegRev) speye(nNegRev) sparse(nNegRev,nEssRev*6+nMetVars)]; -0306 nrRows = [nrRows1;nrRows2]; +0297 % Per-reaction big-M: use each reaction own flux capacity (capped at 1000). +0298 % This replaces the fixed big-M=100, which underestimates capacity for standard +0299 % RAVEN models where ub=1000. Must be at least forceOnLim so the constraints are feasible. +0300 bigMCap = 1000; +0301 ubSafe = min(milpModel.ub, bigMCap); ubSafe(~isfinite(ubSafe)) = bigMCap; +0302 lbSafe = min(-milpModel.lb, bigMCap); lbSafe(~isfinite(lbSafe)) = bigMCap; lbSafe = max(lbSafe, 0); +0303 bigMPosRev = max(max(ubSafe(posRevRxns), lbSafe(posRevRxns)), forceOnLim); +0304 bigMNegIrrev = max(ubSafe(negIrrevRxns), forceOnLim); +0305 bigMNegRev = max(max(ubSafe(negRevRxns), lbSafe(negRevRxns)), forceOnLim); +0306 bigMEssRev = max(max(ubSafe(essRevRxns), lbSafe(essRevRxns)), forceOnLim); 0307 -0308 erRows1 = [sEye(essRevRxns,:) sparse(nEssRev,nYBlock + nPosIrrev + nPosRev*6 + nNegIrrev + nNegRev*varsPerNegRev) speye(nEssRev)*-1 speye(nEssRev) sparse(nEssRev, nEssRev*4+nMetVars)]; -0309 erRows2 = [sparse(nEssRev,nRxns + nYBlock + nPosIrrev + nPosRev*6 + nNegIrrev + nNegRev*varsPerNegRev) speye(nEssRev) speye(nEssRev) speye(nEssRev)*-1 sparse(nEssRev, nEssRev*3+nMetVars)]; -0310 erRows3 = [sparse(nEssRev,nRxns + nYBlock + nPosIrrev + nPosRev*6 + nNegIrrev + nNegRev*varsPerNegRev) speye(nEssRev) sparse(nEssRev, nEssRev*2) spdiags(-bigMEssRev,0,nEssRev,nEssRev) speye(nEssRev) sparse(nEssRev, nEssRev+nMetVars)]; -0311 erRows4 = [sparse(nEssRev,nRxns + nYBlock + nPosIrrev + nPosRev*6 + nNegIrrev + nNegRev*varsPerNegRev + nEssRev) speye(nEssRev) sparse(nEssRev, nEssRev) spdiags(bigMEssRev,0,nEssRev,nEssRev) sparse(nEssRev, nEssRev) speye(nEssRev) sparse(nEssRev, nMetVars)]; -0312 -0313 %now the mets -0314 if ~isempty(metData) -0315 %Order of rxn "on" vars: Ypi Ypr Yni Ynr. This is a bit messy, since they are not in the -0316 %same order as the reactions in the S matrix or metData. We therefore resort the vars in -0317 %metData to match the order of the vars. -0318 srtMetData = sparse([metData(:,posIrrevRxns) metData(:,posRevRxns) metData(:,negIrrevRxns) metData(:,negRevRxns)]); -0319 -0320 %First the setup for giving bonus if the met is included. -0321 %The mon vars come first followed by the mv1 vars -0322 metRows1 = [sparse(nMetabolMets,nRxns) -srtMetData sparse(nMetabolMets, nPosIrrev + nPosRev*6 + nNegIrrev + nNegRev*varsPerNegRev + nEssRev*6) speye(nMetabolMets) speye(nMetabolMets) sparse(nMetabolMets, 4*nMetNegRev + nMetNegIrrev)]; -0323 %Then negative rev: -0324 % Variable order: eye(vnrbm),eye(vnrvm1),eye(vnrvm2),eye(vnrvm3) -0325 % vnrp,vnrn are the two first variables among the neg rev vars. -0326 % For reversible, we need a bool to make sure that one of vnrp and vnrn stays zero: -0327 % vnrbm E {0,1}: vnrp <= 100*vnrbm (if bool is 0, vnrp is zero): vnrp - 100*vnrbm + vnrvm1 == 0, vnrvm1 >= 0 (metRows2) -0328 % vnrn <= (1-vnrbm)*100 (if bool is one, vnrn is zero): vnrn + 100*vnrbm + vnrvm2 == 0, -100 <= vnrvm2 <= inf (metRows3) -0329 % We then also say that vnrp + vnrn >= 0.1*Yi, -0.1*Yi + vnrp + vnrn - vnrvm3 == 0, vnrvm3 >= 0 (metRows4) -0330 nrEye = speye(nNegRev); -0331 %Force a column: when no met reaction is negative-reversible, indexing a -0332 %scalar bigMNegRev with an all-false mask returns a 0-by-0 empty, which -0333 %the spdiags calls below reject (they need a 0-by-1 column). -0334 bigMMetNegRev = bigMNegRev(metNegRev(negRevRxns)); -0335 bigMMetNegRev = bigMMetNegRev(:); -0336 %vnrp - M*vnrbm + vnrvm1 == 0 -0337 metRows2 = [sparse(nMetNegRev,nRxns + nYBlock + nPosIrrev + nPosRev*6 + nNegIrrev) ... %zeros up to vnrp -0338 nrEye(metNegRev(negRevRxns),:) ... %vnrp -0339 sparse(nMetNegRev, nNegRev*(varsPerNegRev-1) + nEssRev*6 + nMetabolMets*2) ... %zeros up to vnrbm -0340 spdiags(-bigMMetNegRev,0,nMetNegRev,nMetNegRev) ... %vnrbm -0341 speye(nMetNegRev) ... % vnrvm1 -0342 sparse(nMetNegRev, nMetNegRev*2 + nMetNegIrrev)];%fill up the rest with zeros -0343 %vnrn + M*vnrbm + vnrvm2 == 0 -0344 metRows3 = [sparse(nMetNegRev,nRxns + nYBlock + nPosIrrev + nPosRev*6 + nNegIrrev + nNegRev) ... %zeros up to vnrn -0345 nrEye(metNegRev(negRevRxns),:) ... %vnrp -0346 sparse(nMetNegRev, nNegRev*(varsPerNegRev-2) + nEssRev*6 + nMetabolMets*2) ... %zeros up to vnrbm -0347 spdiags(bigMMetNegRev,0,nMetNegRev,nMetNegRev) ... %vnrbm -0348 sparse(nMetNegRev, nMetNegRev) ... %zeros up to vnrvm2 -0349 speye(nMetNegRev) ... % vnrvm2 -0350 sparse(nMetNegRev, nMetNegRev + nMetNegIrrev)];%fill up the rest with zeros -0351 %-0.1*Yi + vnrp + vnrn - vnrvm3 == 0 -0352 metRows4 = [sparse(nMetNegRev,nRxns + nPosIrrev + nPosRev + nNegIrrev) ... %zeros up to Yi for neg rev -0353 nrEye(metNegRev(negRevRxns),:)*-0.1 ... %Yi for met neg rev -0354 sparse(nMetNegRev, nPosIrrev + nPosRev*6 + nNegIrrev) ... %zeros up to vnrp -0355 nrEye(metNegRev(negRevRxns),:) ... %vnrp -0356 nrEye(metNegRev(negRevRxns),:) ... %vnrn -0357 sparse(nMetNegRev, nNegRev*(varsPerNegRev-2) + nEssRev*6 + nMetabolMets*2 + nMetNegRev*3) ...%zeros up to vnrvm3 -0358 speye(nMetNegRev)*-1 ... % vnrvm3 -0359 sparse(nMetNegRev, nMetNegIrrev)];%fill up the rest with zeros -0360 -0361 %Then negative irrev, i.e. flux - 0.1*Yi - vnim == 0 -0362 niEye = speye(nNegIrrev); -0363 metRows5 = [sEye(metNegIrrev,:) ... %flux -0364 sparse(nMetNegIrrev, nPosIrrev + nPosRev) ... % zeros up to Yi for neg irrev -0365 niEye(metNegIrrev(negIrrevRxns),:)*-0.1 ... %Yi for met neg irrev -0366 sparse(nMetNegIrrev, nNegRev + nPosIrrev + nPosRev*6 + nNegIrrev + nNegRev*varsPerNegRev + nEssRev*6 + nMetabolMets*2 + 4*nMetNegRev) ... %zeros up to vnim -0367 -speye(nMetNegIrrev) ]; %vnim -0368 metRows = [metRows1;metRows2;metRows3;metRows4;metRows5]; -0369 metVarC = [ones(nMetabolMets,1)*-prodWeight;zeros(nMetVars - nMetabolMets,1)]; -0370 %vnrbm is boolean, so between 0 and 1 -0371 %vnrvm1 >= 0 -0372 %-100 <= vnrvm2 <= inf -0373 % vnrvm3 >= 0 -0374 %0 <= vnim <= Inf -0375 metLb = [zeros(nMetabolMets*2 + 2*nMetNegRev,1);-bigMMetNegRev;zeros(nMetNegRev + nMetNegIrrev,1)]; -0376 metUb = [ones(nMetabolMets,1);inf(nMetabolMets,1);ones(nMetNegRev,1);inf(3*nMetNegRev + nMetNegIrrev,1)]; -0377 metVartype = [repmat('C', 1, nMetabolMets*2), ... -0378 repmat('B', 1, nMetNegRev), ... -0379 repmat('C', 1, 3*nMetNegRev + nMetNegIrrev)]; -0380 -0381 else -0382 metRows = []; -0383 metVarC = []; -0384 metLb = []; -0385 metUb = []; -0386 metVartype = []; -0387 end -0388 -0389 prob.a = [sRow;piRows;prRows1;prRows2;prRows3;prRows4;niRows;nrRows;erRows1;erRows2;erRows3;erRows4;metRows]; -0390 prob.A = prob.a; -0391 prob.b = zeros(size(prob.A,1),1); -0392 prob.c = [zeros(nRxns,1);rxnScores(posIrrevRxns)*-1;rxnScores(posRevRxns)*-1;rxnScores(negIrrevRxns)*-1;rxnScores(negRevRxns)*-1;zeros(nPosIrrev + nPosRev*6 + nNegIrrev + nNegRev*varsPerNegRev + nEssRev*6,1);metVarC]; -0393 prob.lb = [milpModel.lb;zeros(nYBlock + nPosIrrev + 5*nPosRev,1);-bigMPosRev;zeros(nNegIrrev+nNegRev*varsPerNegRev+nEssRev*2,1);ones(nEssRev,1)*forceOnLim;zeros(nEssRev*2,1);-bigMEssRev;metLb]; -0394 prob.lb(essIrrevRxns) = forceOnLimEss(essIrrevRxns);%force flux for the ess irrev rxns - this is the only way these are handled -0395 prob.ub = [milpModel.ub;ones(nYBlock,1);inf(nPosIrrev+nPosRev*2,1);ones(nPosRev,1);inf(3*nPosRev+nNegIrrev+varsPerNegRev*nNegRev+3*nEssRev,1);ones(nEssRev,1);inf(2*nEssRev,1);metUb]; -0396 -0397 prob.vartype = [repmat('C', 1, nRxns + nPosIrrev + nPosRev), ... -0398 repmat('B', 1, nNegIrrev + nNegRev), ... -0399 repmat('C', 1, nPosIrrev + 2*nPosRev), ... -0400 repmat('B', 1, nPosRev), ... -0401 repmat('C', 1, 3*nPosRev + nNegIrrev + varsPerNegRev*nNegRev + 3*nEssRev), ... -0402 repmat('B', 1, nEssRev), ... -0403 repmat('C', 1, 2*nEssRev), ... -0404 metVartype]; -0405 -0406 onoffVarInd = (1:(nPosIrrev + nPosRev + nNegIrrev + nNegRev)) + nRxns; -0407 onoffPosIrrev = onoffVarInd(1:nPosIrrev); -0408 onoffPosRev = onoffVarInd((1:nPosRev)+nPosIrrev); -0409 onoffNegIrrev = onoffVarInd((1:nNegIrrev)+nPosIrrev+nPosRev); -0410 onoffNegRev = onoffVarInd((1:nNegRev)+nPosIrrev+nPosRev+nNegIrrev); -0411 -0412 metVarInd = (1:nMetabolMets) + (length(prob.vartype) - nMetVars); -0413 -0414 if allowExcretion -0415 %S*v >= 0: a metabolite may be produced in excess of what is consumed, -0416 %and the surplus is implicitly excreted. 'L' would say S*v <= 0, which -0417 %lets a metabolite be consumed without ever being produced -- free -0418 %uptake, the opposite of excretion, and enough for the MILP to switch a -0419 %reaction on by conjuring its substrates out of nothing. -0420 prob.csense = [repmat('G', 1, length(milpModel.mets)), ... -0421 repmat('E', 1, length(prob.b) - length(milpModel.mets))]; -0422 else -0423 %One character per row: optimizeProb maps csense elementwise for glpk and -0424 %cobra, so a scalar '=' only happens to work on gurobi. -0425 prob.csense = repmat('E', 1, length(prob.b)); -0426 end -0427 -0428 params.intTol = 10^-7; %This value is very important. If set too low -0429 %there is a risk that gurobi fails due to numerical -0430 %issues - this happened for Gurobi v. 10.0 with TestModelL. -0431 %On the other hand, it should not be too large -0432 %either. With this value, fluxes of 10-7 can slip -0433 %through, which should be fine. Another option if -0434 %this becomes a problem is to set NumericFocus=2, -0435 %which makes the solver slower but fixes the issue -0436 %with TestModelL. -0437 -0438 prob.osense = 1; %minimize -0439 -0440 if ~isempty(startVals) -0441 prob.start = startVals; %This doesn't work... -0442 end -0443 -0444 res=optimizeProb(prob,params,verbose); -0445 -0446 -0447 if ~checkSolution(res) -0448 if res.hitTimeLimit -0449 EM='Time limit reached without finding a solution. Try increasing the TimeLimit parameter.'; -0450 else -0451 EM='The problem is infeasible'; -0452 end -0453 error('RAVEN:badInput', '%s', EM); -0454 end -0455 -0456 if resolveTies && (nNegIrrev + nNegRev) > 0 -0457 %This MILP is highly degenerate: many reaction subsets reach the same -0458 %score optimum, and the solver returns an arbitrary one -- reproducible -0459 %for a fixed solver build + Seed, but fragile to a solver version bump -0460 %(raven-gecko-parity#104). This runs a lexicographic phase 2, holding -0461 %the score objective at its optimum with a floor constraint: first -0462 %minimise the count of "on" removable reactions (the sparsest optimum), -0463 %then, among the sparsest, minimise their summed reaction-id rank -0464 %(prefer lower ids). Only the negative-score "on" variables (onoffNegIrrev, -0465 %onoffNegRev) are a genuine tie to resolve: positive-score ones are -0466 %continuous, pinned near 1 by the score objective rather than a free -0467 %binary choice. Ports raven-toolbox's _resolve_ties (raven-toolbox#114). -0468 negCols = [onoffNegIrrev, onoffNegRev]; -0469 negRxnIdx = [negIrrevRxns; negRevRxns]; -0470 primaryObj = full(prob.c' * res.full); -0471 tol = max(abs(primaryObj)*1e-7, 1e-7); +0308 varsPerNegRev = 3; +0309 +0310 +0311 %Figure out the number of variables needed for metabolomics +0312 if ~isempty(metData) +0313 nMetVars = 2*size(metData,1) + 4*nMetNegRev + nMetNegIrrev; %mon,mv1; vnrbm,vnrvm1,vnrvm2,vnrvm3; vnim +0314 else +0315 nMetVars = 0; +0316 end +0317 +0318 +0319 sRow = [milpModel.S sparse(nMets, nPosIrrev*2+nPosRev*7+nNegIrrev*2+nNegRev*(1+varsPerNegRev)+nEssRev*6 + nMetVars)]; +0320 sEye = speye(nRxns); +0321 nYBlock = nPosIrrev+nPosRev+nNegIrrev+nNegRev; +0322 piRows = [sEye(posIrrevRxns,:) speye(nPosIrrev)*-forceOnLim sparse(nPosIrrev,nPosRev+nNegIrrev+nNegRev) speye(nPosIrrev)*-1 sparse(nPosIrrev,nPosRev*6+nNegIrrev+nNegRev*varsPerNegRev+nEssRev*6+nMetVars)]; +0323 +0324 prRows1 = [sEye(posRevRxns,:) sparse(nPosRev,nYBlock + nPosIrrev) speye(nPosRev)*-1 speye(nPosRev) sparse(nPosRev,nPosRev*4+nNegIrrev+nNegRev*varsPerNegRev+nEssRev*6+nMetVars)]; +0325 prRows2 = [sparse(nPosRev,nRxns + nPosIrrev) speye(nPosRev)*-forceOnLim sparse(nPosRev,nNegIrrev + nNegRev + nPosIrrev) speye(nPosRev) speye(nPosRev) sparse(nPosRev,nPosRev) speye(nPosRev)*-1 sparse(nPosRev,nPosRev*2+nNegIrrev+nNegRev*varsPerNegRev+nEssRev*6+nMetVars)]; +0326 prRows3 = [sparse(nPosRev,nRxns + nYBlock + nPosIrrev) speye(nPosRev) sparse(nPosRev,nPosRev) spdiags(-bigMPosRev,0,nPosRev,nPosRev) sparse(nPosRev,nPosRev) speye(nPosRev) sparse(nPosRev,nPosRev+nNegIrrev+nNegRev*varsPerNegRev+nEssRev*6+nMetVars)]; +0327 prRows4 = [sparse(nPosRev,nRxns + nYBlock + nPosIrrev + nPosRev) speye(nPosRev) spdiags(bigMPosRev,0,nPosRev,nPosRev) sparse(nPosRev,nPosRev*2) speye(nPosRev) sparse(nPosRev, nNegIrrev+nNegRev*varsPerNegRev+nEssRev*6+nMetVars)]; +0328 +0329 niRows = [sEye(negIrrevRxns,:) sparse(nNegIrrev,nPosIrrev + nPosRev) spdiags(-bigMNegIrrev,0,nNegIrrev,nNegIrrev) sparse(nNegIrrev,nNegRev + nPosIrrev + nPosRev*6) speye(nNegIrrev) sparse(nNegIrrev,nNegRev*varsPerNegRev + nEssRev*6+nMetVars)]; +0330 +0331 nrRows1 = [sEye(negRevRxns,:) sparse(nNegRev,nYBlock + nPosIrrev + nPosRev*6 + nNegIrrev) speye(nNegRev)*-1 speye(nNegRev) sparse(nNegRev,nNegRev + nEssRev*6+nMetVars)]; +0332 nrRows2 = [sparse(nNegRev,nRxns + nPosIrrev + nPosRev + nNegIrrev) spdiags(-bigMNegRev,0,nNegRev,nNegRev) sparse(nNegRev,nPosIrrev + nPosRev*6 + nNegIrrev) speye(nNegRev) speye(nNegRev) speye(nNegRev) sparse(nNegRev,nEssRev*6+nMetVars)]; +0333 nrRows = [nrRows1;nrRows2]; +0334 +0335 erRows1 = [sEye(essRevRxns,:) sparse(nEssRev,nYBlock + nPosIrrev + nPosRev*6 + nNegIrrev + nNegRev*varsPerNegRev) speye(nEssRev)*-1 speye(nEssRev) sparse(nEssRev, nEssRev*4+nMetVars)]; +0336 erRows2 = [sparse(nEssRev,nRxns + nYBlock + nPosIrrev + nPosRev*6 + nNegIrrev + nNegRev*varsPerNegRev) speye(nEssRev) speye(nEssRev) speye(nEssRev)*-1 sparse(nEssRev, nEssRev*3+nMetVars)]; +0337 erRows3 = [sparse(nEssRev,nRxns + nYBlock + nPosIrrev + nPosRev*6 + nNegIrrev + nNegRev*varsPerNegRev) speye(nEssRev) sparse(nEssRev, nEssRev*2) spdiags(-bigMEssRev,0,nEssRev,nEssRev) speye(nEssRev) sparse(nEssRev, nEssRev+nMetVars)]; +0338 erRows4 = [sparse(nEssRev,nRxns + nYBlock + nPosIrrev + nPosRev*6 + nNegIrrev + nNegRev*varsPerNegRev + nEssRev) speye(nEssRev) sparse(nEssRev, nEssRev) spdiags(bigMEssRev,0,nEssRev,nEssRev) sparse(nEssRev, nEssRev) speye(nEssRev) sparse(nEssRev, nMetVars)]; +0339 +0340 %now the mets +0341 if ~isempty(metData) +0342 %Order of rxn "on" vars: Ypi Ypr Yni Ynr. This is a bit messy, since they are not in the +0343 %same order as the reactions in the S matrix or metData. We therefore resort the vars in +0344 %metData to match the order of the vars. +0345 srtMetData = sparse([metData(:,posIrrevRxns) metData(:,posRevRxns) metData(:,negIrrevRxns) metData(:,negRevRxns)]); +0346 +0347 %First the setup for giving bonus if the met is included. +0348 %The mon vars come first followed by the mv1 vars +0349 metRows1 = [sparse(nMetabolMets,nRxns) -srtMetData sparse(nMetabolMets, nPosIrrev + nPosRev*6 + nNegIrrev + nNegRev*varsPerNegRev + nEssRev*6) speye(nMetabolMets) speye(nMetabolMets) sparse(nMetabolMets, 4*nMetNegRev + nMetNegIrrev)]; +0350 %Then negative rev: +0351 % Variable order: eye(vnrbm),eye(vnrvm1),eye(vnrvm2),eye(vnrvm3) +0352 % vnrp,vnrn are the two first variables among the neg rev vars. +0353 % For reversible, we need a bool to make sure that one of vnrp and vnrn stays zero: +0354 % vnrbm E {0,1}: vnrp <= 100*vnrbm (if bool is 0, vnrp is zero): vnrp - 100*vnrbm + vnrvm1 == 0, vnrvm1 >= 0 (metRows2) +0355 % vnrn <= (1-vnrbm)*100 (if bool is one, vnrn is zero): vnrn + 100*vnrbm + vnrvm2 == 0, -100 <= vnrvm2 <= inf (metRows3) +0356 % We then also say that vnrp + vnrn >= 0.1*Yi, -0.1*Yi + vnrp + vnrn - vnrvm3 == 0, vnrvm3 >= 0 (metRows4) +0357 nrEye = speye(nNegRev); +0358 %Force a column: when no met reaction is negative-reversible, indexing a +0359 %scalar bigMNegRev with an all-false mask returns a 0-by-0 empty, which +0360 %the spdiags calls below reject (they need a 0-by-1 column). +0361 bigMMetNegRev = bigMNegRev(metNegRev(negRevRxns)); +0362 bigMMetNegRev = bigMMetNegRev(:); +0363 %vnrp - M*vnrbm + vnrvm1 == 0 +0364 metRows2 = [sparse(nMetNegRev,nRxns + nYBlock + nPosIrrev + nPosRev*6 + nNegIrrev) ... %zeros up to vnrp +0365 nrEye(metNegRev(negRevRxns),:) ... %vnrp +0366 sparse(nMetNegRev, nNegRev*(varsPerNegRev-1) + nEssRev*6 + nMetabolMets*2) ... %zeros up to vnrbm +0367 spdiags(-bigMMetNegRev,0,nMetNegRev,nMetNegRev) ... %vnrbm +0368 speye(nMetNegRev) ... % vnrvm1 +0369 sparse(nMetNegRev, nMetNegRev*2 + nMetNegIrrev)];%fill up the rest with zeros +0370 %vnrn + M*vnrbm + vnrvm2 == 0 +0371 metRows3 = [sparse(nMetNegRev,nRxns + nYBlock + nPosIrrev + nPosRev*6 + nNegIrrev + nNegRev) ... %zeros up to vnrn +0372 nrEye(metNegRev(negRevRxns),:) ... %vnrp +0373 sparse(nMetNegRev, nNegRev*(varsPerNegRev-2) + nEssRev*6 + nMetabolMets*2) ... %zeros up to vnrbm +0374 spdiags(bigMMetNegRev,0,nMetNegRev,nMetNegRev) ... %vnrbm +0375 sparse(nMetNegRev, nMetNegRev) ... %zeros up to vnrvm2 +0376 speye(nMetNegRev) ... % vnrvm2 +0377 sparse(nMetNegRev, nMetNegRev + nMetNegIrrev)];%fill up the rest with zeros +0378 %-0.1*Yi + vnrp + vnrn - vnrvm3 == 0 +0379 metRows4 = [sparse(nMetNegRev,nRxns + nPosIrrev + nPosRev + nNegIrrev) ... %zeros up to Yi for neg rev +0380 nrEye(metNegRev(negRevRxns),:)*-0.1 ... %Yi for met neg rev +0381 sparse(nMetNegRev, nPosIrrev + nPosRev*6 + nNegIrrev) ... %zeros up to vnrp +0382 nrEye(metNegRev(negRevRxns),:) ... %vnrp +0383 nrEye(metNegRev(negRevRxns),:) ... %vnrn +0384 sparse(nMetNegRev, nNegRev*(varsPerNegRev-2) + nEssRev*6 + nMetabolMets*2 + nMetNegRev*3) ...%zeros up to vnrvm3 +0385 speye(nMetNegRev)*-1 ... % vnrvm3 +0386 sparse(nMetNegRev, nMetNegIrrev)];%fill up the rest with zeros +0387 +0388 %Then negative irrev, i.e. flux - 0.1*Yi - vnim == 0 +0389 niEye = speye(nNegIrrev); +0390 metRows5 = [sEye(metNegIrrev,:) ... %flux +0391 sparse(nMetNegIrrev, nPosIrrev + nPosRev) ... % zeros up to Yi for neg irrev +0392 niEye(metNegIrrev(negIrrevRxns),:)*-0.1 ... %Yi for met neg irrev +0393 sparse(nMetNegIrrev, nNegRev + nPosIrrev + nPosRev*6 + nNegIrrev + nNegRev*varsPerNegRev + nEssRev*6 + nMetabolMets*2 + 4*nMetNegRev) ... %zeros up to vnim +0394 -speye(nMetNegIrrev) ]; %vnim +0395 metRows = [metRows1;metRows2;metRows3;metRows4;metRows5]; +0396 metVarC = [ones(nMetabolMets,1)*-prodWeight;zeros(nMetVars - nMetabolMets,1)]; +0397 %vnrbm is boolean, so between 0 and 1 +0398 %vnrvm1 >= 0 +0399 %-100 <= vnrvm2 <= inf +0400 % vnrvm3 >= 0 +0401 %0 <= vnim <= Inf +0402 metLb = [zeros(nMetabolMets*2 + 2*nMetNegRev,1);-bigMMetNegRev;zeros(nMetNegRev + nMetNegIrrev,1)]; +0403 metUb = [ones(nMetabolMets,1);inf(nMetabolMets,1);ones(nMetNegRev,1);inf(3*nMetNegRev + nMetNegIrrev,1)]; +0404 metVartype = [repmat('C', 1, nMetabolMets*2), ... +0405 repmat('B', 1, nMetNegRev), ... +0406 repmat('C', 1, 3*nMetNegRev + nMetNegIrrev)]; +0407 +0408 else +0409 metRows = []; +0410 metVarC = []; +0411 metLb = []; +0412 metUb = []; +0413 metVartype = []; +0414 end +0415 +0416 prob.a = [sRow;piRows;prRows1;prRows2;prRows3;prRows4;niRows;nrRows;erRows1;erRows2;erRows3;erRows4;metRows]; +0417 prob.A = prob.a; +0418 prob.b = zeros(size(prob.A,1),1); +0419 prob.c = [zeros(nRxns,1);rxnScores(posIrrevRxns)*-1;rxnScores(posRevRxns)*-1;rxnScores(negIrrevRxns)*-1;rxnScores(negRevRxns)*-1;zeros(nPosIrrev + nPosRev*6 + nNegIrrev + nNegRev*varsPerNegRev + nEssRev*6,1);metVarC]; +0420 prob.lb = [milpModel.lb;zeros(nYBlock + nPosIrrev + 5*nPosRev,1);-bigMPosRev;zeros(nNegIrrev+nNegRev*varsPerNegRev+nEssRev*2,1);ones(nEssRev,1)*forceOnLim;zeros(nEssRev*2,1);-bigMEssRev;metLb]; +0421 prob.lb(essIrrevRxns) = forceOnLimEss(essIrrevRxns);%force flux for the ess irrev rxns - this is the only way these are handled +0422 prob.ub = [milpModel.ub;ones(nYBlock,1);inf(nPosIrrev+nPosRev*2,1);ones(nPosRev,1);inf(3*nPosRev+nNegIrrev+varsPerNegRev*nNegRev+3*nEssRev,1);ones(nEssRev,1);inf(2*nEssRev,1);metUb]; +0423 +0424 prob.vartype = [repmat('C', 1, nRxns + nPosIrrev + nPosRev), ... +0425 repmat('B', 1, nNegIrrev + nNegRev), ... +0426 repmat('C', 1, nPosIrrev + 2*nPosRev), ... +0427 repmat('B', 1, nPosRev), ... +0428 repmat('C', 1, 3*nPosRev + nNegIrrev + varsPerNegRev*nNegRev + 3*nEssRev), ... +0429 repmat('B', 1, nEssRev), ... +0430 repmat('C', 1, 2*nEssRev), ... +0431 metVartype]; +0432 +0433 onoffVarInd = (1:(nPosIrrev + nPosRev + nNegIrrev + nNegRev)) + nRxns; +0434 onoffPosIrrev = onoffVarInd(1:nPosIrrev); +0435 onoffPosRev = onoffVarInd((1:nPosRev)+nPosIrrev); +0436 onoffNegIrrev = onoffVarInd((1:nNegIrrev)+nPosIrrev+nPosRev); +0437 onoffNegRev = onoffVarInd((1:nNegRev)+nPosIrrev+nPosRev+nNegIrrev); +0438 +0439 metVarInd = (1:nMetabolMets) + (length(prob.vartype) - nMetVars); +0440 +0441 if allowExcretion +0442 %S*v >= 0: a metabolite may be produced in excess of what is consumed, +0443 %and the surplus is implicitly excreted. 'L' would say S*v <= 0, which +0444 %lets a metabolite be consumed without ever being produced -- free +0445 %uptake, the opposite of excretion, and enough for the MILP to switch a +0446 %reaction on by conjuring its substrates out of nothing. +0447 prob.csense = [repmat('G', 1, length(milpModel.mets)), ... +0448 repmat('E', 1, length(prob.b) - length(milpModel.mets))]; +0449 else +0450 %One character per row: optimizeProb maps csense elementwise for glpk and +0451 %cobra, so a scalar '=' only happens to work on gurobi. +0452 prob.csense = repmat('E', 1, length(prob.b)); +0453 end +0454 +0455 params.intTol = 10^-7; %This value is very important. If set too low +0456 %there is a risk that gurobi fails due to numerical +0457 %issues - this happened for Gurobi v. 10.0 with TestModelL. +0458 %On the other hand, it should not be too large +0459 %either. With this value, fluxes of 10-7 can slip +0460 %through, which should be fine. Another option if +0461 %this becomes a problem is to set NumericFocus=2, +0462 %which makes the solver slower but fixes the issue +0463 %with TestModelL. +0464 +0465 prob.osense = 1; %minimize +0466 +0467 if ~isempty(startVals) +0468 prob.start = startVals; %This doesn't work... +0469 end +0470 +0471 res=optimizeProb(prob,params,verbose); 0472 -0473 floorRow = prob.c'; -0474 tieProb = prob; -0475 tieProb.A = [prob.A; floorRow]; -0476 tieProb.a = tieProb.A; -0477 tieProb.b = [prob.b; primaryObj+tol]; -0478 tieProb.csense = [prob.csense 'L']; -0479 -0480 %An integer objective needs only a cheap absolute-gap proof, not a tiny -0481 %relative one -- mirrors raven-toolbox's own MIPGap=0/MIPGapAbs=0.4 for -0482 %the same two phases. -0483 tieParams = params; -0484 tieParams.MIPGap = 0; -0485 tieParams.MIPGapAbs = 0.4; -0486 -0487 unproven = {}; -0488 -0489 %Phase 2a: fewest "on" removable reactions among the score-optimal solutions. -0490 countObj = zeros(size(prob.c)); -0491 countObj(negCols) = 1; -0492 tieProb.c = countObj; -0493 res2a = optimizeProb(tieProb,tieParams,verbose); -0494 [isFeasible2a, isOptimal2a] = checkSolution(res2a); -0495 if ~isOptimal2a -0496 unproven{end+1} = 'phase2a-parsimony'; -0497 end -0498 if isFeasible2a -0499 kmin = full(countObj'*res2a.full); -0500 -0501 %Phase 2b: among the sparsest, prefer lower reaction ids -- a -0502 %deterministic tie-break independent of solver/seed. -0503 countRow = sparse(1,size(tieProb.A,2)); -0504 countRow(negCols) = 1; -0505 tieProb2b = tieProb; -0506 tieProb2b.A = [tieProb.A; countRow]; -0507 tieProb2b.a = tieProb2b.A; -0508 tieProb2b.b = [tieProb.b; kmin+0.5]; -0509 tieProb2b.csense = [tieProb.csense 'L']; -0510 -0511 [~, sortOrder] = sort(model.rxns(negRxnIdx)); -0512 ranks = zeros(numel(negRxnIdx),1); -0513 ranks(sortOrder) = (1:numel(negRxnIdx))'; -0514 idObj = zeros(size(prob.c)); -0515 idObj(negCols) = ranks; -0516 tieProb2b.c = idObj; -0517 res2b = optimizeProb(tieProb2b,tieParams,verbose); -0518 [isFeasible2b, isOptimal2b] = checkSolution(res2b); -0519 if ~isOptimal2b -0520 unproven{end+1} = 'phase2b-idrank'; -0521 end -0522 if isFeasible2b -0523 %Adopt the tie-resolved solution for the on/off read-out below. -0524 res = res2b; -0525 end -0526 end -0527 if ~isempty(unproven) -0528 EM = ['ftINITInternalAlg tie resolution did not converge (' strjoin(unproven,', ') '): the selection among equally score-optimal solutions is itself an unproven incumbent, so resolveTies has reduced but not removed the run-to-run spread. Raise params.TimeLimit for a proven tie-break.']; -0529 warning('RAVEN:warning', '%s', EM); -0530 end -0531 end -0532 -0533 %get the on/off vals -0534 onoff = ones(nRxnsWithOnOff,1);%standard is on, i.e. all reactions not included in the problem + ess is on -0535 onoff(posIrrevRxns) = res.full(onoffPosIrrev); -0536 onoff(posRevRxns) = res.full(onoffPosRev); -0537 onoff(negIrrevRxns) = res.full(onoffNegIrrev); -0538 onoff(negRevRxns) = res.full(onoffNegRev); -0539 -0540 onoff2 = zeros(nRxnsWithOnOff,1);%standard is off, i.e. all reactions not included in the problem + ess is off -0541 onoff2(posIrrevRxns) = res.full(onoffPosIrrev); -0542 onoff2(posRevRxns) = res.full(onoffPosRev); -0543 onoff2(negIrrevRxns) = res.full(onoffNegIrrev); -0544 onoff2(negRevRxns) = res.full(onoffNegRev); -0545 -0546 -0547 %investigate a bit - this code could be used for fault finding. We have -0548 %this code commented out, but it is definitely worth testing this if for example -0549 %the solver is changed. -0550 %problematic = onoff < 0.99 & onoff > 0.01 & (rxnScores ~= 0).'; -0551 %if sum(problematic) > 0 -0552 % disp("There are reactions that are in between on and off in the MILP. This may be due to the MILP parameter settings, the tolerances may be too large.") -0553 % disp(["No problematic reactions: ", num2str(sum(problematic))]) -0554 %end -0555 -0556 %so, it is only positive that are problematic. Likely because the 0.1 is too large. Test to change to 0.01 -0557 %unique(onoff(onoff < 0.99 & onoff > 0.01)) -0558 -0559 %Get all reactions used in the irreversible model -0560 %The reaction score check is for metabolomics - we add those reactions as -0561 %positive even though the score is 0. And, we do not want them included in the -0562 %results. -0563 deletedRxns=(onoff < 0.5).' & (rxnScores ~= 0).'; -0564 turnedOnRxns=(onoff2 >= 0.5).' & (rxnScores ~= 0).'; -0565 -0566 fluxes = res.full(1:nRxns); -0567 -0568 %extract the met data -0569 metProduction = logical(round(res.full(metVarInd))); -0570 -0571 end

    +0473 +0474 if ~checkSolution(res) +0475 if res.hitTimeLimit +0476 EM='Time limit reached without finding a solution. Try increasing the TimeLimit parameter.'; +0477 else +0478 EM='The problem is infeasible'; +0479 end +0480 error('RAVEN:badInput', '%s', EM); +0481 end +0482 +0483 if resolveTies && (nNegIrrev + nNegRev) > 0 && ~res.hitTimeLimit +0484 %Skipped when the primary solve stopped at the time limit: the floor +0485 %constraint would pin an unproven incumbent, so the phases cannot select +0486 %among true optima. +0487 %This MILP is highly degenerate: many reaction subsets reach the same +0488 %score optimum, and the solver returns an arbitrary one -- reproducible +0489 %for a fixed solver build + Seed, but fragile to a solver version bump +0490 %(raven-gecko-parity#104). This runs a lexicographic phase 2, holding +0491 %the score objective at its optimum with a floor constraint: first +0492 %minimise the count of "on" removable reactions (the sparsest optimum), +0493 %then, among the sparsest, minimise their summed reaction-id rank +0494 %(prefer lower ids). Only the negative-score "on" variables (onoffNegIrrev, +0495 %onoffNegRev) are a genuine tie to resolve: positive-score ones are +0496 %continuous, pinned near 1 by the score objective rather than a free +0497 %binary choice. Ports raven-toolbox's _resolve_ties (raven-toolbox#114). +0498 negCols = [onoffNegIrrev, onoffNegRev]; +0499 negRxnIdx = [negIrrevRxns; negRevRxns]; +0500 primaryObj = full(prob.c' * res.full); +0501 tol = max(abs(primaryObj)*1e-7, 1e-7); +0502 +0503 floorRow = prob.c'; +0504 tieProb = prob; +0505 tieProb.A = [prob.A; floorRow]; +0506 tieProb.a = tieProb.A; +0507 tieProb.b = [prob.b; primaryObj+tol]; +0508 tieProb.csense = [prob.csense 'L']; +0509 +0510 %An integer objective needs only a cheap absolute-gap proof, not a tiny +0511 %relative one -- mirrors raven-toolbox's own MIPGap=0/MIPGapAbs=0.4 for +0512 %the same two phases. +0513 tieParams = params; +0514 tieParams.MIPGap = 0; +0515 tieParams.MIPGapAbs = 0.4; +0516 %Phases 2a and 2b get half of the time limit each, as in raven-toolbox. +0517 if isfield(tieParams,'TimeLimit') +0518 tieParams.TimeLimit = max(1,floor(tieParams.TimeLimit*0.5)); +0519 end +0520 +0521 unproven = {}; +0522 +0523 %Phase 2a: fewest "on" removable reactions among the score-optimal solutions. +0524 countObj = zeros(size(prob.c)); +0525 countObj(negCols) = 1; +0526 tieProb.c = countObj; +0527 res2a = optimizeProb(tieProb,tieParams,verbose); +0528 [isFeasible2a, isOptimal2a] = checkSolution(res2a); +0529 if ~isOptimal2a +0530 unproven{end+1} = 'phase2a-parsimony'; +0531 end +0532 if isFeasible2a +0533 kmin = full(countObj'*res2a.full); +0534 +0535 %Phase 2b: among the sparsest, prefer lower reaction ids -- a +0536 %deterministic tie-break independent of solver/seed. +0537 countRow = sparse(1,size(tieProb.A,2)); +0538 countRow(negCols) = 1; +0539 tieProb2b = tieProb; +0540 tieProb2b.A = [tieProb.A; countRow]; +0541 tieProb2b.a = tieProb2b.A; +0542 tieProb2b.b = [tieProb.b; kmin+0.5]; +0543 tieProb2b.csense = [tieProb.csense 'L']; +0544 +0545 [~, sortOrder] = sort(rankKeys(negRxnIdx)); +0546 ranks = zeros(numel(negRxnIdx),1); +0547 ranks(sortOrder) = (1:numel(negRxnIdx))'; +0548 idObj = zeros(size(prob.c)); +0549 idObj(negCols) = ranks; +0550 tieProb2b.c = idObj; +0551 res2b = optimizeProb(tieProb2b,tieParams,verbose); +0552 [isFeasible2b, isOptimal2b] = checkSolution(res2b); +0553 if ~isOptimal2b +0554 unproven{end+1} = 'phase2b-idrank'; +0555 end +0556 if isFeasible2b +0557 %Adopt the tie-resolved solution for the on/off read-out below. +0558 res = res2b; +0559 +0560 %Phases 2c and 2d change the fluxes, not the on/off state; the next +0561 %ftINIT step reads the fluxes, and pinFluxes is false when no step +0562 %follows. +0563 if pinFluxes +0564 %Phase 2c: the least total absolute flux for this on/off state. +0565 %The next step forces each turned-on reaction in the direction +0566 %of its flux, at min(0.99*|flux|,0.1), so the flux read-out +0567 %comes from a minimal-flux solution instead of an arbitrary +0568 %vertex. A binary on/off variable is fixed at its value; a +0569 %continuous one that is on keeps at least its value, one that +0570 %is off at most its value. Directions of reversible scored +0571 %reactions stay free, so this is still a MILP. +0572 %It stops at a relative or absolute gap of 1e-6, as raven-toolbox's +0573 %phase 2c. +0574 nCols = numel(prob.c); +0575 minProb = tieProb2b; +0576 if isfield(minProb,'start') +0577 minProb = rmfield(minProb,'start'); +0578 end +0579 x = res2b.full; +0580 negVal = round(x(negCols)); +0581 minProb.lb(negCols) = negVal; +0582 minProb.ub(negCols) = negVal; +0583 posCols = [onoffPosIrrev, onoffPosRev]; +0584 posVal = min(max(x(posCols),0),1); +0585 posOn = posVal >= 0.5; +0586 minProb.lb(posCols(posOn)) = posVal(posOn); +0587 minProb.ub(posCols(~posOn)) = posVal(~posOn); +0588 %|v| is v for a forward-only flux, -v for a backward-only one, +0589 %and an added t >= v, t >= -v otherwise. +0590 fwdOnly = minProb.lb(1:nRxns) >= 0; +0591 bwdOnly = ~fwdOnly & minProb.ub(1:nRxns) <= 0; +0592 tRxns = find(~fwdOnly & ~bwdOnly); +0593 nT = numel(tRxns); +0594 selV = sparse(1:nT, tRxns, 1, nT, nCols); +0595 minProb.A = [minProb.A sparse(size(minProb.A,1),nT); -selV speye(nT); selV speye(nT)]; +0596 minProb.a = minProb.A; +0597 minProb.b = [minProb.b; zeros(2*nT,1)]; +0598 minProb.csense = [minProb.csense repmat('G',1,2*nT)]; +0599 minProb.lb = [minProb.lb; zeros(nT,1)]; +0600 minProb.ub = [minProb.ub; inf(nT,1)]; +0601 minProb.vartype = [minProb.vartype repmat('C',1,nT)]; +0602 minProb.c = [zeros(nCols,1); ones(nT,1)]; +0603 minProb.c(fwdOnly) = 1; +0604 minProb.c(bwdOnly) = -1; +0605 minParams = params; +0606 minParams.MIPGap = 1e-6; +0607 minParams.MIPGapAbs = 1e-6; +0608 res2c = optimizeProb(minProb,minParams,verbose); +0609 [isFeasible2c, isOptimal2c] = checkSolution(res2c); +0610 if ~isOptimal2c +0611 unproven{end+1} = 'phase2c-minflux'; +0612 end +0613 if isFeasible2c +0614 %Only the fluxes change; the status stays that of phase 2b. +0615 res.full = res2c.full(1:nCols); +0616 +0617 %Phase 2d: the least total flux can leave a direction open +0618 %when two directions carry the same total flux, for example +0619 %a pair of reversible reactions closing a loop either way. +0620 %Keep the on/off state of phase 2c, hold the total absolute +0621 %flux at most at its phase-2c value plus +0622 %max(1e-7*total, 1e-6), and minimise the backward flux +0623 %max(0,-v) of the reactions whose flux can take either sign, +0624 %weighted by id rank from the end: sorted by rankKeys, the +0625 %first of n reactions gets weight n and the last 1, so a +0626 %reaction with a lower id prefers its forward direction. +0627 if nT > 0 +0628 total = minProb.c' * res2c.full; +0629 dirProb = minProb; +0630 nCols2c = numel(minProb.c); +0631 [~, ord] = sort(rankKeys(tRxns)); +0632 w = zeros(nT,1); +0633 w(ord) = (nT:-1:1)'; +0634 dirProb.A = [minProb.A sparse(size(minProb.A,1),nT); ... +0635 minProb.c' sparse(1,nT); ... +0636 selV sparse(nT,nT) speye(nT)]; +0637 dirProb.a = dirProb.A; +0638 dirProb.b = [minProb.b; total + max(abs(total)*1e-7, 1e-6); zeros(nT,1)]; +0639 dirProb.csense = [minProb.csense 'L' repmat('G',1,nT)]; +0640 dirProb.lb = [minProb.lb; zeros(nT,1)]; +0641 dirProb.ub = [minProb.ub; inf(nT,1)]; +0642 dirProb.vartype = [minProb.vartype repmat('C',1,nT)]; +0643 dirProb.c = [zeros(nCols2c,1); w]; +0644 %The phase-2c solution is the MIP start: the tight cap on +0645 %the total flux leaves the solver's heuristics little room +0646 %to find a first solution. +0647 dirProb.start = [res2c.full; max(0, -res2c.full(tRxns))]; +0648 res2d = optimizeProb(dirProb,minParams,verbose); +0649 [isFeasible2d, isOptimal2d] = checkSolution(res2d); +0650 if ~isOptimal2d +0651 unproven{end+1} = 'phase2d-direction'; +0652 end +0653 if isFeasible2d +0654 res.full = res2d.full(1:nCols); +0655 end +0656 end +0657 end +0658 end +0659 end +0660 end +0661 if ~isempty(unproven) +0662 EM = ['ftINITInternalAlg tie resolution did not converge (' strjoin(unproven,', ') '): the selection among equally score-optimal solutions is itself an unproven incumbent, so resolveTies has reduced but not removed the run-to-run spread. Raise params.TimeLimit for a proven tie-break.']; +0663 warning('RAVEN:warning', '%s', EM); +0664 end +0665 end +0666 +0667 %get the on/off vals +0668 onoff = ones(nRxnsWithOnOff,1);%standard is on, i.e. all reactions not included in the problem + ess is on +0669 onoff(posIrrevRxns) = res.full(onoffPosIrrev); +0670 onoff(posRevRxns) = res.full(onoffPosRev); +0671 onoff(negIrrevRxns) = res.full(onoffNegIrrev); +0672 onoff(negRevRxns) = res.full(onoffNegRev); +0673 +0674 onoff2 = zeros(nRxnsWithOnOff,1);%standard is off, i.e. all reactions not included in the problem + ess is off +0675 onoff2(posIrrevRxns) = res.full(onoffPosIrrev); +0676 onoff2(posRevRxns) = res.full(onoffPosRev); +0677 onoff2(negIrrevRxns) = res.full(onoffNegIrrev); +0678 onoff2(negRevRxns) = res.full(onoffNegRev); +0679 +0680 +0681 %investigate a bit - this code could be used for fault finding. We have +0682 %this code commented out, but it is definitely worth testing this if for example +0683 %the solver is changed. +0684 %problematic = onoff < 0.99 & onoff > 0.01 & (rxnScores ~= 0).'; +0685 %if sum(problematic) > 0 +0686 % disp("There are reactions that are in between on and off in the MILP. This may be due to the MILP parameter settings, the tolerances may be too large.") +0687 % disp(["No problematic reactions: ", num2str(sum(problematic))]) +0688 %end +0689 +0690 %so, it is only positive that are problematic. Likely because the 0.1 is too large. Test to change to 0.01 +0691 %unique(onoff(onoff < 0.99 & onoff > 0.01)) +0692 +0693 %Get all reactions used in the irreversible model +0694 %The reaction score check is for metabolomics - we add those reactions as +0695 %positive even though the score is 0. And, we do not want them included in the +0696 %results. +0697 deletedRxns=(onoff < 0.5).' & (rxnScores ~= 0).'; +0698 turnedOnRxns=(onoff2 >= 0.5).' & (rxnScores ~= 0).'; +0699 +0700 fluxes = res.full(1:nRxns); +0701 +0702 %extract the met data +0703 metProduction = logical(round(res.full(metVarInd))); +0704 +0705 end

    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/INIT/mergeLinear.html b/docs/html/INIT/mergeLinear.html index b1dbb106..03b32a75 100644 --- a/docs/html/INIT/mergeLinear.html +++ b/docs/html/INIT/mergeLinear.html @@ -41,7 +41,8 @@

    DESCRIPTION ^SOURCE CODE ^% reducedModel an updated model structure 0014 % origRxnIds Cell array: original rxn ids, used together with the groupIds variable 0015 % groupIds Vector of ids: merged rxns have the same id, same length as origRxnIds -0016 % 0 means a reaction was not merged. -0017 % reversedRxns Vector of booleans, saying for each rxn if it is reversed in the combined rxns. -0018 % -0019 % Usage: [reducedModel,origRxnIds,groupIds]=mergeLinear(model,noMergeRxns) -0020 +0016 % 0 means a reaction was not merged. The groups are numbered 1, 2, ... +0017 % in the order of their first reaction in origRxnIds. +0018 % reversedRxns Vector of booleans, saying for each rxn if it is reversed in the combined rxns. +0019 % +0020 % Usage: [reducedModel,origRxnIds,groupIds]=mergeLinear(model,noMergeRxns) 0021 -0022 reducedModel=model; -0023 +0022 +0023 reducedModel=model; 0024 -0025 reducedModel.genes={}; -0026 reducedModel.rxnGeneMat=sparse(numel(reducedModel.rxns),0); -0027 reducedModel.grRules(:)={''}; -0028 -0029 if isfield(reducedModel,'geneShortNames') -0030 reducedModel.geneShortNames={}; -0031 end -0032 if isfield(reducedModel,'proteins') -0033 reducedModel.proteins={}; -0034 end -0035 if isfield(reducedModel,'geneMiriams') -0036 reducedModel.geneMiriams={}; -0037 end -0038 if isfield(reducedModel,'geneComps') -0039 reducedModel.geneComps=[]; -0040 end -0041 -0042 nextGroupId = 1; -0043 origRxnIds = reducedModel.rxns; -0044 groupIds = zeros(numel(reducedModel.rxns),1); -0045 reversedRxns = false(numel(reducedModel.rxns),1); -0046 %rxn id -> original index lookup, built once rather than re-scanning all -0047 %of origRxnIds (via strcmp) for every merge candidate examined below. -0048 origRxnIdMap = containers.Map(origRxnIds,num2cell(1:numel(origRxnIds))); -0049 -0050 %Loop through and iteratively group linear reactions -0051 while 1 -0052 %Get the banned reaction indexes. Note that the indexes will change -0053 %in each iteration, but the names will not as they won't be merged -0054 %with any other reaction -0055 bannedIndexes=getIndexes(reducedModel,noMergeRxns,'rxns'); -0056 -0057 %Select all metabolites that are only present as reactants/products -0058 %in one reaction -0059 twoNonZero = find(sum(reducedModel.S ~= 0, 2) == 2); -0060 -0061 mergedSome=false; -0062 -0063 %Loop through each of them and see if the reactions should be -0064 %merged -0065 for i=1:numel(twoNonZero) -0066 involvedRxns=find(reducedModel.S(twoNonZero(i),:)); -0067 %Check that we can have one positive and one negative -0068 pos = sum(reducedModel.S(twoNonZero(i),involvedRxns).' > 0 | reducedModel.rev(involvedRxns)); -0069 neg = sum(reducedModel.S(twoNonZero(i),involvedRxns).' < 0 | reducedModel.rev(involvedRxns)); -0070 -0071 -0072 %Check so that one or both of the reactions haven't been merged -0073 %already -0074 if numel(involvedRxns)==2 && isempty(intersect(bannedIndexes,involvedRxns)) && pos >= 1 && neg >= 1 -0075 %first, take care of a special case: If the first reaction is producing the metabolite and if it is reversible, -0076 %and the second is also producing it and is not reversible, change the order - the code below will not work otherwise -0077 if reducedModel.rev(involvedRxns(1)) && (~reducedModel.rev(involvedRxns(2))) && ... -0078 (reducedModel.S(twoNonZero(i),involvedRxns(1)) > 0) && (reducedModel.S(twoNonZero(i),involvedRxns(2)) > 0) -0079 involvedRxns = flip(involvedRxns); -0080 end -0081 -0082 %first make sure the first reaction is producing the metabolite -0083 if reducedModel.S(twoNonZero(i),involvedRxns(1)) < 0 -0084 %it is not producing the metabolite - fix that -0085 %first choice: use the second reaction as producer if it is producing -0086 if reducedModel.S(twoNonZero(i),involvedRxns(2)) > 0 -0087 involvedRxns = flip(involvedRxns);%make the second the first -0088 else -0089 %now we know that the second reaction is not producing, so we can safely try to make the first a producer -0090 if reducedModel.rev(involvedRxns(1)) == 1 -0091 [reducedModel,reversedRxns] = flipRxn(reducedModel, involvedRxns(1), groupIds, reversedRxns); -0092 else %ok, finally try to flip the second reaction -0093 if reducedModel.rev(involvedRxns(2)) == 1 -0094 [reducedModel,reversedRxns] = flipRxn(reducedModel, involvedRxns(2), groupIds, reversedRxns); -0095 involvedRxns = flip(involvedRxns);%make the second the first -0096 else -0097 error('We should never end up here!'); -0098 end -0099 end -0100 end -0101 end -0102 %Now, make sure the second rxn is a consumer -0103 if reducedModel.S(twoNonZero(i),involvedRxns(2)) > 0 -0104 if reducedModel.rev(involvedRxns(2)) == 1 -0105 [reducedModel,reversedRxns] = flipRxn(reducedModel, involvedRxns(2), groupIds, reversedRxns); -0106 else -0107 error('We should never end up here!'); -0108 end -0109 end -0110 -0111 %Calculate how many times the second reaction has to be -0112 %multiplied before being merged with the first -0113 stoichRatio=abs(reducedModel.S(twoNonZero(i),involvedRxns(1))/reducedModel.S(twoNonZero(i),involvedRxns(2))); -0114 -0115 %Add the second to the first -0116 reducedModel.S(:,involvedRxns(1))=reducedModel.S(:,involvedRxns(1))+reducedModel.S(:,involvedRxns(2))*stoichRatio; -0117 -0118 %Clear the second reaction -0119 reducedModel.S(:,involvedRxns(2))=0; -0120 -0121 %This is to prevent numerical issues. It should be 0 -0122 %already -0123 reducedModel.S(twoNonZero(i),involvedRxns(1))=0; -0124 -0125 %At this point the second reaction is certain to be deleted -0126 %in a later step and can therefore be ignored -0127 -0128 %Recalculate the bounds for the new reaction. This can be -0129 %problematic since the scale of the bounds may change -0130 %dramatically. Let the most constraining reaction determine -0131 %the new bound -0132 lb1=reducedModel.lb(involvedRxns(1)); -0133 lb2=reducedModel.lb(involvedRxns(2)); -0134 ub1=reducedModel.ub(involvedRxns(1)); -0135 ub2=reducedModel.ub(involvedRxns(2)); -0136 -0137 if lb2~=-inf -0138 reducedModel.lb(involvedRxns(1))=max(lb1,lb2/stoichRatio); -0139 end -0140 if ub2~=inf -0141 reducedModel.ub(involvedRxns(1))=min(ub1,ub2/stoichRatio); -0142 end -0143 -0144 %take care of the .rev flag - it could be that the combined rxn changes from rev to irrev -0145 reducedModel.rev(involvedRxns(1)) = reducedModel.rev(involvedRxns(1))*reducedModel.rev(involvedRxns(2));%this is a way to do an "and" operation with 0 and 1 numbers -0146 -0147 %Then recalculate the objective coefficient. The resulting -0148 %coefficient is the weighted sum of the previous -0149 reducedModel.c(involvedRxns(1))=reducedModel.c(involvedRxns(1))+reducedModel.c(involvedRxns(2))*stoichRatio; -0150 -0151 %store which reactions that have been merged -0152 rxnInd1 = origRxnIdMap(reducedModel.rxns{involvedRxns(1)}); -0153 rxnInd2 = origRxnIdMap(reducedModel.rxns{involvedRxns(2)}); -0154 grpId = max(groupIds(rxnInd1),groupIds(rxnInd2)); -0155 if grpId == 0 -0156 grpId = nextGroupId; -0157 nextGroupId = nextGroupId + 1; -0158 end -0159 -0160 if groupIds(rxnInd1) ~= grpId -0161 if groupIds(rxnInd1) == 0 -0162 %not merged before, just set the group id -0163 groupIds(rxnInd1) = grpId; -0164 else -0165 %merged before - all rxns with the same group id should be changed -0166 groupIds(groupIds == groupIds(rxnInd1)) = grpId; -0167 end -0168 end -0169 if groupIds(rxnInd2) ~= grpId -0170 if groupIds(rxnInd2) == 0 -0171 %not merged before, just set the group id -0172 groupIds(rxnInd2) = grpId; -0173 else -0174 %merged before - all rxns with the same group id should be changed -0175 groupIds(groupIds == groupIds(rxnInd2)) = grpId; -0176 end -0177 end -0178 -0179 %Iterate again -0180 mergedSome=true; -0181 end -0182 end -0183 -0184 %All possible reactions merged -0185 if mergedSome==false -0186 break; -0187 end -0188 -0189 %Now delete all reactions that involve no metabolites -0190 I=find(sum(reducedModel.S~=0,1)==0); -0191 -0192 %Remove reactions -0193 reducedModel=removeReactions(reducedModel,I); -0194 -0195 %Remove metabolites -0196 notInUse=sum(reducedModel.S~=0,2)==0; -0197 reducedModel=removeMets(reducedModel,notInUse); -0198 end -0199 -0200 function [model1,reversedRxns1] = flipRxn(model1, rxnInd, groupIds1, reversedRxns1) -0201 model1.S(:,rxnInd) = model1.S(:,rxnInd)*-1; -0202 %swap the bounds -0203 ub = model1.ub(rxnInd); -0204 model1.ub(rxnInd) = -model1.lb(rxnInd); -0205 model1.lb(rxnInd) = -ub; -0206 %flip the objective -0207 model1.c(rxnInd) = -model1.c(rxnInd); -0208 -0209 %now take care of the reversedRxns - if this is a group, reverse all of the -0210 %reactions in the group in the reversedRxns index - they will all be reversed at the -0211 %same time since they are the same rxn. -0212 rxnIndices = rxnInd; -0213 if groupIds1(rxnInd) > 0 -0214 rxnIndices = find(groupIds1 == groupIds1(rxnInd)); -0215 end -0216 reversedRxns1(rxnIndices) = ~reversedRxns1(rxnIndices); -0217 end -0218 end

    +0025 +0026 reducedModel.genes={}; +0027 reducedModel.rxnGeneMat=sparse(numel(reducedModel.rxns),0); +0028 reducedModel.grRules(:)={''}; +0029 +0030 if isfield(reducedModel,'geneShortNames') +0031 reducedModel.geneShortNames={}; +0032 end +0033 if isfield(reducedModel,'proteins') +0034 reducedModel.proteins={}; +0035 end +0036 if isfield(reducedModel,'geneMiriams') +0037 reducedModel.geneMiriams={}; +0038 end +0039 if isfield(reducedModel,'geneComps') +0040 reducedModel.geneComps=[]; +0041 end +0042 +0043 nextGroupId = 1; +0044 origRxnIds = reducedModel.rxns; +0045 groupIds = zeros(numel(reducedModel.rxns),1); +0046 reversedRxns = false(numel(reducedModel.rxns),1); +0047 %rxn id -> original index lookup, built once rather than re-scanning all +0048 %of origRxnIds (via strcmp) for every merge candidate examined below. +0049 origRxnIdMap = containers.Map(origRxnIds,num2cell(1:numel(origRxnIds))); +0050 +0051 %Loop through and iteratively group linear reactions +0052 while 1 +0053 %Get the banned reaction indexes. Note that the indexes will change +0054 %in each iteration, but the names will not as they won't be merged +0055 %with any other reaction +0056 bannedIndexes=getIndexes(reducedModel,noMergeRxns,'rxns'); +0057 +0058 %Select all metabolites that are only present as reactants/products +0059 %in one reaction +0060 twoNonZero = find(sum(reducedModel.S ~= 0, 2) == 2); +0061 +0062 mergedSome=false; +0063 +0064 %Loop through each of them and see if the reactions should be +0065 %merged +0066 for i=1:numel(twoNonZero) +0067 involvedRxns=find(reducedModel.S(twoNonZero(i),:)); +0068 %Check that we can have one positive and one negative +0069 pos = sum(reducedModel.S(twoNonZero(i),involvedRxns).' > 0 | reducedModel.rev(involvedRxns)); +0070 neg = sum(reducedModel.S(twoNonZero(i),involvedRxns).' < 0 | reducedModel.rev(involvedRxns)); +0071 +0072 +0073 %Check so that one or both of the reactions haven't been merged +0074 %already +0075 if numel(involvedRxns)==2 && isempty(intersect(bannedIndexes,involvedRxns)) && pos >= 1 && neg >= 1 +0076 %first, take care of a special case: If the first reaction is producing the metabolite and if it is reversible, +0077 %and the second is also producing it and is not reversible, change the order - the code below will not work otherwise +0078 if reducedModel.rev(involvedRxns(1)) && (~reducedModel.rev(involvedRxns(2))) && ... +0079 (reducedModel.S(twoNonZero(i),involvedRxns(1)) > 0) && (reducedModel.S(twoNonZero(i),involvedRxns(2)) > 0) +0080 involvedRxns = flip(involvedRxns); +0081 end +0082 +0083 %first make sure the first reaction is producing the metabolite +0084 if reducedModel.S(twoNonZero(i),involvedRxns(1)) < 0 +0085 %it is not producing the metabolite - fix that +0086 %first choice: use the second reaction as producer if it is producing +0087 if reducedModel.S(twoNonZero(i),involvedRxns(2)) > 0 +0088 involvedRxns = flip(involvedRxns);%make the second the first +0089 else +0090 %now we know that the second reaction is not producing, so we can safely try to make the first a producer +0091 if reducedModel.rev(involvedRxns(1)) == 1 +0092 [reducedModel,reversedRxns] = flipRxn(reducedModel, involvedRxns(1), groupIds, reversedRxns); +0093 else %ok, finally try to flip the second reaction +0094 if reducedModel.rev(involvedRxns(2)) == 1 +0095 [reducedModel,reversedRxns] = flipRxn(reducedModel, involvedRxns(2), groupIds, reversedRxns); +0096 involvedRxns = flip(involvedRxns);%make the second the first +0097 else +0098 error('We should never end up here!'); +0099 end +0100 end +0101 end +0102 end +0103 %Now, make sure the second rxn is a consumer +0104 if reducedModel.S(twoNonZero(i),involvedRxns(2)) > 0 +0105 if reducedModel.rev(involvedRxns(2)) == 1 +0106 [reducedModel,reversedRxns] = flipRxn(reducedModel, involvedRxns(2), groupIds, reversedRxns); +0107 else +0108 error('We should never end up here!'); +0109 end +0110 end +0111 +0112 %Calculate how many times the second reaction has to be +0113 %multiplied before being merged with the first +0114 stoichRatio=abs(reducedModel.S(twoNonZero(i),involvedRxns(1))/reducedModel.S(twoNonZero(i),involvedRxns(2))); +0115 +0116 %Add the second to the first +0117 reducedModel.S(:,involvedRxns(1))=reducedModel.S(:,involvedRxns(1))+reducedModel.S(:,involvedRxns(2))*stoichRatio; +0118 +0119 %Clear the second reaction +0120 reducedModel.S(:,involvedRxns(2))=0; +0121 +0122 %This is to prevent numerical issues. It should be 0 +0123 %already +0124 reducedModel.S(twoNonZero(i),involvedRxns(1))=0; +0125 +0126 %At this point the second reaction is certain to be deleted +0127 %in a later step and can therefore be ignored +0128 +0129 %Recalculate the bounds for the new reaction. This can be +0130 %problematic since the scale of the bounds may change +0131 %dramatically. Let the most constraining reaction determine +0132 %the new bound +0133 lb1=reducedModel.lb(involvedRxns(1)); +0134 lb2=reducedModel.lb(involvedRxns(2)); +0135 ub1=reducedModel.ub(involvedRxns(1)); +0136 ub2=reducedModel.ub(involvedRxns(2)); +0137 +0138 if lb2~=-inf +0139 reducedModel.lb(involvedRxns(1))=max(lb1,lb2/stoichRatio); +0140 end +0141 if ub2~=inf +0142 reducedModel.ub(involvedRxns(1))=min(ub1,ub2/stoichRatio); +0143 end +0144 +0145 %take care of the .rev flag - it could be that the combined rxn changes from rev to irrev +0146 reducedModel.rev(involvedRxns(1)) = reducedModel.rev(involvedRxns(1))*reducedModel.rev(involvedRxns(2));%this is a way to do an "and" operation with 0 and 1 numbers +0147 +0148 %Then recalculate the objective coefficient. The resulting +0149 %coefficient is the weighted sum of the previous +0150 reducedModel.c(involvedRxns(1))=reducedModel.c(involvedRxns(1))+reducedModel.c(involvedRxns(2))*stoichRatio; +0151 +0152 %store which reactions that have been merged +0153 rxnInd1 = origRxnIdMap(reducedModel.rxns{involvedRxns(1)}); +0154 rxnInd2 = origRxnIdMap(reducedModel.rxns{involvedRxns(2)}); +0155 grpId = max(groupIds(rxnInd1),groupIds(rxnInd2)); +0156 if grpId == 0 +0157 grpId = nextGroupId; +0158 nextGroupId = nextGroupId + 1; +0159 end +0160 +0161 if groupIds(rxnInd1) ~= grpId +0162 if groupIds(rxnInd1) == 0 +0163 %not merged before, just set the group id +0164 groupIds(rxnInd1) = grpId; +0165 else +0166 %merged before - all rxns with the same group id should be changed +0167 groupIds(groupIds == groupIds(rxnInd1)) = grpId; +0168 end +0169 end +0170 if groupIds(rxnInd2) ~= grpId +0171 if groupIds(rxnInd2) == 0 +0172 %not merged before, just set the group id +0173 groupIds(rxnInd2) = grpId; +0174 else +0175 %merged before - all rxns with the same group id should be changed +0176 groupIds(groupIds == groupIds(rxnInd2)) = grpId; +0177 end +0178 end +0179 +0180 %Iterate again +0181 mergedSome=true; +0182 end +0183 end +0184 +0185 %All possible reactions merged +0186 if mergedSome==false +0187 break; +0188 end +0189 +0190 %Now delete all reactions that involve no metabolites +0191 I=find(sum(reducedModel.S~=0,1)==0); +0192 +0193 %Remove reactions +0194 reducedModel=removeReactions(reducedModel,I); +0195 +0196 %Remove metabolites +0197 notInUse=sum(reducedModel.S~=0,2)==0; +0198 reducedModel=removeMets(reducedModel,notInUse); +0199 end +0200 +0201 %Number the groups in the order of their first reaction in origRxnIds, so +0202 %the numbers do not depend on the order in which the reactions were merged +0203 merged=groupIds~=0; +0204 [~,~,groupIds(merged)]=unique(groupIds(merged),'stable'); +0205 +0206 function [model1,reversedRxns1] = flipRxn(model1, rxnInd, groupIds1, reversedRxns1) +0207 model1.S(:,rxnInd) = model1.S(:,rxnInd)*-1; +0208 %swap the bounds +0209 ub = model1.ub(rxnInd); +0210 model1.ub(rxnInd) = -model1.lb(rxnInd); +0211 model1.lb(rxnInd) = -ub; +0212 %flip the objective +0213 model1.c(rxnInd) = -model1.c(rxnInd); +0214 +0215 %now take care of the reversedRxns - if this is a group, reverse all of the +0216 %reactions in the group in the reversedRxns index - they will all be reversed at the +0217 %same time since they are the same rxn. rxnInd indexes model1, which loses +0218 %reactions after each pass, while groupIds1 and reversedRxns1 follow +0219 %origRxnIds, so the reaction is looked up by its id. +0220 origInd = origRxnIdMap(model1.rxns{rxnInd}); +0221 rxnIndices = origInd; +0222 if groupIds1(origInd) > 0 +0223 rxnIndices = find(groupIds1 == groupIds1(origInd)); +0224 end +0225 reversedRxns1(rxnIndices) = ~reversedRxns1(rxnIndices); +0226 end +0227 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/gapfilling/gapFillTopological.html b/docs/html/gapfilling/gapFillTopological.html index 978acda2..de4ecdbf 100644 --- a/docs/html/gapfilling/gapFillTopological.html +++ b/docs/html/gapfilling/gapFillTopological.html @@ -62,8 +62,11 @@

    DESCRIPTION ^SOURCE CODE ^% metabolites of every exchange reaction whose bounds let it supply the 0035 % metabolite, i.e. getExchangeRxns 'uptake' plus 'reverse'. 0036 % 'targets' (default []) -0037 % Metabolite IDs (cell array) that should be produced. Default: -0038 % substrates of the objective (biomass) reaction. -0039 % 'verbose' (default true) -0040 % print a summary of the results. -0041 % -0042 % Returns -0043 % ------- -0044 % result : struct -0045 % .reachableMets — logical vector (length nMets) marking producible mets -0046 % .blockedMets — cell array of metabolite IDs that cannot be reached -0047 % .candidateRxns — n-by-1 cell array of universal rxn ID lists, one -0048 % cell per blocked metabolite (same order as blockedMets) -0049 % .pruningFraction — fraction of universal reactions pruned (not needed -0050 % for any blocked metabolite) -0051 % -0052 % See also -0053 % -------- -0054 % gapFillFastLP, gapFillMILP, fillGaps -0055 -0056 % ---- Parse optional arguments ---- -0057 p = inputParser(); -0058 addParameter(p, 'seeds', [], @(x) isempty(x) || iscell(x)); -0059 addParameter(p, 'targets', [], @(x) isempty(x) || iscell(x)); -0060 addParameter(p, 'verbose', true, @islogical); -0061 parse(p, varargin{:}); -0062 seeds = p.Results.seeds; -0063 targets = p.Results.targets; -0064 verbose = p.Results.verbose; -0065 -0066 nMets = numel(model.mets); -0067 nRxns = numel(model.rxns); +0037 % Metabolite IDs (cell array) that should be produced. Default: every +0038 % metabolite of the objective (biomass) reaction, substrates and +0039 % products; the first one when there are several objective reactions. +0040 % Without targets and without an objective reaction the function raises +0041 % RAVEN:badInput. +0042 % 'verbose' (default true) +0043 % print a summary of the results. +0044 % +0045 % Returns +0046 % ------- +0047 % result : struct +0048 % .reachableMets — logical vector (length nMets) marking producible mets +0049 % .blockedMets — cell array of metabolite IDs that cannot be reached +0050 % .candidateRxns — n-by-1 cell array of universal rxn ID lists, one +0051 % cell per blocked metabolite (same order as blockedMets) +0052 % .pruningFraction — fraction of universal reactions pruned (not needed +0053 % for any blocked metabolite) +0054 % +0055 % See also +0056 % -------- +0057 % gapFillFastLP, gapFillMILP, fillGaps +0058 +0059 % ---- Parse optional arguments ---- +0060 p = inputParser(); +0061 addParameter(p, 'seeds', [], @(x) isempty(x) || iscell(x)); +0062 addParameter(p, 'targets', [], @(x) isempty(x) || iscell(x)); +0063 addParameter(p, 'verbose', true, @islogical); +0064 parse(p, varargin{:}); +0065 seeds = p.Results.seeds; +0066 targets = p.Results.targets; +0067 verbose = p.Results.verbose; 0068 -0069 % ---- Identify seed metabolites ---- -0070 if isempty(seeds) -0071 % Default: metabolites that an exchange reaction can supply. Ask -0072 % getExchangeRxns rather than testing lb < 0 here: RAVEN writes an uptake -0073 % exchange either as "=> met" (addExchangeRxns 'in', so lb = 0 and a -0074 % positive flux supplies the metabolite) or as "met =>" with lb < 0. Only -0075 % the second has lb < 0, so a hand-rolled lb < 0 test silently misses -0076 % every 'in' exchange. -0077 % -0078 % 'uptake' alone is not enough either: it means the bounds allow *only* -0079 % uptake, so a reversible exchange ("met <=>") is classified 'reverse' -0080 % even though it supplies the metabolite just as well. The union of the -0081 % two is exactly "forward flux produces it, or reverse flux does", which -0082 % is what analyse_topology's lower_bound < 0 test means under cobra's -0083 % convention, where every exchange is written "met <=>". -0084 [~, onlyUptakeIdx] = getExchangeRxns(model, 'uptake'); -0085 [~, reversibleIdx] = getExchangeRxns(model, 'reverse'); -0086 uptakeIdx = union(onlyUptakeIdx, reversibleIdx); -0087 if isempty(uptakeIdx) -0088 warning(['gapFillTopological: no uptake exchange reactions found. ' ... -0089 'Provide seeds manually via the ''seeds'' option.']); -0090 seedMetIdx = []; -0091 else -0092 [seedRows, ~] = find(model.S(:, uptakeIdx) ~= 0); -0093 seedMetIdx = unique(seedRows); -0094 end -0095 else -0096 [~, seedMetIdx] = ismember(seeds, model.mets); -0097 seedMetIdx = seedMetIdx(seedMetIdx > 0); -0098 end -0099 -0100 % ---- Identify target metabolites ---- -0101 if isempty(targets) -0102 % Default: substrates of the objective (biomass) reaction -0103 objRxns = find(model.c ~= 0); -0104 if isempty(objRxns) -0105 error(['gapFillTopological: no objective function defined. ' ... -0106 'Provide targets manually via the ''targets'' option.']); -0107 end -0108 biomassIdx = objRxns(1); -0109 targetMetIdx = find(model.S(:, biomassIdx) ~= 0); -0110 else -0111 [~, targetMetIdx] = ismember(targets, model.mets); -0112 targetMetIdx = targetMetIdx(targetMetIdx > 0); -0113 end -0114 -0115 % ---- Build adjacency lists ---- -0116 % subOf{m} = reaction indices where metabolite m is a substrate (S < 0) -0117 % prodOf{m} = reaction indices where metabolite m is a product (S > 0) -0118 % rxnSubs{j} = substrate metabolite indices of reaction j -0119 % rxnProds{j} = product metabolite indices of reaction j -0120 % -0121 % Use find() on the sparse S directly to avoid materialising a dense logical -0122 % matrix: extract all nonzeros then partition by sign. -0123 [allRows, allCols, allVals] = find(model.S); -0124 subMask = allVals < 0; -0125 proMask = allVals > 0; -0126 subMetRows = allRows(subMask); -0127 subRxnCols = allCols(subMask); -0128 proMetRows = allRows(proMask); -0129 proRxnCols = allCols(proMask); -0130 -0131 subOf = cell(nMets, 1); -0132 prodOf = cell(nMets, 1); -0133 for k = 1:numel(subMetRows) -0134 subOf{subMetRows(k)}(end+1) = subRxnCols(k); -0135 end -0136 for k = 1:numel(proMetRows) -0137 prodOf{proMetRows(k)}(end+1) = proRxnCols(k); +0069 nMets = numel(model.mets); +0070 nRxns = numel(model.rxns); +0071 +0072 % ---- Identify seed metabolites ---- +0073 if isempty(seeds) +0074 % Default: metabolites that an exchange reaction can supply. Ask +0075 % getExchangeRxns rather than testing lb < 0 here: RAVEN writes an uptake +0076 % exchange either as "=> met" (addExchangeRxns 'in', so lb = 0 and a +0077 % positive flux supplies the metabolite) or as "met =>" with lb < 0. Only +0078 % the second has lb < 0, so a hand-rolled lb < 0 test silently misses +0079 % every 'in' exchange. +0080 % +0081 % 'uptake' alone is not enough either: it means the bounds allow *only* +0082 % uptake, so a reversible exchange ("met <=>") is classified 'reverse' +0083 % even though it supplies the metabolite just as well. The union of the +0084 % two is exactly "forward flux produces it, or reverse flux does", which +0085 % is what analyse_topology's lower_bound < 0 test means under cobra's +0086 % convention, where every exchange is written "met <=>". +0087 [~, onlyUptakeIdx] = getExchangeRxns(model, 'uptake'); +0088 [~, reversibleIdx] = getExchangeRxns(model, 'reverse'); +0089 uptakeIdx = union(onlyUptakeIdx, reversibleIdx); +0090 if isempty(uptakeIdx) +0091 warning(['gapFillTopological: no uptake exchange reactions found. ' ... +0092 'Provide seeds manually via the ''seeds'' option.']); +0093 seedMetIdx = []; +0094 else +0095 [seedRows, ~] = find(model.S(:, uptakeIdx) ~= 0); +0096 seedMetIdx = unique(seedRows); +0097 end +0098 else +0099 [~, seedMetIdx] = ismember(seeds, model.mets); +0100 seedMetIdx = seedMetIdx(seedMetIdx > 0); +0101 end +0102 +0103 % ---- Identify target metabolites ---- +0104 if isempty(targets) +0105 % Default: every metabolite of the objective (biomass) reaction +0106 objRxns = find(model.c ~= 0); +0107 if isempty(objRxns) +0108 error('RAVEN:badInput', ['gapFillTopological: no objective function defined. ' ... +0109 'Provide targets manually via the ''targets'' option.']); +0110 end +0111 biomassIdx = objRxns(1); +0112 targetMetIdx = find(model.S(:, biomassIdx) ~= 0); +0113 else +0114 [~, targetMetIdx] = ismember(targets, model.mets); +0115 targetMetIdx = targetMetIdx(targetMetIdx > 0); +0116 end +0117 +0118 % ---- Build adjacency lists ---- +0119 % subOf{m} = reaction indices where metabolite m is a substrate (S < 0) +0120 % prodOf{m} = reaction indices where metabolite m is a product (S > 0) +0121 % rxnSubs{j} = substrate metabolite indices of reaction j +0122 % rxnProds{j} = product metabolite indices of reaction j +0123 % +0124 % Use find() on the sparse S directly to avoid materialising a dense logical +0125 % matrix: extract all nonzeros then partition by sign. +0126 [allRows, allCols, allVals] = find(model.S); +0127 subMask = allVals < 0; +0128 proMask = allVals > 0; +0129 subMetRows = allRows(subMask); +0130 subRxnCols = allCols(subMask); +0131 proMetRows = allRows(proMask); +0132 proRxnCols = allCols(proMask); +0133 +0134 subOf = cell(nMets, 1); +0135 prodOf = cell(nMets, 1); +0136 for k = 1:numel(subMetRows) +0137 subOf{subMetRows(k)}(end+1) = subRxnCols(k); 0138 end -0139 -0140 rxnSubs = cell(nRxns, 1); -0141 rxnProds = cell(nRxns, 1); -0142 for k = 1:numel(subMetRows) -0143 rxnSubs{subRxnCols(k)}(end+1) = subMetRows(k); -0144 end -0145 for k = 1:numel(proMetRows) -0146 rxnProds{proRxnCols(k)}(end+1) = proMetRows(k); +0139 for k = 1:numel(proMetRows) +0140 prodOf{proMetRows(k)}(end+1) = proRxnCols(k); +0141 end +0142 +0143 rxnSubs = cell(nRxns, 1); +0144 rxnProds = cell(nRxns, 1); +0145 for k = 1:numel(subMetRows) +0146 rxnSubs{subRxnCols(k)}(end+1) = subMetRows(k); 0147 end -0148 -0149 isRev = model.lb < 0; -0150 -0151 % ---- BFS scope computation ---- -0152 % Uses a countdown approach: a reaction fires when all its substrates are -0153 % reachable. Each time a metabolite becomes reachable, decrement the -0154 % substrate count of all reactions it participates in. -0155 subCountFwd = cellfun(@numel, rxnSubs); % remaining unreachable substrates (fwd) -0156 subCountRev = cellfun(@numel, rxnProds); % remaining unreachable products (rev) -0157 -0158 reachable = false(nMets, 1); -0159 firedFwd = false(nRxns, 1); -0160 firedRev = false(nRxns, 1); -0161 -0162 % Initialize queue with seed metabolites -0163 queue = seedMetIdx(:)'; -0164 qHead = 1; -0165 reachable(seedMetIdx) = true; -0166 -0167 % Reactions with no substrates have nothing to wait for and fire straight -0168 % away. They are in no subOf list, so the countdown below never reaches them -0169 % and their products would otherwise stay unreachable forever. This is where -0170 % "=> met" uptake exchanges enter the scope. -0171 for j = find(subCountFwd == 0)' -0172 firedFwd(j) = true; -0173 for p = rxnProds{j} -0174 if ~reachable(p) -0175 reachable(p) = true; -0176 queue(end+1) = p; %#ok<AGROW> -0177 end -0178 end -0179 end -0180 -0181 while qHead <= numel(queue) -0182 m = queue(qHead); -0183 qHead = qHead + 1; -0184 -0185 % --- Forward direction --- -0186 % m is a substrate of rxn j (S(m,j) < 0) -0187 for j = subOf{m} -0188 subCountFwd(j) = subCountFwd(j) - 1; -0189 if subCountFwd(j) == 0 && ~firedFwd(j) -0190 firedFwd(j) = true; -0191 for p = rxnProds{j} -0192 if ~reachable(p) -0193 reachable(p) = true; -0194 queue(end+1) = p; -0195 end -0196 end -0197 end -0198 end -0199 -0200 % --- Reverse direction (reversible reactions only) --- -0201 % m is a product of rxn j (S(m,j) > 0), which in reverse means m is a -0202 % substrate; if all products are reachable, the reaction can fire in -0203 % reverse and produce the substrates. -0204 for j = prodOf{m} -0205 if isRev(j) -0206 subCountRev(j) = subCountRev(j) - 1; -0207 if subCountRev(j) == 0 && ~firedRev(j) -0208 firedRev(j) = true; -0209 for s = rxnSubs{j} -0210 if ~reachable(s) -0211 reachable(s) = true; -0212 queue(end+1) = s; -0213 end -0214 end -0215 end -0216 end -0217 end -0218 end -0219 -0220 % ---- Report blocked target metabolites ---- -0221 targetReachable = reachable(targetMetIdx); -0222 blockedMetIdx = targetMetIdx(~targetReachable); -0223 blockedMets = model.mets(blockedMetIdx); -0224 -0225 if verbose -0226 fprintf('gapFillTopological: %d/%d target metabolites are reachable.\n', ... -0227 sum(targetReachable), numel(targetMetIdx)); -0228 if ~isempty(blockedMets) -0229 fprintf('gapFillTopological: %d blocked target metabolite(s).\n', numel(blockedMets)); -0230 end -0231 end -0232 -0233 % ---- Find candidate universal reactions for each blocked metabolite ---- -0234 candidateRxns = cell(numel(blockedMets), 1); -0235 allCandidates = {}; -0236 -0237 for k = 1:numel(blockedMetIdx) -0238 m = blockedMetIdx(k); -0239 metId = model.mets{m}; -0240 -0241 % Find this metabolite in the universal model -0242 univMetIdx = find(strcmp(universalModel.mets, metId)); -0243 if isempty(univMetIdx) -0244 candidateRxns{k} = {}; -0245 continue; -0246 end -0247 univMetIdx = univMetIdx(1); % take first match; IDs should be unique -0248 -0249 univSRow = full(universalModel.S(univMetIdx, :)); -0250 -0251 % Forward production: rxns with S(m,j) > 0 -0252 fwdProdMask = univSRow > 0; -0253 % Reverse production: rxns with S(m,j) < 0 that can carry negative flux -0254 revProdMask = univSRow < 0 & (universalModel.lb' < 0); -0255 -0256 candidateMask = fwdProdMask | revProdMask; -0257 candidateRxns{k} = universalModel.rxns(candidateMask); -0258 allCandidates = [allCandidates; candidateRxns{k}]; -0259 end -0260 -0261 % ---- Pruning fraction ---- -0262 uniqueCandidates = unique(allCandidates); -0263 pruningFraction = 1 - numel(uniqueCandidates) / numel(universalModel.rxns); -0264 -0265 if verbose -0266 fprintf('gapFillTopological: %d/%d universal reactions pruned (%.0f%%).\n', ... -0267 numel(universalModel.rxns) - numel(uniqueCandidates), ... -0268 numel(universalModel.rxns), pruningFraction * 100); -0269 end -0270 -0271 % ---- Pack output ---- -0272 result.reachableMets = reachable; -0273 result.blockedMets = blockedMets; -0274 result.candidateRxns = candidateRxns; -0275 result.pruningFraction = pruningFraction; -0276 end +0148 for k = 1:numel(proMetRows) +0149 rxnProds{proRxnCols(k)}(end+1) = proMetRows(k); +0150 end +0151 +0152 isRev = model.lb < 0; +0153 +0154 % ---- BFS scope computation ---- +0155 % Uses a countdown approach: a reaction fires when all its substrates are +0156 % reachable. Each time a metabolite becomes reachable, decrement the +0157 % substrate count of all reactions it participates in. +0158 subCountFwd = cellfun(@numel, rxnSubs); % remaining unreachable substrates (fwd) +0159 subCountRev = cellfun(@numel, rxnProds); % remaining unreachable products (rev) +0160 +0161 reachable = false(nMets, 1); +0162 firedFwd = false(nRxns, 1); +0163 firedRev = false(nRxns, 1); +0164 +0165 % Initialize queue with seed metabolites +0166 queue = seedMetIdx(:)'; +0167 qHead = 1; +0168 reachable(seedMetIdx) = true; +0169 +0170 % Reactions with no substrates have nothing to wait for and fire straight +0171 % away. They are in no subOf list, so the countdown below never reaches them +0172 % and their products would otherwise stay unreachable forever. This is where +0173 % "=> met" uptake exchanges enter the scope. +0174 for j = find(subCountFwd == 0)' +0175 firedFwd(j) = true; +0176 for p = rxnProds{j} +0177 if ~reachable(p) +0178 reachable(p) = true; +0179 queue(end+1) = p; %#ok<AGROW> +0180 end +0181 end +0182 end +0183 +0184 while qHead <= numel(queue) +0185 m = queue(qHead); +0186 qHead = qHead + 1; +0187 +0188 % --- Forward direction --- +0189 % m is a substrate of rxn j (S(m,j) < 0) +0190 for j = subOf{m} +0191 subCountFwd(j) = subCountFwd(j) - 1; +0192 if subCountFwd(j) == 0 && ~firedFwd(j) +0193 firedFwd(j) = true; +0194 for p = rxnProds{j} +0195 if ~reachable(p) +0196 reachable(p) = true; +0197 queue(end+1) = p; +0198 end +0199 end +0200 end +0201 end +0202 +0203 % --- Reverse direction (reversible reactions only) --- +0204 % m is a product of rxn j (S(m,j) > 0), which in reverse means m is a +0205 % substrate; if all products are reachable, the reaction can fire in +0206 % reverse and produce the substrates. +0207 for j = prodOf{m} +0208 if isRev(j) +0209 subCountRev(j) = subCountRev(j) - 1; +0210 if subCountRev(j) == 0 && ~firedRev(j) +0211 firedRev(j) = true; +0212 for s = rxnSubs{j} +0213 if ~reachable(s) +0214 reachable(s) = true; +0215 queue(end+1) = s; +0216 end +0217 end +0218 end +0219 end +0220 end +0221 end +0222 +0223 % ---- Report blocked target metabolites ---- +0224 targetReachable = reachable(targetMetIdx); +0225 blockedMetIdx = targetMetIdx(~targetReachable); +0226 blockedMets = model.mets(blockedMetIdx); +0227 +0228 if verbose +0229 fprintf('gapFillTopological: %d/%d target metabolites are reachable.\n', ... +0230 sum(targetReachable), numel(targetMetIdx)); +0231 if ~isempty(blockedMets) +0232 fprintf('gapFillTopological: %d blocked target metabolite(s).\n', numel(blockedMets)); +0233 end +0234 end +0235 +0236 % ---- Find candidate universal reactions for each blocked metabolite ---- +0237 candidateRxns = cell(numel(blockedMets), 1); +0238 allCandidates = {}; +0239 +0240 for k = 1:numel(blockedMetIdx) +0241 m = blockedMetIdx(k); +0242 metId = model.mets{m}; +0243 +0244 % Find this metabolite in the universal model +0245 univMetIdx = find(strcmp(universalModel.mets, metId)); +0246 if isempty(univMetIdx) +0247 candidateRxns{k} = {}; +0248 continue; +0249 end +0250 univMetIdx = univMetIdx(1); % take first match; IDs should be unique +0251 +0252 univSRow = full(universalModel.S(univMetIdx, :)); +0253 +0254 % Forward production: rxns with S(m,j) > 0 +0255 fwdProdMask = univSRow > 0; +0256 % Reverse production: rxns with S(m,j) < 0 that can carry negative flux +0257 revProdMask = univSRow < 0 & (universalModel.lb' < 0); +0258 +0259 candidateMask = fwdProdMask | revProdMask; +0260 candidateRxns{k} = universalModel.rxns(candidateMask); +0261 allCandidates = [allCandidates; candidateRxns{k}]; +0262 end +0263 +0264 % ---- Pruning fraction ---- +0265 uniqueCandidates = unique(allCandidates); +0266 pruningFraction = 1 - numel(uniqueCandidates) / numel(universalModel.rxns); +0267 +0268 if verbose +0269 fprintf('gapFillTopological: %d/%d universal reactions pruned (%.0f%%).\n', ... +0270 numel(universalModel.rxns) - numel(uniqueCandidates), ... +0271 numel(universalModel.rxns), pruningFraction * 100); +0272 end +0273 +0274 % ---- Pack output ---- +0275 result.reachableMets = reachable; +0276 result.blockedMets = blockedMets; +0277 result.candidateRxns = candidateRxns; +0278 result.pruningFraction = pruningFraction; +0279 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/manipulation/mergeCompartments.html b/docs/html/manipulation/mergeCompartments.html index 99f2ad43..f634bb5d 100644 --- a/docs/html/manipulation/mergeCompartments.html +++ b/docs/html/manipulation/mergeCompartments.html @@ -70,8 +70,13 @@

    DESCRIPTION ^CROSS-REFERENCE INFORMATION ^

    @@ -130,112 +135,123 @@

    SOURCE CODE ^% be useful for example to ensure that there are metabolic capabilities to 0043 % synthesize all metabolites. 0044 % -0045 % If the metabolite IDs reflect the compartment that they are in the IDs may -0046 % no longer be representative. -0047 -0048 p=parseRAVENargs(varargin, {'keepUnconstrained',false; 'deleteRxnsWithOneMet',false; 'distReverse',true}); -0049 keepUnconstrained=p.keepUnconstrained; -0050 deleteRxnsWithOneMet=p.deleteRxnsWithOneMet; -0051 distReverse=p.distReverse; +0045 % Metabolites with the same name are merged into one. The merged metabolite +0046 % keeps the id and the other fields of one of the copies: the copy that is +0047 % already in compartment "s" if there is one, otherwise the copy whose id +0048 % comes first in character code order (for example ACCOA_c before ACCOA_m). +0049 % With keepUnconstrained only the copies that are not unconstrained are +0050 % candidates. If the metabolite IDs reflect the compartment that they are in +0051 % the IDs may no longer be representative. 0052 -0053 if ~isfield(model,'unconstrained') -0054 keepUnconstrained=false; -0055 end -0056 -0057 %Keep track of which reactions only contained one metabolite before the -0058 %merging as they are probable exchange reactions. -0059 if deleteRxnsWithOneMet==true -0060 reservedRxns=model.rxns(sum(model.S~=0)==1); -0061 if ~isempty(reservedRxns) && ~isfield(model,'unconstrained') -0062 %If there is no unconstrained field these reactions are probably -0063 %exchange reactions and shall be kept. If not then print a warning -0064 EM='There are reactions with only one metabolite. Cannot determine whether they are exchange reactions since there is no unconstrained field'; -0065 warning('RAVEN:warning', '%s', EM); -0066 end -0067 end -0068 -0069 %Loop through each metabolite, and if it is not unconstrained then change -0070 %the S matrix to use the metabolite with the lowest index in model.comps -0071 %instead -0072 %Group metabolite indices by name once, rather than re-scanning all of -0073 %model.metNames (via ismember) for every unique name, which is quadratic -0074 %in a genome-scale model's (unique names x mets) count. -0075 [uNames,~,nameGroup]=unique(model.metNames); -0076 sameNameIdx=accumarray(nameGroup,(1:numel(nameGroup))',[numel(uNames) 1],@(v) {v}); -0077 for i=1:numel(uNames) -0078 %Find all metabolites with this name.. -0079 I=false(numel(model.metNames),1); -0080 I(sameNameIdx{i})=true; -0081 -0082 %Find the first of those that is not unconstrained. This is the one -0083 %that the other "un-unconstrained" should be changed to. -0084 if keepUnconstrained==true -0085 mergeTo=find(I & model.unconstrained==false,1); -0086 -0087 %This could happen if there is only one metabolite and it is -0088 %unconstrained -0089 if isempty(mergeTo) -0090 continue; -0091 end -0092 else -0093 mergeTo=find(I,1); -0094 end -0095 I(mergeTo)=false; %Do not do anything for itself -0096 I=find(I); -0097 -0098 %Go through each of the metabolites with this name and update them to -0099 %mergeTo -0100 for j=1:numel(I) -0101 if keepUnconstrained==true && model.unconstrained(I(j))==true -0102 continue; -0103 end -0104 %Update the S matrix -0105 model.S(mergeTo,:)=model.S(mergeTo,:)+model.S(I(j),:); -0106 model.S(I(j),:)=0; -0107 end -0108 end -0109 -0110 %Remove all metabolites that are no longer used (with a bit of a trick) -0111 model=removeReactions(model,{},true); -0112 -0113 %Update all mets so that they are in compartment "s" with id "1" -0114 model.compNames={'System'}; -0115 model.comps={'s'}; -0116 model.compOutside={''}; -0117 model.metComps=ones(numel(model.mets),1); -0118 %The per-compartment MIRIAM annotations no longer apply once every -0119 %compartment is merged into one, and leaving them would desync compMiriams -0120 %from the rebuilt comps. Drop them. -0121 if isfield(model,'compMiriams') -0122 model=rmfield(model,'compMiriams'); -0123 end -0124 -0125 %Add exchange mets to another compartment "b" with id "2" -0126 if keepUnconstrained==true -0127 model.compNames=[model.compNames; {'Unconstrained'}]; -0128 model.comps=[model.comps; {'b'}]; -0129 model.compOutside=[model.compOutside; {'s'}]; -0130 model.metComps(model.unconstrained~=0)=2; -0131 end -0132 -0133 %Transport reactions on the form A <=> B will have been deleted by the -0134 %merging. Remove those reactions -0135 model=removeMets(model,{},false,true,true); -0136 -0137 if deleteRxnsWithOneMet==true -0138 I=model.rxns(sum(model.S~=0)==1); -0139 -0140 %Delete the reactions that contain only one metabolite after the -0141 %merging but not before -0142 deletedRxns=setdiff(I,reservedRxns); -0143 model=removeReactions(model,deletedRxns,true,true); -0144 else -0145 deletedRxns={}; -0146 end +0053 p=parseRAVENargs(varargin, {'keepUnconstrained',false; 'deleteRxnsWithOneMet',false; 'distReverse',true}); +0054 keepUnconstrained=p.keepUnconstrained; +0055 deleteRxnsWithOneMet=p.deleteRxnsWithOneMet; +0056 distReverse=p.distReverse; +0057 +0058 if ~isfield(model,'unconstrained') +0059 keepUnconstrained=false; +0060 end +0061 +0062 %Keep track of which reactions only contained one metabolite before the +0063 %merging as they are probable exchange reactions. +0064 if deleteRxnsWithOneMet==true +0065 reservedRxns=model.rxns(sum(model.S~=0)==1); +0066 if ~isempty(reservedRxns) && ~isfield(model,'unconstrained') +0067 %If there is no unconstrained field these reactions are probably +0068 %exchange reactions and shall be kept. If not then print a warning +0069 EM='There are reactions with only one metabolite. Cannot determine whether they are exchange reactions since there is no unconstrained field'; +0070 warning('RAVEN:warning', '%s', EM); +0071 end +0072 end +0073 +0074 %Loop through each metabolite name, and if a copy is not unconstrained then +0075 %change the S matrix to use the copy chosen as described in the Notes +0076 %Group metabolite indices by name once, rather than re-scanning all of +0077 %model.metNames (via ismember) for every unique name, which is quadratic +0078 %in a genome-scale model's (unique names x mets) count. +0079 [uNames,~,nameGroup]=unique(model.metNames); +0080 sameNameIdx=accumarray(nameGroup,(1:numel(nameGroup))',[numel(uNames) 1],@(v) {v}); +0081 for i=1:numel(uNames) +0082 %Find all metabolites with this name.. +0083 I=false(numel(model.metNames),1); +0084 I(sameNameIdx{i})=true; +0085 +0086 %Choose the copy that the other "un-unconstrained" copies are merged +0087 %into: one already in compartment "s" if there is one, otherwise the +0088 %one whose id sorts first. +0089 if keepUnconstrained==true +0090 candidates=find(I & model.unconstrained==false); +0091 +0092 %This could happen if there is only one metabolite and it is +0093 %unconstrained +0094 if isempty(candidates) +0095 continue; +0096 end +0097 else +0098 candidates=find(I); +0099 end +0100 inMerged=candidates(strcmp(model.comps(model.metComps(candidates)),'s')); +0101 if ~isempty(inMerged) +0102 candidates=inMerged; +0103 end +0104 [~,first]=sort(model.mets(candidates)); +0105 mergeTo=candidates(first(1)); +0106 I(mergeTo)=false; %Do not do anything for itself +0107 I=find(I); +0108 +0109 %Go through each of the metabolites with this name and update them to +0110 %mergeTo +0111 for j=1:numel(I) +0112 if keepUnconstrained==true && model.unconstrained(I(j))==true +0113 continue; +0114 end +0115 %Update the S matrix +0116 model.S(mergeTo,:)=model.S(mergeTo,:)+model.S(I(j),:); +0117 model.S(I(j),:)=0; +0118 end +0119 end +0120 +0121 %Remove all metabolites that are no longer used (with a bit of a trick) +0122 model=removeReactions(model,{},true); +0123 +0124 %Update all mets so that they are in compartment "s" with id "1" +0125 model.compNames={'System'}; +0126 model.comps={'s'}; +0127 model.compOutside={''}; +0128 model.metComps=ones(numel(model.mets),1); +0129 %The per-compartment MIRIAM annotations no longer apply once every +0130 %compartment is merged into one, and leaving them would desync compMiriams +0131 %from the rebuilt comps. Drop them. +0132 if isfield(model,'compMiriams') +0133 model=rmfield(model,'compMiriams'); +0134 end +0135 +0136 %Add exchange mets to another compartment "b" with id "2" +0137 if keepUnconstrained==true +0138 model.compNames=[model.compNames; {'Unconstrained'}]; +0139 model.comps=[model.comps; {'b'}]; +0140 model.compOutside=[model.compOutside; {'s'}]; +0141 model.metComps(model.unconstrained~=0)=2; +0142 end +0143 +0144 %Transport reactions on the form A <=> B will have been deleted by the +0145 %merging. Remove those reactions +0146 model=removeMets(model,{},false,true,true); 0147 -0148 %And then finally merge the identical reactions -0149 [model, ~, duplicateRxns]=contractModel(model,distReverse); -0150 end +0148 if deleteRxnsWithOneMet==true +0149 I=model.rxns(sum(model.S~=0)==1); +0150 +0151 %Delete the reactions that contain only one metabolite after the +0152 %merging but not before +0153 deletedRxns=setdiff(I,reservedRxns); +0154 model=removeReactions(model,deletedRxns,true,true); +0155 else +0156 deletedRxns={}; +0157 end +0158 +0159 %And then finally merge the identical reactions +0160 [model, ~, duplicateRxns]=contractModel(model,distReverse); +0161 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/manipulation/removeGenes.html b/docs/html/manipulation/removeGenes.html index 8aa7a0da..8b5d420a 100644 --- a/docs/html/manipulation/removeGenes.html +++ b/docs/html/manipulation/removeGenes.html @@ -51,7 +51,15 @@

    DESCRIPTION ^SOURCE CODE ^% Returns 0024 % ------- 0025 % reducedModel : struct -0026 % an updated model structure. -0027 % -0028 % Examples -0029 % -------- -0030 % reducedModel = removeGenes(model, genesToRemove, removeUnusedMets, ... -0031 % removeBlockedRxns, standardizeRules); -0032 -0033 p=parseRAVENargs(varargin, {'removeUnusedMets',false; 'removeBlockedRxns',false; 'standardizeRules',true}); -0034 removeUnusedMets=p.removeUnusedMets; -0035 removeBlockedRxns=p.removeBlockedRxns; -0036 standardizeRules=p.standardizeRules; -0037 %Format grRules and rxnGeneMatrix: -0038 if standardizeRules -0039 [grRules,rxnGeneMat,toCheck] = standardizeGrRules(model,true); -0040 model.grRules = grRules; -0041 model.rxnGeneMat = rxnGeneMat; -0042 else -0043 toCheck = []; -0044 rxnGeneMat = model.rxnGeneMat; -0045 end -0046 reducedModel = model; -0047 %Only remove genes that are actually in the model -0048 if ~(islogical(genesToRemove) || isnumeric(genesToRemove)) -0049 genesToRemove=convertCharArray(genesToRemove); -0050 genesToRemove=genesToRemove(ismember(genesToRemove,model.genes)); -0051 end -0052 if ~isempty(genesToRemove) -0053 indexesToRemove = getIndexes(model,genesToRemove,'genes'); -0054 if ~isempty(indexesToRemove) -0055 %Make 0 corresponding columns from rxnGeneMat: -0056 reducedModel.rxnGeneMat(:,indexesToRemove) = 0; -0057 genes = model.genes(indexesToRemove); -0058 %Check if conflicting grRules contain any of the genes to remove -0059 if ~isempty(toCheck) -0060 for i=1:numel(toCheck) -0061 index = toCheck(i); -0062 gIdxs = find(rxnGeneMat(index,:)); -0063 g2check = model.genes(gIdxs); -0064 if any(ismember(g2check,genes)) -0065 warning(['Conflicting grRule #' num2str(index) ' (' model.rxns{index} ') contains at least one of the genes to be removed, this grRule will be bypassed in order to avoid logical errors']) -0066 end -0067 end -0068 end -0069 canCarryFlux = true(size(model.rxns)); -0070 %Loop through genes and adapt rxns: -0071 for i = 1:length(genes) -0072 %Find all reactions for this gene and loop through them: -0073 geneRxns = find(rxnGeneMat(:,indexesToRemove(i))); -0074 if ~isempty(geneRxns) -0075 for j = 1:numel(geneRxns) -0076 index = geneRxns(j); -0077 grRule = reducedModel.grRules{index}; -0078 if ~ismember(index,toCheck) && canCarryFlux(index) && ~isempty(grRule) -0079 %Check if rxn can carry flux without this gene: -0080 canCarryFlux(index) = canRxnCarryFlux(grRule,genes{i}); -0081 %Adapt gene rule & gene matrix: -0082 grRule = removeGeneFromRule(grRule,genes{i}); -0083 reducedModel.grRules{index} = grRule; -0084 end -0085 end -0086 end -0087 end -0088 %Delete or block the reactions that cannot carry flux: -0089 if removeBlockedRxns -0090 rxnsToRemove = reducedModel.rxns(~canCarryFlux); -0091 reducedModel = removeReactions(reducedModel,rxnsToRemove,removeUnusedMets,true); -0092 else -0093 reducedModel = removeReactions(reducedModel,[],removeUnusedMets,true); -0094 reducedModel.lb(~canCarryFlux) = 0; -0095 reducedModel.ub(~canCarryFlux) = 0; -0096 end -0097 end -0098 end -0099 %Format grRules and rxnGeneMatrix after all modifications -0100 if standardizeRules -0101 [grRules,rxnGeneMat] = standardizeGrRules(reducedModel,true); -0102 reducedModel.grRules = grRules; -0103 reducedModel.rxnGeneMat = rxnGeneMat; -0104 end -0105 end -0106 -0107 function canIt = canRxnCarryFlux(geneRule,geneToRemove) -0108 %Evaluate the rule with geneToRemove absent and every other gene present. -0109 %A complex (AND) needs all of its subunits, isozymes (OR) need only one. -0110 tree = parseGrRule(geneRule); -0111 if isempty(tree) -0112 canIt = true; -0113 return -0114 end -0115 canIt = evalWithout(tree,geneToRemove); -0116 end -0117 -0118 function tf = evalWithout(node,geneToRemove) -0119 switch node.type -0120 case 'gene' -0121 tf = ~strcmp(node.id,geneToRemove); -0122 case 'and' -0123 tf = all(cellfun(@(c) evalWithout(c,geneToRemove),node.children)); -0124 case 'or' -0125 tf = any(cellfun(@(c) evalWithout(c,geneToRemove),node.children)); -0126 otherwise -0127 error('RAVEN:badGrRule',['Unexpected grRule node type: ' node.type]); -0128 end -0129 end -0130 -0131 function geneRule = removeGeneFromRule(geneRule,geneToRemove) -0132 %This function receives a gene rule and it returns it without the chosen -0133 %gene. A complex that loses a subunit is dropped whole; an isozyme is -0134 %dropped from its OR without disturbing the alternatives. -0135 tree = parseGrRule(geneRule); -0136 geneRule = grRuleToString(pruneGene(tree,geneToRemove)); +0026 % an updated model structure. Genes in genesToRemove that are not in the +0027 % model are ignored. When none of them is in the model, no gene, reaction +0028 % or bound changes (with standardizeRules true the gene rules are still +0029 % written in the standard format). Otherwise, besides genesToRemove, every +0030 % gene that no remaining reaction used before the removal is deleted as +0031 % well, also when it was not requested (for example a gene that no +0032 % reaction uses). A gene whose only reaction is kept but blocked +0033 % (removeBlockedRxns false), such as the other subunit of a complex that +0034 % lost one, stays. +0035 % +0036 % Examples +0037 % -------- +0038 % reducedModel = removeGenes(model, genesToRemove, removeUnusedMets, ... +0039 % removeBlockedRxns, standardizeRules); +0040 +0041 p=parseRAVENargs(varargin, {'removeUnusedMets',false; 'removeBlockedRxns',false; 'standardizeRules',true}); +0042 removeUnusedMets=p.removeUnusedMets; +0043 removeBlockedRxns=p.removeBlockedRxns; +0044 standardizeRules=p.standardizeRules; +0045 %Format grRules and rxnGeneMatrix: +0046 if standardizeRules +0047 [grRules,rxnGeneMat,toCheck] = standardizeGrRules(model,true); +0048 model.grRules = grRules; +0049 model.rxnGeneMat = rxnGeneMat; +0050 else +0051 toCheck = []; +0052 rxnGeneMat = model.rxnGeneMat; +0053 end +0054 reducedModel = model; +0055 %Only remove genes that are actually in the model +0056 if ~(islogical(genesToRemove) || isnumeric(genesToRemove)) +0057 genesToRemove=convertCharArray(genesToRemove); +0058 genesToRemove=genesToRemove(ismember(genesToRemove,model.genes)); +0059 end +0060 if ~isempty(genesToRemove) +0061 indexesToRemove = getIndexes(model,genesToRemove,'genes'); +0062 if ~isempty(indexesToRemove) +0063 %Make 0 corresponding columns from rxnGeneMat: +0064 reducedModel.rxnGeneMat(:,indexesToRemove) = 0; +0065 genes = model.genes(indexesToRemove); +0066 %Check if conflicting grRules contain any of the genes to remove +0067 if ~isempty(toCheck) +0068 for i=1:numel(toCheck) +0069 index = toCheck(i); +0070 gIdxs = find(rxnGeneMat(index,:)); +0071 g2check = model.genes(gIdxs); +0072 if any(ismember(g2check,genes)) +0073 warning(['Conflicting grRule #' num2str(index) ' (' model.rxns{index} ') contains at least one of the genes to be removed, this grRule will be bypassed in order to avoid logical errors']) +0074 end +0075 end +0076 end +0077 canCarryFlux = true(size(model.rxns)); +0078 %Loop through genes and adapt rxns: +0079 for i = 1:length(genes) +0080 %Find all reactions for this gene and loop through them: +0081 geneRxns = find(rxnGeneMat(:,indexesToRemove(i))); +0082 if ~isempty(geneRxns) +0083 for j = 1:numel(geneRxns) +0084 index = geneRxns(j); +0085 grRule = reducedModel.grRules{index}; +0086 if ~ismember(index,toCheck) && canCarryFlux(index) && ~isempty(grRule) +0087 %Check if rxn can carry flux without this gene: +0088 canCarryFlux(index) = canRxnCarryFlux(grRule,genes{i}); +0089 %Adapt gene rule & gene matrix: +0090 grRule = removeGeneFromRule(grRule,genes{i}); +0091 reducedModel.grRules{index} = grRule; +0092 end +0093 end +0094 end +0095 end +0096 %Delete or block the reactions that cannot carry flux: +0097 if removeBlockedRxns +0098 rxnsToRemove = reducedModel.rxns(~canCarryFlux); +0099 reducedModel = removeReactions(reducedModel,rxnsToRemove,removeUnusedMets,true); +0100 else +0101 reducedModel = removeReactions(reducedModel,[],removeUnusedMets,true); +0102 reducedModel.lb(~canCarryFlux) = 0; +0103 reducedModel.ub(~canCarryFlux) = 0; +0104 end +0105 end +0106 end +0107 %Format grRules and rxnGeneMatrix after all modifications +0108 if standardizeRules +0109 [grRules,rxnGeneMat] = standardizeGrRules(reducedModel,true); +0110 reducedModel.grRules = grRules; +0111 reducedModel.rxnGeneMat = rxnGeneMat; +0112 end +0113 end +0114 +0115 function canIt = canRxnCarryFlux(geneRule,geneToRemove) +0116 %Evaluate the rule with geneToRemove absent and every other gene present. +0117 %A complex (AND) needs all of its subunits, isozymes (OR) need only one. +0118 tree = parseGrRule(geneRule); +0119 if isempty(tree) +0120 canIt = true; +0121 return +0122 end +0123 canIt = evalWithout(tree,geneToRemove); +0124 end +0125 +0126 function tf = evalWithout(node,geneToRemove) +0127 switch node.type +0128 case 'gene' +0129 tf = ~strcmp(node.id,geneToRemove); +0130 case 'and' +0131 tf = all(cellfun(@(c) evalWithout(c,geneToRemove),node.children)); +0132 case 'or' +0133 tf = any(cellfun(@(c) evalWithout(c,geneToRemove),node.children)); +0134 otherwise +0135 error('RAVEN:badGrRule',['Unexpected grRule node type: ' node.type]); +0136 end 0137 end 0138 -0139 function node = pruneGene(node,geneToRemove) -0140 %Remove geneToRemove from the tree, returning [] when nothing is left that -0141 %can catalyse the reaction. -0142 if isempty(node) -0143 node = []; -0144 return +0139 function geneRule = removeGeneFromRule(geneRule,geneToRemove) +0140 %This function receives a gene rule and it returns it without the chosen +0141 %gene. A complex that loses a subunit is dropped whole; an isozyme is +0142 %dropped from its OR without disturbing the alternatives. +0143 tree = parseGrRule(geneRule); +0144 geneRule = grRuleToString(pruneGene(tree,geneToRemove)); 0145 end -0146 switch node.type -0147 case 'gene' -0148 if strcmp(node.id,geneToRemove) -0149 node = []; -0150 end -0151 case 'and' -0152 %A complex missing any subunit cannot form at all. -0153 for k = 1:numel(node.children) -0154 if isempty(pruneGene(node.children{k},geneToRemove)) -0155 node = []; -0156 return -0157 end +0146 +0147 function node = pruneGene(node,geneToRemove) +0148 %Remove geneToRemove from the tree, returning [] when nothing is left that +0149 %can catalyse the reaction. +0150 if isempty(node) +0151 node = []; +0152 return +0153 end +0154 switch node.type +0155 case 'gene' +0156 if strcmp(node.id,geneToRemove) +0157 node = []; 0158 end -0159 case 'or' -0160 kept = {}; +0159 case 'and' +0160 %A complex missing any subunit cannot form at all. 0161 for k = 1:numel(node.children) -0162 child = pruneGene(node.children{k},geneToRemove); -0163 if ~isempty(child) -0164 kept{end+1} = child; %#ok<AGROW> +0162 if isempty(pruneGene(node.children{k},geneToRemove)) +0163 node = []; +0164 return 0165 end 0166 end -0167 if isempty(kept) -0168 node = []; -0169 elseif isscalar(kept) -0170 node = kept{1}; -0171 else -0172 node.children = kept; -0173 end -0174 otherwise -0175 error('RAVEN:badGrRule',['Unexpected grRule node type: ' node.type]); -0176 end -0177 end +0167 case 'or' +0168 kept = {}; +0169 for k = 1:numel(node.children) +0170 child = pruneGene(node.children{k},geneToRemove); +0171 if ~isempty(child) +0172 kept{end+1} = child; %#ok<AGROW> +0173 end +0174 end +0175 if isempty(kept) +0176 node = []; +0177 elseif isscalar(kept) +0178 node = kept{1}; +0179 else +0180 node.children = kept; +0181 end +0182 otherwise +0183 error('RAVEN:badGrRule',['Unexpected grRule node type: ' node.type]); +0184 end +0185 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/manipulation/simplifyModel.html b/docs/html/manipulation/simplifyModel.html index 01eea06d..063951a2 100644 --- a/docs/html/manipulation/simplifyModel.html +++ b/docs/html/manipulation/simplifyModel.html @@ -283,150 +283,153 @@

    SOURCE CODE ^if constrainReversible==true 0187 revs=find(reducedModel.rev); -0188 [I,J]=getAllowedBounds(reducedModel,revs); -0189 -0190 I=abs(I); -0191 J=abs(J); -0192 -0193 %Get the "small" values -0194 K=I<10^-10; -0195 L=J<10^-10; -0196 -0197 %Keep the values where only one direction is zero (small) -0198 I(K==L)=[]; -0199 J(K==L)=[]; -0200 revs(K==L)=[]; -0201 -0202 %Change the reversibility of the remaining reactions -0203 reducedModel.rev(revs(J>10^-10))=0; %Ignore reverse direction -0204 reducedModel.lb(revs(J>10^-10))=0; -0205 -0206 toSwitch=revs(I>10^-10); -0207 reducedModel.rev(toSwitch)=0; %Change directionality -0208 reducedModel.ub(toSwitch)=reducedModel.lb(toSwitch)*-1; -0209 reducedModel.lb(toSwitch)=0; -0210 reducedModel.S(:,toSwitch)=reducedModel.S(:,toSwitch).*-1; -0211 reducedModel.c(toSwitch)=reducedModel.c(toSwitch)*-1; -0212 end -0213 if groupLinear==true -0214 if suppressWarnings==false -0215 fprintf('NOTE: You have chosen to group linear reactions. This option does not keep track of gene/reaction associations when reactions are merged. Deleting all gene information\n'); -0216 end -0217 -0218 reducedModel.genes={}; -0219 reducedModel.rxnGeneMat=sparse(numel(reducedModel.rxns),0); -0220 reducedModel.grRules(:)={''}; -0221 -0222 if isfield(reducedModel,'geneShortNames') -0223 reducedModel.geneShortNames={}; -0224 end -0225 if isfield(reducedModel,'proteins') -0226 reducedModel.proteins={}; +0188 %getAllowedBounds treats an empty reaction list as all reactions +0189 if ~isempty(revs) +0190 [I,J]=getAllowedBounds(reducedModel,revs); +0191 +0192 I=abs(I); +0193 J=abs(J); +0194 +0195 %Get the "small" values +0196 K=I<10^-10; +0197 L=J<10^-10; +0198 +0199 %Keep the values where only one direction is zero (small) +0200 I(K==L)=[]; +0201 J(K==L)=[]; +0202 revs(K==L)=[]; +0203 +0204 %Change the reversibility of the remaining reactions +0205 reducedModel.rev(revs(J>10^-10))=0; %Ignore reverse direction +0206 reducedModel.lb(revs(J>10^-10))=0; +0207 +0208 toSwitch=revs(I>10^-10); +0209 reducedModel.rev(toSwitch)=0; %Change directionality +0210 reducedModel.ub(toSwitch)=reducedModel.lb(toSwitch)*-1; +0211 reducedModel.lb(toSwitch)=0; +0212 reducedModel.S(:,toSwitch)=reducedModel.S(:,toSwitch).*-1; +0213 reducedModel.c(toSwitch)=reducedModel.c(toSwitch)*-1; +0214 end +0215 end +0216 if groupLinear==true +0217 if suppressWarnings==false +0218 fprintf('NOTE: You have chosen to group linear reactions. This option does not keep track of gene/reaction associations when reactions are merged. Deleting all gene information\n'); +0219 end +0220 +0221 reducedModel.genes={}; +0222 reducedModel.rxnGeneMat=sparse(numel(reducedModel.rxns),0); +0223 reducedModel.grRules(:)={''}; +0224 +0225 if isfield(reducedModel,'geneShortNames') +0226 reducedModel.geneShortNames={}; 0227 end -0228 if isfield(reducedModel,'geneMiriams') -0229 reducedModel.geneMiriams={}; +0228 if isfield(reducedModel,'proteins') +0229 reducedModel.proteins={}; 0230 end -0231 if isfield(reducedModel,'geneComps') -0232 reducedModel.geneComps=[]; +0231 if isfield(reducedModel,'geneMiriams') +0232 reducedModel.geneMiriams={}; 0233 end -0234 -0235 %Convert the model to irreversible -0236 irrevModel=convertToIrrev(reducedModel); +0234 if isfield(reducedModel,'geneComps') +0235 reducedModel.geneComps=[]; +0236 end 0237 -0238 %Loop through and iteratively group linear reactions -0239 while 1 -0240 %Get the banned reaction indexes. Note that the indexes will change -0241 %in each iteration, but the names will not as they will not be merged -0242 %with any other reaction -0243 bannedIndexes=getIndexes(irrevModel,reservedRxns,'rxns'); -0244 -0245 %Select all metabolites that are only present as reactants/products -0246 %in one reaction -0247 singleNegative=find(sum(irrevModel.S'<0)==1); -0248 singlePositive=find(sum(irrevModel.S'>0)==1); -0249 -0250 %Retrieve the common metabolites -0251 common=intersect(singleNegative,singlePositive); +0238 %Convert the model to irreversible +0239 irrevModel=convertToIrrev(reducedModel); +0240 +0241 %Loop through and iteratively group linear reactions +0242 while 1 +0243 %Get the banned reaction indexes. Note that the indexes will change +0244 %in each iteration, but the names will not as they will not be merged +0245 %with any other reaction +0246 bannedIndexes=getIndexes(irrevModel,reservedRxns,'rxns'); +0247 +0248 %Select all metabolites that are only present as reactants/products +0249 %in one reaction +0250 singleNegative=find(sum(irrevModel.S'<0)==1); +0251 singlePositive=find(sum(irrevModel.S'>0)==1); 0252 -0253 mergedSome=false; -0254 -0255 %Loop through each of them and see if the reactions should be -0256 %merged -0257 for i=1:numel(common) -0258 involvedRxns=find(irrevModel.S(common(i),:)); -0259 -0260 %Check so that one or both of the reactions have not been merged -0261 %already -0262 if numel(involvedRxns)==2 && isempty(intersect(bannedIndexes,involvedRxns)) -0263 %Calculate how many times the second reaction has to be -0264 %multiplied before being merged with the first -0265 stoichRatio=abs(irrevModel.S(common(i),involvedRxns(1))/irrevModel.S(common(i),involvedRxns(2))); -0266 -0267 %Add the second to the first -0268 irrevModel.S(:,involvedRxns(1))=irrevModel.S(:,involvedRxns(1))+irrevModel.S(:,involvedRxns(2))*stoichRatio; +0253 %Retrieve the common metabolites +0254 common=intersect(singleNegative,singlePositive); +0255 +0256 mergedSome=false; +0257 +0258 %Loop through each of them and see if the reactions should be +0259 %merged +0260 for i=1:numel(common) +0261 involvedRxns=find(irrevModel.S(common(i),:)); +0262 +0263 %Check so that one or both of the reactions have not been merged +0264 %already +0265 if numel(involvedRxns)==2 && isempty(intersect(bannedIndexes,involvedRxns)) +0266 %Calculate how many times the second reaction has to be +0267 %multiplied before being merged with the first +0268 stoichRatio=abs(irrevModel.S(common(i),involvedRxns(1))/irrevModel.S(common(i),involvedRxns(2))); 0269 -0270 %Clear the second reaction -0271 irrevModel.S(:,involvedRxns(2))=0; +0270 %Add the second to the first +0271 irrevModel.S(:,involvedRxns(1))=irrevModel.S(:,involvedRxns(1))+irrevModel.S(:,involvedRxns(2))*stoichRatio; 0272 -0273 %This is to prevent numerical issues. It should be 0 -0274 %already -0275 irrevModel.S(common(i),involvedRxns(1))=0; -0276 -0277 %At this point the second reaction is certain to be deleted -0278 %in a later step and can therefore be ignored +0273 %Clear the second reaction +0274 irrevModel.S(:,involvedRxns(2))=0; +0275 +0276 %This is to prevent numerical issues. It should be 0 +0277 %already +0278 irrevModel.S(common(i),involvedRxns(1))=0; 0279 -0280 %Recalculate the bounds for the new reaction. This can be -0281 %problematic since the scale of the bounds may change -0282 %dramatically. Let the most constraining reaction determine -0283 %the new bound -0284 lb1=irrevModel.lb(involvedRxns(1)); -0285 lb2=irrevModel.lb(involvedRxns(2)); -0286 ub1=irrevModel.ub(involvedRxns(1)); -0287 ub2=irrevModel.ub(involvedRxns(2)); -0288 -0289 if lb2~=-inf -0290 irrevModel.lb(involvedRxns(1))=max(lb1,lb2/stoichRatio); -0291 end -0292 if ub2~=inf -0293 irrevModel.ub(involvedRxns(1))=min(ub1,ub2/stoichRatio); +0280 %At this point the second reaction is certain to be deleted +0281 %in a later step and can therefore be ignored +0282 +0283 %Recalculate the bounds for the new reaction. This can be +0284 %problematic since the scale of the bounds may change +0285 %dramatically. Let the most constraining reaction determine +0286 %the new bound +0287 lb1=irrevModel.lb(involvedRxns(1)); +0288 lb2=irrevModel.lb(involvedRxns(2)); +0289 ub1=irrevModel.ub(involvedRxns(1)); +0290 ub2=irrevModel.ub(involvedRxns(2)); +0291 +0292 if lb2~=-inf +0293 irrevModel.lb(involvedRxns(1))=max(lb1,lb2/stoichRatio); 0294 end -0295 -0296 %Then recalculate the objective coefficient. The resulting -0297 %coefficient is the weighted sum of the previous -0298 irrevModel.c(involvedRxns(1))=irrevModel.c(involvedRxns(1))+irrevModel.c(involvedRxns(2))*stoichRatio; -0299 -0300 %Iterate again -0301 mergedSome=true; -0302 end -0303 end -0304 -0305 %All possible reactions merged -0306 if mergedSome==false -0307 break; -0308 end -0309 -0310 %Now delete all reactions that involve no metabolites -0311 I=find(sum(irrevModel.S~=0)==0); +0295 if ub2~=inf +0296 irrevModel.ub(involvedRxns(1))=min(ub1,ub2/stoichRatio); +0297 end +0298 +0299 %Then recalculate the objective coefficient. The resulting +0300 %coefficient is the weighted sum of the previous +0301 irrevModel.c(involvedRxns(1))=irrevModel.c(involvedRxns(1))+irrevModel.c(involvedRxns(2))*stoichRatio; +0302 +0303 %Iterate again +0304 mergedSome=true; +0305 end +0306 end +0307 +0308 %All possible reactions merged +0309 if mergedSome==false +0310 break; +0311 end 0312 -0313 %Remove reactions -0314 irrevModel=removeReactions(irrevModel,I); +0313 %Now delete all reactions that involve no metabolites +0314 I=find(sum(irrevModel.S~=0)==0); 0315 -0316 %Remove metabolites -0317 notInUse=orphanedMets(irrevModel); -0318 irrevModel=removeMets(irrevModel,notInUse); -0319 end -0320 -0321 reducedModel=irrevModel; -0322 end -0323 end -0324 -0325 function I=orphanedMets(model) -0326 %Metabolites that take part in no reaction, and can therefore be removed -0327 %without changing what the model can do. Metabolites with a non-zero -0328 %model.b are kept: b is a boundary condition on the metabolite, so removing -0329 %the row would drop that constraint along with it. -0330 I=sum(model.S~=0,2)==0 & ~any(model.b~=0,2); -0331 end +0316 %Remove reactions +0317 irrevModel=removeReactions(irrevModel,I); +0318 +0319 %Remove metabolites +0320 notInUse=orphanedMets(irrevModel); +0321 irrevModel=removeMets(irrevModel,notInUse); +0322 end +0323 +0324 reducedModel=irrevModel; +0325 end +0326 end +0327 +0328 function I=orphanedMets(model) +0329 %Metabolites that take part in no reaction, and can therefore be removed +0330 %without changing what the model can do. Metabolites with a non-zero +0331 %model.b are kept: b is a boundary condition on the metabolite, so removing +0332 %the row would drop that constraint along with it. +0333 I=sum(model.S~=0,2)==0 & ~any(model.b~=0,2); +0334 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/utils/grRuleToDNF.html b/docs/html/utils/grRuleToDNF.html index 61c0941e..996d9722 100644 --- a/docs/html/utils/grRuleToDNF.html +++ b/docs/html/utils/grRuleToDNF.html @@ -36,7 +36,10 @@

    DESCRIPTION ^DESCRIPTION ^SOURCE CODE ^% Parameters 0009 % ---------- 0010 % rule : char or string or struct -0011 % a grRule, or a tree from parseGrRule. -0012 % -0013 % Returns -0014 % ------- -0015 % clauses : cell -0016 % one cell array of gene IDs per AND-clause. An empty rule yields {}. A -0017 % rule with no OR anywhere yields a single clause. -0018 % -0019 % Notes -0020 % ----- -0021 % Gene order within a clause follows the order the genes appear in the rule. -0022 % Duplicates are not removed: "G1 and G1" stays a two-element clause, since -0023 % collapsing it would silently rewrite the user's rule. -0024 % -0025 % The number of clauses is the product of the OR-arities across a complex, -0026 % so a pathological rule can expand combinatorially. Callers that build one -0027 % object per clause should check numel(clauses) first. -0028 % -0029 % Examples -0030 % -------- -0031 % grRuleToDNF('(G1 and G2) or G3') % {{'G1','G2'}, {'G3'}} -0032 % grRuleToDNF('G1 and (G2 or G3)') % {{'G1','G2'}, {'G1','G3'}} -0033 % -0034 % See also: parseGrRule, isDnfGrRule, expandModel -0035 -0036 if ~isstruct(rule) -0037 rule=parseGrRule(rule); -0038 end -0039 if isempty(rule) -0040 clauses={}; -0041 return -0042 end -0043 clauses=nodeToDNF(rule); -0044 end -0045 -0046 -0047 function clauses=nodeToDNF(node) -0048 switch node.type -0049 case 'gene' -0050 clauses={{node.id}}; -0051 case 'or' -0052 % OR: concatenate the disjuncts' clauses. -0053 clauses={}; -0054 for i=1:numel(node.children) -0055 clauses=[clauses nodeToDNF(node.children{i})]; %#ok<AGROW> -0056 end -0057 case 'and' -0058 % AND: cross-product of the children's clauses. -0059 clauses={{}}; -0060 for i=1:numel(node.children) -0061 childDNF=nodeToDNF(node.children{i}); -0062 newClauses=cell(1,numel(clauses)*numel(childDNF)); -0063 k=0; -0064 for a=1:numel(clauses) -0065 for b=1:numel(childDNF) -0066 k=k+1; -0067 newClauses{k}=[clauses{a} childDNF{b}]; -0068 end -0069 end -0070 clauses=newClauses; -0071 end -0072 otherwise -0073 error('RAVEN:badGrRule',['Unexpected grRule node type: ' node.type]); -0074 end -0075 end +0011 % a grRule, or a tree from parseGrRule. A grRule is read by parseGrRule: +0012 % genes joined by "and"/"or" in any case or by "&", "&&", "|", "||", +0013 % each operator separated from the gene ids by whitespace or a bracket, +0014 % with AND binding tighter than OR. +0015 % +0016 % Returns +0017 % ------- +0018 % clauses : cell +0019 % one cell array of gene IDs per AND-clause. An empty rule yields {}. A +0020 % rule with no OR anywhere yields a single clause. +0021 % +0022 % Notes +0023 % ----- +0024 % A malformed rule (an operator without an operand, unbalanced or empty +0025 % parentheses, two genes without an operator between them) raises +0026 % RAVEN:badGrRule. +0027 % +0028 % Gene order within a clause follows the order the genes appear in the rule. +0029 % Duplicates are not removed: "G1 and G1" stays a two-element clause, since +0030 % collapsing it would silently rewrite the user's rule. +0031 % +0032 % The number of clauses is the product of the OR-arities across a complex, +0033 % so a pathological rule can expand combinatorially. Callers that build one +0034 % object per clause should check numel(clauses) first. +0035 % +0036 % Examples +0037 % -------- +0038 % grRuleToDNF('(G1 and G2) or G3') % {{'G1','G2'}, {'G3'}} +0039 % grRuleToDNF('G1 and (G2 or G3)') % {{'G1','G2'}, {'G1','G3'}} +0040 % +0041 % See also: parseGrRule, isDnfGrRule, expandModel +0042 +0043 if ~isstruct(rule) +0044 rule=parseGrRule(rule); +0045 end +0046 if isempty(rule) +0047 clauses={}; +0048 return +0049 end +0050 clauses=nodeToDNF(rule); +0051 end +0052 +0053 +0054 function clauses=nodeToDNF(node) +0055 switch node.type +0056 case 'gene' +0057 clauses={{node.id}}; +0058 case 'or' +0059 % OR: concatenate the disjuncts' clauses. +0060 clauses={}; +0061 for i=1:numel(node.children) +0062 clauses=[clauses nodeToDNF(node.children{i})]; %#ok<AGROW> +0063 end +0064 case 'and' +0065 % AND: cross-product of the children's clauses. +0066 clauses={{}}; +0067 for i=1:numel(node.children) +0068 childDNF=nodeToDNF(node.children{i}); +0069 newClauses=cell(1,numel(clauses)*numel(childDNF)); +0070 k=0; +0071 for a=1:numel(clauses) +0072 for b=1:numel(childDNF) +0073 k=k+1; +0074 newClauses{k}=[clauses{a} childDNF{b}]; +0075 end +0076 end +0077 clauses=newClauses; +0078 end +0079 otherwise +0080 error('RAVEN:badGrRule',['Unexpected grRule node type: ' node.type]); +0081 end +0082 end
    Generated by m2html © 2005
    \ No newline at end of file From 1b742dff964b291bb6cc9de64e8204d292c8ef42 Mon Sep 17 00:00:00 2001 From: Eduard Kerkhoven Date: Sat, 31 Oct 2026 16:36:22 +0100 Subject: [PATCH 38/48] Rewrite the help blocks under the documentation prose rules (#16) * doc: rewrite the help blocks under the documentation prose rules The help blocks state behaviour in impersonal, present-tense sentences with British spelling, without emphasis, history or slash alternatives. Code is unchanged; the sections that `docs/scripts/gen_api_pages.py` parses are kept, and `reverseRxns`, `gapFillMILP`, `gapFillFastLP` and `gapFillTopological` use the RAVEN 3 help structure. Statements that differed from the code are corrected, among them: - `simplifyModel` `deleteDuplicates` keeps the first occurrence; bounds and objective are merged by `contractModel`. - `removeBadRxns` raises an error for a supplied `refModel`; `removeGenes` leaves a gene that only occurs in a rule part of a removed gene. - `addRxns` and `changeRxns`: new metabolites take annotation from a same-name metabolite through `addMets`; ids are generated with prefix `m_`; `model.annotation`, `model.comps` and `setParam` are the names the code uses. - `replaceMets`: `removedRxns` and `idxDuplRxns` are never assigned. - `setExchangeBounds` raises an error when `compNames` or `metComps` is missing with `mediaOnly`. - `getModelFromHomology`: `maxE` is strict, `preserveNotes` does not touch `rxnReferences`, `mapNewGenesToOld` applies at strictness 2. - `getKEGGModelForOrganism`: `dataDir`, `outDir`, `maxPhylDist` and `cutOff` follow the code. - `getMinNrFluxes`: `params` is used; the large flux is `max(5 x largest LP flux, 1000)` for `irrev`. - `randomSampling`, `getFluxZ`, `getMinimalMedium`, `haveFlux`, `runProductionEnvelope`, `traceFluxPath`, `ftINIT`, `ftINITInternalAlg`, `rescaleModelForINIT`, `scoreModel`, `checkRaven` and others: definitions of options and outputs follow the code. * docs: changelog entry for the documentation rewrite --- CHANGELOG.md | 4 + checkInstallation.m | 8 +- checkRaven.m | 41 ++--- src/INIT/INITStepDesc.m | 4 +- src/INIT/ftINIT.m | 171 ++++++++++-------- src/INIT/ftINITInternalAlg.m | 90 ++++----- src/INIT/getINITSteps.m | 34 ++-- src/INIT/groupRxnScores.m | 13 +- src/INIT/mergeLinear.m | 12 +- src/INIT/parseHPA.m | 5 +- src/INIT/parseHPArna.m | 3 +- src/INIT/prepINITModel.m | 54 +++--- src/INIT/rescaleModelForINIT.m | 17 +- src/INIT/scoreModel.m | 42 ++--- src/INIT/tINIT/getINITModel.m | 92 +++++----- src/INIT/tINIT/runINIT.m | 55 +++--- src/analysis/FSEOF.m | 38 ++-- src/analysis/analyzeSampling.m | 18 +- src/analysis/compareFluxes.m | 53 +++--- src/analysis/findGeneDeletions.m | 29 ++- src/analysis/getAllSubGraphs.m | 6 +- src/analysis/getAllowedBounds.m | 16 +- src/analysis/getFluxZ.m | 9 +- src/analysis/getMinimalMedium.m | 34 ++-- src/analysis/haveFlux.m | 22 +-- src/analysis/randomSampling.m | 73 ++++---- src/analysis/reporterMetabolites.m | 23 ++- src/analysis/runDynamicFBA.m | 19 +- src/analysis/runProductionEnvelope.m | 10 +- src/analysis/runRobustnessAnalysis.m | 12 +- src/analysis/runSimpleOptKnock.m | 15 +- src/analysis/sampleACHR.m | 7 +- src/analysis/sampleCHRR.m | 24 +-- src/analysis/sampleMaxVolEllipse.m | 17 +- src/analysis/sampleWarmupPoints.m | 9 +- src/analysis/traceFluxPath.m | 46 ++--- src/analysis/walkFluxes.m | 30 +-- src/annotation/assignSBOterms.m | 47 ++--- src/annotation/editMiriam.m | 28 ++- src/annotation/extractMiriam.m | 26 ++- src/annotation/loadDeltaGCSV.m | 21 ++- src/annotation/saveDeltaGCSV.m | 10 +- src/biomass/fitParameters.m | 26 +-- src/biomass/getBiomassFractions.m | 38 ++-- src/biomass/scaleBiomassFraction.m | 13 +- src/biomass/scaleBiomassPseudoreaction.m | 19 +- src/biomass/setGAM.m | 17 +- src/comparison/compareMultipleModels.m | 65 ++++--- src/comparison/diffModels.m | 49 +++-- src/conditions/applyCondition.m | 39 ++-- src/conversion/addIdentifierPrefix.m | 17 +- src/conversion/ravenCobraWrapper.m | 23 ++- src/conversion/removeIdentifierPrefix.m | 24 +-- src/conversion/standardizeModelFieldOrder.m | 11 +- src/curation/curateModelFromTables.m | 53 +++--- src/curation/downloadGenomeData.m | 19 +- src/curation/getGeneData.m | 52 +++--- src/curation/processProteinFastaFile.m | 32 ++-- src/curation/renameModelGenes.m | 51 +++--- src/deprecated/canConsume.m | 2 +- src/deprecated/canProduce.m | 2 +- src/deprecated/consumeSomething.m | 10 +- src/deprecated/makeSomething.m | 2 +- src/gapfilling/canExchange.m | 12 +- src/gapfilling/checkProduction.m | 50 +++-- src/gapfilling/fillGaps.m | 104 ++++++----- src/gapfilling/findLeakMetabolite.m | 48 ++--- src/gapfilling/fitTasks.m | 36 ++-- src/gapfilling/ftINITFillGaps.m | 48 ++--- src/gapfilling/gapFillFastCore.m | 12 +- src/gapfilling/gapFillFastLP.m | 34 ++-- src/gapfilling/gapFillMILP.m | 48 ++--- src/gapfilling/gapFillSwiftCore.m | 18 +- src/gapfilling/gapFillTopological.m | 38 ++-- src/gapfilling/gapReport.m | 22 +-- src/installation/addRavenToUserPath.m | 13 +- src/installation/checkFunctionUniqueness.m | 12 +- src/installation/downloadRavenBinaries.m | 16 +- src/installation/fetchToolboxAsset.m | 13 +- src/installation/findRAVENroot.m | 21 ++- src/installation/removeRavenFromPath.m | 7 +- src/installation/updateDocumentation.m | 12 +- src/io/checkFileExistence.m | 20 +- src/io/cleanSheet.m | 11 +- src/io/exportForGit.m | 31 ++-- src/io/exportModel.m | 16 +- src/io/exportToExcelFormat.m | 8 +- src/io/importModel.m | 37 ++-- src/io/parseYAML.m | 17 +- src/io/readYAMLmodel.m | 10 +- src/io/writeYAMLmodel.m | 15 +- src/localization/defaultCompartmentMap.m | 32 ++-- src/localization/getUniProtScores.m | 40 ++-- src/localization/parseScores.m | 83 ++++----- src/localization/predictLocalization.m | 65 +++---- src/manipulation/addExchangeRxns.m | 16 +- src/manipulation/addGenesRaven.m | 13 +- src/manipulation/addMets.m | 34 ++-- src/manipulation/addRxns.m | 92 +++++----- src/manipulation/addRxnsGenesMets.m | 37 ++-- src/manipulation/addTransport.m | 24 +-- src/manipulation/changeGrRules.m | 15 +- src/manipulation/changeRxns.m | 54 +++--- src/manipulation/closeModel.m | 10 +- src/manipulation/contractModel.m | 35 ++-- src/manipulation/convertToIrrev.m | 6 +- src/manipulation/copyToComps.m | 24 +-- src/manipulation/expandModel.m | 24 +-- src/manipulation/findDuplicateRxns.m | 12 +- src/manipulation/findPotentialErrors.m | 30 ++- src/manipulation/generateNewIds.m | 15 +- src/manipulation/mergeCompartments.m | 38 ++-- src/manipulation/mergeModels.m | 30 +-- src/manipulation/removeBadRxns.m | 94 +++++----- src/manipulation/removeGenes.m | 16 +- src/manipulation/removeLowScoreGenes.m | 47 ++--- src/manipulation/removeMets.m | 15 +- src/manipulation/removeReactions.m | 7 +- src/manipulation/replaceMets.m | 34 ++-- src/manipulation/reverseRxns.m | 29 ++- src/manipulation/setExchangeBounds.m | 43 +++-- src/manipulation/setParam.m | 26 ++- src/manipulation/simplifyModel.m | 37 ++-- src/manipulation/sortIdentifiers.m | 4 +- src/manipulation/sortModel.m | 35 ++-- src/manipulation/standardizeGrRules.m | 30 +-- src/queries/buildEquation.m | 9 +- src/queries/checkModelStruct.m | 33 ++-- src/queries/constructEquations.m | 22 ++- src/queries/constructS.m | 22 ++- src/queries/getAllRxnsFromGenes.m | 9 +- src/queries/getElementalBalance.m | 24 ++- src/queries/getExchangeRxns.m | 9 +- src/queries/getGenesFromGrRules.m | 18 +- src/queries/getIndexes.m | 18 +- src/queries/getRxnsInComp.m | 11 +- src/queries/getTransportRxns.m | 3 +- src/queries/modelSummary.m | 28 +-- src/queries/parseFormulas.m | 24 ++- src/queries/parseRxnEqu.m | 11 +- src/queries/printFluxes.m | 13 +- src/queries/printModelStats.m | 22 ++- src/reconstruction/guessComposition.m | 32 ++-- src/reconstruction/homology/getBlast.m | 15 +- src/reconstruction/homology/getDiamond.m | 20 +- .../homology/getModelFromHomology.m | 84 +++++---- .../homology/hitsFromSuppOrth.m | 2 +- .../homology/makeFakeBlastStructure.m | 18 +- src/reconstruction/kegg/buildGlobalGPR.m | 14 +- .../kegg/buildGlobalKEGGModel.m | 14 +- .../kegg/getKEGGModelForOrganism.m | 116 ++++++------ src/reconstruction/kegg/getModelFromKEGG.m | 23 ++- src/reconstruction/kegg/getPhylDist.m | 2 +- src/reconstruction/kegg/keggDataVersion.m | 11 +- src/reconstruction/kegg/readKEGGTable.m | 10 +- src/solver/getMinNrFluxes.m | 58 +++--- src/solver/optimizeProb.m | 27 +-- src/solver/setRavenSolver.m | 8 +- src/solver/solveLP.m | 47 ++--- src/solver/splitProbForConditioning.m | 25 +-- src/tasks/checkTasks.m | 30 +-- src/tasks/parseTaskList.m | 64 +++---- src/utils/convertCharArray.m | 8 +- src/utils/deprecationWarning.m | 8 +- src/utils/grRuleToDNF.m | 8 +- src/utils/grRuleToString.m | 7 +- src/utils/isDnfGrRule.m | 10 +- src/utils/legacyMethodNotice.m | 25 ++- src/utils/parallelWorkersRAVEN.m | 20 +- src/utils/parseGrRule.m | 24 ++- src/utils/parseRAVENargs.m | 49 +++-- src/utils/printOrange.m | 10 +- src/utils/progressReport.m | 32 ++-- src/utils/ravenList.m | 13 +- src/utils/ravenModelFields.m | 15 +- src/utils/runRAVENtests.m | 5 +- src/utils/setRavenProgress.m | 17 +- 177 files changed, 2558 insertions(+), 2315 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f197f7a1..cb5d234a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -57,6 +57,10 @@ case, `&`, `&&`, `|`, `||`, operators as whole words, so `G1&G2` is one gene id), and that a malformed rule raises `RAVEN:badGrRule`. +### Documentation + +* Help blocks of 176 files are rewritten in impersonal, present-tense wording with British spelling; the code is unchanged. Statements that differed from the code are corrected, for example `simplifyModel` `deleteDuplicates` (first occurrence kept), `removeBadRxns` `refModel`, `replaceMets` outputs, `getMinNrFluxes` `params`, `checkRaven` `developMode` and the `downloadRavenBinaries` offline bundle. `reverseRxns` has the RAVEN 3 help structure. + ## 3.0.0b1 — 2026-09-07 First beta toward 3.0. Changes below are relative to RAVEN 2.9.3, the last 2.x release: diff --git a/checkInstallation.m b/checkInstallation.m index 298453cc..8bfe180b 100644 --- a/checkInstallation.m +++ b/checkInstallation.m @@ -3,10 +3,10 @@ % % Forwards all arguments and outputs to checkRaven. % -% NOTE: this function is run before RAVEN has been added to the MATLAB -% path, so it must not call any other RAVEN functions until checkRaven has -% added RAVEN to the path itself. It cannot use the shared -% utils/deprecationWarning for the same reason. +% This function runs before RAVEN is on the MATLAB path. It therefore +% calls no other RAVEN function until checkRaven has added RAVEN to the +% path, and it does not use the shared deprecationWarning function in +% the utils folder. % % Usage: [currVer, installType] = checkInstallation(developMode, checkBinaries) diff --git a/checkRaven.m b/checkRaven.m index a0963f89..380c1e2f 100755 --- a/checkRaven.m +++ b/checkRaven.m @@ -1,35 +1,34 @@ function [currVer, installType] = checkRaven(developMode, checkBinaries) -% checkRaven -% Checks that all necessary functions are installed and working. It also -% checks whether there are any functions -% with overlapping names between RAVEN and other toolboxes or -% user-defined functions, which are accessible from MATLAB pathlist +% checkRaven Check the RAVEN installation and set up the MATLAB path. % -% NOTE: this function is run before RAVEN has been added to the MATLAB -% path, so it must not call any other RAVEN functions until it has added -% RAVEN to the path itself. Its arguments are therefore parsed directly -% rather than via parseRAVENargs. +% Reports the installation type and RAVEN version, adds RAVEN to the +% MATLAB path, tests model import and export, the LP solvers and the +% binary executables, and checks for functions with overlapping names +% between RAVEN and other toolboxes or user-defined functions on the +% MATLAB path. +% +% This function runs before RAVEN is on the MATLAB path. It therefore +% calls no other RAVEN function until it has added RAVEN to the path, and +% it parses its arguments without parseRAVENargs. % % Parameters % ---------- -% developMode logical indicating development mode, which includes -% testing of binaries that are required to update KEGG -% HMMs (default false). If 'versionOnly' is -% specified, only the version is reported as currVer, no -% further installation or tests are performed. -% checkBinaries logical whether non-developMode binaries should be -% checked for functionality. If false, it overwrites -% developMode. Default true. +% developMode 'versionOnly' to return the version in currVer without +% any further installation check or test. Any other +% value runs all checks. Default false. +% checkBinaries logical, true to test that the BLAST+, DIAMOND and +% HMMER binaries execute. If false, the binary check is +% skipped. Default true. % % Output: % currVer current RAVEN version % installType how RAVEN is installed -% 0: via git (as .git folder is found) +% 0: via git (a .git folder is found) % 1: as MATLAB Add-On -% 2: neither of the above, direct download of ZIP file -% This matches the installations mentioned in the wiki: +% 2: direct download of a ZIP file +% The values correspond to the installation types +% Advanced (0), Easy (1) and Medium (2) described at % https://raven-docs.readthedocs.io/en/latest/installation/raven.html -% 0 = advanced / 1 = easy / 2 = medium % % Usage: [currVer, installType] = checkRaven(developMode) diff --git a/src/INIT/INITStepDesc.m b/src/INIT/INITStepDesc.m index 6bc3acb7..5f9fce4d 100644 --- a/src/INIT/INITStepDesc.m +++ b/src/INIT/INITStepDesc.m @@ -1,6 +1,6 @@ classdef INITStepDesc % Describes a step in the ftINIT algorithm. A cell array of objects of this -% class is used as input to ftINIT to specify how the algorithm should be run. +% class is the input to ftINIT that specifies how the algorithm is run. properties PosRevOff %If true, positive-scored reversible reactions are removed %from the MILP problem (used in step 1 of ftINIT) @@ -21,7 +21,7 @@ % b8 - All rxns without GPRs MetsToIgnore % Structure describing mets that can be removed from the model - % before running ftINIT, such as water etc. + % before running ftINIT, such as water and other ubiquitous metabolites. % .simpleMets % .mets Names of metabolites to remove % .compsToKeep Compartments for which metabolites should be kept. diff --git a/src/INIT/ftINIT.m b/src/INIT/ftINIT.m index 61de0623..6b5ba6e4 100644 --- a/src/INIT/ftINIT.m +++ b/src/INIT/ftINIT.m @@ -1,132 +1,147 @@ function [model, metProduction, addedRxnsForTasks, deletedRxnsInINIT, fullMipRes] = ftINIT(prepData, tissue, celltype, hpaData, varargin) % ftINIT Generate a model using the ftINIT algorithm. % -% Main function for generating a model using the ftINIT algorithm, based on -% proteomics and/or transcriptomics and/or metabolomics and/or metabolic tasks. -% The algorithm is not designed for running with metabolomics only. The -% function prepINITModel needs to be run first for the template model (such as -% the generic Human-GEM), but only needs to be run once. This function -% precalculates things independent of the omics to speed up the model -% generation process, and outputs the prepData, which is input to this +% Generates a model from proteomics, transcriptomics, metabolomics and +% metabolic tasks, in any combination except metabolomics alone. The function +% prepINITModel must be run first on the template model (such as the generic +% Human-GEM). It runs once per template and precalculates the parts that are +% independent of the omics data. Its output, prepData, is the input to this % function. % % Parameters % ---------- % prepData : struct -% the prepData for the model. +% the prepData for the model, as returned by prepINITModel. % tissue : char -% tissue to score for. Should exist in either hpaData.tissues or +% tissue to score for. Must exist in hpaData.tissues or % transcrData.tissues. % celltype : char -% cell type to score for. Should exist in either hpaData.celltypes or -% transcrData.celltypes for this tissue (default is to use the max values -% among all the cell types for the tissue). +% cell type to score for. Must exist in hpaData.celltypes or +% transcrData.celltypes for this tissue. If empty, the maximum value +% among all the cell types of the tissue is used. % hpaData : struct -% HPA data structure from parseHPA (optional if transcrData is supplied, -% default []). +% HPA data structure from parseHPA (required if transcrData is not +% supplied, default []). % % Name-Value Arguments % -------------------- % transcrData : struct -% gene expression data structure (optional if hpaData is supplied, default -% []). With fields: +% gene expression data structure (required if hpaData is not supplied, +% default []). With fields: % % - genes : cell array with the unique gene names. -% - tissues : cell array with the tissue names. The list may not be unique, -% as there can be multiple cell types per tissue. +% - tissues : cell array with the tissue names. The list can contain +% duplicates, as there can be multiple cell types per tissue. % - celltypes : cell array with the cell type names for each tissue. % - levels : GENESxTISSUES array with the expression level for each gene in -% each tissue/celltype. NaN should be used when no measurement was -% performed. +% each tissue and cell type. NaN marks a gene that was not measured. % - threshold : a single value or a vector of gene expression thresholds, -% above which genes are considered to be "expressed". (default 1; by -% default, the mean expression levels of each gene across all tissues in -% transcrData will be used as the threshold values). -% - singleCells : binary value selecting whether to use the single-cell -% algorithm to identify expressed genes. If used, specify cell -% subpopulations in CELLTYPES (default []). -% - plotResults : true if single cell probability distributions should be -% plotted (default false). +% above which genes are considered to be "expressed". If omitted, the +% mean expression level of each gene across all tissues in transcrData +% is used as the threshold. % metabolomicsData : cell % cell array with metabolite names that the model should produce (default % []). % INITSteps : cell % specifies the steps in the algorithm. For more info, see INITStepDesc and -% getINITSteps (default getINITSteps(), which is the standard ftINIT). +% getINITSteps (default getINITSteps([],'1+1'), the standard ftINIT). % removeGenes : logical -% if true, low-abundance genes will be removed from grRules, unless they +% if true, low-score genes are removed from grRules, unless they % are the only gene associated with a reaction, or a subunit of an enzyme % complex (see "removeLowScoreGenes" function for details). If false, -% grRules will not be modified; however, genes that were associated only -% with removed reactions will not be present in the final model (default -% true). +% grRules are not modified; genes that were associated only with removed +% reactions are not present in the final model (default true). % useScoresForTasks : logical -% true if the calculated reaction scores should be used as weights when -% fitting to tasks (default true). +% true if the calculated reaction scores are used as weights when +% fitting to tasks. Scores above -0.1 are set to -0.1 for this step +% (default true). % paramsFT : struct -% *obsolete option*. +% solver parameters for the gap-filling step that fits the tasks, passed +% to fitTasks as params (default []). % verbose : logical -% if true, the MILP progression will be shown (default false). +% if true, the MILP progression is shown (default false). % resolveTies : logical -% if true, pin each step's degenerate MILP to a canonical answer instead -% of relying on the solver Seed alone: hold the score objective at its -% optimum, then minimise the count of removable ("on") reactions, then, -% among the sparsest, minimise their summed reaction-id rank. This -% applies to every main-extraction step and to the final gap-filling -% step (fitTasks' resolveTies) -- see raven-gecko-parity#104 and -% raven-toolbox's resolve_ties. In each main-extraction step but the -% last (ftINITInternalAlg) the fluxes, which set the direction and -% magnitude a reaction is forced at in the next step, then come from the -% least-total-flux solution for that on/off state and, among those, from -% the one with the least id-rank-weighted backward flux (a merged -% reaction ranked by the smallest original id in its group). The last -% step skips these flux phases, as no step reads its fluxes. A step -% whose primary solve stops at the time limit keeps its primary -% solution without any of these phases; the count and id-rank phases -% get half of the step's TimeLimit each, and a warning -% names each phase that does not prove optimal or finds no solution -% (default false). +% if true, the degenerate MILP of each step is solved to a canonical +% answer in addition to the solver Seed: the score objective is held at +% its optimum, then the count of removable ("on") reactions is +% minimised, then, among the sparsest solutions, their summed +% reaction-id rank is minimised. This applies to every main-extraction +% step and to the final gap-filling step (resolveTies of fitTasks); see +% raven-gecko-parity#104 and resolve_ties in raven-toolbox. In each +% main-extraction step except the last (ftINITInternalAlg), the fluxes +% set the direction and magnitude at which a reaction is forced in the +% next step. They are taken from the least-total-flux solution for that +% on/off state and, among those, from the solution with the least +% id-rank-weighted backward flux (a merged reaction is ranked by the +% smallest original id in its group). The last step skips these flux +% phases, as no later step reads its fluxes. A step whose primary solve +% stops at the time limit keeps its primary solution without any of +% these phases. The count and id-rank phases get half of the step's +% TimeLimit each. A warning names each phase that does not prove optimal +% or finds no solution. Measured with raven-toolbox's ftinit, whose phase +% handling differs from ftINIT, on one cell line (Human-GEM v2.0.0-38, +% seeds 1234 and 7, time limit 1800 s): two seeds with the default +% escalation differ by 110 reactions and 17 predicted essential genes, +% and with resolveTies alone by 41 reactions and 1 gene, at about 3.6 +% times the run time (about 14 min against 4 min) (default false). % proveAbsGap : double -% if set, prove each step to this fixed *absolute* MILP gap in a single -% solve, instead of the relative-gap escalation below (stp.MILPParams). -% The escalation's final near-zero-objective run otherwise accepts an -% arbitrary within-gap incumbent -- measured on genome-scale Human-GEM to -% return a solution 2.0-4.0 objective units below the true optimum (see -% raven-toolbox's prove_abs_gap and the ftINIT reproducibility study on -% raven-docs). 1.0 is the recommended value: tighter (down to 0.05) -% proves nothing further and costs substantially more runtime. Pairs -% naturally with resolveTies (default [], i.e. use the escalation). +% if set, each step is proven to this fixed absolute MILP gap in a single +% solve, replacing the relative-gap escalation in stp.MILPParams. The +% final near-zero-objective run of the escalation accepts an arbitrary +% incumbent within the gap. A warning is raised when a step reaches the +% time limit before the proof. The figures below were measured with +% raven-toolbox's ftinit, whose phase handling differs from ftINIT, on one +% cell line (Human-GEM v2.0.0-38, time limit 1800 s) with resolveTies on +% and without gap-filling, in a sweep over four seeds (1234, 7, 1 and 2). +% The step 1 objective of the escalation ended 4.0 to 12.0 units below the +% proven optimum; with seeds 1234 and 7 alone it ended 7.0 and 12.0 +% below. A value of 5.0 stopped 2.0 below the optimum for one seed (seed +% 2), and values from 2.0 to 0.05 reached it for all four seeds and gave +% the same models. Every primary solve of the steps (48 solves) ended +% optimal; the flux phases phase2c-minflux (3 of 4 seeds) and +% phase2d-direction (4 of 4 seeds) reached the time limit and were adopted +% as unproven incumbents. With 1.0 and resolveTies the models of two seeds +% differ by 1 reaction and 0 predicted essential genes, at about 26 times +% the run time of the default escalation (about 100 min against 4 min); +% the mean of a whole build was 5766 s at 0.05 and 5641 s at 1.0. The +% study recommends 1.0 together with resolveTies when the variation of +% resolveTies alone is too large and the run time is acceptable. Its +% full-pipeline runs used a time limit of 600 s per step and its +% determinism builds 1800 s (default [], which uses the escalation). % % Returns % ------- % model : struct % the resulting model structure. -% metProduction : double -% array that indicates which of the metabolites in metabolomicsData that -% could be produced. Note that this is before the gap-filling process to -% enable defined tasks. To see which metabolites that can be produced in -% the final model, use canExchange: -% -% - -2 : metabolite name not found in model. -% - -1 : metabolite found, but it could not be produced. -% - 1 : metabolite could be produced. +% metProduction : logical +% array from the last MILP step that indicates which of the metabolites in +% metabolomicsData could be produced (true) or not (false). Metabolites +% with a producing reaction that is essential are not part of the MILP +% and are not in the array. The array is determined before the +% gap-filling for the defined tasks. To see which metabolites can be +% produced in the final model, use canExchange. % addedRxnsForTasks : cell % cell array of the reactions which were added in order to perform the % tasks. % deletedRxnsInINIT : cell -% cell array of reactions deleted because they could not carry flux (INIT -% requires a functional input model). +% cell array of the reactions of the template model that are not in the +% final model, excluding exchange reactions (these are all kept). It +% contains the reactions removed by the MILP steps that gap-filling did +% not add back. % fullMipRes : struct % the solver results from the last MILP step run. % % Notes % ----- -% Exchange metabolites should normally not be removed from the model when using -% this approach, since checkTasks/fitTasks rely on putting specific constraints -% for each task. The INIT algorithm will remove exchange metabolites if any are +% The template model keeps its exchange metabolites, +% since checkTasks and fitTasks put specific constraints on them for each +% task. The INIT algorithm removes exchange metabolites if any are % present. Use importModel(file,false) to import a model with exchange % metabolites remaining. % +% ftINIT passes a prodWeight of 5 (the reward for each produced metabolite of +% metabolomicsData) to ftINITInternalAlg, whose own default is 0.5. +% p=parseRAVENargs(varargin, {'transcrData',[]; 'metabolomicsData',[]; 'INITSteps',[]; 'removeGenes',[]; 'useScoresForTasks',[]; 'paramsFT',[]; 'verbose',false; 'resolveTies',false; 'proveAbsGap',[]}); transcrData=p.transcrData; diff --git a/src/INIT/ftINITInternalAlg.m b/src/INIT/ftINITInternalAlg.m index af29c682..1665b356 100644 --- a/src/INIT/ftINITInternalAlg.m +++ b/src/INIT/ftINITInternalAlg.m @@ -1,55 +1,61 @@ function [deletedRxns,metProduction,res,turnedOnRxns,fluxes]=ftINITInternalAlg(model,rxnScores,metData,essentialRxns,prodWeight,allowExcretion,remPosRev,params,startVals,fluxes,verbose,forceOn,resolveTies,rankKeys,pinFluxes) % ftINITInternalAlg -% This function runs the MILP for a step in ftINIT. +% Run the MILP for one step of ftINIT. % % model a reference model structure % rxnScores a vector of scores for the reactions in the model. % Positive scores are reactions to keep and negative -% scores are reactions to exclude. Rxns set to 0 are excluded -% from the problem. +% scores are reactions to exclude. Reactions with score 0 +% are not part of the problem % metData boolean matrix with mets as rows and rxns as columns % saying which reaction produces each detected met (optional, default []) % essentialRxns cell array of reactions that are essential and that -% have to be in the resulting model. This is normally -% used when fitting a model to task (see fitTasks) -% prodWeight a score that determines the value of having -% net-production of metabolites. This is a way of having -% a more functional network as it provides a reason for -% including bad reactions for connectivity reasons. This -% score is for each metabolite, and the sum of these weights -% and the scores for the reactions is what is optimized -% allowExcretion true if excretion of all metabolites should be allowed. +% have to be in the resulting model. ftINIT passes the +% reactions that the tasks require (see fitTasks) +% prodWeight a reward for each detected metabolite that has at least +% one producing reaction turned on; net production is not +% checked. The sum of these rewards and the scores for the +% reactions is optimised. The reward gives a reason for +% including reactions with a negative score for +% connectivity reasons, which gives a more functional +% network (default 0.5) +% allowExcretion true if excretion of all metabolites is allowed. % This results in fewer reactions being considered -% dead-ends, but all reactions in the resulting model may -% not be able to carry flux. If this is "false" then the +% dead-ends, but not every reaction in the resulting model +% can carry flux. If this is "false" then the % equality constraints are taken from model.b. If the -% input model lacks exchange reactions then this should -% probably be "true", or a large proportion of the model -% would be excluded for connectivity reasons +% input model lacks exchange reactions, set it to true; +% with false a large part of the model is excluded for +% connectivity reasons % remPosRev If true, the positive reversible reactions are removed from the problem. % This is used in step 1 of ftINIT % params parameters for the MILP, for example MIPGap and TimeLimit -% startVals Start values for the MILP, typically used when rerunning -% with a higher MIPGap, to use the results from the previous -% run -% fluxes Fluxes from the last run. -% verbose If true, the MILP progression will be shown. +% startVals Start values for the MILP. ftINIT passes the result of +% the previous run when it reruns the MILP with a higher +% MIPGap +% fluxes Fluxes from the last run. The flux of an essential +% irreversible reaction is forced to at least +% min(0.99*|flux|, 0.1), clamped to the reaction's capacity +% in the forced direction (its upper bound, as ftINIT +% passes reactions in the forward direction) +% verbose If true, the MILP progression is shown. % forceOn minimum flux magnitude enforced through a reaction once % it is turned on in the MILP (optional, default 0.1) -% resolveTies if true, pin the MILP's degenerate optimum to a canonical -% answer instead of relying on the solver Seed alone: hold -% the score objective at its optimum, then minimise the -% count of "on" removable (negative-score) reactions, then, -% among the sparsest, minimise their summed reaction-id -% rank. Positive-score reactions carry a continuous, not a -% free binary, on/off variable (pinned near 1 by the score -% objective), so only the negative-score ones are a genuine -% tie to resolve. The fluxes are then taken from the +% resolveTies if true, the MILP's degenerate optimum is solved to a +% canonical answer in addition to the solver Seed: the +% score objective is held at its optimum, then the count +% of "on" removable (negative-score) reactions is +% minimised, then, among the sparsest solutions, their +% summed reaction-id rank is minimised. Positive-score +% reactions have a continuous on/off variable (pinned near +% 1 by the score objective) and only the negative-score +% ones have a binary on/off variable, so only these are +% tied. The fluxes are then taken from the % least-total-flux solution for that on/off state and, % among those, from the one with the least id-rank-weighted % backward flux, so the directions and forced magnitudes -% the next ftINIT step reads from them are pinned too -- -% matches raven-toolbox's resolve_ties +% the next ftINIT step reads from them are fixed too. +% This follows resolve_ties in raven-toolbox % (raven-gecko-parity#104). The phases are skipped when % the primary solve stops at params.TimeLimit, and the % primary solution is kept. Phases 2a and 2b get half of @@ -71,22 +77,22 @@ % % deletedRxns reactions which were deleted by the algorithm (only % rxns included in the problem) -% metProduction array that indicates which of the -% metabolites in presentMets that could be -% produced -% 0: metabolite could not be produced -% 1: metabolite could be produced +% metProduction logical array that indicates which of the metabolites in +% metData could be produced (true) or not (false). +% Metabolites with a producing reaction that is essential +% are not part of the MILP and are not in the array % res The result from the MILP % turnedOnRxns The reactions determined to be present (only % rxns included in the problem) % fluxes The fluxes from the MILP % -% This function is the actual implementation of the algorithm. See +% This function is the implementation of the algorithm. See % ftINIT for a higher-level function for model reconstruction. % -% Usage: [deletedRxns,metProduction,res,turnedOnRxns,fluxes]=runINIT9(model,... -% rxnScores,presentMets,essentialRxns,prodWeight,allowExcretion,... -% remPosRev,params) +% Usage: [deletedRxns,metProduction,res,turnedOnRxns,fluxes]=ftINITInternalAlg(model,... +% rxnScores,metData,essentialRxns,prodWeight,allowExcretion,... +% remPosRev,params,startVals,fluxes,verbose,forceOn,resolveTies,... +% rankKeys,pinFluxes) if nargin < 12 || isempty(forceOn) forceOn = 0.1; diff --git a/src/INIT/getINITSteps.m b/src/INIT/getINITSteps.m index 7cbcdff1..afa7e051 100644 --- a/src/INIT/getINITSteps.m +++ b/src/INIT/getINITSteps.m @@ -1,38 +1,36 @@ function steps = getINITSteps(varargin) % getINITSteps Define the steps used as input to ftINIT. % -% Defines the steps in the way ftINIT is run. Note that all combinations of -% steps will not work. Avoid "exclude" if you want to define new -% ways to run the algorithm. +% Defines the steps in the way ftINIT is run. Not all combinations of steps +% work. A new series that sets the HowToUsePrevResults option to "exclude", +% which no predefined series uses, may not work. % % Name-Value Arguments % -------------------- % metsToIgnore : struct % structure describing mets that can be removed from the model before -% running ftINIT, such as water etc. With fields: +% running ftINIT, such as water and other ubiquitous metabolites. With fields: % % - simpleMets.mets : names of metabolites to remove. % - simpleMets.compsToKeep : compartments for which metabolites are kept. % -% (default []). +% (default []). Ignored for series "full". % series : char % describes the way to run ftINIT (default "1+1"): % -% - "1+1" : standard behavior. Step 1 and 2 described in the paper are +% - "1+1" : standard behaviour. Step 1 and 2 described in the paper are % merged into 1. % - "2+1" : the 3-step procedure described in the paper. Faster and -% slightly less accurate than "1+1 steps". -% - "1+0" : same as "1+1 steps", but skips step 3 described in the paper. -% This will result in a model including a lot of reactions without GPRs. -% It is particularly useful for structural comparison, since the -% reactions removed in step 3 may be a bit random and does not really add -% any extra information. Faster than "1+1 steps". -% - "2+0" : same as "2+1 steps", but skips step 3 described in the paper. -% Faster and slightly less accurate than "1+0 steps", but will yield -% similar results. -% - "full" : 1-step run - similar to the old tINIT version but without -% simplifications. Accurate, but very slow. This is mainly used for -% testing purposes. +% slightly less accurate than "1+1". +% - "1+0" : same as "1+1", but skips step 3 described in the paper. +% The model includes many reactions without GPRs. The series suits +% structural comparison, because the reactions removed in step 3 can +% be arbitrary and add no information. Faster than "1+1". +% - "2+0" : same as "2+1", but skips step 3 described in the paper. +% Faster and slightly less accurate than "1+0", with similar results. +% - "full" : 1-step run, similar to tINIT, without the model +% simplifications that ftINIT applies in the other series. Accurate, but +% slow. This series is used for testing. % % Returns % ------- diff --git a/src/INIT/groupRxnScores.m b/src/INIT/groupRxnScores.m index 453af242..28e01e05 100644 --- a/src/INIT/groupRxnScores.m +++ b/src/INIT/groupRxnScores.m @@ -1,8 +1,8 @@ function newRxnScores = groupRxnScores(model, origRxnScores, origRxnIds, groupIds, origRxnsToZero) % groupRxnScores -% This function sums up the reaction scores for all reactions that were merged -% into one by the linear merge. -% +% Sums the reaction scores of all reactions that were merged into one by the +% linear merge. +% % model The model with linearly merged rxns. % origRxnScores The rxnScores from the model before the linear merge. % origRxnIds The rxn ids of the model before the linear merge. @@ -11,10 +11,11 @@ % the reaction was not merged with any other reaction. A % non-zero integer means the reaction was merged with all % other rxns sharing the same integer. -% origRxnsToZero A logical vector saying which of the original rxns that should not +% origRxnsToZero A logical vector saying which of the original rxns should not % be part of the problem; the score for these reactions is set to 0. -% To keep 0 uniquely meaning "excluded", any genuine rxnScore that is -% (or sums to) exactly 0 is nudged to 0.01 instead. +% Since 0 means "excluded", an original score of 0 and a group +% score that sums to 0 while at least one score of the group +% is not 0 are set to 0.01. newRxnScores = zeros(length(model.rxns),1); diff --git a/src/INIT/mergeLinear.m b/src/INIT/mergeLinear.m index 66847e98..cf08d8ac 100644 --- a/src/INIT/mergeLinear.m +++ b/src/INIT/mergeLinear.m @@ -1,10 +1,10 @@ function [reducedModel,origRxnIds,groupIds,reversedRxns]=mergeLinear(model, noMergeRxns) % mergeLinear -% Simplifies a model by merging rxns with linear dependencies. There are two -% differences from the function in simplifyModel: -% 1) Here we return which reactions were merged -% 2) Here we allow reversible reactions to be merged as well, which will -% probably reduce the size of the model more. +% Simplifies a model by merging rxns with linear dependencies. It differs +% from the grouping in simplifyModel in two ways: +% 1) It returns which reactions were merged. +% 2) It also merges reversible reactions. +% The reduced model has no genes and empty grRules. % % model a model structure % noMergeRxns Cell array with reaction IDs that are not allowed to be @@ -17,7 +17,7 @@ % in the order of their first reaction in origRxnIds. % reversedRxns Vector of booleans, saying for each rxn if it is reversed in the combined rxns. % -% Usage: [reducedModel,origRxnIds,groupIds]=mergeLinear(model,noMergeRxns) +% Usage: [reducedModel,origRxnIds,groupIds,reversedRxns]=mergeLinear(model,noMergeRxns) reducedModel=model; diff --git a/src/INIT/parseHPA.m b/src/INIT/parseHPA.m index 1207f49e..5baf8c2f 100755 --- a/src/INIT/parseHPA.m +++ b/src/INIT/parseHPA.m @@ -10,8 +10,7 @@ % Name-Value Arguments % -------------------- % version : double -% accepted for backward compatibility but ignored; the format is now -% inferred from the column headers (default 19). +% ignored; the format is inferred from the column headers (default 19). % % Returns % ------- @@ -20,7 +19,7 @@ % % - genes : cell array with the unique gene IDs (Ensembl in v>=18) % - geneNames : cell array with the gene symbols (present in v>=18 only) -% - tissues : cell array with the tissue names (may not be unique; one +% - tissues : cell array with the tissue names (can repeat; one % entry per tissue-cell-type combination) % - celltypes : cell array with the cell type names for each tissue % - levels : cell array with the unique expression level labels diff --git a/src/INIT/parseHPArna.m b/src/INIT/parseHPArna.m index 3d6e7631..a955cbdf 100755 --- a/src/INIT/parseHPArna.m +++ b/src/INIT/parseHPArna.m @@ -10,8 +10,7 @@ % Name-Value Arguments % -------------------- % version : double -% accepted for backward compatibility but ignored; the format is now -% inferred from the column headers (default 19). +% ignored; the format is inferred from the column headers (default 19). % % Returns % ------- diff --git a/src/INIT/prepINITModel.m b/src/INIT/prepINITModel.m index e129778c..d2f96933 100644 --- a/src/INIT/prepINITModel.m +++ b/src/INIT/prepINITModel.m @@ -1,16 +1,15 @@ function prepData = prepINITModel(origRefModel, taskStruct, varargin) % prepINITModel Precompute omics-independent data for ftINIT. % -% Runs time-consuming calculation steps that are not dependent on the RNA-Seq -% data. +% Runs the calculation steps that do not depend on the omics data, once per +% template model. % % Parameters % ---------- % origRefModel : struct -% the model to use. Expected to be something such as Human-GEM, Mouse-GEM, -% etc. +% the template model, such as Human-GEM or Mouse-GEM. % taskStruct : struct -% the essential tasks. Can be loaded with for example +% the essential tasks, for example loaded with % taskStruct = parseTaskList('../data/metabolicTasks_Essential.txt'). % The task ids must be character vectors: a non-text id or a missing id % field raises RAVEN:badInput. Ids may repeat (for example as category @@ -23,31 +22,34 @@ % the spontaneous rxns (default {}). % convertGenes : logical % if true the genes are converted to gene names (from ENSEMBL). This -% relies on translateGrRules, which is distributed with Human-GEM rather -% than with RAVEN, so Human-GEM has to be on the MATLAB path (default -% false). +% uses translateGrRules, which is distributed with Human-GEM and not with +% RAVEN, so Human-GEM must be on the MATLAB path (default false). % customRxnsToIgnore : cell -% these reactions can be ignored in the ignore mask (specifying b7=1) -% (default {}). +% reactions that are left outside the MILP when bit b7 of the +% RxnsToIgnoreMask of INITStepDesc is 1 (default {}). % extComp : char -% name of the external compartment, typically 's' or 'e'. This is used for -% identifying exch and import rxns (default 'e'). +% name of the external compartment, such as 's' or 'e'. This is used for +% identifying exchange and import reactions (default 'e'). % skipScaling : logical -% if true the scaling step is not run on the minimal model. The scaling is -% there to remove large differences between the stoichiometric coefficients -% within a reaction, since such differences creates numerical issues in the -% ftINIT algorithm due to limitations in solver resolution. However, the -% current scaling step is also risky and may lead to that some reactions -% cannot carry flux, since it changes the stoichiometry of the reactions -% with large differences. If you experience problems where the solution is -% infeasible, it may be worth trying to turn off the scaling. Note that it -% is only the minModel that is scaled, the scaling will not be present in -% the final model (default false). +% if true the scaling step is not run on the minimal model. The scaling +% removes large differences between the stoichiometric coefficients +% within a reaction (see rescaleModelForINIT), since such differences +% create numerical issues in the ftINIT algorithm due to limitations in +% solver resolution. The scaling changes the stoichiometry of the +% reactions with large differences, and some reactions can become unable +% to carry flux, which can make the solution infeasible. If the solution +% is infeasible, turning the scaling off may help; the bounds then stay as in +% the template and the MILP caps the per-reaction big-M at 1000 (bigMCap +% in ftINITInternalAlg). In raven-toolbox's ftinit on Human-GEM (one cell +% line, big_m 1000) the pipeline without scaling was feasible but took +% 157 s against 83 s with scaling. The bounds of the scaled model are set to 1000 where the upper bound is above 0 and +% to -1000 where the lower bound is below 0; zero bounds stay 0. Only the +% minModel is scaled; the scaling is not present in the final model +% (default false). % runParallel : logical -% true to run the task-essentiality check in Step 3 (checkTasks) across -% parallel workers, since each task is independent of the others (see -% parallelWorkersRAVEN). Default false, matching the previous (serial) -% behaviour. +% true to run the task-essentiality check (checkTasks) across parallel +% workers, since each task is independent of the others (see +% parallelWorkersRAVEN) (default false, serial). % % Returns % ------- diff --git a/src/INIT/rescaleModelForINIT.m b/src/INIT/rescaleModelForINIT.m index 4082b70e..f0b02d37 100644 --- a/src/INIT/rescaleModelForINIT.m +++ b/src/INIT/rescaleModelForINIT.m @@ -1,21 +1,24 @@ function model=rescaleModelForINIT(model, varargin) % rescaleModelForINIT Rescale a model to avoid large stoichiometric differences. % -% Rescales the MILP problem in ftINIT to avoid large differences in flux -% magnitudes between reactions. Such differences cause among other things -% difficulties regarding tolerances for integer variables. For now it just -% scales down all reactions with high stoichiometric coefficients. +% Rescales the stoichiometric matrix for the MILP problem in ftINIT to reduce +% differences in flux magnitudes between reactions. Such differences cause, +% among other effects, difficulties with the tolerances for integer variables. +% In each reaction, each coefficient with an absolute value above +% maxStoichDiff times the smallest absolute coefficient of the reaction is +% set to that limit (keeping its sign). The reaction is then multiplied by a +% factor that sets the mean absolute coefficient to 1. % % Parameters % ---------- % model : struct -% the model to be modified (input and output). +% a model structure. % % Name-Value Arguments % -------------------- % maxStoichDiff : double -% all reactions with a stoichiometric coefficient higher than this will be -% scaled down (default 25). +% largest allowed ratio between the largest and the smallest absolute +% stoichiometric coefficient in a reaction (default 25). % % Returns % ------- diff --git a/src/INIT/scoreModel.m b/src/INIT/scoreModel.m index 60380615..cd0ee606 100644 --- a/src/INIT/scoreModel.m +++ b/src/INIT/scoreModel.m @@ -2,11 +2,8 @@ % scoreModel Score reactions and genes in a model with complex gene rules. % % Scores the reactions and genes in a model containing complex gene rules -% based on expression data from HPA and/or gene arrays. -% -% It is highly recommended that the model grRules are "cleaned" and verified by -% the "cleanModelGeneRules" function prior to scoring the model with -% scoreModel. +% based on expression data from HPA, gene arrays or both. The model grRules +% should be standardised with standardizeGrRules before scoring. % % Parameters % ---------- @@ -20,26 +17,27 @@ % []). With fields: % % - genes : cell array with the unique gene names. -% - tissues : cell array with the tissue names. The list may not be unique, -% as there can be multiple cell types per tissue. +% - tissues : cell array with the tissue names. The list can contain +% duplicates, as there can be multiple cell types per tissue. % - celltypes : cell array with the cell type names for each tissue. % - levels : GENESxTISSUES array with the expression level for each gene in -% each tissue/celltype. NaN should be used when no measurement was -% performed. +% each tissue and cell type. NaN marks a gene that was not measured. % - threshold : a single value or a vector of gene expression thresholds, -% above which genes are considered to be "expressed". (optional, by -% default, the mean expression levels of each gene across all tissues in -% arrayData will be used as the threshold values). +% above which genes are considered to be "expressed". If omitted, the +% mean expression level of each gene across all tissues in arrayData +% is used as the threshold. % tissue : char -% tissue to score for. Should exist in either hpaData.tissues or -% arrayData.tissues. +% tissue to score for. Must exist in either hpaData.tissues or +% arrayData.tissues (matched without regard to case); otherwise an error +% is raised. % % Name-Value Arguments % -------------------- % celltype : char -% cell type to score for. Should exist in either hpaData.celltypes or -% arrayData.celltypes for this tissue (default is to use the max values -% among all the cell types for the tissue). +% cell type to score for. Must exist in either hpaData.celltypes or +% arrayData.celltypes (matched without regard to case); otherwise an error +% is raised (default [], which uses all cell types of the tissue, +% combined as set by multipleCellScoring). % noGeneScore : double % score for reactions without genes (default -2). % isozymeScoring : char @@ -55,8 +53,9 @@ % ("max" or "average") (default "max"). % hpaLevelScores : struct % structure with numerical scores for the expression level categories from -% HPA. The structure should have a "names" and a "scores" field (default, -% see code for default scores). +% HPA. The structure has a "names" and a "scores" field. The default names +% are High, Medium, Low, None, Strong, Moderate, Weak, Negative and Not +% detected, with the scores 20, 15, 10, -8, 20, 15, 10, -8 and -8. % dataPrecedence : char % how hpaData takes precedence over arrayData when both are supplied % (default "gene"): @@ -73,8 +72,9 @@ % scores for each of the reactions in model. % geneScores : double % scores for each of the genes in model. Genes which are not in the -% dataset(s) have NaN as scores, which removeLowScoreGenes reads as "no -% evidence" and leaves in place, where a negative score would be pruned. +% dataset(s) have NaN as scores. removeLowScoreGenes reads NaN as "no +% evidence" and leaves the gene in place, and prunes genes with a +% negative score. % hpaScores : double % scores for each of the genes in model if only taking hpaData into % account. Genes which are not in the dataset(s) have -Inf as scores. diff --git a/src/INIT/tINIT/getINITModel.m b/src/INIT/tINIT/getINITModel.m index e2bd52db..c7f3551f 100644 --- a/src/INIT/tINIT/getINITModel.m +++ b/src/INIT/tINIT/getINITModel.m @@ -1,36 +1,36 @@ function [model, metProduction, essentialRxnsForTasks, addedRxnsForTasks, deletedDeadEndRxns, deletedRxnsInINIT, taskReport]=getINITModel(refModel, tissue, varargin) % getINITModel Generate a model using the original tINIT algorithm. % -% Generates a model using the tINIT algorithm, based on proteomics and/or -% transcriptomics and/or metabolomics and/or metabolic tasks (PLoS Comput Biol. -% 2012;8(5):e1002518, Mol Syst Biol. 2014;10:721). +% Generates a model using the tINIT algorithm, from proteomics, +% transcriptomics, metabolomics and metabolic tasks in any combination (PLoS +% Comput Biol. 2012;8(5):e1002518, Mol Syst Biol. 2014;10:721). % -% ftINIT is the recommended method for context-specific model extraction. Use -% this function to reproduce models built with the original tINIT. It remains -% supported, and issues a RAVEN:legacyMethod notice once per session, which +% ftINIT replaces this function for context-specific model extraction. This +% function is kept to reproduce models built with the original tINIT. It +% issues a RAVEN:legacyMethod notice once per session, which % warning('off','RAVEN:legacyMethod') silences. % -% Not all settings are reachable through this function; runINIT and fitTasks -% can be called individually for the ones that are not. +% Not all settings are available through this function; runINIT and fitTasks +% can be called individually to set the others. % % Parameters % ---------- % refModel : struct -% a model structure. The model should be in the closed form (no exchange -% reactions open). Import using import(filename,false). If the model is not -% loaded using importModel, it might be that there is no "unconstrained" -% field. In that case, manually add the field like: +% a model structure in the closed form (no exchange reactions open), +% imported with importModel(filename,false). A model that is not loaded +% with importModel can lack the "unconstrained" field, and the function +% then raises RAVEN:badInput. The field can be added with % model.unconstrained=false(numel(model.mets),1). % tissue : char -% tissue to score for. Should exist in either hpaData.tissues or -% arrayData.tissues. +% tissue to score for. Must exist in either hpaData.tissues or +% arrayData.tissues (scoreModel raises an error otherwise). % % Name-Value Arguments % -------------------- % celltype : char -% cell type to score for. Should exist in either hpaData.celltypes or -% arrayData.celltypes for this tissue (default is to use the best values -% among all the cell types for the tissue). +% cell type to score for. Must exist in either hpaData.celltypes or +% arrayData.celltypes (scoreModel raises an error otherwise). The default +% is to use the maximum values among all the cell types for the tissue. % hpaData : struct % HPA data structure from parseHPA (optional if arrayData is supplied, % default []). @@ -39,20 +39,19 @@ % []). With fields: % % - genes : cell array with the unique gene names. -% - tissues : cell array with the tissue names. The list may not be unique, -% as there can be multiple cell types per tissue. +% - tissues : cell array with the tissue names. The list can contain +% duplicates, as there can be multiple cell types per tissue. % - celltypes : cell array with the cell type names for each tissue. % - levels : GENESxTISSUES array with the expression level for each gene in -% each tissue/celltype. NaN should be used when no measurement was -% performed. +% each tissue and cell type. NaN marks a gene that was not measured. % - threshold : a single value or a vector of gene expression thresholds, -% above which genes are considered to be "expressed". (default, by -% default the mean expression levels of each gene across all tissues in -% arrayData will be used as the threshold values). +% above which genes are considered to be "expressed". If omitted, the +% mean expression level of each gene across all tissues in arrayData +% is used as the threshold. % - singleCells : binary value selecting whether to use the single-cell -% algorithm to identify expressed genes. If used, specify cell -% subpopulations in CELLTYPES (default []). -% - plotResults : true if single cell probability distributions should be +% algorithm to identify expressed genes. If used, the cell +% subpopulations are specified in celltypes (default []). +% - plotResults : true if single cell probability distributions are % plotted (default false). % metabolomicsData : cell % cell array with metabolite names that the model should produce (default @@ -60,33 +59,32 @@ % taskFile : char % a task list in Excel format. See parseTaskList for details (default []). % useScoresForTasks : logical -% true if the calculated reaction scores should be used as weights in the -% fitting to tasks (default true). +% true if the calculated reaction scores are used as weights in the +% fitting to tasks. Scores above -0.1 are set to -0.1 (default true). % printReport : logical -% true if a report should be printed to the screen (default true). +% true if a report is printed to the screen (default true). % taskStructure : struct -% task structure as from parseTaskList. Can be used as an alternative way -% to define tasks when Excel sheets are not suitable. Overrides taskFile -% (default []). +% task structure as returned by parseTaskList. It defines the tasks when +% Excel sheets are not used and overrides taskFile (default []). % params : struct % parameter structure for use by the INIT solver (default []). % paramsFT : struct % parameter structure for the fitTasks step (default []). % seed : double -% seed for the random number generator used to break ties among -% genes with equally uninformative (-Inf) scores, so which gene is -% kept is reproducible across runs (default: current rng state is -% left untouched). +% seed for the random number generator used to break ties among genes +% with equal uninformative (-Inf) scores, which makes the choice of the +% kept gene reproducible across runs (default [], which leaves the rng +% state unchanged). % % Returns % ------- % model : struct % the resulting model structure. % metProduction : double -% array that indicates which of the metabolites in metabolomicsData that -% could be produced. Note that this is before the gap-filling process to -% enable defined tasks. To see which metabolites that can be produced in -% the final model, use canExchange: +% array that indicates which of the metabolites in metabolomicsData could +% be produced. The array is determined before the gap-filling for the +% defined tasks. To see which metabolites can be produced in the final +% model, use canExchange: % % - -2 : metabolite name not found in model. % - -1 : metabolite found, but it could not be produced. @@ -114,13 +112,13 @@ % % Notes % ----- -% Exchange metabolites should normally not be removed from the model when using -% this approach, since checkTasks/fitTasks rely on putting specific constraints -% for each task. The INIT algorithm will remove exchange metabolites if any are -% present. Use importModel(file,false) to import a model with exchange -% metabolites remaining. +% The model keeps its exchange metabolites, since checkTasks +% and fitTasks put specific constraints on them for each task. The INIT +% algorithm removes exchange metabolites if any are present. Use +% importModel(file,false) to import a model with exchange metabolites +% remaining. % -% See Also +% See also % -------- % ftINIT, prepINITModel, runINIT, scoreModel diff --git a/src/INIT/tINIT/runINIT.m b/src/INIT/tINIT/runINIT.m index 0d6824b4..fb0d705c 100644 --- a/src/INIT/tINIT/runINIT.m +++ b/src/INIT/tINIT/runINIT.m @@ -1,15 +1,16 @@ function [outModel, deletedRxns, metProduction, fValue]=runINIT(model,varargin) % runINIT Solve the MILP of the original INIT algorithm. % -% Generates a model using the INIT algorithm, based on proteomics and/or -% transcriptomics and/or metabolomics and/or metabolic tasks. This function is -% the actual implementation of the algorithm; see getINITModel for the +% Generates a model using the INIT algorithm, from proteomics, +% transcriptomics, metabolomics and metabolic tasks in any combination. This +% function is the implementation of the algorithm; see getINITModel for the % higher-level reconstruction function, and PLoS Comput Biol. 2012;8(5):e1002518 % for the formulation. % -% ftINIT is the recommended method for context-specific model extraction, and -% solves a different MILP in ftINITInternalAlg. This function remains -% supported, and issues a RAVEN:legacyMethod notice once per session, which +% ftINIT replaces this function for context-specific model extraction and +% solves a different MILP in ftINITInternalAlg. This function is kept to +% reproduce models built with the original INIT. It issues a +% RAVEN:legacyMethod notice once per session, which % warning('off','RAVEN:legacyMethod') silences. % % Parameters @@ -28,31 +29,35 @@ % (default []). % essentialRxns : cell % cell array of reactions that are essential and that have to be in the -% resulting model. This is normally used when fitting a model to task (see -% fitTasks) (default []). +% resulting model. fitTasks passes the reactions that the tasks require +% (default []). % prodWeight : double -% a score that determines the value of having net-production of -% metabolites. This is a way of having a more functional network as it -% provides a reason for including bad reactions for connectivity reasons. -% This score is for each metabolite, and the sum of these weights and the -% scores for the reactions is what is optimized (default 0.5). +% a score for each metabolite that has net production. The sum of these +% weights and the scores for the reactions is optimised. Net production +% of metabolites gives a reason for including reactions with a negative +% score for connectivity reasons, which gives a more functional network +% (default 0.5). % allowExcretion : logical -% true if excretion of all metabolites should be allowed. This results in -% fewer reactions being considered dead-ends, but all reactions in the -% resulting model may not be able to carry flux. If this is "false" then +% true if excretion of all metabolites is allowed. This results in +% fewer reactions being considered dead-ends, but not every reaction in the +% resulting model can carry flux. If this is "false" then % the equality constraints are taken from model.b. If the input model -% lacks exchange reactions then this should probably be "true", or a large -% proportion of the model would be excluded for connectivity reasons +% lacks exchange reactions, set it to true; with false a large part of +% the model is excluded for connectivity reasons % (default false). % noRevLoops : logical -% true if reversible reactions should be constrained to only carry flux in -% one direction. This prevents reversible reactions from being wrongly -% assigned as connected (the forward and backward reactions can form a loop -% and therefore appear connected), but it makes the problem significantly -% more computationally intensive to solve (two more integer constraints per -% reversible reaction) (default false). +% true if reversible reactions are constrained to carry flux in only one +% direction. This prevents reversible reactions from being assigned as +% connected wrongly (the forward and backward reactions can form a loop +% and therefore appear connected). It adds two binary variables per +% reversible reaction to the problem, which makes the MILP larger and more +% computationally intensive (default false). % params : struct % parameter structure for use by optimizeProb. +% eps : double +% the lower flux bound of a reaction that is turned on, which also +% applies to the essential reactions (capped by their upper bound) +% (default 1). % % Returns % ------- @@ -71,7 +76,7 @@ % objective value (sum of (the negative of) reaction scores for the % included reactions and prodWeight*number of produced metabolites). % -% See Also +% See also % -------- % getINITModel, ftINITInternalAlg, ftINIT diff --git a/src/analysis/FSEOF.m b/src/analysis/FSEOF.m index 627cfebf..85437177 100755 --- a/src/analysis/FSEOF.m +++ b/src/analysis/FSEOF.m @@ -3,10 +3,10 @@ % % Implements the Flux Scanning based on Enforced Objective Flux algorithm. % This function writes a tab-delimited file or prints to the command -% window. If an output has been specified (targets), it will also generate -% a structure indicating for each model reaction whether it is identified -% by FSEOF as a target and the slope of the reaction when switching from -% biomass formation to product formation. +% window. If an output is specified (targets), it also returns a structure +% indicating for each model reaction whether FSEOF identifies it as a target +% and the slope of the reaction when switching from biomass formation to +% product formation. % % Parameters % ---------- @@ -20,33 +20,35 @@ % Name-Value Arguments % -------------------- % iterations : double -% number of iterations (default 10). +% number of enforced target flux levels, equally spaced up to coefficient +% times the maximum target flux (default 10). % coefficient : double -% ratio of optimal target reaction flux, must be less than 1 -% (default 0.9). +% ratio of the maximum target reaction flux that is enforced at the last +% iteration, less than 1 (default 0.9). % outputFile : char % output filename (default prints to command window). % corrThreshold : double -% minimum absolute Pearson correlation coefficient between a -% reaction absolute flux and the enforced product flux for the -% reaction to be selected as a target. Values in [0, 1]; higher -% values require a more linear response and suppress LP alternative- -% optima noise (default 0.9). +% minimum absolute Pearson correlation coefficient between the absolute +% flux of a reaction and the enforced product flux for the reaction to be +% selected as a target. Values are in [0, 1]. The filter keeps reactions +% whose flux is close to linear in the enforced product flux, and a higher +% value keeps fewer reactions (default 0.9). % % Returns % ------- % targets : struct % structure with information for identified targets, with fields: % -% - logical : logical array, true for amplification targets (backward -% compatible alias for amplify). +% - logical : logical array, true for amplification targets (identical to +% amplify). % - amplify : logical array, reactions whose absolute flux increases % with enforced product flux. -% - knockdown : logical array, reactions driven toward but not to zero. -% - knockout : logical array, reactions driven to zero at max product. +% - knockdown : logical array, reactions whose absolute flux decreases but +% stays above zero at the highest enforced product flux. +% - knockout : logical array, reactions whose absolute flux decreases to +% zero at the highest enforced product flux. % - slope : numeric array with per-reaction regression slopes of -% absolute flux versus enforced product flux (all reactions, not -% just targets). +% absolute flux versus enforced product flux (for all reactions). % % Examples % -------- diff --git a/src/analysis/analyzeSampling.m b/src/analysis/analyzeSampling.m index 56d52d34..d5da5e7c 100755 --- a/src/analysis/analyzeSampling.m +++ b/src/analysis/analyzeSampling.m @@ -8,14 +8,12 @@ % ---------- % Tex : double % a vector of t-scores for the change in gene expression for each -% reaction. This score could be the Student t between the two -% conditions, or you can calculate it from a p-value (by computing the -% inverse of the so called error function). If you choose the second -% alternative you should be aware that the transcripts that increased -% in expression level should have positive values and those who -% decreased in expression level should have negative values (the -% p-values only tell you if the fluxes changed or not but not in which -% direction). +% reaction. The score is the Student t between the two conditions, or +% is calculated from a p-value by the inverse of the error function. +% Transcripts that increased in expression level have positive values +% and transcripts that decreased have negative values. A p-value does +% not give the direction of the change, so a score calculated from a +% p-value takes its sign from the direction of the expression change. % df : double % the degrees of freedom in the t-test. % solutionsA : double @@ -28,8 +26,8 @@ % Name-Value Arguments % -------------------- % printResults : logical -% prints the most significant reactions in each category -% (default false). +% true to print the 10 reactions with the highest probability in each +% category (default false). % % Returns % ------- diff --git a/src/analysis/compareFluxes.m b/src/analysis/compareFluxes.m index 4e9e303b..26361dfa 100644 --- a/src/analysis/compareFluxes.m +++ b/src/analysis/compareFluxes.m @@ -1,5 +1,5 @@ function result = compareFluxes(model, fluxes1, fluxes2, varargin) -% compareFluxes Find the most significant flux changes between two conditions. +% compareFluxes Find the reactions whose flux differs between two flux vectors. % % Compares two flux vectors from the same model and returns all reactions % that changed, sorted by absolute flux difference. Classifies reactions as @@ -8,22 +8,23 @@ % Parameters % ---------- % model : struct -% RAVEN model structure. +% a model structure. % fluxes1 : double -% reference flux vector (condition A, e.g. wild-type). +% reference flux vector (condition A, such as wild-type). % fluxes2 : double -% comparison flux vector (condition B, e.g. mutant or knockin). +% comparison flux vector (condition B, such as a mutant or knock-in). % % Name-Value Arguments % -------------------- % cutoff : double -% minimum |flux| to consider a reaction active (default 1e-8). +% minimum |flux| for a reaction to be active, and minimum |flux2 - flux1| +% for a reaction to be changed (default 1e-8). % metaboliteList : cell % cell array of metabolite names. Only reactions involving at least one -% of these metabolites are compared; every other reaction is left out of -% the result entirely. Matching is against model.metNames and is case -% insensitive. A name that matches no metabolite is reported as a warning -% rather than an error (default all reactions). +% of these metabolites are compared; all other reactions are excluded +% from every field of the result. Matching is against model.metNames and +% is case insensitive. A name that matches no metabolite is reported as +% a warning (default all reactions). % nMax : double % maximum rows printed in the summary table (default 20). % verbose : logical @@ -32,18 +33,25 @@ % Returns % ------- % result : struct -% .turnedOn — cell, rxn IDs newly active in fluxes2 -% .turnedOff — cell, rxn IDs silenced in fluxes2 -% .flipped — cell, rxn IDs that reversed sign between conditions -% .changed — struct with fields (all reactions with |dflux|>cutoff, -% sorted by |dflux| descending): -% .rxn — cell of reaction IDs -% .flux1 — double, flux in condition A -% .flux2 — double, flux in condition B -% .absDelta — double, |flux2 - flux1| -% .relChange — double, (flux2-flux1)/|flux1|; NaN when -% flux1 is near-zero (reaction emerged) -% .type — cell, 'on'|'off'|'flip'|'' per reaction +% structure with fields: +% +% - turnedOn : cell array with the IDs of reactions that are active in +% fluxes2 and not in fluxes1. +% - turnedOff : cell array with the IDs of reactions that are active in +% fluxes1 and not in fluxes2. +% - flipped : cell array with the IDs of reactions that are active in +% both conditions and change sign. +% - changed : struct with fields for all reactions with |flux2 - flux1| +% above cutoff, sorted by |flux2 - flux1| descending: +% +% - rxn : cell array with the reaction IDs. +% - flux1 : double array with the flux in condition A. +% - flux2 : double array with the flux in condition B. +% - absDelta : double array with |flux2 - flux1|. +% - relChange : double array with (flux2 - flux1)/|flux1|. NaN when +% |flux1| is below cutoff. +% - type : cell array with 'on', 'off', 'flip' or '' (changed +% magnitude) for each reaction. % % Examples % -------- @@ -55,7 +63,8 @@ % result = compareFluxes(model, solA.x, solB.x, 'metaboliteList', {'ATP','NADH'}) % % See also -% walkFluxes, traceFluxPath, modelSummary +% -------- +% walkFluxes, traceFluxPath, modelSummary p = parseRAVENargs(varargin, {'cutoff',1e-8; 'metaboliteList',[]; 'nMax',20; 'verbose',true}); cutoff = p.cutoff; diff --git a/src/analysis/findGeneDeletions.m b/src/analysis/findGeneDeletions.m index 14f4c0fa..9ad6d278 100755 --- a/src/analysis/findGeneDeletions.m +++ b/src/analysis/findGeneDeletions.m @@ -1,8 +1,9 @@ function [genes, fluxes, originalGenes, details, grRatioMuts]=findGeneDeletions(model,varargin) % findGeneDeletions Delete genes and track the resulting fluxes. % -% Deletes genes, optimizes the model by FBA, and keeps track of the -% resulting fluxes. This is used for identifying gene deletion targets. +% Deletes single genes or gene pairs, solves the model by FBA after each +% deletion, and returns the resulting fluxes. The output identifies gene +% deletion targets. % % Parameters % ---------- @@ -12,7 +13,7 @@ % Name-Value Arguments % -------------------- % testType : char -% single/double gene deletion (default "sgd"): +% type of gene deletion (default "sgd"): % % - "sgd" : single gene deletion % - "dgd" : double gene deletion @@ -20,7 +21,7 @@ % Returns % ------- % genes : double -% a matrix with the genes that were deleted in each optimization (the +% a matrix with the genes that were deleted in each simulation (the % gene indexes in originalGenes). Each row corresponds to a column in % fluxes. % fluxes : double @@ -28,25 +29,23 @@ % an unsolvable problem have all zero flux. Single deletions that % result in an unsolvable problem are indicated in details instead. % originalGenes : cell -% simply the genes in the input model. Included for simple -% presentation of the output. +% the genes in the input model. % details : double -% not all genes will be deleted in all analyses. It is for example not -% necessary to delete genes for dead end reactions. This is a vector -% with details about each gene in originalGenes and why or why not it -% was deleted: +% a vector with one element per gene in originalGenes that states +% whether the gene was deleted and why. Genes that only belong to +% dead-end reactions, for example, are not deleted: % -% - 1 : was deleted, and the problem was solved (a lethal deletion +% - 1 : deleted, and the problem was solved (a lethal deletion % with zero growth also gets 1) % - 2 : the single-deletion problem had no optimal solution % (infeasible or failed); fluxes and grRatioMuts are 0 -% - 3 : redundant, no longer used +% - 3 : redundant (not assigned) % - 4 : involved in dead-end reaction % grRatioMuts : double % growth rate ratio between mutated strain and wild type, matching the -% originalGenes(genes) mutants. Note that this does not directly map -% to model.genes, as is the case for COBRA getEssentialGenes. However, -% this can be obtained by afterwards running: +% originalGenes(genes) mutants. The elements do not map directly to +% model.genes, unlike the output of COBRA getEssentialGenes. The +% mapping to model.genes is obtained with: % % grRatio=zeros(1,numel(model.genes)); % grRatio(genes)=grRatioMuts; diff --git a/src/analysis/getAllSubGraphs.m b/src/analysis/getAllSubGraphs.m index e8a869ef..17f6cdac 100755 --- a/src/analysis/getAllSubGraphs.m +++ b/src/analysis/getAllSubGraphs.m @@ -11,9 +11,9 @@ % Returns % ------- % subGraphs : logical -% a boolean matrix where the rows correspond to the metabolites and -% the columns to which subgraph they are assigned to. The columns are -% ordered so that larger subgraphs come first. +% a logical matrix with one row per metabolite and one column per +% subgraph. An element is true if the metabolite belongs to the +% subgraph. The columns are ordered by decreasing subgraph size. % % Examples % -------- diff --git a/src/analysis/getAllowedBounds.m b/src/analysis/getAllowedBounds.m index 41d9b08e..68a88805 100755 --- a/src/analysis/getAllowedBounds.m +++ b/src/analysis/getAllowedBounds.m @@ -13,12 +13,11 @@ % number of elements as reactions in the model, or a vector of % reaction indexes (default model.rxns). % runParallel : logical -% speed up calculations by parallel processing. This is not beneficial -% if allowed bounds are calculated for only a few reactions, as the -% overhead of parallel processing will take longer. It requires MATLAB -% Parallel Computing Toolbox. If this is not installed, the -% calculations will not be parallelized, regardless of what is -% indicated as runParallel (default true). +% run the calculations in parallel (default true). Parallel processing +% requires the MATLAB Parallel Computing Toolbox; without it, the +% calculations run serially regardless of this setting. For a few +% reactions, the overhead of parallel processing exceeds the time it +% saves. % % Returns % ------- @@ -27,8 +26,9 @@ % maxFluxes : double % maximal allowed fluxes. % exitFlags : double -% exit flags for min/max for each of the reactions. True if it was -% possible to calculate a flux. +% a matrix with one row per reaction. Column 1 holds the solver status +% of the minimisation and column 2 the status of the maximisation +% (1 if a flux could be calculated). % % Notes % ----- diff --git a/src/analysis/getFluxZ.m b/src/analysis/getFluxZ.m index f705cfe3..3f28155d 100755 --- a/src/analysis/getFluxZ.m +++ b/src/analysis/getFluxZ.m @@ -13,9 +13,12 @@ % Returns % ------- % Z : double -% a vector with Z-scores that tells you for each reaction how likely it -% is for its flux to have increased (positive sign) or decreased -% (negative sign) in the second condition with respect to the first. +% a vector with one Z-score per reaction: the difference between the +% mean fluxes of solutionsB and solutionsA, divided by the square root +% of the sum of their variances. A positive sign indicates an increase +% of the flux in the second condition with respect to the first, a +% negative sign a decrease. Values are limited to the range -100 to +% 100. % % Examples % -------- diff --git a/src/analysis/getMinimalMedium.m b/src/analysis/getMinimalMedium.m index 186f044b..b4c7c496 100644 --- a/src/analysis/getMinimalMedium.m +++ b/src/analysis/getMinimalMedium.m @@ -2,21 +2,22 @@ % getMinimalMedium Find the minimal set of uptake reactions for growth. % % Uses MILP to find the smallest set of exchange reactions that can carry -% uptake flux (lb < 0) while still allowing the model to achieve a target -% growth rate. Unlike iterative heuristics, the MILP formulation finds the -% globally minimal medium. +% uptake flux (lb < 0) while the model achieves a target growth rate. The +% MILP formulation gives a medium with the globally minimal number of +% uptake reactions. % % Parameters % ---------- % model : struct -% RAVEN model structure. +% a model structure. % % Name-Value Arguments % -------------------- % minGrowth : double -% minimum objective value (growth rate) the medium must support. -% Defaults to 10 % of the unconstrained optimum computed automatically. -% Set explicitly to avoid an extra FBA call (e.g. 'minGrowth', 0.1). +% minimum objective value (growth rate) the medium must support. If +% empty, 10 % of the optimum objective value under the current bounds, +% which requires one extra FBA call. A value given explicitly avoids +% that call (for example 'minGrowth', 0.1) (default empty). % verbose : logical % print the resulting minimal medium (default true). % @@ -36,9 +37,8 @@ % % Notes % ----- -% Only exchange reactions with lb < 0 are considered as candidate uptake -% reactions. Exchange reactions that are always secretion-only (lb >= 0) or -% that are completely blocked are not included. +% Only exchange reactions with lb < 0 are candidate uptake reactions. +% Exchange reactions with lb >= 0 are not included. % % The MILP formulation: % minimise sum(y_i) @@ -51,15 +51,15 @@ % A y_i = 0 forces reaction i to carry non-negative flux (no net uptake); % y_i = 1 allows it to carry flux as low as lb_i. % -% The minimal number of uptakes is guaranteed, but the medium itself need -% not be unique: when several media of the same size support the growth -% rate, the solver returns one of them, and another solver or solver -% version can return a different one. +% The number of uptake reactions is minimal, but the medium is not +% necessarily unique: when several media of the same size support the +% growth rate, the solver returns one of them, and another solver or +% solver version can return a different one. % % The MILP is solved with the solver set by setRavenSolver. RAVEN's bundled -% GLPK solves it for small models (with a RAVEN:glpkMILP warning); for -% larger models Gurobi is much faster and more reliable -% (setRavenSolver('gurobi')). +% GLPK solves it for small models. The RAVEN:glpkMILP warning issued with +% GLPK states that GLPK works for small problems and that Gurobi is much +% faster and more reliable for larger ones (setRavenSolver('gurobi')). p = parseRAVENargs(varargin, {'minGrowth',[]; 'verbose',true}); minGrowth = p.minGrowth; diff --git a/src/analysis/haveFlux.m b/src/analysis/haveFlux.m index fc04d630..c76a020a 100755 --- a/src/analysis/haveFlux.m +++ b/src/analysis/haveFlux.m @@ -2,9 +2,10 @@ % haveFlux Check which reactions can carry a flux. % % Checks which reactions can carry a (positive or negative) flux under the -% model's current bounds; other media or bounds are not explored. Is used -% as a faster version of getAllowedBounds if it is only interesting whether -% the reactions can carry a flux or not. +% model's current bounds; other media or bounds are not explored. Unlike +% getAllowedBounds, which minimises and maximises every reaction, it skips a +% reaction once an earlier solution has given it a flux, and minimises only +% reversible reactions that still have none. % % Parameters % ---------- @@ -14,16 +15,16 @@ % Name-Value Arguments % -------------------- % cutOff : double -% the flux value that a reaction has to carry to be identified as -% positive (default 10^-6). +% the absolute flux value that a reaction has to exceed to be identified +% as carrying flux (default 10^-6). % rxns : cell or logical or double % either a cell array of IDs, a logical vector with the same number of -% elements as metabolites in the model, or a vector of indexes (default +% elements as reactions in the model, or a vector of indexes (default % model.rxns). % seed : double -% seed for the random number generator used to shuffle the order in -% which reactions are tested, so that order is reproducible across -% runs (default: current rng state is left untouched). +% seed for the random number generator that shuffles the order in +% which reactions are tested. The order is then reproducible across +% runs (default: the current rng state is left untouched). % % Returns % ------- @@ -37,8 +38,7 @@ % % Notes % ----- -% If a model has +/- Inf bounds then those are replaced with an arbitary -% large value of +/- 10000 prior to solving. +% Bounds of +/- Inf are replaced with +/- 10000 before solving. % % Dead-end reactions are removed (simplifyModel) before solving. A dead-end % reaction with a forced flux (lb > 0 or ub < 0) makes the model infeasible, diff --git a/src/analysis/randomSampling.m b/src/analysis/randomSampling.m index 8d3347fe..2f1360dd 100755 --- a/src/analysis/randomSampling.m +++ b/src/analysis/randomSampling.m @@ -5,18 +5,19 @@ % 'achr' (default) Artificially Centered Hit-and-Run; near-uniform MCMC % sampling of the polytope interior (see sampleACHR). % 'chrr' Coordinate Hit-and-Run with Rounding; near-uniform MCMC -% with maximum-volume-ellipsoid rounding, for better mixing -% on thin/ill-conditioned polytopes such as enzyme- -% constrained models (see sampleCHRR). +% with maximum-volume-ellipsoid rounding, which improves +% mixing on thin or ill-conditioned polytopes such as +% enzyme-constrained models (see sampleCHRR). % 'randomObjective' the random-objective vertex method of Bordel et al. (2010) % PLoS Comput Biol (doi:10.1371/journal.pcbi.1000859): each -% sample maximises a small random objective, returning a -% polytope vertex. +% sample optimises a random objective of nObjectives +% reactions with random weights, returning a polytope +% vertex. % -% The 'achr'/'chrr' methods draw the (near-)uniform interior distribution; the -% 'randomObjective' method draws diverse vertices. Arguments below marked -% [randomObjective] apply only to that method; 'thinning'/'nBurnin' apply only -% to the MCMC methods. +% The 'achr' and 'chrr' methods draw from the (near-)uniform distribution +% over the interior; the 'randomObjective' method draws vertices. Arguments +% below marked [randomObjective] apply only to that method; 'thinning' and +% 'nBurnin' apply only to the MCMC methods. % % Parameters % ---------- @@ -30,35 +31,36 @@ % method : char % sampling method: 'achr' (default), 'chrr', or 'randomObjective'. % thinning : double -% MCMC steps between recorded samples for 'achr'/'chrr' (default 100). +% MCMC steps between recorded samples for 'achr' and 'chrr' (default 100). % nBurnin : double % burn-in steps discarded before the first sample for 'chrr' (default 1000). % replaceBoundsWithInf : logical % [randomObjective] % replace the largest upper bounds with Inf and the smallest lower -% bounds with -Inf. This is needed to get solutions without loops if -% your model has, for example, 1000/-1000 as arbitrarily large bounds. -% If your model only has "biologically relevant" bounds, then set this -% to false (default true). +% bounds with -Inf (the latter only if the smallest lower bound is +% negative). This is required to get solutions without loops if the +% model has arbitrarily large bounds, for example 1000 and -1000. A +% model with only biologically relevant bounds can use false +% (default true). % supressErrors : logical -% the program will halt if it has problems finding non-zero solutions -% which are not involved in loops. This could be because the constraints -% on the model are too relaxed (such as unlimited glucose uptake) or too -% strict (such as too many and too narrow constraints) (default false). +% if false, randomSampling raises an error after 99 consecutive failed +% attempts to find a non-zero solution that is not involved in loops. +% Failures can result from constraints that are too relaxed (such as +% unlimited glucose uptake) or too strict (such as many narrow +% constraints). If true, the attempts continue without an error +% (default false). % runParallel : logical -% speed up calculations by parallel processing. Requires the MATLAB -% Parallel Computing Toolbox. If this is not installed, the calculations -% will not be parallelized, regardless of what is indicated as -% runParallel (default true). +% run the calculations in parallel (default true). Requires the MATLAB +% Parallel Computing Toolbox; without it, the calculations run serially +% regardless of this setting. % goodRxns : double % vector of indexes of those reactions that are not involved in loops % and can be used as random objective functions, as generated by a % previous run of randomSampling on the same model (default empty). % minFlux : logical -% determines if a second optimization should be performed for each -% random sample, to minimize the number of fluxes and thereby prevent -% loops. Typically, loops are averaged out when a large number of -% samples are taken, but this is not always the case (default false). +% determines if a second optimisation is performed for each random +% sample, which minimises the sum of absolute fluxes to prevent loops +% (default false). % nObjectives : double % number of reactions included in the random objective function per % sample iteration (default 2). @@ -81,16 +83,17 @@ % % Notes % ----- -% With 'randomObjective', the solutions are generated by maximizing (with -% random weights) for a random set of nObjectives reactions (default 2). -% For reversible reactions it randomly chooses between maximizing and -% minimizing. The default 'achr' and the 'chrr' methods use no random -% objectives; they take hit-and-run steps through the interior of the -% solution space. +% With 'randomObjective', the solutions are generated by optimising (with +% random weights) a random set of nObjectives reactions (default 2). The +% sign of each weight is chosen at random, which selects maximisation or +% minimisation of the reaction; reactions with an upper bound below 0 always +% receive a positive weight. The default 'achr' and the 'chrr' methods use +% no random objectives; they take hit-and-run steps through the interior of +% the solution space. % % If the model is a GECKO v3+ ecModel, then usage_prot reactions are not -% selected for sampling, instead focusing on sampling from the metabolic -% aspects that form the solution space. +% selected as random objectives; the objectives are drawn from the +% metabolic reactions. % % Examples % -------- @@ -98,7 +101,7 @@ % solutions = randomSampling(model, 1000); % % Coordinate hit-and-run with rounding: % solutions = randomSampling(model, 1000, 'method', 'chrr', 'seed', 1); -% % Historical random-objective vertex method: +% % Random-objective vertex method: % solutions = randomSampling(model, 1000, 'method', 'randomObjective'); % % See also diff --git a/src/analysis/reporterMetabolites.m b/src/analysis/reporterMetabolites.m index 47256351..209d3cc6 100755 --- a/src/analysis/reporterMetabolites.m +++ b/src/analysis/reporterMetabolites.m @@ -1,15 +1,15 @@ function repMets=reporterMetabolites(model,genes,genePValues,varargin) % reporterMetabolites Identify metabolites around which transcriptional changes occur. % -% The Reporter Metabolites algorithm for identifying metabolites around -% which transcriptional changes occur. +% Applies the Reporter Metabolites algorithm, which scores each metabolite +% by the differential expression of the genes around it. % % Parameters % ---------- % model : struct % a model structure. % genes : cell -% a cell array of gene names (should match with model.genes). +% a cell array of gene names (matching model.genes). % genePValues : double % P-values for differential expression of the genes. Values must lie in % [0, 1]; NaN values are ignored, any other value raises an error. @@ -17,14 +17,15 @@ % Name-Value Arguments % -------------------- % printResults : logical -% true if the top 20 Reporter Metabolites should be printed to the -% screen (default false). +% true to print the top 20 Reporter Metabolites to the screen +% (default false). % outputFile : char % the results are printed to this file (default none). % geneFoldChanges : double -% log-fold changes for the genes. If supplied, then Reporter -% Metabolites are calculated for only up/down-regulated genes in -% addition to the full test (default none). +% log-fold changes for the genes. If supplied, Reporter Metabolites are +% also calculated for the upregulated genes (fold change >= 0) alone and +% for the downregulated genes (fold change < 0) alone, in addition to +% the full test (default none). % % Returns % ------- @@ -32,11 +33,13 @@ % an array of structures with the following fields: % % - test : a string that describes the genes used to calculate the -% Reporter Metabolites ("all", "only up", or "only down"). The two -% latter structures are only calculated if geneFoldChanges are +% Reporter Metabolites ("all", "only up", or "only down"). The tests +% "only up" and "only down" are only calculated if geneFoldChanges is % supplied. % - mets : a cell array of metabolite IDs for the metabolites for which % a score could be calculated. +% - metNames : a cell array with the names of the metabolites in +% "mets". % - metZScores : background-corrected Z-scores for differential % expression around each metabolite in "mets". The entries of all % fields are sorted by this score, highest first. diff --git a/src/analysis/runDynamicFBA.m b/src/analysis/runDynamicFBA.m index f07d466b..088539f6 100755 --- a/src/analysis/runDynamicFBA.m +++ b/src/analysis/runDynamicFBA.m @@ -1,5 +1,5 @@ function [concentrationMatrix, excRxnNames, timeVec, biomassVec] = runDynamicFBA(model, substrateRxns, initConcentrations, initBiomass, timeStep, nSteps, plotRxns, exclUptakeRxns) -% runDynamicFBA Perform dynamic FBA using the static optimization approach. +% runDynamicFBA Perform dynamic FBA using the static optimisation approach. % % Parameters % ---------- @@ -7,8 +7,8 @@ % a model structure. % substrateRxns : cell % cell array with exchange reaction identifiers for substrates that are -% initially in the media, whose concentration may change (e.g. not h2o -% or co2). +% initially in the media, whose concentration may change (for example +% not h2o or co2). % initConcentrations : double % initial concentrations of substrates (matching substrateRxns). % initBiomass : double @@ -22,7 +22,7 @@ % concentration should be plotted. % exclUptakeRxns : cell % cell array with exchange reaction identifiers for substrates whose -% concentration does not change (e.g. co2, o2, h2o, h). +% concentration does not change (for example co2, o2, h2o, h). % % Returns % ------- @@ -45,12 +45,11 @@ % Notes % ----- % If no initial concentration is given for a substrate that has an open -% uptake in the model (i.e. model.lb < 0) the concentration is assumed to -% be high enough to not be limiting. If the uptake rate for a nutrient is -% calculated to exceed the maximum uptake rate for that nutrient specified -% in the model and the max uptake rate specified is > 0, the maximum uptake -% rate specified in the model is used instead of the calculated uptake -% rate. +% uptake in the model (that is, model.lb < 0), the initial concentration is set +% to 1000. If the uptake bound calculated from the concentration exceeds +% the maximum uptake rate specified in the model for that nutrient, and the +% maximum uptake rate specified is > 0, the maximum uptake rate specified +% in the model is used as the uptake bound. % % Modified from COBRA Toolbox dynamicFBA.m. diff --git a/src/analysis/runProductionEnvelope.m b/src/analysis/runProductionEnvelope.m index a4fa966b..238437ca 100755 --- a/src/analysis/runProductionEnvelope.m +++ b/src/analysis/runProductionEnvelope.m @@ -13,14 +13,18 @@ % Name-Value Arguments % -------------------- % nPts : double -% number of points in the plot (default 20). +% number of biomass values at which the target bounds are calculated +% (default 20). % % Returns % ------- % biomassValues : double -% biomass values for plotting. +% nPts biomass values, evenly spaced from the minimum to the maximum +% biomass flux. % targetValues : double -% target upper and lower bounds for plotting. +% a matrix with two rows: the lower bounds (row 1) and the upper bounds +% (row 2) of the target reaction flux at each biomass value. NaN where +% the problem has no solution. % % Examples % -------- diff --git a/src/analysis/runRobustnessAnalysis.m b/src/analysis/runRobustnessAnalysis.m index c80c8299..032ffceb 100755 --- a/src/analysis/runRobustnessAnalysis.m +++ b/src/analysis/runRobustnessAnalysis.m @@ -1,8 +1,9 @@ function [controlFlux, objFlux] = runRobustnessAnalysis(model, controlRxn, varargin) % runRobustnessAnalysis Perform robustness analysis for a reaction and objective. % -% Performs robustness analysis for a reaction of interest and an objective -% of interest. +% Varies the flux of a reaction of interest from its minimum to its maximum +% and calculates the optimal value of an objective of interest at each +% flux. % % Parameters % ---------- @@ -14,12 +15,13 @@ % Name-Value Arguments % -------------------- % nPoints : double -% number of points to show on plot (default 20). +% number of control flux values (default 20). % objRxn : char -% reaction identifier of objective to be maximized (default uses the +% reaction identifier of the objective to be maximised (default: the % objective defined in the model). % plotRedCost : logical -% whether reduced cost should also be plotted (default false). +% whether the reduced cost of the control reaction is also plotted on +% the right axis (default false). % % Returns % ------- diff --git a/src/analysis/runSimpleOptKnock.m b/src/analysis/runSimpleOptKnock.m index b3fd9346..173505cd 100755 --- a/src/analysis/runSimpleOptKnock.m +++ b/src/analysis/runSimpleOptKnock.m @@ -1,10 +1,14 @@ function out = runSimpleOptKnock(model, targetRxn, biomassRxn, varargin) -% runSimpleOptKnock Simple OptKnock for growth-coupled production. +% runSimpleOptKnock Find deletions that couple metabolite production to growth. % -% Simple OptKnock algorithm that checks all gene or reaction deletions for -% growth-coupled metabolite production, by testing all possible -% combinations. This is not defined as MILP, and is therefore slow (but -% simple). +% Checks all gene or reaction deletions for growth-coupled metabolite +% production, by testing combinations of up to maxNumKO deletions. It tries +% a deeper deletion only after a deletion whose growth rate is above +% minGrowth. A combination is reported if the growth rate exceeds +% minGrowth and the product of growth rate and production rate exceeds the +% value without the last added deletion (the wild type for the first +% deletion) by more than 1 %. The method is not defined as a MILP and is +% therefore slow. % % Parameters % ---------- @@ -37,6 +41,7 @@ % - KO : cell array with gene(s) or reaction(s) to be deleted. % - growthRate : vector with growth rates after deletion. % - prodRate : vector with production rates after deletion. +% - score : vector with the product of growth rate and production rate. % % Examples % -------- diff --git a/src/analysis/sampleACHR.m b/src/analysis/sampleACHR.m index 355a06cb..124843b6 100644 --- a/src/analysis/sampleACHR.m +++ b/src/analysis/sampleACHR.m @@ -3,8 +3,9 @@ % % Draws nSamples flux vectors approximately uniformly from the flux polytope % {v : S*v = 0, lb <= v <= ub}, using the ACHR algorithm (Kaufman & Smith 1998). -% No rounding is performed, so on strongly elongated polytopes (e.g. -% enzyme-constrained models) mixing is slower than sampleCHRR; prefer CHRR there. +% No rounding is performed, so on strongly elongated polytopes (for example +% enzyme-constrained models) mixing is slower than with sampleCHRR, which +% rounds the polytope. % Reactions whose bounds or warmup values coincide never move. The remaining % reactions are corrected towards S*v = 0 every few steps and before each % recorded sample, with a sparse Cholesky factor of the row-normalised S*S' @@ -18,7 +19,7 @@ % Parameters % ---------- % model : struct -% a RAVEN model structure. +% a model structure. % nSamples : double % number of flux vectors to return (default 1000). % thinning : double diff --git a/src/analysis/sampleCHRR.m b/src/analysis/sampleCHRR.m index fe81ad59..53a9548d 100644 --- a/src/analysis/sampleCHRR.m +++ b/src/analysis/sampleCHRR.m @@ -3,17 +3,18 @@ % % Draws nSamples flux vectors approximately uniformly from the flux polytope % {v : S*v = 0, lb <= v <= ub}, using the CHRR algorithm (Haraldsdottir et al. -% 2017, Bioinformatics 33:1741). The polytope is rounded before sampling, so -% mixing stays fast even on strongly elongated polytopes (e.g. -% enzyme-constrained models), unlike sampleACHR. +% 2017, Bioinformatics 33:1741). The polytope is rounded before sampling, +% which improves mixing on strongly elongated polytopes (for example +% enzyme-constrained models) in comparison with sampleACHR, which does not +% round. % -% Set any constraints you want to condition on (e.g. a biomass lower bound or -% measured exchange fluxes) on the model before calling. +% Constraints to condition on (for example a biomass lower bound or measured +% exchange fluxes) are set on the model before the call. % % Parameters % ---------- % model : struct -% a RAVEN model structure. +% a model structure. % nSamples : double % number of flux vectors to return (default 1000). % thinning : double @@ -24,17 +25,18 @@ % random seed for reproducibility (default: current RNG state). % fixedWidthTol : double % a reaction whose allowed flux range is narrower than this is treated as -% stoichiometrically fixed and folded into the equality system, keeping the -% reduced polytope full-dimensional (default 1e-7). +% stoichiometrically fixed and added to the equality system at the middle +% of its range, which keeps the reduced polytope full-dimensional +% (default 1e-7). % % Returns % ------- % solutions : double % nRxns-by-nSamples matrix of sampled flux distributions. % info : struct -% .nDimensions — dimension of the sampled flux polytope -% .mveConverged — whether MVE rounding reached tolerance -% .fixedRxns — IDs of reactions folded in as fixed +% .nDimensions : dimension of the sampled flux polytope +% .mveConverged : whether MVE rounding reached tolerance +% .fixedRxns : IDs of reactions added to the equality system as fixed % % See also % -------- diff --git a/src/analysis/sampleMaxVolEllipse.m b/src/analysis/sampleMaxVolEllipse.m index 314e6630..ac2259e4 100644 --- a/src/analysis/sampleMaxVolEllipse.m +++ b/src/analysis/sampleMaxVolEllipse.m @@ -6,7 +6,7 @@ % {z : A*z <= b}, using the Zhang & Gao (2003) primal-dual interior-point % method (the regularised variant used in COBRA's CHRR sampler). This is the % rounding step of sampleCHRR: the returned transform E maps the polytope to a -% well-conditioned body in which coordinate hit-and-run mixes rapidly. +% well-conditioned body in which coordinate hit-and-run samples. % % This function is pure linear algebra (no LP solver) except when x0 is empty, % in which case a Chebyshev centre is obtained via sampleChebyshevCenter. @@ -26,8 +26,8 @@ % tol : double (optional) % convergence tolerance (default 1e-6). % reg : double (optional) -% diagonal / Levenberg regularisation keeping the Newton solves -% well-conditioned (default 1e-8). +% diagonal (Levenberg) regularisation of the Newton solves +% (default 1e-8). % % Returns % ------- @@ -37,14 +37,15 @@ % n-by-n lower-triangular rounding transform with E*E' = E2 (the SPD % ellipsoid matrix). The rounding substitution is z = x + E*y. % converged : logical -% whether tol was reached within maxiter. A false value is not fatal — -% the last iterate is still a valid (if looser) rounding. +% whether tol was reached within maxiter. If false, the last iterate +% is returned as the centre and transform, which is a valid but looser +% rounding. % % Notes % ----- -% Validated against analytic cases: a box maps to the unit ball (E = I); a -% sheared box {z : -1 <= M*z <= 1} gives E*E' = inv(M)*inv(M)'; a triangle -% gives its Steiner inellipse. +% Analytic cases, tested in tSampling: a box maps to the unit ball (E = I); +% a sheared box {z : -1 <= M*z <= 1} gives E*E' = inv(M)*inv(M)'; a +% triangle gives its Steiner inellipse. % % Based on Y. Zhang & L. Gao (2003), SIAM J. Optim. 14:53-76; as used in % Haraldsdottir et al. (2017) Bioinformatics 33:1741 (CHRR). diff --git a/src/analysis/sampleWarmupPoints.m b/src/analysis/sampleWarmupPoints.m index 6edff507..688e7fff 100644 --- a/src/analysis/sampleWarmupPoints.m +++ b/src/analysis/sampleWarmupPoints.m @@ -3,18 +3,19 @@ % % Generates warmup points for sampleACHR by maximising and minimising each % reaction in turn and storing the full flux distribution at each optimum. -% These vertices span the flux polytope and seed the hit-and-run directions. +% These vertices span the flux polytope and define the hit-and-run +% directions. % % Parameters % ---------- % model : struct -% a RAVEN model structure. +% a model structure. % % Name-Value Arguments % -------------------- % runParallel : logical -% speed up calculations by parallel processing, as in getAllowedBounds, -% which solves the same per-reaction min/max LPs (default true). +% run the calculations in parallel, as in getAllowedBounds, which solves +% the same per-reaction minimisation and maximisation LPs (default true). % % Returns % ------- diff --git a/src/analysis/traceFluxPath.m b/src/analysis/traceFluxPath.m index 45b01bce..1ea425a9 100644 --- a/src/analysis/traceFluxPath.m +++ b/src/analysis/traceFluxPath.m @@ -5,16 +5,16 @@ % network, quantifying what fraction of the source reaction's flux reaches % the target. At each metabolite junction the flux is split proportionally % among all consuming reactions; the path with the highest cumulative -% fraction is returned and printed. +% fraction is returned, and printed if verbose is true. % -% By default the search excludes currency metabolites (ATP, NAD, H2O, etc.) -% so the returned path represents material (carbon-skeleton) flow rather -% than cofactor shortcuts. Set traceMaterial=false to disable this. +% By default the search excludes currency metabolites (for example ATP, +% NAD, H2O), which makes the returned path follow material flow. The +% setting traceMaterial=false disables this. % % Parameters % ---------- % model : struct -% RAVEN model structure. +% a model structure. % fluxes : double % flux vector (length = numel(model.rxns)). % fromRxn : char @@ -25,25 +25,27 @@ % Name-Value Arguments % -------------------- % cutoff : double -% minimum |flux| for a reaction to be considered flux-carrying -% (default 1e-8). +% minimum net flux through a metabolite (|S_ij * v_j|) for a consuming +% reaction to be considered (default 1e-8). % maxHops : double -% maximum number of reactions in the path; longer paths are pruned -% (default 30). +% paths of more than maxHops reactions are not extended further, so a +% returned path has at most maxHops + 1 reactions (default 30). % verbose : logical % print the path to the command window (default true). % traceMaterial : logical -% exclude common currency/cofactor metabolites (ATP, ADP, NAD, NADH, -% H2O, CoA, Pi, CO2, …) so the path follows material (carbon-skeleton) -% flux rather than energy-carrier shortcuts (default true). +% exclude common currency and cofactor metabolites (ATP, ADP, NAD, NADH, +% H2O, CoA, Pi, CO2 and others); the path then follows material flux +% (default true). % carbonOnly : logical % additionally require each intermediate metabolite to contain at least % one carbon atom, inferred from model.metFormulas. Drops H+, H2O, Pi, -% O2, NH3, CO2 that are not already caught by the currency list. -% Silently ignored when model.metFormulas is absent (default false). +% O2, NH3, CO2 that are not already caught by the currency list. Ignored +% with a warning when model.metFormulas is absent or empty +% (default false). % excludeMets : cell of char % extra metabolite names or IDs (after stripping compartment suffix) to -% exclude, in addition to the built-in currency list (default {}). +% exclude, in addition to the built-in currency list (which applies when +% traceMaterial is true) (default {}). % % Returns % ------- @@ -53,13 +55,13 @@ % pathMets : cell % metabolite IDs at each junction (numel = numel(pathRxns)-1). % cumFrac : double -% fraction of fromRxn's flux that routes through this exact path +% fraction of fromRxn's flux that passes through this exact path % (0 = no path found, 1 = all flux reaches toRxn directly). % % Examples % -------- % [p, m, f] = traceFluxPath(model, sol.x, 'PFK', 'ATPS4rpp') -% % disable material filter to see all connections including ATP/ADP: +% % disable material filter to see all connections including ATP and ADP: % [p, m, f] = traceFluxPath(model, sol.x, 'PFK', 'ATPS4rpp', 'traceMaterial', false) % % add model-specific cofactors to the exclusion list: % [p, m, f] = traceFluxPath(model, sol.x, 'PFK', 'CS', 'excludeMets', {'acetyl-CoA'}) @@ -67,12 +69,12 @@ % Notes % ----- % The algorithm follows metabolites in the forward direction only: it -% traces the metabolites that fromRxn PRODUCES and finds which consuming +% traces the metabolites that fromRxn produces and finds which consuming % reactions eventually reach toRxn. If fromRxn and toRxn are not connected -% in the forward flux direction (e.g. they are in parallel branches), an -% empty path is returned. A best-first (greedy) search ensures the -% highest-fraction path is found efficiently; a visited set prevents -% revisiting reactions. +% in the forward flux direction (for example they are in parallel branches), an +% empty path is returned. A best-first search, which expands the entry with +% the highest cumulative fraction first, finds the highest-fraction path; +% a path does not revisit a reaction. p = parseRAVENargs(varargin, {'cutoff',1e-8; 'maxHops',30; 'verbose',true; ... 'traceMaterial',true; 'carbonOnly',false; 'excludeMets',{}}); diff --git a/src/analysis/walkFluxes.m b/src/analysis/walkFluxes.m index 3ec9cc93..7e27ebe6 100644 --- a/src/analysis/walkFluxes.m +++ b/src/analysis/walkFluxes.m @@ -1,26 +1,30 @@ function walkFluxes(model, fluxes, startRxn, varargin) % walkFluxes Interactively navigate a flux distribution reaction by reaction. % -% Starting from a chosen reaction, displays all flux-carrying reactions +% Starting from a chosen reaction, displays the flux-carrying reactions % connected through shared metabolites, grouped by metabolite and labelled -% with each neighbour's role (produces / consumes). Select a neighbour by -% number to step to it; build a history and rewind with 'b'. +% with each neighbour's role (produces or consumes). Entering the number of +% a neighbour steps to it. The visited reactions are kept in a history, and +% 'b' steps back through it. % % Parameters % ---------- % model : struct -% RAVEN model structure. +% a model structure. % fluxes : double % flux vector (length = numel(model.rxns)). % startRxn : char or double -% starting reaction ID (string) or column index. +% starting reaction ID (string) or reaction index. % % Name-Value Arguments % -------------------- % cutoff : double -% minimum |flux| to display a reaction as a neighbour (default 1e-8). +% minimum net flux through a metabolite (|S_ij * v_j|) for the metabolite +% to be listed for the current reaction and for a reaction to be +% displayed as a neighbour (default 1e-8). % maxPerMet : double -% maximum neighbour reactions displayed per metabolite (default 8). +% maximum neighbour reactions displayed per metabolite, selected by +% decreasing absolute flux (default 8). % % Examples % -------- @@ -31,14 +35,12 @@ function walkFluxes(model, fluxes, startRxn, varargin) % Notes % ----- % Navigation commands: -% — step to that neighbour reaction -% b — go back to the previous reaction -% q — quit +% : step to that neighbour reaction +% b : go back to the previous reaction +% q : quit % -% This function is interactive and reads from the terminal at every step; -% it errors immediately under -batch, in deployed code, or otherwise without -% a real terminal, rather than blocking forever waiting for input that will -% never arrive. +% This function is interactive and reads from the terminal at every step. +% It raises an error under -batch and in deployed code. isBatch = false; try diff --git a/src/annotation/assignSBOterms.m b/src/annotation/assignSBOterms.m index d2a42190..ab071724 100644 --- a/src/annotation/assignSBOterms.m +++ b/src/annotation/assignSBOterms.m @@ -1,26 +1,26 @@ function model = assignSBOterms(model, varargin) % assignSBOterms Assign SBO terms to metabolites and reactions. % -% Assign SBO terms to metabolites and reactions following a generic rule -% set. The MATLAB counterpart of yeast-GEM's legacy addSBOterms; -% organism-agnostic, parameterised entirely by opts. +% Assigns SBO terms to metabolites and reactions following a generic rule +% set that opts parameterises. The function is the MATLAB counterpart of +% yeast-GEM's addSBOterms. % -% SBO is written via editMiriam(..., 'fill') so pre-existing SBO -% annotations are preserved. The terms are stored in model.metMiriams and +% SBO is written via editMiriam(..., 'fill'), which preserves pre-existing +% SBO annotations. The terms are stored in model.metMiriams and % model.rxnMiriams under the namespace 'sbo' (there is no separate SBO -% field); read them with extractMiriam(model.rxnMiriams, 'sbo'). +% field); they can be read with extractMiriam(model.rxnMiriams, 'sbo'). % exportModel writes them as the SBML sboTerm attribute. % % Parameters % ---------- % model : struct -% RAVEN model struct. +% a model structure. % % Name-Value Arguments % -------------------- % opts : struct -% Struct with any of the following fields. Missing fields take the -% defaults shown: +% structure with any of the following fields. Missing or empty fields +% take the defaults shown: % % - biomassMetNames : {'biomass','DNA','RNA','protein','carbohydrate', % 'lipid','cofactor','ion'} @@ -28,15 +28,19 @@ % - biomassRxnName : 'biomass pseudoreaction' % - ngamRxnName : 'non-growth associated maintenance reaction' % - pseudoreactionSubstrings : {'pseudoreaction','SLIME rxn'} -% - onlyLastReactionForPseudo : false. yeast-GEM bug-compat flag that -% replicates the legacy `for i=numel(...)` typo (pseudoreaction SBOs -% applied only to the last reaction). Off by default; turn ON for -% byte-equivalent yeast-GEM output. +% - onlyLastReactionForPseudo : false. If true, the biomass, NGAM and +% pseudoreaction terms are applied only to the last reaction of the +% model. This reproduces yeast-GEM's addSBOterms.m, which applies the +% pseudoreaction rule to the last reaction only, because of a typo in +% the loop header (for i=numel(...)). +% - transportRxnIdxs : indexes of the transport reactions (default: +% the reactions identified by getTransportRxns). % % Returns % ------- % model : struct -% Modified model. +% model structure with the SBO terms added to model.metMiriams and +% model.rxnMiriams. % % Examples % -------- @@ -47,22 +51,21 @@ % ----- % Metabolites: % -% SBO:0000649 (Biomass) when met.name is in opts.biomassMetNames, or -% ends with any of opts.biomassMetSuffixes. Otherwise SBO:0000247 -% (Simple chemical). +% SBO:0000649 (Biomass) when the metabolite name is in +% opts.biomassMetNames, or ends with any of opts.biomassMetSuffixes. +% Otherwise SBO:0000247 (Simple chemical). % % Reactions (default → override → pseudoreaction override): % % SBO:0000176 (Metabolic reaction) default. % Single-reactant reactions become: % SBO:0000627 (exchange) if the lone metabolite is extracellular -% (compartment 'e' or compartment name containing -% 'extracellular'), +% (compartment 'e' or compartment name 'extracellular'), % SBO:0000632 (sink) if coef < 0, % SBO:0000628 (demand) otherwise. -% Transport reactions (detected by opts.transportDetector or the -% default heuristic: same metName in ≥ 2 compartments in a single -% reaction) → SBO:0000655. +% Transport reactions (opts.transportRxnIdxs or, by default, the +% reactions identified by getTransportRxns: the same metabolite +% name more than once in a single reaction) → SBO:0000655. % Reactions whose name matches opts.biomassRxnName → SBO:0000629. % Reactions whose name matches opts.ngamRxnName → SBO:0000630. % Reactions whose name contains any of opts.pseudoreactionSubstrings diff --git a/src/annotation/editMiriam.m b/src/annotation/editMiriam.m index b287573e..66ea9a89 100755 --- a/src/annotation/editMiriam.m +++ b/src/annotation/editMiriam.m @@ -4,34 +4,32 @@ % Parameters % ---------- % model : struct -% model structure. +% a model structure. % type : char -% 'met', 'rxn', 'gene' or 'comp' dependent on which objects the -% annotations should be assigned to. +% 'met', 'rxn', 'gene' or 'comp', the type of object that the +% annotations are assigned to. % object : cell or logical or double or char % either a cell array of IDs, a logical vector with the same number of -% elements as the type (see above) in the model, a vector of indexes, +% elements as objects of that type in the model, a vector of indexes, % or 'all'. % miriamName : char % string specifying the namespace of the identifier, for instance -% 'bigg.metabolite'. Should be a valid prefix from identifiers.org -% (e.g. https://registry.identifiers.org/registry/bigg.metabolite). +% 'bigg.metabolite'. A valid prefix from identifiers.org is expected +% (for example https://registry.identifiers.org/registry/bigg.metabolite). % miriams : char or cell -% string or cell array of strings with annotation identifiers, e.g. +% string or cell array of strings with annotation identifiers, for example % '12dgr161'. % keep : char % one of the following strings, specifying what should be done if an % object already has existing MIRIAM annotations with the same % miriamName: % -% - 'replace' : discard all existing annotations, all will be -% overwritten, even if the new annotation is an empty field. Should -% only be used if you do not want to keep any of the old annotation -% with the same miriamName. -% - 'fill' : only add annotations to those objects that did not yet -% have an annotation with that miriamName. Otherwise, the existing -% annotation is kept, even if it is different from the suggested new -% annotation. +% - 'replace' : discard all existing annotations with the same +% miriamName; they are overwritten, even if the new annotation is an +% empty field. +% - 'fill' : add annotations only to those objects that have no +% annotation with that miriamName. Otherwise, the existing annotation +% is kept, even if it differs from the new annotation. % - 'add' : keep all existing annotations, and add any new % annotations, after removing duplicates. % - 'remove' : remove all annotations with the given miriamName from diff --git a/src/annotation/extractMiriam.m b/src/annotation/extractMiriam.m index cafab891..be509d11 100755 --- a/src/annotation/extractMiriam.m +++ b/src/annotation/extractMiriam.m @@ -1,32 +1,28 @@ function [miriams,extractedMiriamNames]=extractMiriam(modelMiriams,varargin) -% extractMiriam Unpack MIRIAM annotations into a human-readable table. +% extractMiriam Unpack MIRIAM annotations into a table. % -% Unpacks metMiriams, rxnMiriams, geneMiriams or compMiriams into a -% human-readable cell array, formatted as in Excel, with the columns split -% so each miriam name gets its own column. +% Unpacks metMiriams, rxnMiriams, geneMiriams or compMiriams into a cell +% array with one column per miriam name. % % Parameters % ---------- % modelMiriams : cell -% a miriam structure (e.g. model.metMiriams) for one or multiple -% metabolites. +% a miriam structure (for example model.metMiriams) for one or more +% metabolites, reactions, genes or compartments. % % Name-Value Arguments % -------------------- % miriamNames : cell or char -% cell array with miriam names to be extracted (default 'all', meaning -% that annotation for all miriam names found in modelMiriams will be -% extracted). +% cell array with miriam names to be extracted (default 'all', which +% extracts the annotation for all miriam names found in modelMiriams). % % Returns % ------- % miriams : cell -% a cell array with extracted miriams. If several miriam names are -% requested, the corresponding information is saved in different -% columns. If there are several ids available for the same entity -% (metabolite, gene, reaction or compartment), they are concatenated -% into one column. The total number of columns represents the number -% of unique miriam names per entity. +% a cell array with extracted miriams, with one row per entity and one +% column per extracted miriam name. If several ids are available for +% the same entity (metabolite, gene, reaction or compartment) and miriam +% name, they are concatenated into one cell, separated by '; '. % extractedMiriamNames : cell % cell array with extracted miriam names. % diff --git a/src/annotation/loadDeltaGCSV.m b/src/annotation/loadDeltaGCSV.m index af787ec5..fe488c07 100644 --- a/src/annotation/loadDeltaGCSV.m +++ b/src/annotation/loadDeltaGCSV.m @@ -2,29 +2,30 @@ % loadDeltaGCSV Load metDeltaG and rxnDeltaG from CSV files. % % Each CSV is a two-column table: identifier, deltaG. On load, rows whose -% identifier is not in the model are silently skipped, and every matched -% value is stored exactly as it appears in the CSV -- including yeast-GEM's -% own "no measurement" placeholder (10000000.0). Callers that need to treat -% a particular value as absent should filter it themselves after loading; -% this function does not interpret CSV values. Pass '' for either metCsv -% or rxnCsv to skip that side. +% identifier is not in the model are skipped without a message, and every +% matched value is stored as it appears in the CSV, including yeast-GEM's +% "no measurement" placeholder (10000000.0). The function does not +% interpret CSV values, so a value that is to be treated as absent has to +% be filtered after loading. An empty string ('') for metCsv or rxnCsv +% skips that side. % % Parameters % ---------- % model : struct -% RAVEN model struct. +% a model structure. % % Name-Value Arguments % -------------------- % metCsv : char -% Path to the metabolite deltaG CSV, or '' to skip. +% path to the metabolite deltaG CSV, or '' to skip (default ''). % rxnCsv : char -% Path to the reaction deltaG CSV, or '' to skip. +% path to the reaction deltaG CSV, or '' to skip (default ''). % % Returns % ------- % model : struct -% Updated model struct. +% model structure. The metDeltaG field is set only if metCsv is given and +% the rxnDeltaG field only if rxnCsv is given. % % Examples % -------- diff --git a/src/annotation/saveDeltaGCSV.m b/src/annotation/saveDeltaGCSV.m index b205d1fc..af2a6e6b 100644 --- a/src/annotation/saveDeltaGCSV.m +++ b/src/annotation/saveDeltaGCSV.m @@ -1,11 +1,11 @@ function saveDeltaGCSV(model, varargin) % saveDeltaGCSV Save metDeltaG and rxnDeltaG to CSV files. % -% Writes a two-column table (identifier, deltaG) per side, one row per -% entity, in model order. An entity with no value stored is written as -% NaN, unchanged from whatever the model field holds -- this function does -% not interpret or substitute values. Pass '' for either metCsv or rxnCsv -% to skip that side. +% Writes a two-column table (identifier, deltaG) for metabolites and one for +% reactions, one row per entity, in model order. Values are written as the +% model field holds them, so an entity with no value stored is written as +% NaN. A file is skipped when its path is '' or when the model lacks the +% corresponding field. % % Parameters % ---------- diff --git a/src/biomass/fitParameters.m b/src/biomass/fitParameters.m index 65670b3a..7965a91a 100755 --- a/src/biomass/fitParameters.m +++ b/src/biomass/fitParameters.m @@ -5,10 +5,11 @@ % between the fluxes the model predicts and the measured ones, using % fminsearch (Nelder-Mead). Each candidate parameter set is written into the % S matrix at the positions given by parameterPositions, and the model is -% solved once per data point. +% solved once per data point with the xRxns bounds fixed to the values in +% xValues. The fitted parameters are returned as absolute values. % -% Use this to *derive* a value from measurements; use setGAM to *apply* a -% known growth-associated maintenance value to a biomass pseudoreaction. +% The function derives a value from measurements. To apply a known +% growth-associated maintenance value to a biomass pseudoreaction, use setGAM. % % Parameters % ---------- @@ -32,30 +33,33 @@ % - position : cell array of vectors where each element contains the % positions in the S-matrix for that parameter % - isNegative : cell array of vectors where the elements are true if -% that position should be the negative of the fitted value (to -% differentiate between production/consumption) +% that position is set to the negative of the fitted value (to +% distinguish production from consumption) % % Name-Value Arguments % -------------------- % fitToRatio : logical -% if the ratio of simulated to measured values should be fitted -% instead of the absolute value. Used to prevent large fluxes from -% having too large an impact (default true). +% true to fit the ratio of simulated to measured values, false to fit +% the absolute difference. The ratio limits the influence of large +% fluxes on the residual sum of squares (default true). % initialGuess : double % initial guess of the parameters (default ones). % plotFitting : logical -% true if the resulting fitting should be plotted (default false). +% true to plot the measured values and the fitted fluxes against +% xValues (default false). % % Returns % ------- % parameters : double -% fitted parameters in the same order as in parameterPositions. +% fitted parameters (absolute values) in the same order as in +% parameterPositions. % fitnessScore : double % the corresponding residual sum of squares. % exitFlag : double % exit status returned by fminsearch. % newModel : struct -% updated model structure with the fitted parameters. +% updated model structure with the fitted parameters. It is assigned +% only when plotFitting is true. % % Examples % -------- diff --git a/src/biomass/getBiomassFractions.m b/src/biomass/getBiomassFractions.m index 70ba89f1..6e8f2d5e 100644 --- a/src/biomass/getBiomassFractions.m +++ b/src/biomass/getBiomassFractions.m @@ -4,9 +4,16 @@ % Compute the mass fraction (g/gDW) per biomass component plus the total. % The MATLAB counterpart of yeast-GEM's legacy sumBioMass. % -% The biomassConfig struct describes the per-organism biomass layout — see -% Parameters below. Components whose pseudoreaction is missing from the -% model contribute 0. +% The biomassConfig struct describes the per-organism biomass layout (see +% Parameters). A component contributes 0 when its pseudoreaction is missing +% from the model or has no substrates. For a component whose pseudoreaction +% exists and has substrates, and whose mass_strategy is not 'grams', an error +% is raised when the molecular weight of a substrate is 0 (an empty +% metFormula field, or a formula that consists only of R), when its formula +% contains an element outside the built-in element table, or when +% mass_strategy is unknown. A component with an unknown mass_strategy whose +% pseudoreaction is missing or has no substrates contributes 0 without an +% error. % % Parameters % ---------- @@ -16,21 +23,22 @@ % Struct describing the biomass layout, with fields: % % - biomass_rxn : rxn id of the top-level biomass pseudoreaction. -% - proton_met : met id of cytosolic H+ (used only by -% rescalePseudoreaction; may be unused here). +% - proton_met : met id of cytosolic H+ (used by +% scaleBiomassPseudoreaction, not by getBiomassFractions). % - components : cell array of component structs, each with fields: % -% - name : component name (e.g. 'protein'). +% - name : component name (for example 'protein'). % - pseudoreaction_name : model.rxnNames entry to identify the % pseudoreaction. -% - mass_strategy : 'mw' | 'mw_minus_2h' | 'mw_minus_water' | -% 'grams' — see Notes below. +% - mass_strategy : 'mw', 'mw_minus_2h', 'mw_minus_water' or +% 'grams' (see Notes). % % Returns % ------- % fractions : struct -% Struct keyed by component name plus 'total': fractions.protein, -% fractions.RNA, ... etc. All values are in g/gDW. +% Struct with one field per component name and a field 'total' +% (for example fractions.protein and fractions.RNA). All values are in +% g/gDW. % % Examples % -------- @@ -41,11 +49,11 @@ % mass_strategy values: % % 'mw' MW from chemical formula -% 'mw_minus_2h' MW − 2.016 g/mol (two protons released per charged -% tRNA — protein-pseudoreaction substrates) -% 'mw_minus_water' MW − 18.015 g/mol (water released per -% polymerisation step — RNA / DNA) -% 'grams' stoichiometry already in g/gDW (lipid backbone) +% 'mw_minus_2h' MW - 2.016 g/mol (two protons released per charged +% tRNA; protein pseudoreaction substrates) +% 'mw_minus_water' MW - 18.015 g/mol (water released per +% polymerisation step; RNA and DNA) +% 'grams' stoichiometry is already in g/gDW (lipid backbone) fractions = struct(); total = 0; diff --git a/src/biomass/scaleBiomassFraction.m b/src/biomass/scaleBiomassFraction.m index 8881f07d..79a9e55a 100644 --- a/src/biomass/scaleBiomassFraction.m +++ b/src/biomass/scaleBiomassFraction.m @@ -11,9 +11,10 @@ % model : struct % RAVEN model struct. % biomassConfig : struct -% Struct (see getBiomassFractions). +% Biomass layout struct, as described in getBiomassFractions. % componentName : char -% Component to rescale. +% Name of the component to rescale. An error is raised if its current +% fraction is 0. % newValue : double % Target fraction in g/gDW. % @@ -21,9 +22,9 @@ % -------------------- % balanceOut : char % Second component name to adjust so the biomass total becomes -% targetTotal. It must be large enough to absorb the change: an error -% is raised if its fraction would become zero or negative, or if it is -% 0 to begin with. Empty / omit to skip balancing. +% targetTotal. An error is raised if its fraction would become zero or +% negative, or if it is 0 to begin with. No balancing is done when +% empty or omitted (default ''). % targetTotal : double % Biomass total in g/gDW that balanceOut brings the components to % (default 1). @@ -31,7 +32,7 @@ % Returns % ------- % model : struct -% Modified model. +% Model with the rescaled pseudoreactions. % % Examples % -------- diff --git a/src/biomass/scaleBiomassPseudoreaction.m b/src/biomass/scaleBiomassPseudoreaction.m index e50649f9..996bca00 100644 --- a/src/biomass/scaleBiomassPseudoreaction.m +++ b/src/biomass/scaleBiomassPseudoreaction.m @@ -6,10 +6,12 @@ % The MATLAB counterpart of yeast-GEM's legacy rescalePseudoReaction. % % "Substrate" means every metabolite in the pseudoreaction whose -% metabolite name does NOT match the component name (the component's -% product is left untouched). After rescaling, the coefficient of -% biomassConfig.proton_met is recomputed so the pseudoreaction's total -% ionic charge sums to zero. +% metabolite name differs from the component name; the coefficient of the +% component's product is unchanged. After rescaling, the coefficient of +% biomassConfig.proton_met is recomputed so that the total charge of the +% pseudoreaction is zero. An error is raised if the model has no +% metCharges field or if a metabolite other than proton_met in the +% pseudoreaction has an undefined (NaN) charge. % % Parameters % ---------- @@ -18,12 +20,11 @@ % biomassConfig : struct % Struct (see getBiomassFractions). % componentName : char -% Name of the component to rescale (must match -% biomassConfig.components{i}.name for some i, AND be the -% model.metNames of the produced metabolite in the matching -% pseudoreaction). +% Name of the component to rescale. It must match +% biomassConfig.components{i}.name for some i and the model.metNames +% entry of the produced metabolite in the matching pseudoreaction. % factor : double -% Multiplicative factor. +% Factor by which the substrate coefficients are multiplied. % % Returns % ------- diff --git a/src/biomass/setGAM.m b/src/biomass/setGAM.m index c0c492e0..0c99bc98 100644 --- a/src/biomass/setGAM.m +++ b/src/biomass/setGAM.m @@ -8,11 +8,11 @@ % % For every metabolite in the biomass pseudoreaction whose model.metNames % entry is in cofactorMetNames, the stoichiometric coefficient is set to -% ±value preserving the sign of the current coefficient. An error is raised +% value with the sign of the current coefficient. An error is raised % before the model is changed if ngamRxn is given and is not in the model, % and if ngamValue is given without ngamRxn (RAVEN:badInput). -% Yeast-GEM scales ATP, ADP, H2O, H+ and phosphate (with ATP and H2O on the -% substrate side, ADP / H+ / phosphate on the product side). +% For yeast-GEM, the cofactors are ATP, ADP, H2O, H+ and phosphate (ATP and +% H2O are substrates; ADP, H+ and phosphate are products). % % Parameters % ---------- @@ -23,7 +23,8 @@ % biomassRxn : char % Reaction id of the biomass pseudoreaction. % cofactorMetNames : cell -% Cell array of metabolite NAMES (not IDs) to rescale, e.g. +% Cell array of metabolite names (matched against model.metNames, not +% metabolite ids) to rescale, for example % {'ATP','ADP','H2O','H+','phosphate'}. % % Name-Value Arguments @@ -31,8 +32,8 @@ % ngamRxn : char % NGAM reaction id. Required when ngamValue is supplied. % ngamValue : double -% NGAM flux to fix. Sets the NGAM reaction's bounds to (ngamValue, -% ngamValue). +% NGAM flux to fix. Both bounds of the NGAM reaction are set to +% ngamValue. % % Returns % ------- @@ -45,8 +46,8 @@ % % See also % -------- -% fitParameters : estimates such a value from measured fluxes instead of -% applying a known one. The two are complementary - fit first, then set. +% fitParameters : derives such a value from measured fluxes; setGAM then +% applies it. if nargin < 4 error('setGAM:missingArgs', ... diff --git a/src/comparison/compareMultipleModels.m b/src/comparison/compareMultipleModels.m index 6407a403..147338c3 100755 --- a/src/comparison/compareMultipleModels.m +++ b/src/comparison/compareMultipleModels.m @@ -3,10 +3,10 @@ % % Compares two or more models on overlap of reactions, metabolites, genes, % EC-codes, metabolite names and reaction equations (with and without -% compartmentalization), and on high-dimensional comparisons in the -% reaction-space: subsystem utilization, structural similarity, an optional -% low-dimensional structural projection, and an optional functional -% (task-based) comparison. +% compartmentalisation), and on high-dimensional comparisons of the +% reaction content: subsystem coverage, structural similarity, a +% low-dimensional structural projection (skipped when neither tsne nor +% mdscale is available), and an optional functional (task-based) comparison. % % Parameters % ---------- @@ -16,18 +16,19 @@ % Name-Value Arguments % -------------------- % printResults : logical -% true if the results should be printed on the screen (default false). +% true to print the results to the command window (default false). % plotResults : logical -% true if the results should be plotted (default false). +% true to plot the results (default false). % groupVector : double or cell -% numeric vector or cell array for grouping similar models, i.e. by -% tissue (default all models ungrouped). +% numeric vector or cell array that assigns each model to a group, for +% example by tissue. The groups colour the points of the structural +% projection plot (default [], no grouping). % funcCompare : logical -% should a functional comparison be run (default false). +% true to run the functional comparison, which requires taskFile +% (default false). % taskFile : char -% string containing the name of the task file to use for the functional -% comparison (should be an .xls or .xlsx file, required for functional -% comparison). +% name of the task file for the functional comparison, in a format that +% parseTaskList reads. Required when funcCompare is true. % % Returns % ------- @@ -40,24 +41,28 @@ % row indicates which models are included in the comparison) and % nElements (vector with the number of elements for each comparison). % "equ" are the reaction equations after sorting and "uEqu" are the -% equations when not taking compartmentalization into account. These -% are the only fields of the seven that use EC-code, metabolite name or -% reaction equation to compare models that do not share an id -% convention; the rest compare by identifier only. +% equations without compartments. The fields rxns, mets and genes +% compare identifiers; eccodes, metNames, equ and uEqu compare EC +% codes, metabolite names and reaction equations, which allows +% overlap between models that use different identifiers. % - subsystems : substructure containing subsystem information, with -% fields matrix (matrix with comparison of number of rxns per -% subsystem) and ID (vector consisting of names of all subsystems) +% fields matrix (number of reactions per subsystem, subsystems in rows +% and models in columns) and ID (cell array with the names of all +% subsystems). Only the first subsystem of each reaction is counted. +% The field is absent when a model lacks the subSystems field. % - reactions : substructure containing reaction information, with -% fields matrix (binary matrix composed of reactions (rows) in each -% model (column), used as the input for the model comparisons) and IDs +% fields matrix (binary matrix with reactions in rows and models in +% columns, used as the input for the model comparisons) and IDs % (list of the reactions contained in the reaction matrix) % - structComp : matrix with pairwise comparisons of model structure -% based on (1-Hamming distance) between models -% - structCompMap : matrix with 3D tSNE (or MDS) mapping of model -% structures based on Hamming distances +% based on (1 - Hamming distance) between models +% - structCompMap : matrix with the 3D tSNE (or MDS) mapping of model +% structures based on Hamming distances. It is empty when neither +% tsne nor mdscale is available. % - funcComp : substructure containing function comparison results, with -% fields matrix (matrix with PASS / FAIL (1 / 0) values for each task) -% and tasks (vector containing names of all tasks) +% fields matrix (1 for a passed task and 0 for a failed task, tasks in +% rows and models in columns) and tasks (the descriptions of all +% tasks). It is present only when funcCompare is true. % % Examples % -------- @@ -71,9 +76,8 @@ % % See also % -------- -% diffModels : takes exactly two models and reports the differing *values* -% (stoichiometry, bounds, grRules), where this reports overlap counts, -% similarity and clustering. +% diffModels : reports the differing values (stoichiometry, bounds, grRules) +% between two models. %% Set up input defaults p=parseRAVENargs(varargin, {'printResults',false; 'plotResults',false; 'groupVector',[]; 'funcCompare',false; 'taskFile',[]}); @@ -429,10 +433,11 @@ function printList(models,toCheck,nElements) % % clust_dist Distance metric to be used for clustering, ignored if % clust_dim is "none". Options are the same as those for -% distance in, e.g., PDIST ("euclidean", "hamming", etc.). +% distance in pdist (for example "euclidean", "hamming" and +% other metrics). % (DEFAULT = "euclidean") % -% col_map Colormap, provided as string (e.g., "parula", "hot", "etc.") +% col_map Colormap, provided as string (for example "parula" or "hot") % or an Nx3 RGB matrix of N colors. % (DEFAULT = "hot") % diff --git a/src/comparison/diffModels.m b/src/comparison/diffModels.m index 00aed406..1b1f91e0 100644 --- a/src/comparison/diffModels.m +++ b/src/comparison/diffModels.m @@ -1,20 +1,17 @@ function report = diffModels(modelA, modelB, varargin) % diffModels Report the semantic differences between two models. % -% Compares two models entry by entry, keyed by identifier, and returns a report listing -% every difference found. Reactions, metabolites and genes are matched by -% id; for the ids present in both models the reaction stoichiometry, bounds, -% objective coefficient, grRule, EC codes, and the metabolite formula, -% charge and compartment are compared. This is the structured -% added/removed/changed diff that compareMultipleModels (which reports -% overlap counts, similarity and clustering across N models) does not -% provide. +% Compares two models entry by entry, keyed by identifier, and returns a +% report listing every difference found. Reactions, metabolites and genes +% are matched by id and reported when present in only one model. For the +% ids present in both models, the reaction stoichiometry, bounds, +% objective coefficient, grRule and EC codes, and the metabolite formula, +% charge and compartment are compared. % -% grRules are compared for logical equality rather than as strings: each -% rule is expanded to disjunctive normal form and the genes within each -% isozyme, and the isozymes themselves, are sorted before comparison, so -% "a and b" equals "b and a" and "(a or b)" equals "(b or a)". This is -% stronger than a case/whitespace string match. +% grRules are compared by logical content: each rule is expanded to +% disjunctive normal form, and the genes within each isozyme and the +% isozymes themselves are sorted before comparison, so "a and b" equals +% "b and a" and "(a or b)" equals "(b or a)". % % Parameters % ---------- @@ -29,10 +26,11 @@ % absolute tolerance for stoichiometric coefficient differences % (default 1e-9). % maxPerCategory : double -% the maximum number of differences reported per category before the -% rest are truncated with a single summary line (default 50). +% maximum number of entries in report.differences per category. Further +% differences of that category are replaced by a single summary line +% (default 50). % printResults : logical -% print the report to the command window (default false). +% true to print the report to the command window (default false). % % Returns % ------- @@ -41,19 +39,18 @@ % % - equal : true if no differences were found. % - differences : cell array of human-readable difference strings, each -% naming the category and the entity affected. -% - rxnsOnlyInA / rxnsOnlyInB : reaction ids present in only one model. -% - metsOnlyInA / metsOnlyInB : metabolite ids present in only one model. -% - genesOnlyInA / genesOnlyInB : gene ids present in only one model. -% -% Usage: report = diffModels(modelA, modelB, ...) +% naming the entity affected. +% - rxnsOnlyInA and rxnsOnlyInB : reaction ids present in only one +% model. +% - metsOnlyInA and metsOnlyInB : metabolite ids present in only one +% model. +% - genesOnlyInA and genesOnlyInB : gene ids present in only one model. +% Both are empty when either model lacks the genes field. % % See also % -------- -% compareMultipleModels : N models, set membership across seven identity -% notions (ids, EC codes, metabolite names, equations), similarity -% matrix, clustering and an optional task-based functional comparison -- -% no values. +% compareMultipleModels : overlap counts, similarity and clustering across +% two or more models. p = parseRAVENargs(varargin, {'stoichTol', 1e-9; 'maxPerCategory', 50; ... 'printResults', false}); diff --git a/src/conditions/applyCondition.m b/src/conditions/applyCondition.m index 02ad2456..0b8ae844 100644 --- a/src/conditions/applyCondition.m +++ b/src/conditions/applyCondition.m @@ -1,28 +1,39 @@ function model = applyCondition(model, condition) % applyCondition Apply a deterministic condition to a model. % -% Apply a deterministic "condition" to a model: a prelude that resets -% exchange bounds, optional metabolite removals + automatic charge -% rebalancing of a pseudoreaction, optional biomass-stoichiometry delta, -% and a per-reaction bounds diff. The schema is intentionally narrow so a -% condition can be reviewed as data. +% The steps of a condition are applied in this order: a prelude that resets +% exchange bounds, metabolite removals from a pseudoreaction with automatic +% charge rebalancing, a change of biomass stoichiometry, and per-reaction +% bound changes. A final check compares the number of uptake bounds with an +% expected count. The schema covers only these steps, so a condition can be +% reviewed as data. % -% The same schema works for any GEM that keeps its condition presets as -% data rather than as code. Project-specific extensions (e.g. yeast-GEM's amino_acid_ratio step that -% rewrites a protein pseudoreaction's stoichiometry from a side-car TSV) -% are handled by the caller before / after this function — kept -% upstream-narrow on purpose. +% The schema applies to any GEM whose condition presets are kept as data. +% Project-specific extensions, for example yeast-GEM's amino_acid_ratio +% step that rewrites the stoichiometry of a protein pseudoreaction from a +% separate TSV file, are applied by the caller before or after this +% function. % % Parameters % ---------- % model : struct % RAVEN model struct. % condition : char or struct -% Either a path to a YAML condition file or a struct already produced -% by parseYAML. The expected schema (all keys optional): +% Path to a YAML condition file, or a struct as produced by parseYAML. +% The schema is as follows (all keys optional). reset_exchanges is +% passed as the reaction type to getExchangeRxns; the lower bound of +% the matching reactions is set to 0 and the upper bound to 1000. +% charge_balance_met receives the coefficient that makes the charge of +% rxn_id zero, which requires the metCharges field; a participant of +% rxn_id other than charge_balance_met with a NaN charge raises +% applyCondition:unknownCharge. add entries are +% added to the existing coefficients. A bounds entry whose rxn is not +% in the model is skipped with a warning. expected_uptake_count is +% compared with the number of bounds entries with lb of -1000, and a +% warning is issued when they differ. % % prelude: -% reset_exchanges: out % truthy -> reset all +% reset_exchanges: out % % cofactor_pseudoreaction: % rxn_id: r_4598 @@ -47,7 +58,7 @@ % Returns % ------- % model : struct -% Modified model. +% Model with the condition applied. % % Examples % -------- diff --git a/src/conversion/addIdentifierPrefix.m b/src/conversion/addIdentifierPrefix.m index cf6b205f..8267d2e2 100644 --- a/src/conversion/addIdentifierPrefix.m +++ b/src/conversion/addIdentifierPrefix.m @@ -1,13 +1,13 @@ function [model, hasChanged]=addIdentifierPrefix(model,varargin) % addIdentifierPrefix Add identifier prefixes required by SBML. % -% If reaction, metabolite, compartment, gene or model identifiers do not -% start with a letter or _, which conflicts with SBML specifications, -% prefixes are added for all identifiers in the respective model field. +% If any reaction, metabolite, compartment, gene or model identifier does +% not start with a letter or _, which conflicts with SBML specifications, +% a prefix is added to all identifiers in the respective model field. % The prefixes are: % % "R_" for model.rxns, -% "M_" for model.mets, +% "M_" for model.mets and model.id, % "C_" for model.comps; % "G_" for model.genes (and also represented in model.grRules) % @@ -19,16 +19,17 @@ % Name-Value Arguments % -------------------- % fields : cell -% cell array with model field names that should be checked if prefixes -% should be added, possible values: 'rxns', 'mets', 'comps', 'genes', -% 'id' (default all listed model fields will be checked). +% cell array with the names of the model fields to check for the need +% of a prefix, possible values: 'rxns', 'mets', 'comps', 'genes', 'id' +% (default all of these fields). % % Returns % ------- % model : struct % modified model. % hasChanged : cell -% cell array with fields and prefixes that are added. +% cell array of char with one entry per field that received a prefix, +% for example 'model.rxns (R_ prefix)'. % % Examples % -------- diff --git a/src/conversion/ravenCobraWrapper.m b/src/conversion/ravenCobraWrapper.m index f82048df..1fba8ee9 100755 --- a/src/conversion/ravenCobraWrapper.m +++ b/src/conversion/ravenCobraWrapper.m @@ -1,19 +1,21 @@ function newModel=ravenCobraWrapper(model) % ravenCobraWrapper Convert between RAVEN and COBRA structures. % -% Recognises a COBRA structure by checking for the field 'rules', found -% only in a COBRA Toolbox structure. If the COBRA model also has a grRules -% field, that is used instead of parsing rules. +% A structure with the field 'rules' is treated as a COBRA Toolbox +% structure and converted to RAVEN; a structure without it is treated as a +% RAVEN structure and converted to COBRA. If a COBRA structure also has a +% grRules field, that field is used and rules is not parsed. % % Parameters % ---------- % model : struct -% a RAVEN/COBRA-compatible model structure. +% a RAVEN or COBRA model structure. % % Returns % ------- % newModel : struct -% a COBRA/RAVEN-compatible model structure. +% the model structure in the other format: COBRA for a RAVEN input and +% RAVEN for a COBRA input. % % Examples % -------- @@ -24,17 +26,18 @@ % During the RAVEN -> COBRA -> RAVEN conversion cycle the following fields % are lost: annotation, compOutside, compMiriams, rxnComps, geneComps, % unconstrained. Boundary metabolites are lost, because the COBRA -% structure does not involve boundary metabolites, so they are removed -% using simplifyModel before RAVEN -> COBRA conversion. The field 'rev' is -% also partially lost, but during COBRA -> RAVEN conversion it is -% reconstructed based on lower bound reaction values. +% structure has no boundary metabolites, and are removed using +% simplifyModel before the RAVEN -> COBRA conversion. The field 'rev' is +% also partially lost; during the COBRA -> RAVEN conversion it is +% reconstructed from the lower bounds (a reaction is reversible if its +% lower bound is below zero). % % During the COBRA -> RAVEN -> COBRA conversion cycle the following fields % are lost: geneEntrezID, modelVersion, proteins. % % The information about mandatory RAVEN fields was taken from the % checkModelStruct function, whereas the corresponding information about -% COBRA fields was fetched from the verifyModel function. +% COBRA fields was taken from the verifyModel function. if isfield(model,'rules') isRaven=false; diff --git a/src/conversion/removeIdentifierPrefix.m b/src/conversion/removeIdentifierPrefix.m index 72b951a2..f7905939 100644 --- a/src/conversion/removeIdentifierPrefix.m +++ b/src/conversion/removeIdentifierPrefix.m @@ -3,15 +3,15 @@ % % Removes identifier prefixes: % -% "R_" for model.rxns, model.rxnNames and model.id, -% "M_" for model.mets and model.metNames, +% "R_" for model.rxns and model.rxnNames, +% "M_" for model.mets, model.metNames and model.id, % "C_" for model.comps; % "G_" for model.genes (and also represented in model.grRules). % % By default, the prefixes are only removed if all entries in a particular -% field have the prefix. The prefixes might have been present because one -% or more identifiers do not start with a letter or _, which conflicts -% with SBML specifications. +% field have the prefix. The prefixes are required when one or more +% identifiers do not start with a letter or _, which conflicts with SBML +% specifications. % % Parameters % ---------- @@ -21,20 +21,20 @@ % Name-Value Arguments % -------------------- % fields : cell -% cell array with model field names from which the identifiers should -% be removed, possible values: 'rxns', 'mets', 'comps', 'genes', -% 'metNames', 'rxnNames', 'id' (default all listed model fields will -% be checked). +% cell array with the names of the model fields from which the prefix +% is removed, possible values: 'rxns', 'mets', 'comps', 'genes', +% 'metNames', 'rxnNames', 'id' (default all of these fields). % forceRemove : logical -% if prefixes should be removed even if not all entries in a model -% field have the prefix (default false). +% true to remove the prefix from the entries that have it when not all +% entries in a model field have the prefix (default false). % % Returns % ------- % model : struct % modified model. % hasChanged : cell -% cell array with fields and prefixes that are removed. +% cell array of char with one entry per field from which a prefix is +% removed, for example 'model.rxns (R_ prefix)'. % % Examples % -------- diff --git a/src/conversion/standardizeModelFieldOrder.m b/src/conversion/standardizeModelFieldOrder.m index acc29029..38134d26 100755 --- a/src/conversion/standardizeModelFieldOrder.m +++ b/src/conversion/standardizeModelFieldOrder.m @@ -1,12 +1,15 @@ function orderedModel=standardizeModelFieldOrder(model) % standardizeModelFieldOrder Order RAVEN model structure fields. % -% Orders fields of a RAVEN model structure as specified at +% Orders the fields of a RAVEN or COBRA model structure. The RAVEN order is +% the one listed in orderRavenFields.csv, described at % https://raven-docs.readthedocs.io/en/latest/guide/model-structure.html +% A structure with the field 'rules' is ordered as a COBRA structure, using +% COBRA_structure_fields.csv. Fields that are not listed in the file are +% placed last, in alphabetical order. % -% The model fields themselves are not changed, only the order is modified. -% For changing model fields between RAVEN and COBRA format, use -% ravenCobraWrapper(). +% The content of the fields is unchanged. To convert model fields between +% RAVEN and COBRA format, use ravenCobraWrapper(). % % Parameters % ---------- diff --git a/src/curation/curateModelFromTables.m b/src/curation/curateModelFromTables.m index 8ef9aba9..62469ff5 100644 --- a/src/curation/curateModelFromTables.m +++ b/src/curation/curateModelFromTables.m @@ -1,17 +1,16 @@ function newModel=curateModelFromTables(model,metsInfo,varargin) % curateModelFromTables Curate or add mets, rxns and genes from tables. % -% Curate existing and/or add new metabolites, reactions and genes from +% Curates existing metabolites, reactions and genes and adds new ones from % tabular data files. % -% If the *.tsv files contain metabolites, reactions and/or genes that are -% already present in the model, then information in the model will be -% overwritten. Note that this includes empty annotations in the *.tsv -% files, unless 'overwriteWithEmpty' is false! Metabolites are matched by -% metaboliteName[comp]; reactions by the stoichiometry of its reactants and -% products; genes by their gene name. -% This function can therefore be used to add new entities in the model, or -% curate those already existing in the model. +% If the *.tsv files contain metabolites, reactions or genes that are +% already present in the model, the information in the model is +% overwritten. This includes empty annotations in the *.tsv files, unless +% 'overwriteWithEmpty' is false. Metabolites are matched by +% metaboliteName[comp]; reactions by the stoichiometry of their reactants +% and products; genes by their gene name. Entities that are not present in +% the model are added. % % Columns are identified by their header (case-insensitive, and spaces around % a header are ignored, also inside its quotes), so their order @@ -49,27 +48,30 @@ % Path to a *.tsv file with reaction information, or 'none'. Columns: % rxnIdx (or index), rxnNames (both required), grRules, lb, ub, rev, % subSystems, eccodes, rxnNotes, rxnReferences, rxnConfidenceScores, -% and MIRIAM. Each rxnIdx may be used by one row only (the rxnsCoeffs -% file has one row per metabolite of the reaction and repeats it). +% and MIRIAM. An index appears in at most one row of this table, +% otherwise RAVEN:badInput is raised (the rxnsCoeffs file has one row per +% metabolite of the reaction and repeats it). The two reaction tables +% are matched on index and name, and reactions or indices that appear +% in only one of them raise RAVEN:badInput. % metPrefix : char -% Prefix used to mint fresh metabolite ids (e.g. 's_' for yeast-GEM, -% 'M_' for the BiGG default) (default 'M_'). +% Prefix used to create new metabolite ids (for example 's_' for +% yeast-GEM, 'M_' for the BiGG default) (default 'M_'). % rxnPrefix : char -% Prefix used to mint fresh reaction ids (default 'R_'). +% Prefix used to create new reaction ids (default 'R_'). % overwriteWithEmpty : logical % if true, an empty cell in a row of an existing metabolite, gene or % reaction empties the corresponding field of the model. If false, an % empty cell leaves the existing value unchanged and only the cells with a % value are written (default true; [] also means the default). Must be a -% scalar logical or 0/1. A cell with only blanks counts as empty in both +% scalar logical, 0 or 1. A cell with only blanks counts as empty in both % modes. Empty grRules cells never overwrite. An empty or NaN lb, ub or % rev cell always keeps the existing value. For the charge and the % confidence score, an empty or NaN cell is treated alike: when % overwriteWithEmpty is true and at least one other row of the column has % a value, it becomes 0; a column in which no row has a value is not % written, and the existing values stay. The same holds for a MIRIAM -% column: a column in which no row has a value does not clear the -% annotations of existing entities. +% column: a column in which no row of an existing entity has a value +% does not clear the annotations. % % Returns % ------- @@ -99,11 +101,12 @@ % given; for new reactions the reversibility is derived from lb and ub. % % Gene rules: a grRules cell must be a well-formed gene rule (genes joined by -% and/or in any case, or by &, &&, | and ||, with balanced parentheses); a -% rule with an operator that lacks an operand, empty parentheses or two genes -% without an operator between them raises RAVEN:badInput before the model is -% changed. A valid rule is stored in RAVEN's standard format. Whether the -% genes exist is checked when the rule is applied. +% the words and or or in any case, or by &, &&, | or ||, with balanced +% parentheses); a rule with an operator that lacks an operand, empty +% parentheses or two genes without an operator between them raises +% RAVEN:badInput before the model is changed. A valid rule is stored in +% RAVEN's standard format. Whether the genes exist is checked when the rule +% is applied. % % Genes: a gene id that the genesInfo table lists more than once raises % RAVEN:badInput, whether the model has the gene or not. @@ -117,9 +120,9 @@ % rxnIdx and a coefficient that is a finite number other than 0. A row of % the rxnsInfo table without any value is ignored; a row with a value needs % a rxnIdx. A rxnIdx is a whole number of at least 1; the indices (and -% names) of the two reaction tables must match exactly, compared by their -% numeric value (1, 1.0 and 1e0 are the same index). Otherwise RAVEN:badInput -% is raised. +% names) of the two reaction tables must match, with indices compared by +% their numeric value (1, 1.0 and 1e0 are the same index). Otherwise +% RAVEN:badInput is raised. p=parseRAVENargs(varargin, {'genesInfo','none'; 'rxnsCoeffs','none'; 'rxnsInfo','none'; 'metPrefix',[]; 'rxnPrefix',[]; 'overwriteWithEmpty',true}); overwriteWithEmpty=p.overwriteWithEmpty; diff --git a/src/curation/downloadGenomeData.m b/src/curation/downloadGenomeData.m index 72877d1b..6f54ae09 100644 --- a/src/curation/downloadGenomeData.m +++ b/src/curation/downloadGenomeData.m @@ -1,16 +1,17 @@ function [gffFile, faaFile] = downloadGenomeData(accession, varargin) % downloadGenomeData Download genome annotation files from NCBI. % -% Retrieves the GFF3 annotation and protein FASTA (.faa) files for a given -% NCBI genome assembly accession using the NCBI Datasets v2 API, which -% returns both files in a single archive. The files are saved locally and -% can be used directly by getGeneData to build a gene-mapping table. +% Downloads the GFF3 annotation and protein FASTA (.faa) files for an NCBI +% genome assembly accession using the NCBI Datasets v2 API, which returns +% both files in a single archive. The files are saved as +% _genomic.gff and _protein.faa in outputDir. They +% are input files for getGeneData. % % Parameters % ---------- % accession : char -% NCBI genome assembly accession, e.g. 'GCF_000002595.2'. -% Both RefSeq (GCF_) and GenBank (GCA_) prefixes are accepted. +% NCBI genome assembly accession, for example 'GCF_000002595.2'. The +% prefix is 'GCF_' (RefSeq) or 'GCA_' (GenBank). % % Name-Value Arguments % -------------------- @@ -40,8 +41,10 @@ % % Notes % ----- -% Requires an active internet connection. Files already present in -% outputDir are not re-fetched; delete them manually to force a refresh. +% An internet connection is required. When both files are already present +% in outputDir, no download takes place; deleting them forces a new +% download. An error is raised when NCBI Datasets does not return both a +% GFF3 file and a protein FASTA file for the accession. if nargin < 1 || isempty(accession) error('downloadGenomeData:missingInput', ... diff --git a/src/curation/getGeneData.m b/src/curation/getGeneData.m index 3f852c7c..497c3c88 100644 --- a/src/curation/getGeneData.m +++ b/src/curation/getGeneData.m @@ -1,39 +1,46 @@ function geneTable = getGeneData(accession, varargin) % getGeneData Build a gene-mapping table from NCBI genome annotation files. % -% Parses the GFF3 annotation and protein FASTA (.faa) files for a given -% NCBI genome assembly and produces a table mapping locus tags to gene -% symbols, protein IDs, and other identifiers. The resulting table can be -% passed directly to renameModelGenes to update gene identifiers in a -% RAVEN model. If no local files are provided, downloadGenomeData is -% called automatically to fetch them. +% Parses the GFF3 annotation file of an NCBI genome assembly and produces +% a table mapping locus tags to gene symbols, protein IDs and other +% identifiers. The table can be passed to renameModelGenes to change gene +% identifiers in a RAVEN model. If the input is an assembly accession, +% downloadGenomeData downloads the GFF3 and protein FASTA (.faa) files to +% downloadDir, unless both are already present there. % % Parameters % ---------- % accession : char -% NCBI genome assembly accession, e.g. 'GCF_000002595.2', or a path to a -% local GFF3 annotation file. +% NCBI genome assembly accession, for example 'GCF_000002595.2', or the +% path to a local GFF3 annotation file. An error is raised if the +% input is neither an existing file nor an accession of the form +% 'GCF_' or 'GCA_' followed by digits and an optional version. % % Name-Value Arguments % -------------------- % outputFile : char -% Path to save the resulting table as a tab-delimited .tsv file. If -% omitted or empty, no file is written and the table is only returned. +% Path of the tab-delimited .tsv file to which the table is written. +% No file is written if omitted or empty (default ''). % downloadDir : char -% Directory where genome files are downloaded if not already present -% (default: current working directory). +% Directory where genome files are downloaded if not already present. +% Only used when the input is an accession (default: current working +% directory). % % Returns % ------- % geneTable : table -% MATLAB table with one row per gene containing: -% locus_tag — stable locus identifier (e.g. 'Cre01.g000001') -% old_locus_tag — previous locus identifier when available -% GeneID — NCBI Gene ID (e.g. '5723799') -% gene_name — common gene symbol when available (e.g. 'rbcL') -% GenBank_protein — protein accession (e.g. 'XP_001698190.2'), matching -% the protein FASTA headers -% UniProt — UniProt accession from Dbxref when available +% MATLAB table with one row per distinct combination of gene and +% protein accession (a CDS feature with a protein accession, repeated +% CDS rows are merged), with the columns below. A column entry is empty +% when the annotation lacks the value. +% +% - locus_tag : locus identifier (for example 'Cre01.g000001') +% - old_locus_tag : earlier locus identifier +% - GeneID : NCBI Gene ID (for example '5723799') +% - gene_name : gene symbol (for example 'rbcL') +% - GenBank_protein : protein accession (for example 'XP_001698190.2'), +% as in the protein FASTA headers +% - UniProt : UniProt accession from the Dbxref attribute of the gene % % Examples % -------- @@ -49,8 +56,9 @@ % Notes % ----- % When outputFile is provided the table is written as UTF-8 encoded -% tab-delimited text with a header row, compatible with readtable and -% renameModelGenes. Rows without a locus_tag are silently discarded. +% tab-delimited text with a header row, which readtable and +% renameModelGenes can read. A CDS whose gene has no locus_tag, or whose +% gene feature is missing, gives a row with an empty locus_tag. if nargin < 1 || isempty(accession) error('getGeneData:missingInput', ... diff --git a/src/curation/processProteinFastaFile.m b/src/curation/processProteinFastaFile.m index eeebf5b4..a5518bfe 100644 --- a/src/curation/processProteinFastaFile.m +++ b/src/curation/processProteinFastaFile.m @@ -1,11 +1,13 @@ function processedFaaFile = processProteinFastaFile(faaFile, geneTable, headerCol, varargin) % processProteinFastaFile Rename protein FASTA headers using a gene mapping table. % -% Reads a protein FASTA file (as downloaded by downloadGenomeData) and -% replaces each sequence header with the value from the specified geneTable -% column, matched via the GenBank_protein accession present in the original -% FASTA header. Sequences whose accession is not found in geneTable are -% kept with their original header unchanged. +% Reads a protein FASTA file (such as one downloaded by downloadGenomeData) +% and replaces each sequence header with the value from the specified +% geneTable column. A sequence is matched to a row through the +% GenBank_protein accession, which is the first token of the original +% FASTA header. Sequences whose accession is not found in geneTable keep +% their original header. The numbers of renamed and kept sequences are +% printed. % % Parameters % ---------- @@ -13,18 +15,19 @@ % Path to the protein FASTA file (.faa) to process. % geneTable : char | table % Either a path to a tab-delimited .tsv file produced by getGeneData, -% or a MATLAB table variable. Must contain at least the columns +% or a MATLAB table variable. It must contain the columns % 'GenBank_protein' and the column named by headerCol. % headerCol : char % Name of the geneTable column whose values will replace each FASTA -% header (e.g. 'locus_tag', 'gene_name', 'GenBank_protein'). +% header (for example 'locus_tag', 'gene_name', 'GenBank_protein'). % % Name-Value Arguments % -------------------- % outputDir : char -% Directory where the processed FASTA file is saved. The output file -% name is the original base name with '_processed' appended before the -% extension (default: current working directory). +% Directory where the processed FASTA file is saved. It is created if +% missing. The output file name is the original base name with +% '_processed' appended before the extension (default: current working +% directory). % % Returns % ------- @@ -46,10 +49,11 @@ % % Notes % ----- -% GenBank_protein must contain accessions matching the first token of each -% FASTA header (after '>'). For prokaryotes this is the protein_id -% (e.g. WP_012345678.1); for eukaryotes it is the GenBank accession stored -% in the GFF3 annotation by getGeneData. +% GenBank_protein must contain accessions equal to the first +% whitespace-delimited token of each FASTA header (after '>'), for example +% WP_012345678.1. getGeneData takes the accession from the protein_id +% attribute of the CDS features of the GFF3 annotation, or from a GenBank +% entry of the Dbxref attribute when protein_id is empty. if nargin < 1 || isempty(faaFile) error('processProteinFastaFile:missingInput', ... diff --git a/src/curation/renameModelGenes.m b/src/curation/renameModelGenes.m index 6a7c8660..9dc2a32f 100644 --- a/src/curation/renameModelGenes.m +++ b/src/curation/renameModelGenes.m @@ -1,34 +1,35 @@ function model = renameModelGenes(model, geneTable, fromCol, toCol) % renameModelGenes Replace gene identifiers in a RAVEN model. % -% Updates model.genes and model.grRules by substituting the identifiers -% currently used in the model (fromCol) with new identifiers (toCol) from -% a mapping table produced by getGeneData or supplied manually. After -% renaming, model.rxnGeneMat is rebuilt automatically. +% Replaces the identifiers in model.genes and model.grRules that are listed +% in column fromCol of a mapping table with the identifiers in column +% toCol. The table is produced by getGeneData or supplied manually. After +% renaming, model.rxnGeneMat is rebuilt. % % Parameters % ---------- % model : struct -% RAVEN model struct. Must contain model.genes and model.grRules; -% model.rxnGeneMat is rebuilt automatically. +% RAVEN model struct. It must contain model.genes and model.grRules. % geneTable : table | char -% Gene-mapping table. Can be supplied as: -% (a) A MATLAB table variable already loaded in the workspace, or -% (b) A path to a tab-delimited .tsv file — loaded automatically. +% Gene-mapping table, either +% (a) a MATLAB table variable, or +% (b) the path to a tab-delimited .tsv file, which is read with +% readtable. % fromCol : char -% Column in geneTable whose values match the identifiers currently in -% model.genes (e.g. 'locus_tag'). +% Name of the column in geneTable whose values match the identifiers +% in model.genes (for example 'locus_tag'). % toCol : char -% Column in geneTable whose values will replace the current -% identifiers (e.g. 'gene_name'). +% Name of the column in geneTable with the new identifiers (for +% example 'gene_name'). % % Returns % ------- % model : struct -% Updated model struct with: -% model.genes — renamed gene list. -% model.grRules — gene-reaction rules using new identifiers. -% model.rxnGeneMat — rebuilt sparse matrix matching the new gene list. +% Model struct with these fields changed: +% +% - model.genes : gene list with the new identifiers. +% - model.grRules : gene-reaction rules with the new identifiers. +% - model.rxnGeneMat : sparse matrix rebuilt for the new gene list. % % Examples % -------- @@ -42,11 +43,17 @@ % Notes % ----- % * Genes with no entry in fromCol, or whose toCol value is empty, are left -% unchanged; a warning lists them for investigation. -% * Word-boundary matching prevents partial substitution (e.g. 'gene1' -% is not replaced inside 'gene10'). -% * grRules are standardized via standardizeGrRules before and after -% renaming to ensure consistent formatting. +% unchanged, and a warning lists them. +% * If a fromCol value occurs in several rows, the first row with a +% non-empty toCol value is used. Values are trimmed of surrounding +% whitespace. +% * An identifier is replaced only where it is delimited by whitespace, +% parentheses or the start or end of the rule, so 'gene1' is not +% replaced inside 'gene10'. +% * Unnecessary outer parentheses are removed from the grRules and the +% grRules are standardised with standardizeGrRules before and after +% renaming. A warning is issued when standardizeGrRules reports rules +% with potential issues. if nargin < 1 || isempty(model) error('renameModelGenes:missingInput', ... diff --git a/src/deprecated/canConsume.m b/src/deprecated/canConsume.m index 4ceb625c..e478f2b0 100644 --- a/src/deprecated/canConsume.m +++ b/src/deprecated/canConsume.m @@ -4,7 +4,7 @@ % Deprecated wrapper kept so existing scripts keep working; it will be % removed in the next major release. % -% See Also +% See also % -------- % canExchange diff --git a/src/deprecated/canProduce.m b/src/deprecated/canProduce.m index f3df9ef7..e87c469a 100644 --- a/src/deprecated/canProduce.m +++ b/src/deprecated/canProduce.m @@ -4,7 +4,7 @@ % Deprecated wrapper kept so existing scripts keep working; it will be % removed in the next major release. % -% See Also +% See also % -------- % canExchange diff --git a/src/deprecated/consumeSomething.m b/src/deprecated/consumeSomething.m index 0b468286..71f5736a 100644 --- a/src/deprecated/consumeSomething.m +++ b/src/deprecated/consumeSomething.m @@ -4,16 +4,14 @@ % Deprecated wrapper kept so existing scripts keep working; it will be % removed in the next major release. % -% Its positional argument order predates findLeakMetabolite and is -% preserved here: +% The positional argument order is: % % consumeSomething(model, ignoreMets, isNames, minNrFluxes, params, ignoreIntBounds) % -% findLeakMetabolite inserts allowExcretion (a produce-only option) in -% position 4, so the arguments are translated rather than forwarded -% verbatim. +% findLeakMetabolite takes allowExcretion (a produce-only option) in +% position 4, so the arguments are translated before they are passed on. % -% See Also +% See also % -------- % findLeakMetabolite diff --git a/src/deprecated/makeSomething.m b/src/deprecated/makeSomething.m index b786755e..20d65b8a 100644 --- a/src/deprecated/makeSomething.m +++ b/src/deprecated/makeSomething.m @@ -5,7 +5,7 @@ % removed in the next major release. Its argument order already matches % findLeakMetabolite's, so the arguments are forwarded as they are. % -% See Also +% See also % -------- % findLeakMetabolite diff --git a/src/gapfilling/canExchange.m b/src/gapfilling/canExchange.m index 6dbb0687..8654413a 100644 --- a/src/gapfilling/canExchange.m +++ b/src/gapfilling/canExchange.m @@ -2,12 +2,14 @@ % canExchange Check which metabolites a model can produce or consume. % % Adds an exchange reaction for each of the specified metabolites and -% checks which of them can carry a flux. This is the unified replacement -% for canProduce ('produce') and canConsume ('consume'). +% checks which of them can carry an absolute flux above 1e-5. The direction +% 'produce' corresponds to the deprecated function canProduce and 'consume' +% to canConsume. % -% This is a less advanced but faster version of checkProduction, which -% additionally reports which metabolites must be connected in order to make -% the remaining ones producible. +% canExchange only tests whether each added exchange reaction can carry flux. +% checkProduction also reports which metabolites must be connected in order +% to make the remaining ones producible, which requires further problems to be +% solved. % % Parameters % ---------- diff --git a/src/gapfilling/checkProduction.m b/src/gapfilling/checkProduction.m index a27607f3..157bede6 100755 --- a/src/gapfilling/checkProduction.m +++ b/src/gapfilling/checkProduction.m @@ -1,16 +1,16 @@ function [notProduced, notProducedNames, neededForProductionMat,minToConnect,model]=checkProduction(model,varargin) % checkProduction Check which metabolites can be produced from a model. % -% Checks which metabolites that can be produced from a model using the -% specified constraints. +% Checks which metabolites can be produced from a model under the current +% constraints. % -% The function is intended to be used to identify which metabolites must be -% connected in order to have a fully connected network. It does so by first -% identifying which metabolites could have a net production in the network. -% Then it calculates which other metabolites must be able to have net -% production in order to have production of all metabolites in the network. -% So, if a network contains the equations A[external]->B, C->D, and D->E it -% will identify that production of C will connect the metabolites D and E. +% The function identifies which metabolites must be connected in order to +% have a fully connected network. It first identifies which metabolites +% can have a net production in the network. It then calculates which other +% metabolites must be able to have net production in order to have +% production of all metabolites in the network. If a network contains the +% equations A[external]->B, C->D, and D->E, production of C connects the +% metabolites D and E. % % Parameters % ---------- @@ -20,31 +20,43 @@ % Name-Value Arguments % -------------------- % checkNeededForProduction : logical -% for each of the metabolites that could not be produced, include an -% artificial production reaction and calculate which new metabolites that -% could be produced as an effect of this (default false). +% true to calculate, for each metabolite that could not be produced, +% which of the other such metabolites can be produced when an uptake +% reaction for it is added (default false). % excretionFromCompartments : cell % cell array with compartment ids from which metabolites can be excreted % (default model.comps). % printDetails : logical -% print details to the screen (default true). +% true to print the metabolites that could not be produced to the command +% window (default true). % % Returns % ------- % notProduced : double -% indices of metabolites that could not be produced. +% indices of metabolites that could not be produced, counted among the +% metabolites in the compartments of excretionFromCompartments, in model +% order. % notProducedNames : cell % cell array with names and compartments for metabolites that could not % be produced. % neededForProductionMat : logical % matrix where n x m is true if metabolite n allows for production of -% metabolite m. +% metabolite m. The rows and columns follow the order of notProduced. +% It is empty if checkNeededForProduction is false. % minToConnect : cell -% the minimal number of metabolites that need to be connected in order to -% be able to produce all other metabolites, and which metabolites each of -% them connects. +% metabolites that need to be connected in order to be able to produce +% all other metabolites, each with the number of metabolites it +% connects. The metabolites are selected iteratively, each time the one +% that connects the most remaining metabolites, so the number of +% metabolites need not be the global minimum. It is empty if +% checkNeededForProduction is false. % model : struct -% updated model structure with excretion reactions added. +% model structure with an excretion reaction added for each metabolite +% in the compartments of excretionFromCompartments. When +% checkNeededForProduction is true, the excretion reaction of the last +% metabolite in notProduced is returned as an uptake reaction, because +% the loop that flips each reaction into an uptake reaction does not +% reset the last one. % % Examples % -------- diff --git a/src/gapfilling/fillGaps.m b/src/gapfilling/fillGaps.m index 0ebe1605..b0000105 100755 --- a/src/gapfilling/fillGaps.m +++ b/src/gapfilling/fillGaps.m @@ -1,54 +1,57 @@ function [newConnected, cannotConnect, addedRxns, newModel, exitFlag]=fillGaps(model,models,varargin) % fillGaps Use template model(s) to fill gaps in a model. % -% This method works by merging the model with the reference model(s) and -% checking which reactions can carry flux. All reactions that cannot carry -% flux are removed (cannotConnect). If useModelConstraints is false it -% then solves the MILP problem of minimizing the number of active -% reactions from the reference models that are required to have flux in -% all the reactions in model. This requires that the input model has -% exchange reactions present for the nutrients that are needed for its -% metabolism. If useModelConstraints is true then the problem is to -% include as few reactions as possible from the reference models in order -% to satisfy the model constraints. +% The model is merged with the reference model(s). If useModelConstraints +% is false, the reactions of the model that cannot carry flux in the model +% are tested for flux in the merged model. The non-reversible reactions that +% carry flux in the merged model are the newConnected reactions. The other +% reactions are returned as cannotConnect: the reactions that cannot carry +% flux in the merged model and the reversible reactions that can, which are +% left out of the MILP target set. A MILP then minimises the number of +% active reactions from the reference models that are required for flux in +% the reactions that can be connected (newConnected). This requires that +% the input model has exchange reactions for the nutrients that its +% metabolism needs. If useModelConstraints is true, the flux test and the +% split into newConnected and cannotConnect are not run, and the MILP +% includes as few reactions as possible from the reference models to +% satisfy the model constraints. % -% The intended use is that the user can attempt a general gap-filling -% using useModelConstraints=false, or a more targeted gap-filling by -% setting constraints in the model structure and then using -% useModelConstraints=true. For example, to include reactions so that all -% biomass components can be synthesized, the user could define a biomass -% equation and set its lower bound to >0. Running this function with -% useModelConstraints=true would then give the smallest set of reactions -% that have to be included in order for the model to produce biomass. +% A general gap-filling uses useModelConstraints=false. A targeted +% gap-filling sets constraints in the model structure and uses +% useModelConstraints=true. For example, a model with a biomass equation +% whose lower bound is set above 0 gets the smallest set of reactions from +% the reference models that allows it to produce biomass. % % Parameters % ---------- % model : struct % a model structure that may contain gaps to be filled. % models : cell or struct -% a cell array of reference models or a model structure. The gaps will -% be filled using reactions from these models. +% a cell array of reference models or a model structure. Reactions +% from these models are used to fill the gaps. % % Name-Value Arguments % -------------------- % allowNetProduction : logical -% true if net production of all metabolites is allowed. A reaction can -% be unable to carry flux because one of the reactants is unavailable -% or because one of the products cannot be further processed. If true, -% only the first type of unconnectivity is considered (default false). +% true to allow net production of all metabolites. A reaction can be +% unable to carry flux because one of the reactants is unavailable or +% because one of the products cannot be further processed. If true, +% only the first cause is considered (default false). % useModelConstraints : logical -% true if the constraints specified in the model structure should be -% used. If false then reactions are included from the template -% model(s) so that as many reactions as possible in model can carry -% flux (default false). +% true to use the constraints specified in the model structure. If +% false, reactions are included from the template model(s) so that as +% many reactions as possible in model can carry flux (default false). % supressWarnings : logical -% false if warnings should be displayed (default false). +% true to suppress the warning and the note about missing exchange +% reactions (default false). % rxnScores : double or cell -% array with scores for each of the reactions in the reference -% model(s). If more than one model is supplied, then rxnScores should -% be a cell array of vectors. The solver will try to maximize the sum -% of the scores for the included reactions (default is -1 for all -% reactions). +% scores for each of the reactions in the reference model(s). If more +% than one model is supplied, rxnScores is a cell array of vectors. The +% solver maximises the sum of the scores of the included reactions. +% Used by the 'reference' algorithm (default -1 for all reactions). +% params : struct +% solver parameters passed to getMinNrFluxes (algorithm 'reference') or +% to gapFillMILP (algorithm 'gapfillMILP') (default empty struct). % algorithm : char % gap-filling algorithm to use (default 'reference'): % @@ -62,20 +65,26 @@ % epsilon : double % minimum flux threshold used by fastLP and swiftLP (default 1e-4). % minGrowth : double -% minimum growth rate for gapfillMILP (default [] = 10% of max FBA). +% minimum growth rate for gapfillMILP (default [], which is 10% of the +% maximum growth rate found by FBA on the merged model). % verbose : logical -% print progress messages for new algorithms (default true). +% true to print progress messages for the algorithms other than +% 'reference' (default true). % % Returns % ------- % newConnected : cell -% cell array with the reactions that could be connected. This is not -% calculated if useModelConstraints is true. +% cell array with the non-reversible reactions of model that cannot +% carry flux in model but can in the merged model. Empty if +% useModelConstraints is true or if the algorithm is not 'reference'. % cannotConnect : cell -% cell array with reactions that could not be connected. This is not -% calculated if useModelConstraints is true. +% cell array with the other reactions of model that cannot carry flux +% in model. For the algorithms 'fastLP' and 'swiftLP' it holds the +% reactions that remain blocked after merging (from gapFillFastLP), and +% for 'topological' the ids of the metabolites that cannot be reached. +% Empty if useModelConstraints is true or for 'gapfillMILP'. % addedRxns : cell -% cell array with the reactions that were added from "models". +% cell array with the reactions added from "models". % newModel : struct % the model with reactions added to fill gaps. % exitFlag : double @@ -97,14 +106,15 @@ % ----- % The 'reference' and 'gapfillMILP' algorithms solve a MILP with the % solver set by setRavenSolver. RAVEN's bundled GLPK solves it for small -% models (with a RAVEN:glpkMILP warning); for larger models Gurobi is much -% faster and more reliable (setRavenSolver('gurobi')). +% models. The RAVEN:glpkMILP warning issued with GLPK states that GLPK works +% for small problems and that Gurobi is much faster and more reliable for +% larger ones (setRavenSolver('gurobi')). % -% See Also +% See also % -------- -% ftINITFillGaps : the ftINIT-side counterpart, used when the reference -% model already contains the model's own reactions, so no per-task -% merge is needed. +% ftINITFillGaps : the variant of fillGaps for ftINIT, used when the +% reference model already contains the reactions of the model. No merge +% per task is needed in that case. %If the user only supplied a single template model if ~iscell(models) diff --git a/src/gapfilling/findLeakMetabolite.m b/src/gapfilling/findLeakMetabolite.m index 96c9f7ae..2d6db847 100644 --- a/src/gapfilling/findLeakMetabolite.m +++ b/src/gapfilling/findLeakMetabolite.m @@ -2,10 +2,10 @@ % findLeakMetabolite Find a metabolite that can be freely made or consumed. % % Tests whether the model can produce (excrete) or consume (take up) any -% metabolite without balanced stoichiometric constraints — a sign that the -% model contains a stoichiometric leak, futile cycle, or unconstrained -% exchange. This is the unified replacement for makeSomething ('produce') -% and consumeSomething ('consume'). +% metabolite without balanced stoichiometric constraints. A positive result +% indicates that the model contains a stoichiometric leak, a futile cycle or +% an unconstrained exchange. The direction 'produce' corresponds to the +% deprecated function makeSomething and 'consume' to consumeSomething. % % Parameters % ---------- @@ -22,33 +22,33 @@ % number of elements as metabolites in the model, or a vector of indexes % for metabolites to exclude from this analysis (default []). % isNames : logical -% true if the supplied mets represent metabolite names (as opposed to -% IDs). This is a way to delete metabolites in several compartments at -% once without knowing the exact IDs. This only works if ignoreMets is a +% true if the supplied mets are metabolite names and false if they are +% IDs. Names exclude the metabolites of all compartments that carry the +% name, so the exact IDs are not needed. Only works if ignoreMets is a % cell array (default false). % minNrFluxes : logical -% solves the MILP problem of minimizing the number of fluxes instead of -% the sum. Slower, but can be used if the sum gives too many fluxes -% (default false). +% true to solve the MILP problem of minimising the number of fluxes. If +% false, the sum of fluxes is minimised. The MILP is slower and applies +% when the sum gives too many fluxes (default false). % allowExcretion : logical -% allow for excretion of all metabolites. Only used when direction is +% true to allow excretion of all metabolites. Only used when direction is % 'produce' (default true). % params : struct -% *obsolete option*. +% solver parameters, passed to solveLP when minNrFluxes is true (default +% a structure with relGap 0.8). % ignoreIntBounds : logical -% true if internal bounds (including reversibility) should be ignored. -% Exchange reactions are not affected. This can be used to find -% unbalanced solutions which are not possible using the default -% constraints (default false). +% true to ignore internal bounds (including reversibility). Exchange +% reactions are not affected. Finds unbalanced solutions that the +% default constraints do not allow (default false). % % Returns % ------- % solution : double % flux vector for the solution. % metabolite : double -% the index of the metabolite(s) which was produced or consumed. If -% possible only one metabolite is reported, but there are situations -% where metabolites can only be exchanged in pairs (or more). +% the index of the metabolite(s) produced or consumed. A single +% metabolite is reported when one suffices; some metabolites can only be +% exchanged in pairs or larger groups. % % Examples % -------- @@ -57,12 +57,12 @@ % % Notes % ----- -% Works by forcing at least 1 unit of "any metabolite" to be produced or -% consumed, then minimising for the sum of fluxes. If more than one -% metabolite is produced/consumed, it tries to find a single metabolite -% that satisfies the constraint on its own. +% The function forces a flux of at least 1 through a pooled exchange of all +% metabolites, then minimises the sum of fluxes. If more than one metabolite +% is produced or consumed, it searches for a single metabolite that +% satisfies the constraint on its own. % -% See Also +% See also % -------- % canExchange diff --git a/src/gapfilling/fitTasks.m b/src/gapfilling/fitTasks.m index d311cbbd..b45beb94 100755 --- a/src/gapfilling/fitTasks.m +++ b/src/gapfilling/fitTasks.m @@ -3,9 +3,9 @@ % % Fills gaps in a model by including reactions from a reference model, so % that the resulting model can perform all the tasks in a task list. The -% gap-filling is done in a task-by-task manner, rather than solving for -% all tasks at once. This means that the order of the tasks could -% influence the result. +% tasks are gap-filled one at a time in list order, and the reactions added +% for one task are kept for the following tasks. The order of the tasks can +% therefore influence the result. % % Parameters % ---------- @@ -15,7 +15,7 @@ % reference model from which to include reactions. % inputFile : char, optional % a task list in Excel format. See the function parseTaskList for -% details (optional if taskStructure is supplied). +% details. Optional if taskStructure is supplied. % % Name-Value Arguments % -------------------- @@ -23,17 +23,16 @@ % true if the results of the test should be displayed (default true). % rxnScores : double % scores for each of the reactions in the reference model. Only -% negative scores are allowed. The solver will try to maximize the sum -% of the scores for the included reactions (default is -1 for all -% reactions). +% negative scores are allowed. The solver maximises the sum of the +% scores of the included reactions (default is -1 for all reactions). % taskStructure : struct -% structure with the tasks, as from parseTaskList. If supplied then +% structure with the tasks, as returned by parseTaskList. If supplied, % inputFile is ignored. % params : struct -% solver parameters, forwarded to the gap-filling back-end and from there -% to optimizeProb for each per-task MILP (default []). +% solver parameters, passed to the gap-filling function and from there to +% optimizeProb for each per-task MILP (default []). % gapFillMode : char -% which gap-filling back-end to use (default 'merge'): +% which gap-filling function to use (default 'merge'): % % - 'merge' : refModel holds only the candidate reactions, and fillGaps % merges it with the model for every task. @@ -41,12 +40,13 @@ % so no merge is needed per task. The task constraints are written % into refModel as well, and ftINITFillGaps does the gap-filling. % verbose : logical -% if true, the MILP progression will be shown. Only read when -% gapFillMode is 'preMerged' (default false). +% true to show the MILP progression. Only read when gapFillMode is +% 'preMerged' (default false). % resolveTies : logical -% if true, pin the gap-fill MILP's degenerate optimum to a canonical answer -% instead of relying on the solver Seed alone. Only read when gapFillMode is -% 'preMerged' -- see ftINITFillGaps' resolveTies (default false). +% true to fix the degenerate optimum of the gap-fill MILP to a canonical +% answer, which otherwise depends on the solver Seed. Only read when +% gapFillMode is 'preMerged'. See resolveTies of ftINITFillGaps +% (default false). % % Returns % ------- @@ -60,8 +60,8 @@ % failedTasks : logical % Nx1, true for each task that could not be gap-filled, either because % no feasible solution exists using the reference model or because the -% attempt threw. Such tasks add no reactions, so they are otherwise -% indistinguishable from tasks that already worked. +% attempt raised an error. Such tasks add no reactions, and the same +% holds for tasks that are feasible without gap-filling. % % Examples % -------- diff --git a/src/gapfilling/ftINITFillGaps.m b/src/gapfilling/ftINITFillGaps.m index 8c0db5ad..0fe836c6 100644 --- a/src/gapfilling/ftINITFillGaps.m +++ b/src/gapfilling/ftINITFillGaps.m @@ -1,29 +1,29 @@ function [addedRxns, newModel, exitFlag]=ftINITFillGaps(tModel, origModel, tRefModel,allowNetProduction,supressWarnings,rxnScores,params,verbose,resolveTies) % ftINITFillGaps -% Variant of fillGaps specially adapted to speed up generation of ftINIT models. +% Variant of fillGaps adapted to speed up the generation of ftINIT models. % % tModel model that contains the task-specific rxns. % origModel model without task-specific rxns. -% tRefModel reference tModel - the full tModel, containing all rxns +% tRefModel reference tModel: the full tModel, containing all rxns % used for gap-filling of tModel + the task-specific rxns % allowNetProduction true if net production of all metabolites is % allowed. A reaction can be unable to carry flux because one of % the reactants is unavailable or because one of the -% products can't be further processed. If this -% parameter is true, only the first type of -% unconnectivity is considered (optional, default false) +% products cannot be processed further. If this +% parameter is true, only the first cause of blocked +% flux is considered (optional, default false) % supressWarnings false if warnings should be displayed (optional, default % false) % rxnScores scores for each of the reactions in the -% reference tModel. -% The solver will try to maximize the sum of the -% scores for the included reactions -% params *obsolete option* -% verbose if true, the MILP progression will be shown. -% resolveTies if true, pin the gap-fill MILP's degenerate optimum to a -% canonical answer instead of relying on the solver Seed -% alone -- see getMinNrFluxes' resolveTies (optional, -% default false) +% reference tModel. The solver maximises the sum of +% the scores of the included reactions (optional, +% default -1 for all reactions) +% params solver parameters, passed to getMinNrFluxes (optional) +% verbose true to show the MILP progression. +% resolveTies true to fix the degenerate optimum of the gap-fill MILP +% to a canonical answer, which otherwise depends on the +% solver Seed. See resolveTies of getMinNrFluxes +% (optional, default false) % % addedRxns the rxns added % newModel the tModel with reactions added to fill gaps @@ -35,16 +35,18 @@ % because the solver stopped before reaching % optimality % -% This method works by merging the tModel to the reference model and -% checking which reactions can carry flux. All reactions that can't -% carry flux are removed. It then solves the MILP problem of including -% as few additional reactions as possible from the reference model so -% that the constraints already set on tModel (e.g. a required biomass -% flux, or exchange bounds) can be satisfied. +% This method works on the reference model, which already contains the +% tModel reactions. Reactions constrained to zero flux are removed +% (simplifyModel(tRefModel,false,false,true), deleteZeroInterval). It then +% solves the MILP problem of including as few additional reactions as +% possible from the reference model so that the constraints already set +% on tModel (for example a required biomass flux, or exchange bounds) can +% be satisfied. % -% See also: fillGaps, the tINIT-side counterpart, which merges a -% reference model with the model being gap-filled on every call instead -% of expecting them pre-merged. +% fillGaps merges the reference model with the model on every call, so it +% does not need a pre-merged reference model. +% +% See also: fillGaps % % Usage: [addedRxns, newModel, exitFlag]=... % ftINITFillGaps(tModel,origModel,tRefModel,allowNetProduction,... diff --git a/src/gapfilling/gapFillFastCore.m b/src/gapfilling/gapFillFastCore.m index 9e6c9547..a791cb8e 100644 --- a/src/gapfilling/gapFillFastCore.m +++ b/src/gapfilling/gapFillFastCore.m @@ -2,8 +2,9 @@ % gapFillFastCore L1-norm LP subroutine for fastGapFill. % % Finds the minimum-L1-flux extension of the model that activates all -% reactions in the core set. Any reaction with non-zero flux in the -% solution is "consistent" with the core. Used internally by gapFillFastLP. +% reactions in the core set. Any reaction with a flux of at least +% epsilon / 2 in the solution is "consistent" with the core. gapFillFastLP +% calls this function. % % Based on FASTCORE (Vlassis et al. 2014, PLoS Comput Biol 10:e1003424). % @@ -21,9 +22,10 @@ % Returns % ------- % activeRxns : logical -% logical vector (length numel(model.rxns)) where true indicates the -% reaction carries flux in the L1-minimal solution. Empty if the LP is -% infeasible (core reactions cannot all carry flux simultaneously). +% logical vector (length numel(model.rxns)) where true indicates that the +% reaction carries flux in the L1-minimal solution. All elements are +% false if the LP is infeasible (the core reactions cannot all carry flux +% simultaneously). % % See also % -------- diff --git a/src/gapfilling/gapFillFastLP.m b/src/gapfilling/gapFillFastLP.m index 4b56927b..b86ec965 100644 --- a/src/gapfilling/gapFillFastLP.m +++ b/src/gapfilling/gapFillFastLP.m @@ -1,5 +1,5 @@ function [addedRxns, newModel, cannotConnect] = gapFillFastLP(model, universalModel, varargin) -% gapFillFastLP LP-based gap-filling (fastGapFill / swiftGapFill). +% gapFillFastLP LP-based gap-filling (fastGapFill or swiftGapFill). % % Merges the draft model with a universal reaction database, then for each % blocked draft reaction calls an LP subroutine (FASTCORE or SWIFTCORE) to @@ -15,19 +15,23 @@ % model : struct % draft RAVEN model to be gap-filled. % universalModel : struct -% universal reaction database (RAVEN model). The caller is responsible -% for building this, e.g. via getKEGGModelForOrganism with broad scope. +% universal reaction database (RAVEN model), for example built with +% getKEGGModelForOrganism with broad scope. % -% Optional name-value parameters -% -------------------------------- -% 'epsilon' (default 1e-4) -% minimum flux threshold for a reaction to be considered active. -% 'variant' (default 'fast') -% 'fast' — use FASTCORE L1-norm LP (gapFillFastCore) -% 'swift' — use SWIFTCORE single-LP (gapFillSwiftCore); faster but -% stochastic (results may vary between runs/solvers). -% 'verbose' (default true) -% print progress messages. +% Name-Value Arguments +% -------------------- +% epsilon : double +% minimum flux threshold for a reaction to be considered active +% (default 1e-4). +% variant : char +% the LP subroutine (default 'fast'): +% +% - 'fast' : use the FASTCORE L1-norm LP (gapFillFastCore). +% - 'swift' : use the SWIFTCORE single LP (gapFillSwiftCore), which +% maximises the sum of the non-core fluxes. The LP is degenerate, so +% results can vary between runs and solvers. +% verbose : logical +% true to print progress messages (default true). % % Returns % ------- @@ -37,8 +41,8 @@ % newModel : struct % copy of model with addedRxns incorporated. % cannotConnect : cell -% blocked draft reaction IDs that remain blocked even after augmenting -% with the full universalModel (cannot be rescued by the database). +% IDs of blocked draft reactions that remain blocked after merging with +% the full universalModel (the database cannot rescue them). % % See also % -------- diff --git a/src/gapfilling/gapFillMILP.m b/src/gapfilling/gapFillMILP.m index fb574dbf..f391e4dd 100644 --- a/src/gapfilling/gapFillMILP.m +++ b/src/gapfilling/gapFillMILP.m @@ -5,14 +5,15 @@ % the model's objective (biomass) >= minGrowth, following the database % repair of Kumar et al. 2007 (BMC Bioinformatics 8:212, % doi:10.1186/1471-2105-8-212). Reactions already in the draft model keep -% their bounds: a reaction with the wrong direction is a curation issue and -% is not changed here. +% their bounds, including a wrong reaction direction. % -% The universal model should contain all candidate reactions, including -% exchange and transport reactions if those repair classes are desired. +% The universal model contains all candidate reactions. Exchange and +% transport reactions are candidates only if the universal model includes +% them. % -% This is a single-level MILP. It is computationally more expensive than -% gapFillFastLP but directly optimises for objective feasibility. +% The function solves a single-level MILP that minimises the number of added +% reactions subject to the objective floor. Compared with gapFillFastLP, the +% MILP takes more computation time. % % Parameters % ---------- @@ -21,19 +22,21 @@ % universalModel : struct % universal reaction database (RAVEN model). % -% Optional name-value parameters -% -------------------------------- -% 'minGrowth' (default []) -% minimum objective value to achieve. If empty, runs FBA on the merged -% model and uses 10% of the maximum as the floor. -% 'bigM' (default 1000) +% Name-Value Arguments +% -------------------- +% minGrowth : double +% minimum objective value to achieve. If empty, FBA on the merged model +% gives the maximum objective value and 10% of it is the floor +% (default []). +% bigM : double % Big-M constant for the coupling constraints of universal reactions -% with an infinite bound. A finite bound is used as it is. -% 'params' (default []) +% with an infinite bound. A finite bound is used as it is (default 1000). +% params : struct % solver parameter struct passed to optimizeProb. Fields such as -% params.TimeLimit and params.intTol can be used to tune the MILP. -% 'verbose' (default true) -% print progress messages. +% params.TimeLimit and params.intTol tune the MILP. If empty, intTol is +% 1e-9 and TimeLimit is 600 (default []). +% verbose : logical +% true to print progress messages (default true). % % Returns % ------- @@ -42,13 +45,16 @@ % newModel : struct % model with addedRxns incorporated. % exitFlag : double -% 1 = optimal, -1 = infeasible, -2 = timeout or other failure. +% 1 = the MILP returned a solution, -1 = no gap-filling result: the FBA +% or MILP is infeasible, the objective of the merged model is not +% positive, or the solver stops without a solution. % % Notes % ----- -% The MILP is solved with the solver set by setRavenSolver. RAVEN's bundled GLPK solves it for small -% models (with a RAVEN:glpkMILP warning); for larger models Gurobi is much -% faster and more reliable (setRavenSolver('gurobi')). +% The MILP is solved with the solver set by setRavenSolver. RAVEN's bundled +% GLPK solves it for small models. The RAVEN:glpkMILP warning issued with +% GLPK states that GLPK works for small problems and that Gurobi is much +% faster and more reliable for larger ones (setRavenSolver('gurobi')). % % See also % -------- diff --git a/src/gapfilling/gapFillSwiftCore.m b/src/gapfilling/gapFillSwiftCore.m index 876ee8e4..2ecea6a8 100644 --- a/src/gapfilling/gapFillSwiftCore.m +++ b/src/gapfilling/gapFillSwiftCore.m @@ -3,14 +3,13 @@ % % Single-LP alternative to gapFillFastCore. Maximises the sum of non-core % fluxes while enforcing that every core reaction carries at least epsilon -% flux. Any reaction with non-zero flux in the solution is "consistent" -% with the core. +% flux. Any reaction with a flux of at least epsilon / 2 in the solution is +% "consistent" with the core. % -% Because the maximisation objective makes the LP degenerate (many optima -% exist that differ only in which non-core reactions carry flux), the -% returned active set can vary between LP solvers and between runs with -% slightly different numerical conditions. This is the expected behaviour -% of SWIFTCORE (Tefagh & Boyd 2020). +% The maximisation objective makes the LP degenerate: many optima differ only +% in which non-core reactions carry flux. The returned active set can +% therefore vary between LP solvers and between runs with slightly different +% numerical conditions. % % Based on SWIFTCORE (Tefagh & Boyd 2020, BMC Bioinformatics 21:23). % @@ -28,8 +27,9 @@ % Returns % ------- % activeRxns : logical -% logical vector (length numel(model.rxns)) where true indicates the -% reaction carries flux in the solution. Empty on infeasibility. +% logical vector (length numel(model.rxns)) where true indicates that the +% reaction carries flux in the solution. All elements are false if the LP +% is infeasible. % % See also % -------- diff --git a/src/gapfilling/gapFillTopological.m b/src/gapfilling/gapFillTopological.m index bf832c02..2915866e 100644 --- a/src/gapfilling/gapFillTopological.m +++ b/src/gapfilling/gapFillTopological.m @@ -3,12 +3,11 @@ % % Identifies which metabolites in the draft model cannot be produced from a % given set of seed metabolites (medium), using a graph-based fixed-point -% "scope" computation (no solver required). For each unreachable metabolite, +% "scope" computation that needs no solver. For each unreachable metabolite, % finds candidate reactions in the universal database that produce it % directly. % -% This is a computationally cheap pre-screening step that prunes the -% universal database before a more expensive gap-filling solve. It is +% The step prunes the universal database before a gap-filling solve. It is % inspired by the topological analysis used in Meneco (Prigent et al. 2017, % PLoS Comput Biol). % @@ -27,30 +26,31 @@ % universalModel : struct % universal reaction database (RAVEN model). % -% Optional name-value parameters -% -------------------------------- -% 'seeds' (default []) -% Metabolite IDs (cell array) available from the medium. Default: +% Name-Value Arguments +% -------------------- +% seeds : cell +% metabolite IDs available from the medium. If empty, the seeds are the % metabolites of every exchange reaction whose bounds let it supply the -% metabolite, i.e. getExchangeRxns 'uptake' plus 'reverse'. -% 'targets' (default []) -% Metabolite IDs (cell array) that should be produced. Default: every +% metabolite, that is getExchangeRxns 'uptake' plus 'reverse' +% (default []). +% targets : cell +% metabolite IDs that should be produced. If empty, the targets are every % metabolite of the objective (biomass) reaction, substrates and % products; the first one when there are several objective reactions. % Without targets and without an objective reaction the function raises -% RAVEN:badInput. -% 'verbose' (default true) -% print a summary of the results. +% RAVEN:badInput (default []). +% verbose : logical +% true to print a summary of the results (default true). % % Returns % ------- % result : struct -% .reachableMets — logical vector (length nMets) marking producible mets -% .blockedMets — cell array of metabolite IDs that cannot be reached -% .candidateRxns — n-by-1 cell array of universal rxn ID lists, one -% cell per blocked metabolite (same order as blockedMets) -% .pruningFraction — fraction of universal reactions pruned (not needed -% for any blocked metabolite) +% .reachableMets: logical vector (length nMets) marking producible mets +% .blockedMets: cell array of metabolite IDs that cannot be reached +% .candidateRxns: n-by-1 cell array of universal rxn ID lists, one cell +% per blocked metabolite (same order as blockedMets) +% .pruningFraction: fraction of universal reactions pruned (not +% candidates for any blocked metabolite) % % See also % -------- diff --git a/src/gapfilling/gapReport.m b/src/gapfilling/gapReport.m index 7212a86d..f8a006d4 100755 --- a/src/gapfilling/gapReport.m +++ b/src/gapfilling/gapReport.m @@ -1,16 +1,16 @@ function [noFluxRxns, noFluxRxnsRelaxed, subGraphs, notProducedMets, minToConnect,... neededForProductionMat, canProduceWithoutInput, canConsumeWithoutOutput, ... connectedFromTemplates, addedFromTemplates]=gapReport(model, varargin) -% gapReport Perform a gap analysis and summarize the results. +% gapReport Perform a gap analysis and summarise the results. % -% Runs a series of connectivity checks on the model - which reactions are +% Runs a series of connectivity checks on the model: which reactions are % blocked, how many isolated sub-networks exist, which metabolites cannot % be produced (and the minimal set of metabolites that would need to be -% connected to fix that), and which metabolites can be produced/consumed -% without any input/output, with every boundary reaction closed. If -% templateModels is supplied, it also attempts to gap-fill using those -% models. A formatted report is printed to the command window; the same -% results are returned as outputs for further use. +% connected to fix that), and which metabolites can be produced without any +% input or consumed without any output, with every boundary reaction closed. +% If templateModels is supplied, it also attempts to gap-fill using those +% models. A formatted report is printed to the command window, and the same +% results are returned as outputs. % % Parameters % ---------- @@ -20,8 +20,8 @@ % Name-Value Arguments % -------------------- % templateModels : cell -% a cell array of template models to use for gap filling. If omitted, -% the automated gap-filling section is skipped. +% a cell array of template models used for gap filling. If omitted, the +% automated gap-filling section is skipped. % % Returns % ------- @@ -63,8 +63,8 @@ % % The model must be feasible under its own bounds and with its boundary % closed. Otherwise gapReport stops with a RAVEN:infeasibleModel error that -% names the reactions with a forced flux; set the bounds of ATP maintenance -% and any other forced flux to zero first. +% names the reactions with a forced flux. Such reactions, for example ATP +% maintenance, need bounds that allow zero flux. % % Examples % -------- diff --git a/src/installation/addRavenToUserPath.m b/src/installation/addRavenToUserPath.m index 1b44769c..3ff23a47 100755 --- a/src/installation/addRavenToUserPath.m +++ b/src/installation/addRavenToUserPath.m @@ -1,15 +1,16 @@ function addRavenToUserPath(varargin) % This function writes a startup.m file in the userpath, adding RAVEN (and -% all subdirectories) to the path each time Matlab is started. -% This function is useful if the user has no rights to save paths to the +% all subdirectories except those with .git or doc in their path) to the +% path each time MATLAB is started. +% The function applies when the user has no rights to save paths to the % pathdef.m file in the matlabroot, for instance due to a lack of admin -% rights. As the startup.m file in the userpath automatically runs with -% each Matlab start, the paths are automatically loaded. +% rights. The startup.m file in the userpath runs at each MATLAB start and +% loads the paths. % % Name-Value Arguments % -------------------- -% overwrite logical, whether startup.m in the userpath should -% overwritten (otherwise the RAVEN paths are appended) +% overwrite logical, true if startup.m in the userpath is +% overwritten and false if the RAVEN paths are appended % (default true) p=parseRAVENargs(varargin, {'overwrite',true}); diff --git a/src/installation/checkFunctionUniqueness.m b/src/installation/checkFunctionUniqueness.m index e7c02649..ce23ce34 100755 --- a/src/installation/checkFunctionUniqueness.m +++ b/src/installation/checkFunctionUniqueness.m @@ -1,16 +1,18 @@ function status = checkFunctionUniqueness(varargin) % checkFunctionUniqueness % Checks whether RAVEN toolbox functions are unique across other -% toolboxes or user-created functions accessible in Matlab pathlist. +% toolboxes and user-created functions on the MATLAB path. % % Name-Value Arguments % -------------------- -% altDirs cell array of alternative directories with functions, -% should only be specified if this function is used for other -% purposes, NOT when looking for RAVEN toolbox functions. +% altDirs char with alternative directories with functions, separated +% by the path separator of the platform (';' on Windows, ':' +% otherwise). Specified only when the function checks +% directories other than the RAVEN toolbox. % % Output: -% status true if all functions are unique, otherwise false +% status true if all functions are unique, otherwise false. Without +% an output argument the function prints Pass or Fail. % % Usage: checkFunctionUniqueness() diff --git a/src/installation/downloadRavenBinaries.m b/src/installation/downloadRavenBinaries.m index 8df2e4a7..881bb725 100644 --- a/src/installation/downloadRavenBinaries.m +++ b/src/installation/downloadRavenBinaries.m @@ -1,25 +1,27 @@ function downloadRavenBinaries(tools) % downloadRavenBinaries % Download RAVEN's external command-line binaries on demand from the -% toolbox-assets repository into RAVEN's src/external/ directory. Only the binary -% matching the current platform is fetched, and only when needed. +% toolbox-assets repository into the src/external directory of RAVEN. Only +% the binary matching the current platform is fetched, and only when it is +% missing. % % Input: % tools cell array, subset of {'blast+','diamond','hmmer'} to % download. Defaults to all of them. Tools whose target executable is % already present are skipped. % -% This requires an internet connection. For an offline / air-gapped install, -% download the "*-binaries" RAVEN release (which bundles all binaries) and -% unzip it into the RAVEN software/ directory instead. +% The download requires an internet connection. An offline installation +% uses the offline bundle (RAVEN--full.zip) attached to each RAVEN +% release, which contains the binaries of all platforms in src/external. % % The binaries are hosted as release assets in % https://github.com/CompMetEng/toolbox-assets. % % Usage: downloadRavenBinaries({'blast+','diamond'}) % -% NOTE: lazily invoked by getBlast/getDiamond/getKEGGModelForOrganism and -% offered by checkRaven when a binary is missing. +% getBlast, getDiamond and getKEGGModelForOrganism call this function +% when a binary is missing. checkRaven calls it for all three tools when its +% checkBinaries input is true. if nargin < 1 || isempty(tools) tools = {'blast+','diamond','hmmer'}; diff --git a/src/installation/fetchToolboxAsset.m b/src/installation/fetchToolboxAsset.m index a2a9abe8..cbd3b0bf 100644 --- a/src/installation/fetchToolboxAsset.m +++ b/src/installation/fetchToolboxAsset.m @@ -3,11 +3,10 @@ % % Downloads assetName from the given release tag of % https://github.com/CompMetEng/toolbox-assets into destDir -% and returns its local path. An already-downloaded copy is reused as-is. -% Generic across toolbox-assets' release families (KEGG artefacts, HMM -% libraries, binaries, ...): nothing here is specific to any one of -% them, so callers supply the release tag and asset name rather than -% this function hardcoding either. +% and returns its local path. An already-downloaded copy is reused without a +% new download or checksum test. The function applies to every toolbox-assets +% release family (KEGG artefacts, HMM libraries, binaries), and the caller +% supplies the release tag and the asset name. % % A download is checked against the SHA-256 checksum that the % toolbox-assets manifest (release manifest-v3) lists for it. A file whose @@ -20,9 +19,9 @@ % destDir : char % directory to download into (created if it does not exist yet). % releaseTag : char -% the toolbox-assets release tag, e.g. 'kegg118'. +% the toolbox-assets release tag, for example 'kegg118'. % assetName : char -% the release asset file name, e.g. 'kegg118_core.tar.gz'. +% the release asset file name, for example 'kegg118_core.tar.gz'. % % Returns % ------- diff --git a/src/installation/findRAVENroot.m b/src/installation/findRAVENroot.m index 02334031..567364d6 100755 --- a/src/installation/findRAVENroot.m +++ b/src/installation/findRAVENroot.m @@ -1,17 +1,18 @@ function [ravenPath, prevDir] = findRAVENroot() % findRAVENroot % Finds the root of the RAVEN directory by searching for the path to -% version.txt. Can also record the current directory, in case a function will -% use the ravenPath to navigate to a precise folder, and it should return to -% the previous directory afterwards. See e.g. optimizeProb calling glpk. +% version.txt. Can also record the current directory, for functions that use +% the ravenPath to navigate to a precise folder and return to the previous +% directory afterwards. See, for example, optimizeProb calling glpk. % -% The resolved path is cached for the rest of the MATLAB session: this is -% called on every solver invocation, and re-reading the RAVEN.ravenPath -% preference from disk every time is both wasteful and, under the sustained -% call volume of a full test run, an intermittent source of failures from -% MATLAB's own preference-file I/O. Run `clear findRAVENroot` after -% changing that preference (checkRaven does this already) to pick -% up the change without restarting MATLAB. +% The RAVEN.ravenPath preference is used if it points to a directory with a +% version.txt file. Otherwise the search starts at the location of this +% function file and moves up the directory tree. +% +% The resolved path is cached for the rest of the MATLAB session, so the +% preference is read once although the function is called on every solver +% invocation. `clear findRAVENroot` drops the cached path after a change of +% the preference (checkRaven does this) without a restart of MATLAB. persistent cachedPath prevDir = pwd(); diff --git a/src/installation/removeRavenFromPath.m b/src/installation/removeRavenFromPath.m index e2738339..c867e62b 100755 --- a/src/installation/removeRavenFromPath.m +++ b/src/installation/removeRavenFromPath.m @@ -1,10 +1,9 @@ function removeRavenFromPath() % removeRavenFromPath % This function removes all RAVEN directories and subdirectories from the -% MATLAB path. This function only removes RAVEN from the MATLAB path, it -% does not delete the RAVEN folder itself. If RAVEN was installed as -% MATLAB Add-On, the user is prompted to instead uninstall RAVEN via the -% Add-On Manager. +% MATLAB path and saves the path. The RAVEN folder itself is kept. If RAVEN +% is installed as MATLAB Add-On, the function raises an error that +% advises uninstalling RAVEN via the Add-On Manager. % % Usage: removeRavenFromPath() diff --git a/src/installation/updateDocumentation.m b/src/installation/updateDocumentation.m index 97892506..e899f706 100755 --- a/src/installation/updateDocumentation.m +++ b/src/installation/updateDocumentation.m @@ -1,12 +1,12 @@ function updateDocumentation() % updateDocumentation -% Regenerates the HTML documentation in RAVEN's docs/html directory from the -% function help texts. The docs/html directory is deleted and rebuilt, so -% anything hand-edited there is lost. +% Regenerates the HTML documentation in the docs/html directory of RAVEN from +% the function help texts. The docs/html directory is deleted and rebuilt, so +% manual edits there are lost. % -% The Documentation workflow runs this on every push to a release branch -% and commits the result, so docs/html stays in step with the sources without -% anyone running it by hand. Run it locally only to preview. +% The Documentation workflow runs this function on every push to the develop +% or main branch that changes a .m file or the workflow file, and commits the +% result. Running the function locally previews the documentation. % % Usage: updateDocumentation() diff --git a/src/io/checkFileExistence.m b/src/io/checkFileExistence.m index 05d0019d..5244719f 100755 --- a/src/io/checkFileExistence.m +++ b/src/io/checkFileExistence.m @@ -1,14 +1,14 @@ function files=checkFileExistence(files,varargin) % checkFileExistence Check whether files exist. % -% If no full path is given a file should be located in the current folder, -% which by default is appended to the filename. +% A file name without a full path refers to a file in the current folder, +% which is prepended to the file name. % % Parameters % ---------- % files : char or cell -% string or cell array of strings with path to file(s) or path or -% filename(s). +% string or cell array of strings with the path or file name of one or +% more files. % % Name-Value Arguments % -------------------- @@ -20,17 +20,17 @@ % - 2 : copy file(s) to system default temporary folder and return % full path % allowSpace : logical -% whether the 'space' character is allowed in the path (default true). +% true if the space character is allowed in the path (default true). % checkExist : logical -% whether file existence should really be checked, as this function can -% also be used to return the full path to a new file (default true). -% Can only be set to false if fullOrTemp is set to 1. +% true if file existence is checked. Setting it to false returns the full +% path to a file that does not exist yet (default true). Can only be set +% to false if fullOrTemp is set to 1. % % Returns % ------- % files : char or cell -% string or cell array of strings with updated paths if fullOrTemp was -% set as 1 or 2, otherwise original paths are returned. +% string or cell array of strings with updated paths if fullOrTemp is 1 +% or 2, otherwise the original paths. % % Examples % -------- diff --git a/src/io/cleanSheet.m b/src/io/cleanSheet.m index 3065abba..6b71cd7a 100755 --- a/src/io/cleanSheet.m +++ b/src/io/cleanSheet.m @@ -1,6 +1,6 @@ % cleanSheet Clean up an Excel sheet. % -% Removes empty rows/columns (and performs some other checks). +% Removes empty rows and columns, and performs further checks. % % Parameters % ---------- @@ -13,16 +13,17 @@ % true if commented lines (non-empty first cell in each row) should be % removed (default true). % removeOnlyCap : logical -% remove columns with captions but no other values (default false). +% true to remove columns with captions but no other values (default +% false). % removeNoCap : logical -% remove columns without captions (default true). +% true to remove columns without captions (default true). % removeEmptyRows : logical -% remove rows with no non-empty cells (default true). +% true to remove rows with no non-empty cells (default true). % % Returns % ------- % raw : cell -% cleaned version. +% the cleaned cell array. % keptRows : double % indices of the kept rows in the original structure. % keptCols : double diff --git a/src/io/exportForGit.m b/src/io/exportForGit.m index 59f289fa..4e183734 100755 --- a/src/io/exportForGit.m +++ b/src/io/exportForGit.m @@ -2,13 +2,13 @@ % exportForGit Export a model for a Git-maintained model repository. % % Generates a directory structure and populates it with model files, ready -% to be committed to a Git(Hub) maintained model repository. Writes the -% model as SBML L3V1 FBCv2 (both XML and YAML), COBRA text, Matlab MAT-file -% and Microsoft Excel formats. +% to be committed to a Git or GitHub repository of a model. Writes the +% model as SBML L3V1 FBCv2 (XML) and YAML, COBRA text, MATLAB MAT-file and +% Microsoft Excel formats. % % The flags mainBranchFlag, subDirs, COBRAtext and neverPrefixIDs accept a -% scalar logical or 0/1, or an empty value for the default. Text and other -% types raise RAVEN:badInput. +% scalar logical, the value 0 or 1, or an empty value for the default. Text +% and other types raise RAVEN:badInput. % % Parameters % ---------- @@ -28,21 +28,20 @@ % should be exported (default all formats as {'mat', 'txt', 'xlsx', % 'xml', 'yml'}). % mainBranchFlag : logical -% if true, function will error if RAVEN (and GECKO for enzyme- -% constrained models) is/are not on the main branch (default false). +% true to raise an error if RAVEN (and GECKO for enzyme-constrained +% models) is not on the main or master branch (default false). % subDirs : logical -% whether model files for each file format should be written in their -% own subdirectory, with 'model' as parent directory, in accordance to -% the standard-GEM repository format. If false, all files are stored in -% the same folder (default true). +% true to write the model files of each file format in their own +% subdirectory, with 'model' as parent directory, following the +% standard-GEM repository format. If false, all files are stored in the +% same folder (default true). % COBRAtext : logical -% whether the txt file should be in COBRA Toolbox format using -% metabolite IDs, instead of metabolite names and compartments -% (default false). +% true to write the txt file in COBRA Toolbox format, which uses +% metabolite IDs. If false, the file uses metabolite names and +% compartments (default false). % neverPrefixIDs : logical % true if prefixes are never added to identifiers, even if they start -% with e.g. digits. This might result in invalid SBML files (default -% false). +% with digits. This can result in invalid SBML files (default false). % % Examples % -------- diff --git a/src/io/exportModel.m b/src/io/exportModel.m index 2bbda7f2..af668d07 100755 --- a/src/io/exportModel.m +++ b/src/io/exportModel.m @@ -9,18 +9,18 @@ function exportModel(model,varargin) % Name-Value Arguments % -------------------- % fileName : char -% filename to export the model to. A dialog window will open if no file -% name is specified. +% file name to export the model to. A dialog window opens if no file name +% is specified. % neverPrefix : logical % true if prefixes are never added to identifiers, even if they start -% with e.g. digits. This might result in invalid SBML files (default -% false). +% with digits. This can result in invalid SBML files (default false). % supressWarnings : logical -% true if warnings should be suppressed. This might result in invalid -% SBML files, as no checks are performed (default false). +% true if warnings should be suppressed. The check of the model +% structure (checkModelStruct) is skipped, which can result in invalid +% SBML files (default false). % sortIds : logical -% whether metabolites, reactions and genes should be sorted -% alphabetically by their identifiers (default false). +% true to sort metabolites, reactions and genes alphabetically by their +% identifiers (default false). % % Examples % -------- diff --git a/src/io/exportToExcelFormat.m b/src/io/exportToExcelFormat.m index 94db137c..fe719d55 100755 --- a/src/io/exportToExcelFormat.m +++ b/src/io/exportToExcelFormat.m @@ -9,11 +9,11 @@ function exportToExcelFormat(model,varargin) % Name-Value Arguments % -------------------- % fileName : char -% file name of the Excel file. Only xlsx format is supported. A dialog -% window will open if fileName is empty. +% file name of the Excel file. Only the xlsx format is supported. A +% dialog window opens if fileName is empty. % sortIds : logical -% whether metabolites, reactions and genes should be sorted -% alphabetically by their identifiers (default false). +% true to sort metabolites, reactions and genes alphabetically by their +% identifiers (default false). % % Examples % -------- diff --git a/src/io/importModel.m b/src/io/importModel.m index 00ec9e95..347e31bb 100755 --- a/src/io/importModel.m +++ b/src/io/importModel.m @@ -4,19 +4,18 @@ % Name-Value Arguments % -------------------- % fileName : char -% a SBML file to import. A dialog window will open if no file name is +% a SBML file to import. A dialog window opens if no file name is % specified. % removeExcMets : logical -% if true, exchange metabolites are removed from the imported model -% (default true). +% true to remove exchange metabolites from the imported model (default +% true). % removePrefix : logical -% true if identifier prefixes should be removed when loading the model: -% G_ for genes, R_ for reactions, M_ for metabolites, and C_ for -% compartments. These are only removed if all identifiers of a certain -% type contain the prefix (default true). +% true to remove identifier prefixes when loading the model: G_ for +% genes, R_ for reactions, M_ for metabolites, and C_ for compartments. +% The prefix of a type is removed only if all identifiers of that type +% contain it (default true). % supressWarnings : logical -% true if warnings regarding the model structure should be supressed -% (default false). +% true to suppress warnings about the model structure (default false). % % Returns % ------- @@ -50,13 +49,13 @@ % - rxnNotes : reaction notes % - rxnReferences : reaction references % - rxnConfidenceScores : reaction confidence scores -% - rxnDeltaG : reaction Gibbs free energy, from a `deltaG` notes key -% (same convention exportModel writes and raven-toolbox reads/writes; -% omitted entirely if no reaction has one) +% - rxnDeltaG : reaction Gibbs free energy, from the `deltaG` notes +% field (the convention that exportModel writes and raven-toolbox +% reads and writes; omitted if no reaction has one) % - genes : list of all genes % - geneComps : compartments for genes % - geneMiriams : structure with MIRIAM information about the genes -% - geneShortNames : gene alternative names (e.g. ERG10) +% - geneShortNames : gene alternative names (for example ERG10) % - proteins : protein associated to each gene % - metNames : metabolite description % - metComps : compartments for metabolites @@ -64,9 +63,9 @@ % - metFormulas : metabolite chemical formula % - metMiriams : structure with MIRIAM information about the metabolites % - metCharges : metabolite charge -% - metDeltaG : metabolite Gibbs free energy, from a `deltaG` notes key -% (same convention exportModel writes and raven-toolbox reads/writes; -% omitted entirely if no metabolite has one) +% - metDeltaG : metabolite Gibbs free energy, from the `deltaG` notes +% field (the convention that exportModel writes and raven-toolbox +% reads and writes; omitted if no metabolite has one) % - unconstrained : true if the metabolite is an exchange metabolite % % Examples @@ -75,9 +74,9 @@ % % Notes % ----- -% A number of consistency checks are performed to ensure that the model is -% valid. Take these warnings seriously and modify the model structure to -% solve them. +% checkModelStruct checks the consistency of the imported model and raises +% warnings about problems in the model structure. supressWarnings skips this +% check. p=parseRAVENargs(varargin, {'fileName',[]; 'removeExcMets',[]; 'removePrefix',[]; 'supressWarnings',false}); fileName=p.fileName; removeExcMets=p.removeExcMets; removePrefix=p.removePrefix; supressWarnings=p.supressWarnings; diff --git a/src/io/parseYAML.m b/src/io/parseYAML.m index 602f4441..c33b61a7 100644 --- a/src/io/parseYAML.m +++ b/src/io/parseYAML.m @@ -1,21 +1,20 @@ function out = parseYAML(filename) -% parseYAML Read a YAML file into a MATLAB struct/cell tree. +% parseYAML Read a YAML file into a tree of MATLAB structs and cells. % % Supports block-style mappings and sequences (indentation-based), % flow-style mappings and sequences (`{ key: value }`, `[a, b]`), quoted % and unquoted scalars, and comments. Does not support multi-document -% files, anchors/aliases, tags, or block scalars (`|`, `>`). +% files, anchors, aliases, tags, or block scalars (`|`, `>`). % -% Use this for parsing YAML configuration / data files with a nested -% mapping/sequence/scalar shape (e.g. condition files consumed by -% applyCondition). For loading a cobra-format model YAML, use -% readYAMLmodel instead — that function knows the model schema and -% returns a populated RAVEN model struct. +% The function parses YAML configuration and data files with nested +% mappings, sequences and scalars (for example condition files consumed by +% applyCondition). readYAMLmodel loads a cobra-format model YAML: it applies +% the model schema and returns a populated RAVEN model struct. % % Parameters % ---------- % filename : char -% Path to the YAML file. +% path to the YAML file. % % Returns % ------- @@ -26,7 +25,7 @@ % - sequence -> cell column vector % - quoted or unquoted string -> char % - integer or float -> double -% - true/false -> logical +% - true, false -> logical % - null / ~ / empty value -> [] % % Examples diff --git a/src/io/readYAMLmodel.m b/src/io/readYAMLmodel.m index 3db80cd6..29a6ff9b 100755 --- a/src/io/readYAMLmodel.m +++ b/src/io/readYAMLmodel.m @@ -1,16 +1,16 @@ function model=readYAMLmodel(varargin) % readYAMLmodel Read a model structure from a YAML file. % -% Reads a yaml file matching (roughly) the standard cobra-format model -% yaml structure. +% Reads a yaml file in the cobra-format model yaml structure that +% writeYAMLmodel writes. % % Name-Value Arguments % -------------------- % fileName : char -% a model file in yaml file format. A dialog window will open if no -% file name is specified. +% a model file in yaml file format. A dialog window opens if no file +% name is specified. % verbose : logical -% set as true to monitor progress (default false). +% true to print the progress (default false). % % Returns % ------- diff --git a/src/io/writeYAMLmodel.m b/src/io/writeYAMLmodel.m index b9afd250..6dd86094 100755 --- a/src/io/writeYAMLmodel.m +++ b/src/io/writeYAMLmodel.m @@ -1,10 +1,10 @@ function writeYAMLmodel(model,varargin) -% writeYAMLmodel Write a model to a yaml file in the standard cobra-format -% !!omap structure. +% writeYAMLmodel Write a model to a YAML file in the cobra-format !!omap structure. % % The format uses the standard !!omap layout, extended with RAVEN-only % top-level per-entry keys (inchis, deltaG, metFrom, rxnFrom, references, -% confidence_score, protein) and the GECKO ec-rxns / ec-enzymes sections. +% confidence_score, protein, notes) and the GECKO ec-rxns and ec-enzymes +% sections. % Reaction EC numbers are written inside the `annotation` block as % `ec-code`, not as a top-level reaction key. % @@ -16,12 +16,11 @@ function writeYAMLmodel(model,varargin) % Name-Value Arguments % -------------------- % fileName : char -% name that the file will have. A dialog window will open if no file -% name is specified. +% name of the file. A dialog window opens if no file name is specified. % sortIds : logical -% if metabolites, reactions, genes and compartments should be sorted -% alphabetically by their identifier, otherwise they are kept in their -% original order (default false). +% true to sort metabolites, reactions, genes and compartments +% alphabetically by their identifier. If false, they keep their original +% order (default false). % % Examples % -------- diff --git a/src/localization/defaultCompartmentMap.m b/src/localization/defaultCompartmentMap.m index a623e3a0..3bad38cf 100644 --- a/src/localization/defaultCompartmentMap.m +++ b/src/localization/defaultCompartmentMap.m @@ -1,25 +1,25 @@ function map = defaultCompartmentMap() -% defaultCompartmentMap Default predictor/database compartment label -> model compartment id. +% defaultCompartmentMap Default map from compartment labels to model compartment ids. % -% Returns a containers.Map from lower-case predictor / database compartment labels (as used by -% DeepLoc, MULocDeep, COMPARTMENTS and UniProt) to compartment ids, tuned for yeast/fungal -% models (e.g. yeast-GEM codes). Pass it (or your own map) to parseScores / getUniProtScores as +% Returns a containers.Map from lower-case compartment labels of predictors and databases (as +% used by DeepLoc, MULocDeep, COMPARTMENTS and UniProt) to compartment ids, for yeast and fungal +% models (for example yeast-GEM codes). The map is passed to parseScores or getUniProtScores as % 'compartmentMap'. % -% The table below is also the worked example of how such a map is built, so read it as three -% deliberate decisions rather than a lookup list: +% The map holds three decisions: % -% - **Which compartments exist.** A label absent from the map is dropped along with its score -% column, so listing only the ids your model has is how unwanted compartments are excluded. -% 'plastid' is absent because fungi lack one; a plant model would add it. -% - **Which labels are the same thing.** Predictors disagree on wording, so 'cytoplasm' and -% 'cytosol' both map to 'c', and 'mitochondrion' / 'mitochondria' / 'mitochondrial' all map -% to 'm'. Merged columns are combined by maximum, not sum. -% - **Which distinct compartments to collapse.** 'lysosome' maps to 'v' not because it is a -% vacuole but because a yeast model has no lysosome and the vacuole is its functional -% equivalent. Change this if your model separates them. +% - Compartments present. A label absent from the map is dropped along with its score column, so +% the map lists only the compartment ids of the model. 'plastid' is absent because fungi have +% no plastid; a plant model adds it. +% - Labels that name the same compartment. Predictors differ in wording, so 'cytoplasm' and +% 'cytosol' both map to 'c', and 'mitochondrion', 'mitochondria' and 'mitochondrial' all map +% to 'm'. Merged columns are combined by their maximum. +% - Distinct compartments that are collapsed. 'lysosome' maps to 'v' because a yeast model has +% no lysosome and the vacuole is its functional equivalent. A model that separates the two +% needs a different mapping. % -% Extend a copy rather than editing this function, e.g. +% Changes to the mapping go into a copy of the map, which leaves this function unedited, for +% example: % % map = defaultCompartmentMap; % map('cell wall') = 'ce'; diff --git a/src/localization/getUniProtScores.m b/src/localization/getUniProtScores.m index 913e8964..3799b031 100644 --- a/src/localization/getUniProtScores.m +++ b/src/localization/getUniProtScores.m @@ -1,36 +1,36 @@ function GSS = getUniProtScores(organism, varargin) -% getUniProtScores Fetch curated subcellular locations from UniProt -> gene scoring structure. +% getUniProtScores Fetch curated subcellular locations from UniProt as a gene scoring structure. % -% Queries the UniProtKB REST API for an organism's "Subcellular location" annotations and parses -% them into the GSS consumed by predictLocalization (no manual export needed). UniProt's curated -% location is qualitative, so each annotated compartment gets score 1.0 (a multi-located protein -% lands in several). Requires an internet connection. +% Queries the UniProtKB REST API for the "Subcellular location" annotations of an organism and +% parses them into the GSS consumed by predictLocalization. No manual export is needed. UniProt +% locations are qualitative, so each annotated compartment gets score 1.0 and a protein with +% several annotated locations scores 1.0 in each of them. The function requires an internet +% connection. % % Parameters % ---------- % organism : double or char -% a UniProt organism / taxonomy id (e.g. 559292 for S. cerevisiae S288C). +% a UniProt organism (taxonomy) id (for example 559292 for S. cerevisiae S288C). % % Name-Value Arguments % -------------------- % idField : char -% which identifier becomes the gene id: 'gene_oln' (ordered locus / ORF, the default, which -% matches yeast-GEM), 'accession', or 'gene_primary'. +% which identifier becomes the gene id: 'gene_oln' (ordered locus or ORF name, the default, +% which matches yeast-GEM), 'accession', or 'gene_primary'. % reviewed : logical -% restrict to curated Swiss-Prot entries (default true). +% true to restrict the query to curated Swiss-Prot entries (default true). % compartmentMap : containers.Map -% UniProt location term -> your model's compartment id (default -% defaultCompartmentMap). Here the map is not only a renaming: its keys are -% also the vocabulary searched for in UniProt's "Subcellular location [CC]" -% free text, matched whole-word and case insensitively, so they must be -% UniProt's own noun forms ('endoplasmic reticulum', not 'ER') and a term -% that is not a key is never looked for. Several keys may share one -% compartment id, which merges them; since every hit scores 1.0, merging -% here means "annotated to any of these counts as this compartment". One -% key cannot be split across two compartment ids - see parseScores for why, -% and for the recipe to duplicate a column on the returned GSS instead. +% maps UniProt location terms to the compartment ids of the model (default +% defaultCompartmentMap). The keys are also the vocabulary searched for in the +% "Subcellular location [CC]" free text of UniProt, matched as whole words and +% case insensitively. The keys must be the noun forms used by UniProt +% ('endoplasmic reticulum', not 'ER'), and a term that is not a key is not searched. +% Several keys may share one compartment id, which merges them; since every hit +% scores 1.0, a protein annotated to any of the merged terms scores 1.0 in that +% compartment. One key cannot map to two compartment ids; parseScores describes +% how to duplicate a column of the returned GSS instead. % extraQuery : char -% an additional UniProt query clause, ANDed into the query (e.g. 'gene:CIT1'). +% an additional UniProt query clause, combined with the query by AND (for example 'gene:CIT1'). % % Returns % ------- diff --git a/src/localization/parseScores.m b/src/localization/parseScores.m index 15f3c7fa..6306da62 100755 --- a/src/localization/parseScores.m +++ b/src/localization/parseScores.m @@ -1,14 +1,14 @@ function GSS = parseScores(inputFile, varargin) -% parseScores Parse predictor / database output into a gene scoring structure (GSS). +% parseScores Parse predictor or database output into a gene scoring structure (GSS). % -% Reads the output of a subcellular-localization predictor or evidence database and +% Reads the output of a subcellular localisation predictor or evidence database and % builds the gene scoring structure consumed by predictLocalization. Scores are -% normalized so the best score for each gene is 1.0. +% normalised so that the highest score of each gene is 1.0. % % Parameters % ---------- % inputFile : char -% the file with the predictor / database output. +% the file with the predictor or database output. % % Name-Value Arguments % -------------------- @@ -18,15 +18,15 @@ % compartment), 'compartments' (a COMPARTMENTS jensenlab.org channel TSV), or % 'uniprot' (a UniProtKB TSV export with a "Subcellular location [CC]" column). % compartmentMap : containers.Map -% renames the predictor's / database's compartment labels to your model's -% compartment ids. Keys are the labels as the predictor writes them, lower -% case; values are the compartment ids you want in the GSS. See -% Compartment mapping below. Optional for every predictor except 'uniprot', -% where it also supplies the location vocabulary and defaults to -% defaultCompartmentMap. +% maps the compartment labels of the predictor or database to the compartment ids of +% the model. Keys are the labels as the predictor writes them, in lower case; values +% are the compartment ids in the GSS. Optional for every predictor except 'uniprot', +% where the map also supplies the location vocabulary and defaults to +% defaultCompartmentMap. See Compartment mapping below. % idColumn : char or double -% column (name or 1-based index) holding the gene id for 'mulocdeep' / 'uniprot'. -% Default: the first column. For yeast-GEM, use the UniProt ordered-locus column. +% column (name or 1-based index) holding the gene id for 'mulocdeep' and 'uniprot' +% (default: the first column). For yeast-GEM, the UniProt ordered-locus column holds +% the matching ids. % minConfidence : double % for 'compartments', drop annotations with confidence below this (default 0). % @@ -37,34 +37,31 @@ % % - genes : cell, one entry per gene. % - compartments : cell, one entry per compartment column. -% - scores : double, numel(genes)-by-numel(compartments), normalized so -% the best score per gene is 1.0. +% - scores : double, numel(genes)-by-numel(compartments), normalised so +% that the highest score per gene is 1.0. % % Compartment mapping % ------------------- -% Predictors emit their own compartment vocabulary ("Endoplasmic reticulum", -% "Golgi apparatus", ...), which rarely matches a model's compartment ids. -% compartmentMap is a containers.Map that translates them, and it is the only -% place that translation happens - the returned GSS already uses your ids. -% defaultCompartmentMap returns a worked example, tuned for yeast/fungal -% models; use it as-is, or as the template for your own. Three rules: +% Predictors use their own compartment vocabulary ("Endoplasmic reticulum", +% "Golgi apparatus", ...), which need not match the compartment ids of a model. +% compartmentMap is a containers.Map that translates the labels; the translation +% happens only there, so the returned GSS contains the ids of the map values. +% defaultCompartmentMap returns an example map for yeast and fungal models, which can +% be used as it is or as a template. Three rules apply: % -% - **A label not in the map is dropped**, together with its whole score -% column. This is how you exclude compartments your model does not have -% (defaultCompartmentMap has no 'plastid' key, so plastid scores never reach -% a fungal model). Passing no map at all keeps every label untouched. -% - **Several labels may share one compartment id**, which merges their -% columns. The merged score for a gene is the *maximum* over the merged -% labels, not their sum, so a gene predicted 0.7 cytoplasm and 0.4 cytosol -% scores 0.7 for 'c'. Use this for synonyms and for deliberately collapsing -% distinct compartments your model does not separate (defaultCompartmentMap -% sends both 'vacuole' and 'lysosome' to 'v'). Column order follows the first -% appearance of each id. -% - **One label cannot be split across two compartment ids**: a containers.Map -% holds one value per key, and the GSS is what predictLocalization scores -% against, so a split has to be an explicit statement about the returned -% scores rather than a mapping rule. Duplicate the column afterwards, e.g. to -% let a gene predicted "golgi" compete for both 'g' and 'v': +% - A label that is not in the map is dropped, together with its whole score +% column. This excludes compartments that the model does not have +% (defaultCompartmentMap has no 'plastid' key, so plastid scores are dropped for a +% fungal model). Without a map, every label is kept unchanged. +% - Several labels may share one compartment id, which merges their +% columns. The merged score of a gene is the maximum over the merged labels, not +% their sum: a gene predicted 0.7 cytoplasm and 0.4 cytosol scores 0.7 for 'c'. +% Sharing an id merges synonyms and collapses compartments that the model does not +% separate (defaultCompartmentMap sends both 'vacuole' and 'lysosome' to 'v'). +% Column order follows the first appearance of each id. +% - One label cannot map to two compartment ids: a containers.Map holds one +% value per key. A split is made on the returned scores. Duplicating the +% column afterwards lets a gene predicted "golgi" compete for both 'g' and 'v': % % GSS = parseScores(file, 'compartmentMap', map); % g = strcmp(GSS.compartments, 'g'); @@ -72,14 +69,14 @@ % GSS.scores(:,end+1) = GSS.scores(:,g); % % The two columns are then independent: predictLocalization assigns each gene -% to exactly one compartment, so it picks whichever of 'g'/'v' keeps the -% network better connected. Halve the copied column instead of duplicating it -% if the evidence should count for less in the second compartment. +% to one compartment, so it picks whichever of 'g' and 'v' keeps the network +% better connected. Halving the copied column gives the evidence half the +% weight in the second compartment. % -% For 'uniprot' the keys do double duty: they are also the terms searched for -% in the "Subcellular location [CC]" free text, matched whole-word and case -% insensitively, so they must be UniProt's own noun forms. A term that is not a -% key is never looked for. +% For 'uniprot', the keys are also the terms searched for in the +% "Subcellular location [CC]" free text, matched as whole words and case +% insensitively. The keys must be the noun forms used by UniProt. A term that is +% not a key is not searched. % % Examples % -------- diff --git a/src/localization/predictLocalization.m b/src/localization/predictLocalization.m index 9920f27e..05c1ca12 100755 --- a/src/localization/predictLocalization.m +++ b/src/localization/predictLocalization.m @@ -3,71 +3,62 @@ defaultCompartment, varargin) % predictLocalization Assign reactions to compartments using localization predictors. % -% Tries to assign reactions to compartments in a manner that is in -% agreement with localization predictors while at the same time maintaining -% connectivity. +% Assigns reactions to compartments so that the assignment agrees with the localization +% predictors and the network stays connected. % % Parameters % ---------- % model : struct -% a model structure. If the model contains several compartments they -% will be merged. +% a model structure. If the model contains several compartments, they +% are merged. % GSS : struct % a gene scoring structure as from parseScores. % defaultCompartment : char % transport reactions are expressed as diffusion between the -% defaultCompartment and the others. This is usually the cytosol. The -% default compartment must have a match in GSS. +% defaultCompartment and the others. The compartment is usually the cytosol. It must +% be one of GSS.compartments. % % Name-Value Arguments % -------------------- % transportCost : double -% the cost for including a transport reaction. If this is a scalar then -% the same cost is used for all metabolites. It can also be a vector of -% costs with the same dimension as model.mets. Negative costs encourage -% transport of the metabolite (default 0.5). +% the cost for including a transport reaction. A scalar applies the same cost to all +% metabolites; a vector must have the same dimension as model.mets. A negative cost +% favours transport of the metabolite (default 0.5). % maxTime : double -% maximum optimization time in minutes (default 15). +% maximum optimisation time in minutes (default 15). % plotResults : logical -% true if the results should be plotted during the optimization -% (default false). +% true to plot the results during the optimisation (default false). % % Returns % ------- % outModel : struct % the resulting model structure. % geneLocalization : struct -% structure with the genes and their resulting localization. +% structure with the genes and their resulting localisation. % transportStruct : struct -% structure with the transport reactions that had to be inferred and -% between which compartments. +% structure with the inferred transport reactions and the compartments between which +% they transport. % scores : struct -% structure that contains the total score history together with the -% score based on gene localization and the score based on included -% transport reactions. +% structure with the total score history, the score based on gene localisation and +% the score based on the included transport reactions. % removedRxns : cell -% cell array with the reaction ids that had to be removed to have a -% connected input model. +% cell array with the ids of the reactions removed to obtain a connected input model. % % Notes % ----- -% This function requires that the starting network is connected when it is -% in one compartment. Reactions that are unconnected are removed and saved -% in removedRxns. Try running fillGaps to have a more connected input model -% if there are many such reactions. The input model should also not include -% any exchange, demand or sink reactions, otherwise this function would not -% provide any results. +% The starting network must be connected when it is in one compartment. Unconnected +% reactions are removed and returned in removedRxns. If many reactions are removed, +% fillGaps can produce a more connected input model. The input model must not include +% exchange, demand or sink reactions; otherwise the function provides no results. % -% In the final model all metabolites are produced in at least one reaction. -% This does not guarantee a fully functional model since there can be -% internal loops. Transport reactions are only included as passive diffusion -% (A <=> B). +% In the final model, every metabolite is produced in at least one reaction. Internal +% loops can still make the model non-functional. Transport reactions are included only +% as passive diffusion (A <=> B). % -% The score of a model is the sum of scores for all genes in their assigned -% compartment minus the cost of all transport reactions that had to be -% included. A gene can only be assigned to one compartment. This is a -% simplification to keep the problem size down. The problem is solved using -% simulated annealing. +% The score of a model is the sum of the scores of all genes in their assigned +% compartment minus the cost of all included transport reactions. A gene is assigned +% to one compartment, which limits the problem size. The problem is solved by simulated +% annealing. % % Examples % -------- diff --git a/src/manipulation/addExchangeRxns.m b/src/manipulation/addExchangeRxns.m index ddb6af8f..d23bfdfb 100755 --- a/src/manipulation/addExchangeRxns.m +++ b/src/manipulation/addExchangeRxns.m @@ -1,9 +1,11 @@ function [model, addedRxns]=addExchangeRxns(model,reactionType,varargin) % addExchangeRxns Add exchange reactions for some metabolites. % -% This is a faster version than addRxns when adding exchange reactions. -% New reactions are named "metName exchange (OUT/IN/BOTH)" while reaction -% ids are formatted as "EXC_OUT/IN/BOTH_METID". +% The function builds the stoichiometric columns directly from the metabolite list, +% without the equation parsing of addRxns, which makes it faster for adding exchange +% reactions. New reactions are +% named "metName exchange (TYPE)" and have the ids "EXC_TYPE_METID", where TYPE is IN, +% OUT or BOTH according to reactionType. % % Parameters % ---------- @@ -14,15 +16,15 @@ % % - 'in' : input reactions % - 'out' : output reactions -% - 'both' : reversible input/output reactions. Positive direction +% - 'both' : reversible input and output reactions. The positive direction % corresponds to output % % Name-Value Arguments % -------------------- % mets : cell or logical or double -% either a cell array of metabolite IDs, a logical vector with the same -% number of elements as metabolites in the model, or a vector of -% indexes to add for (default model.mets). +% a cell array of metabolite ids, a logical vector with one element per metabolite of +% the model, or a vector of metabolite indexes to add reactions for +% (default model.mets). % % Returns % ------- diff --git a/src/manipulation/addGenesRaven.m b/src/manipulation/addGenesRaven.m index cb00a033..a3e7ef37 100755 --- a/src/manipulation/addGenesRaven.m +++ b/src/manipulation/addGenesRaven.m @@ -1,8 +1,7 @@ function newModel=addGenesRaven(model,genesToAdd) % addGenesRaven Add genes to a model. % -% This function does not make extensive checks about MIRIAM formats, -% forbidden characters or such. +% The function checks neither MIRIAM formats nor forbidden characters in gene ids. % % Parameters % ---------- @@ -11,15 +10,15 @@ % genesToAdd : struct % the genes to add, which can have the following fields: % -% - genes : cell array with unique strings that identifies each gene. -% Only characters which are allowed in SBML ids are allowed (mainly -% a-z, 0-9 and "_"). However, there is no check for this performed, -% as it only matters if the model should be exported to SBML +% - genes : cell array with unique strings that identify each gene. +% The characters allowed in SBML ids (mainly a-z, 0-9 and "_") are the +% valid ones, but the function does not check them; they matter only for +% export to SBML % - geneShortNames : cell array of gene abbreviations (optional, % default "") % - geneMiriams : cell array with MIRIAM structures (optional, % default []) -% - proteins : cell array of protein names associated to each gene +% - proteins : cell array of the protein names associated with each gene % (optional, default "") % % Returns diff --git a/src/manipulation/addMets.m b/src/manipulation/addMets.m index c8858de4..d9c01ec9 100755 --- a/src/manipulation/addMets.m +++ b/src/manipulation/addMets.m @@ -1,8 +1,7 @@ function newModel=addMets(model,metsToAdd,varargin) % addMets Add metabolites to a model. % -% This function does not make extensive checks about MIRIAM formats, -% forbidden characters or such. +% The function checks neither MIRIAM formats nor forbidden characters in metabolite ids. % % Parameters % ---------- @@ -11,13 +10,14 @@ % metsToAdd : struct % the metabolite structure, which can have the following fields: % -% - mets : cell array with unique strings that identifies each -% metabolite (optional, default IDs of new metabolites are numbered -% with the prefix defined below) -% - metNames : cell array with the names of each metabolite +% - mets : cell array with unique strings that identify each +% metabolite (optional, default: ids numbered with the prefix defined +% below) +% - metNames : cell array with the names of each metabolite (optional if +% mets is given, default: the ids) % - compartments : cell array with the compartment of each metabolite. -% Should match model.comps. If this is a string rather than a cell -% array it is assumed that all mets are in that compartment +% Each entry must match model.comps. A single string, or a cell array +% with one entry, assigns all metabolites to that compartment % - b : Nx1 or Nx2 matrix with equality constraints for each % metabolite (optional, default 0) % - unconstrained : vector describing if each metabolite is an exchange @@ -29,21 +29,19 @@ % default []) % - metCharges : metabolite charge (optional, default NaN) % - metDeltaG : Gibbs free energy of formation at biochemical standard -% condition in kJ/mole (optional, default NaN) +% condition in kJ/mol (optional, default NaN) % - metNotes : cell array with metabolite notes as strings (optional, % default "") % % Name-Value Arguments % -------------------- % copyInfo : logical -% when adding metabolites to a compartment where it previously did not -% exist, the function will copy any available annotation from the -% metabolite in another compartment (default true). +% true to copy the available annotation from the metabolite with the same name in +% another compartment of the model (default true). % prefix : char -% when metsToAdd.mets is not specified, new metabolite IDs are -% generated with the prefix specified here. If IDs with the prefix are -% already used in the model then the numbering will start from the -% highest existing integer+1 (default "m_"). +% prefix of the generated metabolite ids when metsToAdd.mets is not specified. If ids +% with the prefix exist in the model, the numbering starts at the highest +% existing number plus 1 (default "m_"). % % Returns % ------- @@ -56,8 +54,8 @@ % % Notes % ----- -% If multiple metabolites are added at once, the metMiriams cell array -% should be defined as (example with ChEBI and KEGG): +% When multiple metabolites are added at once, metMiriams is a cell array with one MIRIAM +% structure per metabolite (example with ChEBI and KEGG): % % metsToAdd.metMiriams{1} = struct("name",{{"chebi";"kegg.compound"}},... % "value",{{"CHEBI:18072";"C11821"}}); diff --git a/src/manipulation/addRxns.m b/src/manipulation/addRxns.m index 574c6985..91dbe531 100755 --- a/src/manipulation/addRxns.m +++ b/src/manipulation/addRxns.m @@ -1,12 +1,12 @@ function newModel=addRxns(model,rxnsToAdd,varargin) % addRxns Add reactions to a model. % -% This function does not make extensive checks about formatting of -% gene-reaction rules. +% The function checks that grRules has one entry per reaction and that every gene of +% a rule is in model.genes (an error otherwise, unless allowNewGenes is true). +% standardizeGrRules then writes the rules in the standard format. % -% When adding metabolites to a compartment where they previously do not -% exist, the function will copy any available information from the -% metabolite in another compartment. +% New metabolites receive the available annotation of the metabolite with the same name +% in another compartment, when one exists. % % Parameters % ---------- @@ -15,26 +15,26 @@ % rxnsToAdd : struct % the reaction structure, which can have the following fields: % -% - rxns : cell array with unique strings that identifies each reaction +% - rxns : cell array with unique strings that identify each reaction % - equations : cell array with equation strings. Decimal coefficients % are expressed as "1.2". Reversibility is indicated by "<=>" or "=>" % - mets : (alternative to equations) cell array with the metabolites -% involved in each reaction as nested arrays. E.g.: -% {{"met1","met2"},{"met1","met3","met4"}}. In the case of one single -% reaction added, it can be a string array: {"met1","met2"} +% involved in each reaction as nested arrays, for example +% {{"met1","met2"},{"met1","met3","met4"}}. For a single added +% reaction, it can be a string array: {"met1","met2"} % - stoichCoeffs : (alternative to equations) cell array with the -% corresponding stoichiometries as nested vectors. E.g.: -% {[-1,+2],[-1,-1,+1]}. In the case of one single reaction added, it -% can be a vector: [-1,+2] +% corresponding stoichiometries as nested vectors, for example +% {[-1,+2],[-1,-1,+1]}. For a single added reaction, it can be a +% vector: [-1,+2] % - rxnNames : cell array with the names of each reaction (optional, % default "") % - lb : vector with the lower bounds (optional, default -% model.annotations.defaultLB or -inf for reversible reactions and 0 -% for irreversible when "equations" is used. When "mets" and -% "stoichCoeffs" are used it defaults for all to -% model.annotations.defaultLB or -inf) +% model.annotation.defaultLB or -inf for reversible reactions and 0 +% for irreversible reactions when "equations" is used. When "mets" and +% "stoichCoeffs" are used, the default for all reactions is +% model.annotation.defaultLB or -inf) % - ub : vector with the upper bounds (optional, default -% model.annotations.defaultUB or inf) +% model.annotation.defaultUB or inf) % - c : vector with the objective function coefficients (optional, % default 0) % - eccodes : cell array with the EC-numbers for each reaction. Delimit @@ -42,18 +42,17 @@ % - subSystems : cell array with the subsystems for each reaction % (optional, default "") % - grRules : cell array with the gene-reaction relationship for each -% reaction. E.g. "(A and B) or (C)" means that the reaction could be -% catalyzed by a complex between A & B or by C on its own. All the -% genes have to be present in model.genes. Add genes with -% addGenesRaven before calling this function if needed (optional, -% default "") +% reaction. "(A and B) or (C)" means that the reaction is +% catalysed by a complex of A and B or by C on its own. All genes must +% be present in model.genes, unless allowNewGenes is true; addGenesRaven +% adds genes (optional, default "") % - rxnMiriams : cell array with Miriam structures (optional, % default []) % - rxnComps : cell array with compartments (as in model.comps) % (optional, default {}) % - rxnNotes : cell array with reaction notes (optional, default "") % - rxnDeltaG : Gibbs free energy at biochemical standard condition in -% kJ/mole (optional, default NaN) +% kJ/mol (optional, default NaN) % - rxnReferences : cell array with reaction references (optional, % default "") % - rxnConfidenceScores : vector with reaction confidence scores @@ -62,36 +61,33 @@ % Name-Value Arguments % -------------------- % eqnType : double or char -% describes how the equation string should be interpreted (default 1). -% Accepts integer values 1/2/3 or the string aliases "id", "name", -% "name[comp]": +% the interpretation of the equation strings (default 1). The values are the +% integers 1, 2 and 3, or the string aliases "id", "name" and "name[comp]": % -% - 1 / "id" : the metabolites are matched to model.mets. New +% - 1 or "id" : the metabolites are matched to model.mets. New % metabolites (if allowed) are added to "compartment" -% - 2 / "name" : the metabolites are matched to model.metNames and all -% metabolites are assigned to "compartment". Any new metabolites that -% are added will be assigned IDs "m1", "m2"... If IDs on the same -% form are already used in the model then the numbering will start -% from the highest used integer+1 -% - 3 / "name[comp]" : the metabolites are written as "metNames[comps]". -% Only compartments in model.comps are allowed. Any new metabolites -% that are added will be assigned IDs "m1", "m2"... If IDs on the -% same form are already used in the model then the numbering will -% start from the highest used integer+1 +% - 2 or "name" : the metabolites are matched to model.metNames and all +% metabolites are assigned to "compartment". New metabolites get +% ids generated by generateNewIds with the prefix "m_" (or the +% prefix given in allowNewMets), numbered from the highest existing +% number with that prefix plus 1 +% - 3 or "name[comp]" : the metabolites are written as "metNames[comps]". +% Only compartments in model.comps are allowed. New metabolites get +% ids generated in the same way as for eqnType 2 % compartment : char -% the compartment the metabolites should be placed in when using -% eqnType=2. Must match model.comps (optional when eqnType=1 or -% eqnType=3). +% the compartment of the metabolites for eqnType=2 and of new metabolites for +% eqnType=1. Must match model.comps. Not used for eqnType=3. % allowNewMets : logical or char -% true if the function is allowed to add new metabolites. Can also be a -% string, which will be used as prefix for the new metabolite IDs. It -% is highly recommended to first add any new metabolites with addMets -% rather than automatically through this function. addMets supports -% more annotation of metabolites, allows for the use of exchange -% metabolites, and using it reduces the risk of parsing errors -% (default false). +% true to allow the function to add new metabolites. A string also allows it and +% is used as the prefix of the new metabolite ids. The function calls addMets +% with copyInfo true, so a new metabolite takes the charge and deltaG from a +% metabolite with the same name in another compartment, and InChI, SMILES, +% formula and MIRIAM annotation from it when its own are empty. When the +% annotation of new metabolites is to be set explicitly, or when they are to be +% exchange (unconstrained) metabolites, adding them with addMets first gives +% that control (default false). % allowNewGenes : logical -% true if the function is allowed to add new genes (default false). +% true to allow the function to add new genes (default false). % % Returns % ------- diff --git a/src/manipulation/addRxnsGenesMets.m b/src/manipulation/addRxnsGenesMets.m index 5cb31994..79205543 100755 --- a/src/manipulation/addRxnsGenesMets.m +++ b/src/manipulation/addRxnsGenesMets.m @@ -1,24 +1,22 @@ function model=addRxnsGenesMets(model,sourceModel,rxns,varargin) % addRxnsGenesMets Copy reactions from a source model into another model. % -% Copies reactions from a source model to a new model, including (new) -% metabolites and genes. +% Copies reactions from a source model to a draft model, together with their new +% metabolites and genes. Reactions that are already in the draft model are skipped. % -% This function only works if the draft model and source model follow the -% same metabolite and compartment naming convention. Metabolites are only -% matched by metaboliteName[compartment]. Useful if one wants to copy -% additional reactions from source to draft after getModelFromHomology was -% used involving the same models. +% The draft model and the source model must follow the same metabolite and compartment +% naming convention, because metabolites are matched only by metaboliteName[compartment]. +% The function can copy additional reactions from a source model to a draft model that +% getModelFromHomology created from the same models. % % Parameters % ---------- % model : struct -% draft model where reactions should be copied to. +% the draft model that receives the reactions. % sourceModel : struct -% model where reactions and metabolites are sourced from. +% the model that the reactions and metabolites are copied from. % rxns : cell or char -% reaction IDs (from source model). Can also be a string if only one -% reaction is added. +% reaction ids from the source model. A string adds one reaction. % % Name-Value Arguments % -------------------- @@ -28,22 +26,21 @@ % - false : no genes are annotated to the new reactions % - true : grRules are copied from the sourceModel and new genes are % added when required -% - string or cell array : new grRules are specified as string or cell -% array, and any new genes are added when required +% - string or cell array : the grRules of the new reactions, as a string or a +% cell array, and new genes are added when required % rxnNote : cell or char -% strings explaining why reactions were copied to the model, to be -% included as newModel.rxnNotes. Can also be a string if the same -% rxnNotes should be added for each new reaction, or only one reaction -% is to be added (default "Added via addRxnsAndMets()"). +% strings that explain why the reactions were copied, stored in rxnNotes of the new +% reactions. A string applies the same note to every new reaction +% (default "Added via addRxnsGenesMets()"). % confidence : double -% integer specifying confidence score for all reactions, following +% integer confidence score for all new reactions, following % doi:10.1038/nprot.2009.203 (default 0): % % - 4 : biochemical data: direct evidence from enzyme assays % - 3 : genetic data: knockout/-in or overexpression analysis -% - 2 : physiological data: indirect evidence, e.g. secretion products +% - 2 : physiological data: indirect evidence, for example secretion products % or defined medium requirement; sequence data: genome annotation -% - 1 : modeling data: required for functional model, hypothetical +% - 1 : modelling data: required for a functional model, hypothetical % reaction % - 0 : no evidence % diff --git a/src/manipulation/addTransport.m b/src/manipulation/addTransport.m index d488ce27..2fbf2538 100755 --- a/src/manipulation/addTransport.m +++ b/src/manipulation/addTransport.m @@ -1,20 +1,22 @@ function [model, addedRxns]=addTransport(model,fromComp,toComps,varargin) % addTransport Add transport reactions between compartments. % -% This is a faster version than addRxns when adding transport reactions. -% New reaction names are formatted as "metaboliteName, fromComp-toComp", -% while new reaction IDs are sequentially counted with a tr_ prefix: -% e.g. tr_0001, tr_0002, etc. +% The function builds the stoichiometric columns directly from the metabolite names, +% without the equation parsing of addRxns, which makes it faster for adding transport +% reactions. New reaction names +% have the format "metaboliteName transport, fromCompName-toCompName", where the +% compartment names are the entries of model.compNames. New reaction ids are numbered +% sequentially with the prefix tr_ (tr_0001, tr_0002, ...). % % Parameters % ---------- % model : struct % a model structure. % fromComp : char -% the id of the compartment to transport from (should match -% model.comps). +% the id of the compartment to transport from. Must match one entry of model.comps. % toComps : cell -% compartment names to transport to (should match model.comps). +% the ids of the compartments to transport to. Each must match an entry of +% model.comps. % % Name-Value Arguments % -------------------- @@ -24,11 +26,11 @@ % isRev : logical % true if the transport reactions should be reversible (default true). % onlyToExisting : logical -% true if transport of a metabolite should only be added if it already -% exists in toComp. If false, then new metabolites are added with -% addMets first (default true). +% true to add transport of a metabolite only if the metabolite exists in the target +% compartment. If false, the missing metabolites are first added with addMets +% (default true). % prefix : char -% prefix to reaction IDs (default "tr_"). +% prefix of the reaction ids (default "tr_"). % % Returns % ------- diff --git a/src/manipulation/changeGrRules.m b/src/manipulation/changeGrRules.m index 09db0fe2..55f792ef 100755 --- a/src/manipulation/changeGrRules.m +++ b/src/manipulation/changeGrRules.m @@ -1,5 +1,7 @@ function model = changeGrRules(model,rxns,grRules,varargin) -% changeGrRules Change multiple grRules at the same time. +% changeGrRules Change the grRules of multiple reactions. +% +% Genes in grRules that are not in the model are added to the model. % % Parameters % ---------- @@ -8,16 +10,15 @@ % rxns : char or cell % reaction IDs. % grRules : char or cell -% additional or replacement gene association. Should be written with -% ' and ' to indicate subunits, ' or ' to indicate isoenzymes, and -% brackets '()' to separate different instances. +% the replacement or additional gene association. ' and ' indicates subunits, ' or ' +% indicates isoenzymes, and brackets '()' separate different instances. A single +% rule applies to all reactions in rxns. % % Name-Value Arguments % -------------------- % replace : logical -% true if old gene association should be replaced with new association. -% False if new gene association should be concatenated to the old -% association (default true). +% true to replace the old gene association with the new association. False to +% combine them as "(old) or (new)" (default true). % % Returns % ------- diff --git a/src/manipulation/changeRxns.m b/src/manipulation/changeRxns.m index feca030e..dc1d4aa5 100755 --- a/src/manipulation/changeRxns.m +++ b/src/manipulation/changeRxns.m @@ -8,9 +8,8 @@ % rxns : cell % cell array with reaction ids. % equations : cell or struct -% cell array with equations. Alternatively, it can be a structure with -% the fields "mets" and "stoichCoeffs", in the same fashion as addRxns. -% E.g.: +% cell array with equations, or a structure with the fields "mets" and +% "stoichCoeffs" as in addRxns, for example: % % - equations.mets = {{"met1","met2"},{"met1","met3"}} % - equations.stoichCoeffs = {[-1,+2],[-1,+1]} @@ -18,30 +17,28 @@ % Name-Value Arguments % -------------------- % eqnType : double -% describes how the equation string should be interpreted (default 1): +% the interpretation of the equation strings (default 1): % % - 1 : the metabolites are matched to model.mets. New metabolites (if % allowed) are added to "compartment". % - 2 : the metabolites are matched to model.metNames and all -% metabolites are assigned to "compartment". Any new metabolites that -% are added will be assigned IDs "m1", "m2"... If IDs on the same form -% are already used in the model then the numbering will start from the -% highest used integer+1. -% - 3 : the metabolites are written as "metNames[compNames]". Only -% compartments in model.compNames are allowed. Any new metabolites -% that are added will be assigned IDs "m1", "m2"... If IDs on the same -% form are already used in the model then the numbering will start -% from the highest used integer+1. +% metabolites are assigned to "compartment". New metabolites get +% ids generated by generateNewIds with the prefix "m_", numbered from the +% highest existing number with that prefix plus 1. +% - 3 : the metabolites are written as "metNames[comps]". Only +% compartments in model.comps are allowed. New metabolites get ids +% generated in the same way as for eqnType 2. % compartment : char -% a string with the compartment the metabolites should be placed in when -% using eqnType=2. Must match model.compNames (optional when eqnType=1 or -% eqnType=3). +% the compartment of the metabolites for eqnType=2 and of new metabolites for +% eqnType=1. Must match model.comps. Not used for eqnType=3. % allowNewMets : logical -% true if the function is allowed to add new metabolites. It is highly -% recommended to first add any new metabolites with addMets rather than -% automatically through this function. addMets supports more annotation -% of metabolites, allows for the use of exchange metabolites, and using -% it reduces the risk of parsing errors (default false). +% true to allow the function to add new metabolites. The function calls addMets +% with copyInfo true, so a new metabolite takes the charge and deltaG from a +% metabolite with the same name in another compartment, and InChI, SMILES, +% formula and MIRIAM annotation from it when its own are empty. When the +% annotation of new metabolites is to be set explicitly, or when they are to be +% exchange (unconstrained) metabolites, adding them with addMets first gives +% that control (default false). % % Returns % ------- @@ -54,16 +51,13 @@ % % Notes % ----- -% This function should be used with some care, since it does not care about -% bounds on the reactions. Changing an irreversible reaction to a reversible -% one (or the other way around) will only change the model.rev field and not -% the model.lb/model.ub fields. The reaction will therefore still be having -% the same reversibility because of the bounds. Use setParams to change the -% bounds. +% The function keeps the bounds of the reactions. Changing an irreversible reaction to a +% reversible one, or the reverse, changes the model.rev field but not model.lb and +% model.ub, so the bounds still define the reversibility of the reaction. setParam +% changes the bounds. % -% When adding metabolites to a compartment where it previously does not -% exist, the function will copy any available information from the metabolite -% in another compartment. +% New metabolites receive the annotation of the metabolite with the same name in +% another compartment, when one exists (see allowNewMets). p=parseRAVENargs(varargin, {'eqnType',1; 'compartment',[]; 'allowNewMets',false}); eqnType=p.eqnType; diff --git a/src/manipulation/closeModel.m b/src/manipulation/closeModel.m index a83ecdff..5def2209 100755 --- a/src/manipulation/closeModel.m +++ b/src/manipulation/closeModel.m @@ -1,7 +1,13 @@ function closedModel=closeModel(model) -% closeModel Add boundary metabolites and their exchange reactions. +% closeModel Add boundary metabolites to exchange reactions. % -% Adds boundary metabolites and their participation in exchange reactions. +% Adds a compartment "b" (boundary) and a boundary metabolite in it for every reaction +% that has metabolites on one side only (exchange, sink and demand reactions). The +% boundary metabolite takes part in the reaction with the coefficient 1 or -1 and is +% marked as unconstrained in model.unconstrained; all other metabolites are marked as +% constrained. A reaction with one metabolite gets the boundary metabolite "_b" with +% the annotation of that metabolite; a reaction with several metabolites gets +% "_b" without annotation. % % Parameters % ---------- diff --git a/src/manipulation/contractModel.m b/src/manipulation/contractModel.m index d99108ed..a0e58b68 100755 --- a/src/manipulation/contractModel.m +++ b/src/manipulation/contractModel.m @@ -1,14 +1,14 @@ function [reducedModel, removedRxns, indexedDuplicateRxns]=contractModel(model,varargin) -% contractModel Contract a model by grouping all identical reactions. +% contractModel Contract a model by merging identical reactions. % -% This is what simplifyModel's deleteDuplicates option runs; it is not a -% separate implementation. If the duplicated reactions have "_EXP_*" suffixes -% (where * is a digit), then the model is assumed to have been passed through -% expandModel, and these suffixes are removed here. +% simplifyModel runs contractModel for its deleteDuplicates option. If the duplicated +% reactions have "_EXP_*" suffixes (where * is a digit), the model has passed through +% expandModel, and the function removes these suffixes. % -% Reactions are merged, so their gene associations are combined rather than -% discarded. Use findDuplicateRxns instead to see which reactions are -% duplicates without changing the model. +% The gene associations and EC numbers of merged reactions are combined. The merged +% reaction keeps the widest bounds and the largest objective coefficient, and a warning +% is given when the duplicates differ in these. findDuplicateRxns lists duplicate +% reactions without changing the model. % % Parameters % ---------- @@ -18,22 +18,22 @@ % Name-Value Arguments % -------------------- % distReverse : logical -% distinguish reactions with same metabolites but different reversibility -% as different reactions (default true). +% true to treat reactions with the same metabolites but different reversibility as +% different reactions (default true). % mets : char or cell -% string or cell array of strings with metabolite identifiers, whose -% involved reactions should be checked for duplication (by default all -% reactions are considered). This option is used by replaceMets. +% metabolite ids as a string or a cell array of strings; only the reactions that +% involve these metabolites are checked for duplication (default: all reactions). +% replaceMets uses this option. % % Returns % ------- % reducedModel : struct % a model structure without duplicate reactions. % removedRxns : cell -% cell array for the removed duplicate reactions. +% cell array with the ids of the removed duplicate reactions. % indexedDuplicateRxns : cell -% indexed cell array for the removed duplicate reactions (multiple values -% separated by semicolon). +% cell array with one entry per reaction of reducedModel, holding the ids of the +% duplicates removed into that reaction (multiple ids separated by semicolon). % % Examples % -------- @@ -41,8 +41,7 @@ % % Notes % ----- -% This code might not work for advanced grRules strings that involve nested -% expressions of "and" and "or". +% The function may not work for grRules strings with nested expressions of "and" and "or". p=parseRAVENargs(varargin, {'distReverse',true; 'mets',[]}); distReverse=p.distReverse; diff --git a/src/manipulation/convertToIrrev.m b/src/manipulation/convertToIrrev.m index faef2815..fe29a9ae 100755 --- a/src/manipulation/convertToIrrev.m +++ b/src/manipulation/convertToIrrev.m @@ -2,8 +2,8 @@ % convertToIrrev Convert a model to irreversible form. % % Reversible reactions are split into one forward and one reverse -% reaction. The reverse reactions are saved as 'rxnID_REV'. A warning is -% shown if some reaction identifiers already end with '_REV'. +% reaction. The reverse reactions are named 'rxnID_REV'. A warning is +% shown if some reaction ids already end with '_REV'. % % Parameters % ---------- @@ -13,7 +13,7 @@ % Name-Value Arguments % -------------------- % rxns : cell -% cell array with the reactions to split, if reversible (default +% cell array with the ids of the reactions to split, if reversible (default % model.rxns). % % Returns diff --git a/src/manipulation/copyToComps.m b/src/manipulation/copyToComps.m index 72a423f2..fdc81131 100755 --- a/src/manipulation/copyToComps.m +++ b/src/manipulation/copyToComps.m @@ -6,26 +6,22 @@ % model : struct % a model structure. % toComps : cell -% cell array of compartment ids. If there is no match to model.comps -% then it is added as a new compartment (see compNames and -% compOutside). +% cell array of compartment ids. An id that is not in model.comps is added as a +% new compartment (see compNames and compOutside). % % Name-Value Arguments % -------------------- % rxns : cell or logical or double -% either a cell array of reaction IDs, a logical vector with the same -% number of elements as reactions in the model, or a vector of indexes -% to copy (default model.rxns). +% a cell array of reaction ids, a logical vector with one element per reaction of +% the model, or a vector of reaction indexes to copy (default model.rxns). % deleteOriginal : logical -% true if the original reactions should be removed, making it move the -% reactions instead (default false). +% true to remove the original reactions, which moves the reactions (default false). % compNames : cell -% cell array of compartment names. Used if new compartments should be -% added (default toComps). +% cell array of compartment names. Used when new compartments are added +% (default toComps). % compOutside : cell -% cell array of the id (as in comps) for the compartment surrounding -% each of the compartments. Used if new compartments should be added -% (default all {''}). +% cell array with the id (as in comps) of the compartment that surrounds each new +% compartment. Used when new compartments are added (default all {''}). % % Returns % ------- @@ -38,7 +34,7 @@ % % Notes % ----- -% New reactions and metabolites will be named as "id_toComps(i)". +% New reactions and metabolites have the ids "id_toComps(i)". p=parseRAVENargs(varargin, {'rxns',[]; 'deleteOriginal',false; 'compNames',[]; 'compOutside',[]}); rxns=p.rxns; diff --git a/src/manipulation/expandModel.m b/src/manipulation/expandModel.m index f3fb2498..441b273f 100755 --- a/src/manipulation/expandModel.m +++ b/src/manipulation/expandModel.m @@ -1,8 +1,8 @@ function [newModel, rxnToCheck]=expandModel(model) % expandModel Expand reactions that use several gene associations. % -% Each reaction that uses several gene associations is split into several -% reactions, each under the control of only one gene. +% Each reaction with several gene associations (isozymes) is split into one reaction per +% association. % % Parameters % ---------- @@ -12,12 +12,12 @@ % Returns % ------- % newModel : struct -% model structure with separate reactions for iso-enzymes, where the -% reaction ids are renamed as id_EXP_1, id_EXP_2, etc. +% model structure with separate reactions for isozymes. The ids of split reactions +% are id_EXP_1, id_EXP_2 and so on, numbered per isozyme. % rxnToCheck : cell -% cell array with original reaction identifiers for those whose grRule -% was not in disjunctive normal form, and so had to be expanded by -% distributivity, plus any whose grRule could not be parsed. +% cell array with the original ids of the reactions whose grRule is not in +% disjunctive normal form and was expanded by distributivity, and of the reactions +% whose grRule cannot be parsed and is left unchanged. % % Examples % -------- @@ -26,11 +26,11 @@ % Notes % ----- % The isozymes are the AND-clauses of the grRule in disjunctive normal form, -% so nested expressions are expanded correctly: "g1 and (g2 or g3)" yields -% "g1 and g2" and "g1 and g3". Reactions listed in rxnToCheck are expanded -% correctly too; they are reported only because a rule needing -% distributivity is often a sign that the GPR was not what its author -% intended. Use standardizeGrRules/findPotentialErrors to inspect them. +% so nested expressions are expanded: "g1 and (g2 or g3)" yields "g1 and g2" and +% "g1 and g3". Reactions listed in rxnToCheck because of distributivity are expanded in +% the same way; they are listed because a rule that needs distributivity can indicate +% that the GPR differs from what its author intended. standardizeGrRules and +% findPotentialErrors inspect them. %Work out the isozymes of every reaction up front. The number of copies is %the number of DNF clauses, which is not the number of ' or ' substrings: diff --git a/src/manipulation/findDuplicateRxns.m b/src/manipulation/findDuplicateRxns.m index be0a18ac..d0a3c928 100644 --- a/src/manipulation/findDuplicateRxns.m +++ b/src/manipulation/findDuplicateRxns.m @@ -1,21 +1,19 @@ function pairs = findDuplicateRxns(model, varargin) % findDuplicateRxns Find reactions that share identical stoichiometry. % -% The upstream version of yeast-GEM's findDuplicatedRxns. -% -% Only stoichiometry is compared — bounds, GPRs, and annotations are -% ignored. The default treats A→B and B→A as duplicates (typical curation -% use case: "find reactions that could be merged"). +% Only the stoichiometry is compared; bounds, GPRs and annotations are ignored. With +% the default ignoreDirection, A→B and B→A are duplicates. The pairs identify +% reactions that could be merged (contractModel merges duplicate reactions). % % Parameters % ---------- % model : struct -% RAVEN model struct. +% a model structure. % % Name-Value Arguments % -------------------- % ignoreDirection : logical -% treat A→B and B→A as duplicates (default true). +% true to treat A→B and B→A as duplicates (default true). % % Returns % ------- diff --git a/src/manipulation/findPotentialErrors.m b/src/manipulation/findPotentialErrors.m index 48960d9a..b4d8780e 100644 --- a/src/manipulation/findPotentialErrors.m +++ b/src/manipulation/findPotentialErrors.m @@ -1,11 +1,10 @@ function issues=findPotentialErrors(model) % findPotentialErrors Identify reactions with non-DNF GPR rules. % -% Detects grRules that are not in disjunctive normal form (DNF) — -% i.e. rules where an OR operator is nested inside an AND context. -% Standard RAVEN format requires DNF: groups of genes that must all be -% present together (enzyme complexes) are written as conjunctions, and -% the reaction is catalysed by at least one complex (disjunction of +% Detects grRules that are not in disjunctive normal form (DNF), that is, rules in +% which an OR operator is nested inside an AND context. The RAVEN format requires DNF: +% groups of genes that must all be present together (enzyme complexes) are written as +% conjunctions, and the reaction is catalysed by at least one complex (a disjunction of % conjunctions). % % Parameters @@ -16,23 +15,22 @@ % Returns % ------- % issues : struct -% struct array — one element per problematic reaction — with fields: +% struct array with one element per problematic reaction and the fields: % -% - index : double — 1-based index into model.rxns. -% - rxn : char — reaction ID. -% - grRule : char — the problematic grRule string. -% - reason : char — short explanation. +% - index : double, 1-based index into model.rxns. +% - rxn : char, reaction id. +% - grRule : char, the problematic grRule string. +% - reason : char, short explanation. % % Notes % ----- -% Detection walks the parse tree from parseGrRule rather than searching the -% raw string, so gene IDs that contain substrings such as "and" or "or" do -% not cause false positives or negatives. +% Detection walks the parse tree from parseGrRule, so gene ids that contain substrings +% such as "and" or "or" are not misread as operators. % % A rule is non-DNF when an AND operator has an OR anywhere beneath it. -% Bracketing alone never makes a rule non-DNF: "((G1 and G2) or G3)" and -% "(G1 or G2) or (G3 and G4)" are both fine. -% Use standardizeGrRules to attempt automatic repair. +% Bracketing alone does not make a rule non-DNF: "((G1 and G2) or G3)" and +% "(G1 or G2) or (G3 and G4)" are both in DNF. standardizeGrRules attempts an +% automatic repair. % % Rules that cannot be parsed at all are reported as issues too, with the % parser's message as the reason. diff --git a/src/manipulation/generateNewIds.m b/src/manipulation/generateNewIds.m index e6227fc0..ca6e157a 100755 --- a/src/manipulation/generateNewIds.m +++ b/src/manipulation/generateNewIds.m @@ -1,8 +1,8 @@ function newIds=generateNewIds(model,type,prefix,varargin) % generateNewIds Generate a list of new metabolite or reaction ids. % -% The ids are sequentially numbered with a defined prefix. The model is -% queried for the highest existing number of that type of id. Suffixes are +% The ids are numbered sequentially with a defined prefix. The highest existing number +% among the ids of that type with the prefix is read from the model. Suffixes are % read with str2double. A suffix counts as a number when the result is a % finite integer value below 2^52; text, Inf, NaN, fractions (r_2.5) and % numbers of 2^52 or more (r_1e300, r_1e400) do not count. @@ -14,17 +14,16 @@ % type : char % type of id, 'rxns' or 'mets'. % prefix : char -% prefix to be used in all ids, e.g. 's_' or 'r_'. +% prefix to be used in all ids, for example 's_' or 'r_'. % % Name-Value Arguments % -------------------- % quantity : double -% number of new ids that should be generated (default 1). +% number of new ids to generate (default 1). % numLength : double -% length of the numerical part of the id. E.g. 4 gives ids like r_0001 -% and 6 gives ids like r_000001. If the prefix is already used in the -% model, then the model-defined length will be used instead -% (default 4). +% length of the numerical part of the id; 4 gives ids like r_0001 and 6 gives ids like +% r_000001. If the prefix is used in the model, the length of the existing ids +% applies instead (default 4). % % Returns % ------- diff --git a/src/manipulation/mergeCompartments.m b/src/manipulation/mergeCompartments.m index ce4e80e9..42de7294 100755 --- a/src/manipulation/mergeCompartments.m +++ b/src/manipulation/mergeCompartments.m @@ -9,28 +9,29 @@ % Name-Value Arguments % -------------------- % keepUnconstrained : logical -% keep metabolites that are unconstrained in a "unconstrained" -% compartment. If these are merged the exchange reactions will most often -% be deleted (default false). +% true to keep the unconstrained metabolites in a separate compartment "b" +% (name "Unconstrained"). If they are merged, an exchange reaction between an +% unconstrained metabolite and a metabolite of the same name has no net +% stoichiometry after merging and is deleted (default false). % deleteRxnsWithOneMet : logical -% delete reactions with only one metabolite. These reactions come from -% reactions such as A[c] + B[c] => A[m]. In some models hydrogen is -% balanced around each membrane with reactions like this (default -% false). +% true to delete reactions that have one metabolite after merging and had more +% before. Such reactions arise from reactions such as A[c] + B[c] => A[m]. Some +% models balance hydrogen around each membrane with reactions like this +% (default false). % distReverse : logical -% distinguish reactions with same metabolites but different reversibility -% as different reactions (default true). +% true to treat reactions with the same metabolites but different reversibility as +% different reactions (default true). % % Returns % ------- % model : struct -% a model with all reactions located to one compartment. +% a model with all reactions in one compartment. % deletedRxns : cell -% reactions that were deleted because of only having one metabolite -% after merging. +% the ids of the reactions deleted because they have one metabolite after merging. % duplicateRxns : cell -% identical reactions that occurred in different compartments and were -% deleted because they turned to be duplicated after merging. +% one entry per reaction of the returned model: '' when no duplicate was merged +% into it, otherwise the ids of the identical reactions from different +% compartments that were removed into it, joined by ";". % % Examples % -------- @@ -38,17 +39,16 @@ % % Notes % ----- -% Merges all compartments into one "s" compartment (for "System"). This can -% be useful for example to ensure that there are metabolic capabilities to -% synthesize all metabolites. +% Merges all compartments into one compartment "s" (name "System"). The merged model +% can be used to test whether the network can synthesise all metabolites. % % Metabolites with the same name are merged into one. The merged metabolite % keeps the id and the other fields of one of the copies: the copy that is % already in compartment "s" if there is one, otherwise the copy whose id % comes first in character code order (for example ACCOA_c before ACCOA_m). % With keepUnconstrained only the copies that are not unconstrained are -% candidates. If the metabolite IDs reflect the compartment that they are in -% the IDs may no longer be representative. +% candidates. If the metabolite ids contain the compartment, the ids of merged +% metabolites can name a compartment that no longer exists. p=parseRAVENargs(varargin, {'keepUnconstrained',false; 'deleteRxnsWithOneMet',false; 'distReverse',true}); keepUnconstrained=p.keepUnconstrained; diff --git a/src/manipulation/mergeModels.m b/src/manipulation/mergeModels.m index 5ece7c76..411a161f 100755 --- a/src/manipulation/mergeModels.m +++ b/src/manipulation/mergeModels.m @@ -1,10 +1,11 @@ function model=mergeModels(models,varargin) % mergeModels Merge models into one model structure. % -% Merges models into one model structure. Reactions are added without any -% checks, so duplicate reactions might appear. Metabolites are matched by -% their name and compartment (metaboliteName[comp]), while genes are matched -% by their name. +% Merges models into one model structure. Reactions are added without a check for +% duplicate reactions, so duplicates can appear. A reaction id that already exists in +% the merged model is renamed by appending "_" and the id of its source model. +% Metabolites are matched by name and compartment (metaboliteName[comp]) and genes by +% name. % % Parameters % ---------- @@ -14,23 +15,22 @@ % Name-Value Arguments % -------------------- % metParam : char -% string, metabolite name ("metNames") or ID ("mets") are used for -% matching (default "metNames"). +% the metabolite field used for matching: "metNames" (name and compartment) or +% "mets" (id) (default "metNames"). % supressWarnings : logical -% whether warnings should be supressed (default false). +% true to suppress warnings (default false). % copyToComps : logical -% internal flag used by copyToComps.m when it calls mergeModels to -% duplicate a model into a new compartment; leave at default when -% calling mergeModels directly (default false). +% internal flag set by copyToComps when it calls mergeModels to duplicate a model into +% a new compartment. Left at the default when calling mergeModels directly +% (default false). % % Returns % ------- % model : struct -% a model structure with the merged model. Follows the structure of -% normal models but also has "rxnFrom/metFrom/geneFrom" fields to -% indicate from which model each reaction/metabolite/gene was taken. If -% the model already has "rxnFrom/metFrom/geneFrom" fields, then these -% fields are not modified. +% the merged model structure. In addition to the usual fields, it has the fields +% rxnFrom, metFrom and geneFrom, which hold the id of the model that each reaction, +% metabolite and gene was taken from. If a model already has these fields, its +% entries are not modified. % % Examples % -------- diff --git a/src/manipulation/removeBadRxns.m b/src/manipulation/removeBadRxns.m index 17a891a8..382ebf70 100755 --- a/src/manipulation/removeBadRxns.m +++ b/src/manipulation/removeBadRxns.m @@ -1,87 +1,77 @@ function [newModel, removedRxns]=removeBadRxns(model,varargin) -% removeBadRxns Remove reactions that enable production/consumption from nothing. +% removeBadRxns Remove reactions that enable production or consumption from nothing. % -% Iteratively removes reactions which enable production/consumption of some -% metabolite without any uptake/excretion. +% Iteratively removes reactions that enable production or consumption of a metabolite +% without any uptake or excretion. % % Parameters % ---------- % model : struct -% a model structure. For the intended function, the model should not -% allow for any uptake/excretion. The easiest way to achieve this is to -% import the model using importModel("filename", false). +% a model structure. The model must not allow any uptake or excretion; the function +% warns about open exchange reactions when the model has no unconstrained field. +% importModel("filename", false) imports a model without uptake and excretion. % % Name-Value Arguments % -------------------- % rxnRules : double % which reactions may be removed (default 1): % -% - 1 : only remove reactions which are unbalanced -% - 2 : also remove reactions which could not be checked for mass -% balancing -% - 3 : all reactions can be removed +% - 1 : remove only unbalanced reactions +% - 2 : also remove reactions that could not be checked for mass +% balance +% - 3 : any reaction can be removed % ignoreMets : cell or logical or double -% either a cell array of metabolite IDs, a logical vector with the same -% number of elements as metabolites in the model, or a vector of indexes -% for metabolites to exclude from this analysis (default []). +% a cell array of metabolite ids, a logical vector with one element per metabolite +% of the model, or a vector of metabolite indexes to exclude from the analysis +% (default []). % isNames : logical -% true if the supplied mets represent metabolite names (as opposed to -% IDs). This is a way to delete metabolites in several compartments at -% once without knowing the exact IDs. This only works if ignoreMets is a +% true if ignoreMets holds metabolite names (false: ids), which excludes the +% metabolites of that name in all compartments. Applies only if ignoreMets is a % cell array (default false). % balanceElements : cell -% a cell array with the elements for which to balance the reactions. May -% contain any combination of the elements defined in parseFormulas -% (default {"C";"P";"S";"N";"O"}). +% a cell array with the elements for which to balance the reactions, chosen from +% the elements defined in parseFormulas (default {"C";"P";"S";"N";"O"}). % refModel : struct -% a reference model which can be used to ensure that the resulting model -% is still functional. The intended use is that the reference model is a -% copy of model, but with uptake/excretion allowed and some objectives -% (such as production of biomass) constrained to a non-zero flux. Before -% a reaction is removed from "model" the function first checks that the -% same deletion in "refModel" does not render the problem unfeasible. +% a reference model, intended as a copy of model with uptake and excretion allowed +% and some objectives (such as production of biomass) constrained to a non-zero +% flux. A supplied reference model triggers a warning that the feature is not +% supported, followed by an error. % ignoreIntBounds : logical -% true if internal bounds (including reversibility) should be ignored. -% Exchange reactions are not affected. This can be used to find -% unbalanced solutions which are not possible using the default -% constraints (default false). +% true to ignore internal bounds (including reversibility). Exchange reactions are +% not affected. Ignoring the bounds finds unbalanced solutions that the default +% constraints do not allow (default false). % printReport : logical -% true if a report should be printed (default false). +% true to print a report (default false). % seed : double -% seed for the random number generator, so that which reaction is -% removed among several equally-valid candidates is reproducible -% across runs (default: current rng state is left untouched). +% seed for the random number generator. A seed makes the choice among several +% candidate reactions reproducible (default: the current rng state is not +% changed). % % Returns % ------- % newModel : struct -% a model structure after the problematic reactions have been deleted. +% the model structure without the problematic reactions. % removedRxns : cell -% a cell array with the reactions that were removed. +% a cell array with the ids of the removed reactions. % % Notes % ----- -% The purpose of this function is to remove reactions which enable -% production/consumption of metabolites even when exchange reactions are not -% used. Many models, especially if they are automatically inferred from -% databases, will have unbalanced reactions which allow for -% net-production/consumption of metabolites without any consumption or -% excretion. A common reason for this is when general compounds have -% different meaning in different reactions (as DNA has in these two -% reactions): +% The function removes reactions that enable production or consumption of metabolites +% when no exchange reaction is used. Models inferred automatically from databases can +% contain unbalanced reactions that allow net production or consumption of metabolites +% without any consumption or excretion. One cause is a general compound that has a +% different meaning in different reactions (as DNA has in these two reactions): % % dATP + dGTP + dCTP + dTTP <=> DNA + 4 PPi % 0.25 dATP + 0.25 dGTP + 0.25 dCTP + 0.25 dTTP <=> DNA + PPi % -% Reactions that are problematic like this are always elementally -% unbalanced, but it is not always the case that you would like to exclude -% all unbalanced reactions from your model. This function tries to remove as -% few problematic reactions as possible so that the model cannot -% produce/consume anything from nothing. This is done by repeatedly calling -% findLeakMetabolite, checking if any of the involved reactions are -% elementally unbalanced, removing one of them, and then iterating until no -% metabolites can be produced/consumed. The 'produce' direction is run -% before the 'consume' one. +% Such reactions are elementally unbalanced, but the model can contain further +% unbalanced reactions that are not problematic. The function removes as few reactions +% as possible so that the model cannot produce or consume anything from nothing. It +% calls findLeakMetabolite repeatedly, selects at random one of the elementally +% unbalanced reactions that carry flux in the solution, removes it, and iterates until +% no metabolite can be produced or consumed. The 'produce' direction runs before the +% 'consume' direction. % % Examples % -------- diff --git a/src/manipulation/removeGenes.m b/src/manipulation/removeGenes.m index 00f3e30f..5c215b9b 100755 --- a/src/manipulation/removeGenes.m +++ b/src/manipulation/removeGenes.m @@ -12,13 +12,12 @@ % Name-Value Arguments % -------------------- % removeUnusedMets : logical -% remove metabolites that are no longer in use (default false). +% remove metabolites that no remaining reaction uses (default false). % removeBlockedRxns : logical -% remove reactions that get blocked after deleting the genes (default -% false). +% remove reactions that cannot carry flux after the genes are deleted. +% When false, the bounds of these reactions are set to 0 (default false). % standardizeRules : logical -% format gene rules to be compliant with the standard format (default -% true). +% write the gene rules in the standard format (default true). % % Returns % ------- @@ -29,9 +28,12 @@ % written in the standard format). Otherwise, besides genesToRemove, every % gene that no remaining reaction used before the removal is deleted as % well, also when it was not requested (for example a gene that no -% reaction uses). A gene whose only reaction is kept but blocked +% reaction uses). A gene that only occurs in a rule part removed with +% another gene stays, because the reaction used it before the removal +% (for example G3 in (G1 and G3) or G2 after removing G1). A gene also +% stays when its reaction remains, even if that reaction is blocked % (removeBlockedRxns false), such as the other subunit of a complex that -% lost one, stays. +% lost one. % % Examples % -------- diff --git a/src/manipulation/removeLowScoreGenes.m b/src/manipulation/removeLowScoreGenes.m index fb9b8c3d..160b81ef 100644 --- a/src/manipulation/removeLowScoreGenes.m +++ b/src/manipulation/removeLowScoreGenes.m @@ -1,10 +1,10 @@ function [newModel,remGenes] = removeLowScoreGenes(model,geneScores,varargin) -% removeLowScoreGenes Remove low-scoring genes from model. +% removeLowScoreGenes Remove low-score genes from a model. % -% This function removes genes from a model based on their scores, a step used -% by the tINIT package. The function recognizes and differentiates between -% isozymes and subunits of an enzyme complex. Genes are removed from each -% grRule, subject to the following conditions: +% Removes genes from a model based on their scores, a step used by the tINIT +% package. The function distinguishes isozymes from subunits of an enzyme +% complex. Genes are removed from each grRule, subject to the following +% conditions: % % 1) At least one gene must remain associated with the reaction. % 2) Genes involved in a complex (joined by ANDs) are not removed. @@ -14,17 +14,18 @@ % model : struct % model structure from which genes are to be removed. % geneScores : double -% a vector of scores associated with the model genes. Genes with a -% positive score will remain in the model, whereas genes with a negative -% score will try to be removed. +% a vector of scores associated with the model genes, with the same +% dimensions as model.genes. Genes with a score of zero or above and genes +% without a score (NaN) remain in the rules; a gene that no rule uses +% afterwards is removed from model.genes. Genes with a negative score +% are removed from the rules when the conditions above allow it. % -% If all genes associated with a reaction have a negative score, then the -% least-negative gene will remain; if there is a tie, the gene listed first -% in the rule will be kept. +% If all genes associated with a reaction have a negative score, the +% least-negative gene remains; in a tie, the gene listed first in the rule +% is kept. % -% If a negative-scoring gene is a subunit in a complex, it will not be -% removed; however, the entire complex may be removed. See the following -% example cases: +% A negative-scoring gene that is a subunit in a complex is not removed, +% but the entire complex may be removed. Example cases: % % - Original: G1 or (G2 and G3 and G4); Negative: G1, G2; New: G2 and G3 % and G4. @@ -36,21 +37,23 @@ % Name-Value Arguments % -------------------- % isozymeScoring : char -% method used to combine the scores of multiple isozymes; "min", "max", -% "median", or "average" (default "max"). +% method used to combine the scores of multiple isozymes: "min", "max", +% "median" or "average" (default "max"). % complexScoring : char % method used to combine the scores of enzyme complex subunits: "min", -% "max", "median", or "average" (default "min"). +% "max", "median" or "average" (default "min"). % % Returns % ------- % newModel : struct -% model with updated gene, grRules, and rxnGeneMat fields after attempting -% to remove negative-score genes. +% model with updated genes, grRules and rxnGeneMat fields, and with the +% gene-associated fields (geneShortNames, proteins, geneMiriams, geneFrom, +% geneComps) trimmed to the remaining genes. % remGenes : cell -% a list of negative-score genes that were fully removed from the model. -% Negative-score genes that were removed from some grRules but not all -% will not be included in this list. +% the genes removed from model.genes: negative-score genes removed from every +% grRule and genes that no rule uses, whatever their score. Negative-score +% genes that are removed from some grRules but not all are not included in +% this list. p=parseRAVENargs(varargin, {'isozymeScoring',[]; 'complexScoring','min'}); diff --git a/src/manipulation/removeMets.m b/src/manipulation/removeMets.m index e494d3ac..e9fb6c3c 100755 --- a/src/manipulation/removeMets.m +++ b/src/manipulation/removeMets.m @@ -13,16 +13,17 @@ % Name-Value Arguments % -------------------- % isNames : logical -% true if the supplied mets represent metabolite names (as opposed to -% IDs). This is a way to delete metabolites in several compartments at -% once without knowing the exact IDs. This only works if metsToRemove -% is a cell array (default false). +% true if the supplied mets are metabolite names, false if they are IDs. +% All metabolites with a listed name are deleted, which removes +% metabolites in several compartments at once. Requires metsToRemove to +% be a cell array (default false). % removeUnusedRxns : logical -% remove reactions that are no longer in use (default false). +% remove reactions that involve no metabolite (default false). % removeUnusedGenes : logical -% remove genes that are no longer in use (default false). +% remove genes that no remaining reaction uses. Takes effect only when +% removeUnusedRxns is true (default false). % removeUnusedComps : logical -% remove compartments that are no longer in use (default false). +% remove compartments that contain no metabolite (default false). % % Returns % ------- diff --git a/src/manipulation/removeReactions.m b/src/manipulation/removeReactions.m index a19fbf9f..ae124b72 100755 --- a/src/manipulation/removeReactions.m +++ b/src/manipulation/removeReactions.m @@ -13,11 +13,12 @@ % Name-Value Arguments % -------------------- % removeUnusedMets : logical -% remove metabolites that are no longer in use (default false). +% remove metabolites that no remaining reaction uses (default false). % removeUnusedGenes : logical -% remove genes that are no longer in use (default false). +% remove genes that no remaining reaction uses (default false). % removeUnusedComps : logical -% remove compartments that are no longer in use (default false). +% remove compartments that contain no metabolite. Takes effect only when +% removeUnusedMets is true (default false). % % Returns % ------- diff --git a/src/manipulation/replaceMets.m b/src/manipulation/replaceMets.m index bcba7d0f..bb1198d2 100755 --- a/src/manipulation/replaceMets.m +++ b/src/manipulation/replaceMets.m @@ -1,20 +1,24 @@ function [model, removedRxns, idxDuplRxns]=replaceMets(model,metabolite,replacement,varargin) % replaceMets Replace a metabolite with another already in the model. % -% Replaces metabolite names and annotation with a replacement metabolite -% that is already in the model. If this results in duplicate metabolites, -% the replacement metabolite will be kept, while the S matrix is updated to -% use the replacement metabolite instead. At the end, contractModel is run -% to remove any duplicate reactions that might have occurred. +% Replaces the name and annotation (formula, miriams, charge, deltaG, InChI +% and SMILES) of a metabolite with those of a replacement metabolite that is +% already in the model. If this results in duplicate metabolites, the S matrix +% is updated to use one of them. With identifiers true the replacement +% metabolite is kept; by name, the metabolite with the lowest index among those +% of the same name and compartment is kept. At the end, contractModel is run to remove +% duplicate reactions that this creates. % % Parameters % ---------- % model : struct % a model structure. % metabolite : char -% string with name of metabolite to be replaced. +% name of the metabolite to be replaced (its identifier if identifiers is +% true). % replacement : char -% string with name of replacement metabolite. +% name of the replacement metabolite (its identifier if identifiers is +% true). % % Name-Value Arguments % -------------------- @@ -22,17 +26,17 @@ % whether to print the ids of reactions that involve the replaced % metabolite (default false). % identifiers : logical -% true if 'metabolite' and 'replacement' refer to metabolite -% identifiers instead of metabolite names (default false). +% true if 'metabolite' and 'replacement' are metabolite identifiers, +% false if they are metabolite names (default false). % % Returns % ------- % model : struct -% model structure with selected metabolites replaced. +% model structure with the selected metabolites replaced. % removedRxns : cell -% identifiers of duplicate reactions that were removed. +% not assigned by the function; requesting this output raises an error. % idxDuplRxns : double -% index of removedRxns in original model. +% not assigned by the function; requesting this output raises an error. % % Examples % -------- @@ -41,9 +45,9 @@ % % Notes % ----- -% This function is useful when the model contains both 'oxygen' and 'o2' as -% metabolite names. If 'oxygen' and 'o2' are identifiers instead, then the -% 'identifiers' flag should be set to true. +% The function applies to a model that contains both 'oxygen' and 'o2' as +% metabolite names. If 'oxygen' and 'o2' are identifiers, 'identifiers' is +% set to true. metabolite=char(metabolite); replacement=char(replacement); diff --git a/src/manipulation/reverseRxns.m b/src/manipulation/reverseRxns.m index efb5f6fc..8ab24ff3 100644 --- a/src/manipulation/reverseRxns.m +++ b/src/manipulation/reverseRxns.m @@ -1,10 +1,27 @@ function model=reverseRxns(model, rxns) -% reverseRxns -% Reverses the given reactions in place: negates their stoichiometry and -% objective coefficient, and swaps/negates their lower and upper bounds, -% so flux in the new positive direction is what was previously negative flux. -% model the model to change (input/output) -% rxns the rxns to reverse +% reverseRxns Reverse the direction of reactions in a model. +% +% Negates the stoichiometric coefficients and the objective coefficient of +% the reactions, and sets the lower bound to the negated upper bound and the +% upper bound to the negated lower bound. A flux in the new positive +% direction equals a flux in the original negative direction. Reaction IDs +% in rxns that are not in the model are ignored. +% +% Parameters +% ---------- +% model : struct +% a model structure. +% rxns : cell +% IDs of the reactions to reverse. +% +% Returns +% ------- +% model : struct +% an updated model structure. +% +% Examples +% -------- +% model = reverseRxns(model, rxns); rxnInd = find(ismember(model.rxns,rxns)); diff --git a/src/manipulation/setExchangeBounds.m b/src/manipulation/setExchangeBounds.m index a3a32b5a..dddc6d5c 100755 --- a/src/manipulation/setExchangeBounds.m +++ b/src/manipulation/setExchangeBounds.m @@ -10,31 +10,34 @@ % -------------------- % mets : cell or double % a cell array of metabolite names (case insensitive) or metabolite -% IDs, or a vector of metabolite indices (default all exchanged +% IDs, or a vector of indices in model.mets (default all exchanged % metabolites). % lb : double -% lower bound of exchange flux. Can be either a vector of bounds -% corresponding to each of the provided metabolites, or a single value -% that will be applied to all (default model.annotation.defaultLB if it -% exists, otherwise -1000). +% lower bound of exchange flux. Either a vector of bounds corresponding +% to each of the provided metabolites, or a single value that is applied +% to all (default model.annotation.defaultLB if it exists, otherwise +% -1000). Without mets, only a single value is accepted. % ub : double -% upper bound of exchange flux. Can be either a vector of bounds -% corresponding to each of the provided metabolites, or a single value -% that will be applied to all (default model.annotation.defaultUB if it -% exists, otherwise 1000). +% upper bound of exchange flux. Either a vector of bounds corresponding +% to each of the provided metabolites, or a single value that is applied +% to all (default model.annotation.defaultUB if it exists, otherwise +% 1000). Without mets, only a single value is accepted. % closeOthers : logical -% close exchange reactions for all other exchanged metabolites not -% present in the provided list. This will prevent IMPORT of the -% metabolites, but their EXPORT will not be modified (default true). +% close the exchange reactions of all exchanged metabolites that are not +% in the provided list. This prevents import of these metabolites and +% leaves their export unchanged. Set to false, with a warning, when the +% exchange reactions are not all written in the same direction +% (default true). % mediaOnly : logical % only consider exchange reactions involving exchange to or from the % extracellular (media) compartment. Reactions such as "sink" reactions % that exchange metabolites directly with an intracellular compartment -% will therefore be ignored even though "getExchangeRxns" identifies -% such reactions as exchange reactions. The function will attempt to -% identify the extracellular compartment by the "compNames" field, and -% also requires the "metComps" field to be present, otherwise the -% mediaOnly flag will be ignored (default false). +% are therefore ignored, although "getExchangeRxns" identifies such +% reactions as exchange reactions. The extracellular compartment is the +% one named "extracellular" in the "compNames" field (case insensitive). +% The "compNames" and "metComps" fields are required; an error is thrown +% if either is missing or if no compartment has this name +% (default false). % % Returns % ------- @@ -42,8 +45,8 @@ % a model structure with updated exchange flux bounds for the provided % set of metabolites. % unusedMets : cell -% metabolites provided by the user that were not used because they are -% not involved in any exchange reactions in the model. +% metabolites in mets that were not used because they are not involved +% in any of the considered exchange reactions in the model. % % Examples % -------- @@ -52,7 +55,7 @@ % % Notes % ----- -% Exchange reactions involving more than one metabolite will be ignored. +% Exchange reactions involving more than one metabolite are ignored. % handle input arguments diff --git a/src/manipulation/setParam.m b/src/manipulation/setParam.m index 07304c7e..df82afb4 100755 --- a/src/manipulation/setParam.m +++ b/src/manipulation/setParam.m @@ -6,23 +6,31 @@ % model : struct % a model structure. % paramType : char -% the type of parameter to set: +% the type of parameter to set. The value is matched case-insensitively, +% except for the reset of the other objective coefficients under "obj" +% (below): % % - "lb" : lower bound. % - "ub" : upper bound. % - "eq" : both upper and lower bound (equality constraint). -% - "obj" : objective coefficient. -% - "rev" : reversibility (only changes the model.rev fields, does not -% affect model.lb and model.ub). +% - "obj" : objective coefficient. The objective coefficients of all +% other reactions are set to 0 only when every element of paramType +% contains "obj" in lower case; with another capitalisation, or when +% paramType also holds other types, the other coefficients keep their +% values. +% - "rev" : reversibility (only changes the model.rev field, does not +% affect model.lb and model.ub). A non-zero value sets the reaction +% reversible. % - "var" : variance around measured bound. -% - "unc" : unconstrained, set lower and upper bound to the default -% values (-1000 and 1000, or any other values that are defined in -% model.annotation.defaultLB and .defaultUB). +% - "unc" : unconstrained, sets the lower and upper bound to +% model.annotation.defaultLB and model.annotation.defaultUB if these +% are defined, otherwise to -1000 and 1000. % rxnList : cell or double % a cell array of reaction IDs or a vector with their corresponding -% indexes. +% indexes. IDs that are not in the model are ignored with a warning. % params : double -% a vector of the corresponding values. +% a vector of the corresponding values, or a single value that is +% applied to all reactions in rxnList. % var : double, optional % percentage of variance around measured value, if "var" is set as % paramType. Defining "var" as 5 results in lb and ub at 97.5% and diff --git a/src/manipulation/simplifyModel.m b/src/manipulation/simplifyModel.m index d579230d..464f4011 100755 --- a/src/manipulation/simplifyModel.m +++ b/src/manipulation/simplifyModel.m @@ -1,9 +1,8 @@ function [reducedModel, deletedReactions, deletedMetabolites]=simplifyModel(model,varargin) % simplifyModel Simplify a model by deleting reactions and metabolites. % -% This function is for reducing the model size by removing reactions and -% associated metabolites that cannot carry flux. It can also be used for -% identifying different types of gaps. +% Deletes reactions and metabolites that cannot carry flux. The deleted +% reactions and metabolites identify different types of gaps. % % Parameters % ---------- @@ -13,27 +12,37 @@ % Name-Value Arguments % -------------------- % deleteUnconstrained : logical -% delete metabolites marked as unconstrained (default true). +% delete metabolites marked as unconstrained in model.unconstrained, and +% the compartments that are then left without metabolites (default true). % deleteDuplicates : logical -% delete all but one of duplicate reactions (default false). +% delete all but the first occurrence of duplicate reactions, as found by +% contractModel. Duplicate reactions have the same stoichiometry and +% reversibility. The kept reaction takes the minimum lower bound, the +% maximum upper bound and the maximum objective coefficient of the +% duplicates, with a warning when these differ (default false). % deleteZeroInterval : logical % delete reactions that are constrained to zero flux (default false). % deleteInaccessible : logical % delete dead end reactions (default false). % deleteMinMax : logical -% delete reactions that cannot carry a flux by trying to -% minimize/maximize the flux through that reaction. May be time -% consuming (default false). +% delete reactions that cannot carry a flux, as determined by haveFlux +% under the bounds of the model. The run time grows with the number of +% reactions, since haveFlux solves linear programs (default false). % groupLinear : logical -% group linearly dependent pathways (default false). +% group linearly dependent pathways. The grouped model is irreversible, +% and all gene information (genes, rxnGeneMat, grRules and the +% gene-associated fields) is deleted (default false). % constrainReversible : logical % check if there are reversible reactions which can only carry flux in -% one direction, and if so constrain them to be irreversible. This -% tends to allow for more reactions grouped when using groupLinear -% (default false). +% one direction, and if so constrain them to be irreversible. A reversible +% reaction that is constrained to one direction is not split into two +% reactions when groupLinear converts the model to irreversible, so the +% metabolites that it connects can meet the condition for grouping (one +% producing and one consuming reaction) (default false). % reservedRxns : cell -% cell array with reaction IDs that are not allowed to be removed -% (default none). +% cell array with reaction IDs that are not deleted by +% deleteZeroInterval, deleteInaccessible and deleteMinMax and not merged +% by groupLinear (default none). % suppressWarnings : logical % true if warnings should be suppressed (default false). % diff --git a/src/manipulation/sortIdentifiers.m b/src/manipulation/sortIdentifiers.m index 5a062a1e..acc0e833 100755 --- a/src/manipulation/sortIdentifiers.m +++ b/src/manipulation/sortIdentifiers.m @@ -1,8 +1,8 @@ function newModel = sortIdentifiers(model) % sortIdentifiers Sort model identifiers alphabetically. % -% Sort reactions, metabolites, genes and compartments alphabetically by -% their identifier. +% Sorts reactions, metabolites, genes (if the genes field exists) and +% compartments alphabetically by their identifier. % % Parameters % ---------- diff --git a/src/manipulation/sortModel.m b/src/manipulation/sortModel.m index 481924b5..010d669c 100755 --- a/src/manipulation/sortModel.m +++ b/src/manipulation/sortModel.m @@ -1,8 +1,9 @@ function model=sortModel(model,varargin) -% sortModel Standardize a model's internal ordering: can orient -% reversible reactions consistently; can also reorder metabolites within -% reaction equations and/or reactions within each subsystem, depending on -% which options are enabled. +% sortModel Sort the metabolites and reactions of a model. +% +% Depending on the options, the function sorts the metabolites by name and +% compartment, orients reversible reactions consistently, and reorders the +% reactions within each subsystem. % % Parameters % ---------- @@ -12,21 +13,25 @@ % Name-Value Arguments % -------------------- % sortReversible : logical -% sorts the reversible reactions so that the metabolite that is first -% in lexicographical order is a reactant (default false). +% orient each reversible reaction by changing the sign of its +% stoichiometric coefficients. The metabolite that is first in +% lexicographical order (metabolite name followed by compartment ID) +% becomes a reactant. Not applied when sortReactionOrder is true +% (default false). % sortMetName : logical -% sort the metabolite names in the equation, also uses compartment -% abbreviation (default false). +% sort the metabolites of the model by metabolite name followed by +% compartment ID (default false). % sortReactionOrder : logical -% sorts the reaction order within each subsystem so that reactions -% consuming some metabolite come after reactions producing it. This -% overrides the sortReversible option and reactions are sorted so that -% the production direction matches the consumption direction (default -% false). +% reorder the reactions within each subsystem, such that reactions +% consuming a metabolite come after reactions producing it. The order is +% found by a random local search that also changes the direction of +% reversible reactions to match production and consumption direction. +% Requires the subSystems field. Subsystems with fewer than 2 or more +% than 250 reactions are not reordered (default false). % seed : double % seed for the random number generator used by the sortReactionOrder -% local search, so its result is reproducible across runs (default: -% current rng state is left untouched). +% local search, which makes its result reproducible across runs +% (default: the current rng state is not changed). % % Returns % ------- diff --git a/src/manipulation/standardizeGrRules.m b/src/manipulation/standardizeGrRules.m index b95babdb..e59ddff4 100755 --- a/src/manipulation/standardizeGrRules.m +++ b/src/manipulation/standardizeGrRules.m @@ -1,13 +1,17 @@ function [grRules,rxnGeneMat,indexes2check] = standardizeGrRules(model,varargin) -% standardizeGrRules Standardize gene-reaction rules in a model. +% standardizeGrRules Standardise gene-reaction rules in a model. % -% The grRules are standardized according to the following: +% The grRules are standardised according to the following: % % - No overall containing brackets -% - Just enzyme complexes are enclosed into brackets -% - " and " and " or " strings are strictly set to lowercase +% - Only enzyme complexes are enclosed in brackets +% - " and " and " or " strings are set to lowercase % -% A rxnGeneMat matrix consistent with the standardized grRules is created. +% A rxnGeneMat matrix consistent with the standardised grRules is created. +% grRules that are not in disjunctive normal form (an "or" nested inside an +% "and") or that cannot be parsed are not standardised, apart from the +% removal of double spaces, spaces inside brackets and uppercase operators. +% The function throws an error if the model has no grRules field. % % Parameters % ---------- @@ -17,21 +21,21 @@ % Name-Value Arguments % -------------------- % embedded : logical -% if true, suppresses the console warning about potentially -% problematic grRules relationships, for use when this function is -% called inside of another RAVEN function that handles reporting -% itself (default false). +% if true, suppresses the console warning about grRules that are not in +% disjunctive normal form or cannot be parsed, for use when this +% function is called inside another RAVEN function that handles +% reporting itself (default false). % % Returns % ------- % grRules : cell -% [nRxns x 1] cell array with the standardized grRules. +% [nRxns x 1] cell array with the standardised grRules. % rxnGeneMat : double -% [nRxns x nGenes] sparse matrix consistent with the standardized +% [nRxns x nGenes] sparse matrix consistent with the standardised % grRules. % indexes2check : double -% indices of the grRules with potentially problematic relationships -% that should be checked manually. +% indices of the grRules that are not in disjunctive normal form or +% cannot be parsed. These grRules need a manual check. % % Examples % -------- diff --git a/src/queries/buildEquation.m b/src/queries/buildEquation.m index 6c93679e..9bb5951c 100755 --- a/src/queries/buildEquation.m +++ b/src/queries/buildEquation.m @@ -6,14 +6,17 @@ % mets : cell % metabolites involved in the reaction. % stoichCoeffs : double -% vector with corresponding stoichiometric coeffs. +% vector with the corresponding stoichiometric coefficients, of the same +% length as mets. Negative coefficients are reactants and positive +% coefficients are products. % isrev : logical -% indicates if the reaction is reversible or not. +% true if the reaction is reversible (written with "<=>"), false if it +% is irreversible (written with "=>"). % % Returns % ------- % equationString : char -% equation as a string. +% the equation as a string. % % Examples % -------- diff --git a/src/queries/checkModelStruct.m b/src/queries/checkModelStruct.m index 59b11149..38e2f4b1 100755 --- a/src/queries/checkModelStruct.m +++ b/src/queries/checkModelStruct.m @@ -1,5 +1,5 @@ function issues=checkModelStruct(model,varargin) -% checkModelStruct Perform a number of checks to ensure a model structure is ok. +% checkModelStruct Check a model structure for errors and inconsistencies. % % Parameters % ---------- @@ -10,32 +10,33 @@ % -------------------- % throwErrors : logical % true if structural problems (wrong types, missing required fields, -% duplicate IDs, etc.) should throw an error. Advisory notices such as +% duplicate IDs and other problems of structure) should throw an error. Advisory notices such as % unused elements and bound inconsistencies are always reported as % warnings regardless of this flag (default true). % trimWarnings : logical -% true if only a maximum of 10 items should be displayed in a given -% warning or error (default true). +% true if a given warning or error lists at most 10 items, the last of +% which reports the number of remaining items (default true). % % Returns % ------- % issues : struct -% When called with an output argument, returns a struct array with one -% element per finding (does not throw or print) instead of the default -% print/throw behaviour. Fields: +% When called with an output argument, the function returns a struct +% array with one element per finding and neither throws nor prints. The +% default behaviour without an output argument is to print warnings and +% throw errors. Fields: % -% - category : char — type of issue: "missing_field", "wrong_type", -% "empty_id", "duplicate", "invalid_id", "invalid_bounds", -% "unused", "objective", "gpr", "invalid_formula", -% "cross_reference", or "other". -% - target : char — the specific item involved (field name, reaction -% ID, metabolite ID, etc.), or "" when not applicable. -% - message : char — the full diagnostic text. +% - category (char): type of issue, one of "missing_field", +% "wrong_type", "empty_id", "duplicate", "invalid_id", +% "invalid_bounds", "unused", "objective", "gpr", "invalid_formula", +% "cross_reference" or "other". +% - target (char): the item involved (field name, reaction ID, +% metabolite ID or another item), or "" when not applicable. +% - message (char): the full diagnostic text. % % Notes % ----- -% This is typically performed after importing or constructing a model, or -% before attempting to export a model to SBML format. +% The check is intended for use after importing or constructing a model and +% before exporting a model to SBML format. % % Examples % -------- diff --git a/src/queries/constructEquations.m b/src/queries/constructEquations.m index de3368a5..23f5d3ee 100755 --- a/src/queries/constructEquations.m +++ b/src/queries/constructEquations.m @@ -18,21 +18,23 @@ % sort reversible reactions so that the metabolite that is first in the % lexicographic order is a reactant (default false). % sortMetNames : logical -% sort the metabolite names in the equation. Uses compartment even if -% useComps is false (default false). +% sort the metabolite names in the equation. The compartment is included +% in the sorting even if useComps is false (default false). % useMetID : logical -% use metabolite ID in generated equations (default false). +% use metabolite ID in generated equations. Cannot be true together with +% useFormula (default false). % useFormula : logical -% use metabolite formula in generated equations (default false). +% use metabolite formula in generated equations. Cannot be true together +% with useMetID (default false). % useRevField : logical -% use the model.rev field to indicate reaction reversibility, -% alternatively this is determined from the model.ub and model.lb fields +% use the model.rev field to indicate reaction reversibility. When +% false, a reaction is reversible if model.lb < 0 and model.ub > 0 % (default true). % % Returns % ------- % equationStrings : cell -% a cell array with equations. +% a cell array with the equation of each reaction. % % Examples % -------- @@ -41,9 +43,9 @@ % % Notes % ----- -% If useRevField is false, then reactions should be organized in their -% forward direction (e.g. ub = 1000 and lb = -1000/0) for the reversibility -% to be correctly determined. +% If useRevField is false, the reactions have to be written in their forward +% direction (for example ub = 1000 and lb = -1000 or 0) for the reversibility +% to be determined correctly. p=parseRAVENargs(varargin, {'rxns',[]; 'useComps',true; 'sortRevRxns',false; 'sortMetNames',false; 'useMetID',false; 'useFormula',false; 'useRevField',true}); rxns=p.rxns; diff --git a/src/queries/constructS.m b/src/queries/constructS.m index 03b03162..f837a4ed 100755 --- a/src/queries/constructS.m +++ b/src/queries/constructS.m @@ -4,32 +4,34 @@ % Parameters % ---------- % equations : cell -% cell array of equations on the form "A + 2 B <=> 3 C", where <=> +% cell array of equations in the form "A + 2 B <=> 3 C", where <=> % indicates reversible and => irreversible reactions. % % Name-Value Arguments % -------------------- % mets : cell % cell array of metabolites. All metabolites in the equations must be -% present in the list (default generated from the equations). +% present in the list, otherwise an error is thrown (default generated +% from the equations). % rxns : cell -% cell array of reaction ids. This is only used for printing reaction ids -% instead of equations in warnings/errors (default []). +% cell array of reaction ids. Used to print reaction ids in error +% messages in place of equations (default []). % % Returns % ------- % S : double % the resulting stoichiometric matrix. % mets : cell -% cell array with metabolites that corresponds to the order in the S +% cell array with the metabolites in the order of the rows of the S % matrix. % badRxns : logical -% boolean vector with the reactions that have one or more metabolites as -% both substrate and product. An example would be the phosphotransferase -% ATP + ADP <=> ADP + ATP. In the stoichiometric matrix this equals to an -% empty reaction which can be problematic. +% logical vector with true for the reactions in which one or more +% metabolites occur more than once, for example as both substrate and +% product. An example is the phosphotransferase reaction +% ATP + ADP <=> ADP + ATP, which gives an empty column in the +% stoichiometric matrix. % reversible : logical -% boolean vector with true if the equation is reversible. +% logical vector with true if the equation is reversible. % % Examples % -------- diff --git a/src/queries/getAllRxnsFromGenes.m b/src/queries/getAllRxnsFromGenes.m index a1710c92..046e7b31 100755 --- a/src/queries/getAllRxnsFromGenes.m +++ b/src/queries/getAllRxnsFromGenes.m @@ -2,7 +2,8 @@ % getAllRxnsFromGenes Find all reactions annotated by the genes of a set. % % Given a list of reactions, this function finds the associated genes in -% the model and returns all reactions that are annotated by these genes. +% the model and returns all reactions that are annotated by these genes, +% including the given reactions. % % Parameters % ---------- @@ -15,9 +16,9 @@ % Returns % ------- % allRxns : cell or logical or double -% either a cell array of IDs, a logical vector with the same number of -% elements as reactions in the model, or a vector of indexes, -% dependent on the format of rxns. +% the reactions in the same format as rxns: a cell array of IDs (unique +% and sorted), a logical vector with the same number of elements as +% reactions in the model, or a vector of indexes (unique and sorted). % % Examples % -------- diff --git a/src/queries/getElementalBalance.m b/src/queries/getElementalBalance.m index f015984c..d13d0593 100755 --- a/src/queries/getElementalBalance.m +++ b/src/queries/getElementalBalance.m @@ -1,7 +1,10 @@ function balanceStructure=getElementalBalance(model,varargin) % getElementalBalance Check whether the reactions of a model are balanced. % -% Checks a model to see if the reactions are elementally balanced. +% Checks whether the reactions are elementally balanced, using the +% metFormulas field or, when that is absent, the inchis field. An error is +% thrown if the model has neither field. The charge balance is reported +% separately. % % Parameters % ---------- @@ -13,13 +16,13 @@ % rxns : cell or logical or double % either a cell array of reaction IDs, a logical vector with the same % number of elements as reactions in the model, or a vector of -% indexes. Only these reactions will be checked (default model.rxns). +% indexes. Only these reactions are checked (default model.rxns). % printUnbalanced : logical -% print warnings about the reactions that were unbalanced +% print warnings about the reactions that are unbalanced % (default false). % printUnparsable : logical -% print warnings about the reactions that cannot be parsed -% (default false). +% print warnings about the reactions that cannot be balanced because of +% missing information or parsing errors (default false). % % Returns % ------- @@ -28,12 +31,13 @@ % % - balanceStatus : 1 if the reaction is balanced, 0 if it is % unbalanced, -1 if it could not be balanced due to missing -% information, -2 if it could not be balanced due to an error. -% Elemental only; charge is reported separately below +% information (including a reaction without metabolites), -2 if it +% could not be balanced because a formula could not be parsed. +% Elemental only; charge is reported in chargeStatus % - chargeStatus : 1 if the reaction is charge balanced, 0 if it is -% not, -1 if any participating metabolite has no charge (or the -% model has no metCharges field), in which case the charge balance -% is unknown rather than zero +% not, -1 if any participating metabolite has no charge, the reaction +% has no metabolites, or the model has no metCharges field; the +% charge balance is then unknown % - chargeResidual : the sum of charges over the reaction, NaN where % chargeStatus is -1 % - elements : struct with fields abbrevs (cell array with diff --git a/src/queries/getExchangeRxns.m b/src/queries/getExchangeRxns.m index aa86b753..b6c35575 100755 --- a/src/queries/getExchangeRxns.m +++ b/src/queries/getExchangeRxns.m @@ -2,7 +2,9 @@ % getExchangeRxns Retrieve the exchange reactions from a model. % % Exchange reactions are identified by having either no substrates or no -% products. +% products. In a model with metabolites marked in the unconstrained field, +% the exchange reactions are the reactions that involve such a metabolite +% (as substrate for 'in', as product for 'out'). % % Parameters % ---------- @@ -39,8 +41,9 @@ % exchangeRxnsIndexes : double % vector with the indexes of the exchange reactions. % exchangedMets : double -% vector with the metabolite index for each exchange reaction (one -% metabolite per exchange reaction). Only computed when requested. +% vector with the index of the first metabolite with a non-zero +% coefficient in each exchange reaction (0 if there is none). Only +% computed when requested. % % Notes % ----- diff --git a/src/queries/getGenesFromGrRules.m b/src/queries/getGenesFromGrRules.m index 5b561ec5..945addb0 100755 --- a/src/queries/getGenesFromGrRules.m +++ b/src/queries/getGenesFromGrRules.m @@ -5,25 +5,25 @@ % ---------- % grRules : cell % a cell array of model grRules, from which a list of genes is to be -% extracted. NOTE: Boolean operators can be text ("and", "or") or -% symbolic ("&", "|"), but there must be a space between operators and -% gene names/IDs. +% extracted. Boolean operators can be text ("and", "or") or symbolic +% ("&", "|"), with a space between operators and gene names or IDs. % % Name-Value Arguments % -------------------- % originalGenes : cell % the original gene list from the model. If supplied, the genes parsed -% from grRules must match it exactly (as a set), an error is thrown -% otherwise, and the returned genes list follows originalGenes' order. +% from grRules must match it as a set, otherwise an error is thrown, and +% the returned genes list follows the order of originalGenes. % % Returns % ------- % genes : cell -% a unique list of all gene IDs that appear in grRules. +% a unique, sorted list of all gene IDs that appear in grRules (in the +% order of originalGenes when supplied). % rxnGeneMat : double -% (optional) a binary matrix indicating which genes participate in each -% reaction, where rows correspond to reactions (entries in grRules) and -% columns correspond to genes. +% (optional) a sparse binary matrix indicating which genes participate in +% each reaction, where rows correspond to reactions (entries in grRules) +% and columns correspond to genes. Only computed when requested. % % Examples % -------- diff --git a/src/queries/getIndexes.m b/src/queries/getIndexes.m index 1c64efa5..61840547 100755 --- a/src/queries/getIndexes.m +++ b/src/queries/getIndexes.m @@ -7,15 +7,19 @@ % a model structure. % objects : cell or logical or double % either a cell array of IDs, a logical vector with the same number of -% elements as metabolites in the model, or a vector of indexes. +% elements as objects of the given type in the model, or a vector of +% indexes. An error is raised if an ID is not found in the model, except +% for type 'metnames', where a name that is not found gives an empty +% index vector. % type : char -% 'rxns', 'mets', or 'genes' depending on what to retrieve. 'metnames' -% queries metabolite names, while 'metcomps' allows providing specific +% 'rxns', 'mets' or 'genes', depending on what to retrieve. 'metnames' +% queries metabolite names and returns, for each name, the indexes of all +% metabolites with that name. 'metcomps' queries specific % metabolites and their compartments in the format metaboliteName[comp]. % An error is raised if such a name matches no metabolite, or more than % one. -% If a model.ec structure exists (GECKO 3), then also 'ecenzymes', -% 'ecrxns' and 'ecgenes' are allowed. +% If a model.ec structure exists (GECKO 3), 'ecenzymes', 'ecrxns' and +% 'ecgenes' are also allowed. % % Name-Value Arguments % -------------------- @@ -26,8 +30,8 @@ % Returns % ------- % indexes : logical or double -% can be a logical array or a double array depending on the value of -% returnLogical. +% a logical array if returnLogical is true, otherwise a double array. For +% 'metnames' with several names, a cell array of double vectors. % % Notes % ----- diff --git a/src/queries/getRxnsInComp.m b/src/queries/getRxnsInComp.m index ce980a90..a4ac4f63 100755 --- a/src/queries/getRxnsInComp.m +++ b/src/queries/getRxnsInComp.m @@ -6,20 +6,21 @@ % model : struct % a model structure. % comp : char -% string with the compartment id. +% the compartment id (case insensitive). An error is thrown if the id +% does not match exactly one compartment. % % Name-Value Arguments % -------------------- % includePartial : logical -% true if reactions with metabolites in several compartments (normally -% transport reactions) should be included (default false). +% true if reactions with metabolites in several compartments (for +% example transport reactions) are included (default false). % % Returns % ------- % I : double -% boolean vector of the reactions. +% indexes of the reactions in the compartment. % rxnNames : cell -% the names of the reactions. +% the names of these reactions. % % Examples % -------- diff --git a/src/queries/getTransportRxns.m b/src/queries/getTransportRxns.m index a1d989f7..e7678af0 100755 --- a/src/queries/getTransportRxns.m +++ b/src/queries/getTransportRxns.m @@ -19,7 +19,8 @@ % Notes % ----- % Transport reactions are defined as reactions involving (at least) one -% metabolite name in more than one compartment. +% metabolite name in more than one compartment. Exchange reactions, as +% identified by getExchangeRxns, are not transport reactions. transportRxns=false(numel(model.rxns),1); diff --git a/src/queries/modelSummary.m b/src/queries/modelSummary.m index a8fa03c9..0a26f6ed 100644 --- a/src/queries/modelSummary.m +++ b/src/queries/modelSummary.m @@ -1,9 +1,9 @@ function modelSummary(model, varargin) -% modelSummary Print a concise overview of a RAVEN model and its flux state. +% modelSummary Print an overview of a RAVEN model and its flux state. % -% Prints model dimensions, the objective function, and — when a flux vector -% is supplied — the objective value plus all net exchange fluxes partitioned -% into uptake and secretion. +% Prints the model dimensions and the objective function. When a flux vector +% is supplied, it also prints the objective value and the net exchange +% fluxes, partitioned into uptake and secretion. % % Parameters % ---------- @@ -13,12 +13,14 @@ function modelSummary(model, varargin) % Name-Value Arguments % -------------------- % fluxes : double -% flux vector (length = numel(model.rxns)). When provided, shows the -% objective value and the exchange-flux summary (default []). +% flux vector with numel(model.rxns) elements. When provided, the +% objective value and the exchange-flux summary are printed +% (default []). % cutoff : double % minimum absolute net exchange flux to include (default 1e-8). % nMax : double -% maximum exchange reactions displayed per direction (default 20). +% maximum number of exchange reactions displayed per direction, in +% descending order of absolute flux (default 20). % % Examples % -------- @@ -28,12 +30,12 @@ function modelSummary(model, varargin) % % See also % -------- -% printModelStats : the structural view instead. It breaks the counts down per -% compartment, adds unique EC codes, biochemically unique reactions and -% unique metabolite names, and optionally reports dead ends and elemental -% imbalances. It knows nothing about fluxes or the objective. -% printFluxes : the exchange fluxes on their own, with more control over -% formatting and filtering than the summary here offers. +% printModelStats : model statistics. It breaks the counts down per +% compartment, adds the number of unique EC codes, biochemically unique +% reactions and unique metabolite names, and optionally reports dead ends +% and elemental imbalances. It prints no fluxes or objective. +% printFluxes : prints the fluxes of reactions with options for filtering +% and for the format of the output. p = parseRAVENargs(varargin, {'fluxes',[]; 'cutoff',1e-8; 'nMax',20}); fluxes = p.fluxes; diff --git a/src/queries/parseFormulas.m b/src/queries/parseFormulas.m index c827e387..d394496a 100755 --- a/src/queries/parseFormulas.m +++ b/src/queries/parseFormulas.m @@ -9,15 +9,16 @@ % Name-Value Arguments % -------------------- % noPolymers : logical -% assume that all polymers consist of one element. Corresponds to -% counting everything between (...)n as n being equal to one. Only one -% set of parentheses is allowed. If this is false then polymers are -% returned as "Could not parse formula" (default false). +% treat polymers as a single unit: everything between (...)n is counted +% with n equal to one. Only one set of parentheses is allowed. If this is +% false, polymers get exitFlag -1, "Could not parse formula" +% (default false). % isInchi : logical % true if the formulas are in the InChI format (default false). % ignoreRX : logical -% ignore R-groups and bound protein. This can be useful since they are -% often used only as intermediates (default false). +% set the atomic weight of R-groups and bound protein to 0, which allows +% MW to be calculated for formulas that contain them. The atoms are still +% counted in useMat (default false). % % Returns % ------- @@ -26,6 +27,8 @@ % % - abbrevs : cell array with abbreviations for all used elements % - names : cell array with the names for all used elements +% +% Elements that occur in none of the formulas are not included. % useMat : double % MxN matrix with the number of atoms for each formula (M) and each % element (N). @@ -36,10 +39,11 @@ % - 0 : no formula found % - -1 : could not parse formula % MW : double -% predicted molecular weight (g/mol). This is only returned for -% formulas which can be successfully parsed, and its calculation -% doesn't affect the exitFlag variable. NaN is returned if the weight -% couldn't be calculated. +% predicted molecular weight (g/mol). This is only calculated when +% requested, and only for formulas which can be successfully parsed; +% its calculation does not affect the exitFlag variable. NaN is returned +% if the weight cannot be calculated, for example for formulas that +% contain R-groups or bound protein while ignoreRX is false. % % Examples % -------- diff --git a/src/queries/parseRxnEqu.m b/src/queries/parseRxnEqu.m index a1ecb934..219ce0ff 100755 --- a/src/queries/parseRxnEqu.m +++ b/src/queries/parseRxnEqu.m @@ -9,7 +9,7 @@ % Returns % ------- % metabolites : cell -% a cell array with the involved metabolites. +% a unique, sorted cell array with the involved metabolites. % % Examples % -------- @@ -17,12 +17,13 @@ % % Notes % ----- -% The equations should be written like: +% The equations are written like: % -% 1 A + 3 B (=> or <=>) 5C + 2 D +% 1 A + 3 B (=> or <=>) 5 C + 2 D % -% If the equation is expressed as for example '... + (n-1) starch' then -% '(n-1) starch' will be interpreted as one metabolite. +% with a space around each arrow and plus sign and between a coefficient and +% its metabolite name. If the equation is expressed as for example +% '... + (n-1) starch', '(n-1) starch' is interpreted as one metabolite. if ~iscell(equations) equations={equations}; diff --git a/src/queries/printFluxes.m b/src/queries/printFluxes.m index c592d499..fde3fb9a 100755 --- a/src/queries/printFluxes.m +++ b/src/queries/printFluxes.m @@ -6,7 +6,8 @@ function printFluxes(model, fluxes, varargin) % model : struct % a model structure. % fluxes : double -% a vector with fluxes. +% a vector with fluxes, or a matrix with one row per reaction and one +% column per flux distribution. % % Name-Value Arguments % -------------------- @@ -22,8 +23,8 @@ function printFluxes(model, fluxes, varargin) % a string that specifies the output of each reaction (default % "%rxnID\t(%rxnName):\t%flux\n"). % metaboliteList : cell -% cell array of metabolite names. Only reactions involving any of these -% metabolites will be printed. +% cell array of metabolite names (case insensitive). Only reactions +% involving any of these metabolites are printed. % % Notes % ----- @@ -36,12 +37,12 @@ function printFluxes(model, fluxes, varargin) % - %obj : objective coefficient % - %eqn : equation % - %flux : flux -% - %element : equation using the metabolite formulas rather than metabolite +% - %element : equation using the metabolite formulas in place of metabolite % names % - %unbalanced : "(*)" if the reaction is unbalanced and "(-)" if it could % not be parsed -% - %lumped : equation where the elemental compositions for the left/right -% hand sides are lumped +% - %lumped : equation where the elemental compositions of the left-hand and +% right-hand sides are lumped % % Examples % -------- diff --git a/src/queries/printModelStats.m b/src/queries/printModelStats.m index c0c8f4ed..595edf9c 100755 --- a/src/queries/printModelStats.m +++ b/src/queries/printModelStats.m @@ -1,5 +1,10 @@ function printModelStats(model, varargin) -% printModelStats Print some statistics about a model to the screen. +% printModelStats Print statistics about a model to the screen. +% +% Prints the numbers of genes, reactions and metabolites in total and per +% compartment, the number of unique EC codes (if the eccodes field exists), +% the number of biochemically unique reactions and the number of unique +% metabolite names. % % Parameters % ---------- @@ -9,11 +14,12 @@ function printModelStats(model, varargin) % Name-Value Arguments % -------------------- % printModelIssues : logical -% true if information about unconnected reactions/metabolites and -% elemental balancing should be printed (default false). +% true if information about unused metabolites, empty reactions, +% dead-end reactions and metabolites, and elemental balancing is printed +% (default false). % printDetails : logical -% true if detailed information should be printed about model issues. -% Only used if printModelIssues is true (default true). +% true if the individual metabolites and reactions of each issue are +% listed. Used only if printModelIssues is true (default true). % % Examples % -------- @@ -21,9 +27,9 @@ function printModelStats(model, varargin) % % See also % -------- -% modelSummary : the shorter view, without the per-compartment breakdown, but -% adding the objective function and - given a flux vector - the objective -% value and the net uptake and secretion. +% modelSummary : prints the model dimensions without the per-compartment +% breakdown, the objective function and, given a flux vector, the +% objective value and the net uptake and secretion. p=parseRAVENargs(varargin, {'printModelIssues',false; 'printDetails',true}); printModelIssues=p.printModelIssues; diff --git a/src/reconstruction/guessComposition.m b/src/reconstruction/guessComposition.m index a2ccb2c6..2eeaf741 100755 --- a/src/reconstruction/guessComposition.m +++ b/src/reconstruction/guessComposition.m @@ -1,8 +1,8 @@ function [model, guessedFor, couldNotGuess]=guessComposition(model, varargin) % guessComposition Guess the composition of metabolites without one. % -% Attempts to guess the composition of metabolites without information about -% elemental composition. +% Derives the elemental composition of metabolites without a formula from the +% reactions they participate in, and writes it to the metFormulas field. % % Parameters % ---------- @@ -12,17 +12,18 @@ % Name-Value Arguments % -------------------- % printResults : logical -% true if the output should be printed (default true). +% true if the predicted compositions are printed (default true). % % Returns % ------- % model : struct -% a model structure with information about elemental composition added. -% guessedFor : double -% indexes for the metabolites for which a composition could be guessed. -% couldNotGuess : double -% indexes for the metabolites for which no composition could be -% assigned. +% a model structure with the predicted formulas added to metFormulas, for +% all metabolites that share the metabolite name. +% guessedFor : cell +% metabolite names for which a composition could be guessed. +% couldNotGuess : cell +% metabolite names, among those without a formula in the input model, +% for which no composition could be assigned. % % Examples % -------- @@ -33,12 +34,13 @@ % A metabolite's composition can only be resolved from a reaction in which % every other participating metabolite already has a known composition; % the function repeats this until no further metabolites can be resolved. -% It requires that the existing compositions and reactions are correct, -% and throws an error if a metabolite would need inconsistent -% compositions in different reactions -- so it can also be used to check -% a model for such errors. Note that successful completion does not -% guarantee correctness if the pre-existing compositions were themselves -% wrong. +% It requires that the existing compositions and reactions are correct. If a +% metabolite would need different compositions in different reactions, or a +% reaction gives both positive and negative atom counts, the function issues +% a warning and assigns no composition to that metabolite, so the warnings +% can also be used to check a model for such inconsistencies. Completion +% without warnings does not show that the pre-existing compositions are +% correct. p=parseRAVENargs(varargin, {'printResults',true}); printResults=p.printResults; diff --git a/src/reconstruction/homology/getBlast.m b/src/reconstruction/homology/getBlast.m index 4701b5fd..f87daa2d 100755 --- a/src/reconstruction/homology/getBlast.m +++ b/src/reconstruction/homology/getBlast.m @@ -19,25 +19,28 @@ % Name-Value Arguments % -------------------- % developMode : logical -% true if blastReport should be generated that is used in the unit -% testing function for BLAST+ (default false). +% true if blastReport is filled, as used in the unit test for BLAST+ +% (default false). % hideVerbose : logical % true if no status messages should be printed (default false). % % Returns % ------- % blastStructure : struct -% structure containing the bidirectional homology measurements that -% can be used by getModelFromHomology. +% structure array with one element per search direction and template +% organism (2 times the number of modelIDs), containing the bidirectional +% homology measurements (fromId, toId, fromGenes, toGenes, evalue, +% identity, aligLen, bitscore and ppos) for use by getModelFromHomology. % blastReport : struct % structure containing MD5 hashes for FASTA database files and -% non-parsed BLAST output data. Will be blank if developMode is false. +% unparsed BLAST output data. The fields are empty if developMode is +% false. % % Notes % ----- % This function calls BLAST+ to perform a bidirectional homology test % between the organism of interest and a set of other organisms using -% standard settings. The only filtering this function does is the removal +% standard settings. The only filtering this function applies is the removal % of hits with an E-value higher than 10e-5. % % Examples diff --git a/src/reconstruction/homology/getDiamond.m b/src/reconstruction/homology/getDiamond.m index 7dedb5bd..09fca52b 100755 --- a/src/reconstruction/homology/getDiamond.m +++ b/src/reconstruction/homology/getDiamond.m @@ -19,28 +19,30 @@ % Name-Value Arguments % -------------------- % developMode : logical -% true if blastReport should be generated that is used in the unit -% testing function for DIAMOND (default false). +% true if diamondReport is filled, as used in the unit test for DIAMOND +% (default false). % hideVerbose : logical % true if no status messages should be printed (default false). % % Returns % ------- % blastStructure : struct -% structure containing the bidirectional homology measurements which -% are used by getModelFromHomology. +% structure array with one element per search direction and template +% organism (2 times the number of modelIDs), containing the bidirectional +% homology measurements (fromId, toId, fromGenes, toGenes, evalue, +% identity, aligLen, bitscore and ppos) for use by getModelFromHomology. % diamondReport : struct % structure containing MD5 hashes for FASTA database files and -% non-parsed BLAST output data. Will be blank if developMode is false. +% unparsed DIAMOND output data. The fields are empty if developMode is +% false. % % Notes % ----- % This function calls DIAMOND to perform a bidirectional homology search % between the organism of interest and a set of other organisms using the -% "--more-sensitive" setting from DIAMOND. For the most sensitive results, -% the use of getBlast() is adviced, however, getDiamond() is a fast -% alternative (>15x faster). The blastStructure generated is in the same -% format as those obtained from getBlast(). +% "--more-sensitive" setting from DIAMOND. getBlast() gives the most +% sensitive results; getDiamond() is >15x faster. The blastStructure is in +% the same format as that obtained from getBlast(). % % Examples % -------- diff --git a/src/reconstruction/homology/getModelFromHomology.m b/src/reconstruction/homology/getModelFromHomology.m index 13dfb33d..41101910 100755 --- a/src/reconstruction/homology/getModelFromHomology.m +++ b/src/reconstruction/homology/getModelFromHomology.m @@ -21,28 +21,30 @@ % Name-Value Arguments % -------------------- % preferredOrder : cell -% the order in which reactions should be added from the models. If not -% supplied, reactions will be included from all models, otherwise one -% gene will only result in reactions from one model (default {}). +% the order in which reactions are added from the models, as a cell array +% of model IDs. If not supplied, reactions are included from all models, +% otherwise one gene results in reactions from one model only +% (default {}). % strictness : double -% integer that specifies which reactions should be included (default 1): +% integer that specifies which reactions are included (default 1): % -% - 1 : map new genes to old for all pairs which have acceptable BLASTP -% results in both directions. -% - 2 : map new genes to old for all pairs which have acceptable BLASTP -% results in the correspondent direction (mapping can be done in the -% opposite direction, see mapNewGenesToOld below). -% - 3 : check all BLASTP results and retain only the best hit (by -% bitscore) for each gene pair in each direction separately, then map -% new genes to old for all pairs which have acceptable BLASTP results -% in both directions. +% - 1 : map new genes to old for all pairs which have BLASTP results +% that pass the filters (maxE, minLen, minIde) in both directions. +% - 2 : map new genes to old for all pairs which have BLASTP results +% that pass the filters in one direction, selected by mapNewGenesToOld. +% - 3 : apply the filters (maxE, minLen, minIde), retain only the best hit +% (highest bitscore, ties included) among the hits that passed them for +% each query gene in each direction separately, then map new genes to +% old for all pairs which have BLASTP results in both directions. % onlyGenesInModels : logical % consider BLASTP results only for genes that exist in the models. This % tends to import a larger fraction from the existing models but may -% give less reliable results. Has effect only if strictness=3 +% give less reliable results. Changes the result only if strictness=3 % (default false). % maxE : double -% only look at genes with E-values <= this value (default 10^-30). +% only look at genes with E-values < this value (default 10^-30). In a +% benchmark against KEGG and OMA orthology, with the minIde filter in +% place, values between 10^-4 and 10^-50 gave the same result. % minLen : double % only look at genes with alignment length >= this value (default 100). % Measured against KEGG and OMA orthology across four organisms: any @@ -50,37 +52,37 @@ % real orthologs without reducing the number of wrong ones. % minIde : double % only look at genes with identity >= this value (default 40 (%)). -% The filter that decides the outcome, and 40 is the measured optimum -% once a wrongly transferred reaction is treated as worse than a missing -% one. maxE, by contrast, changes nothing between 10^-4 and 10^-50. +% This filter determines the result. In the calibration, with a wrongly +% transferred reaction counted as worse than a missing one, 40 lies in the +% 35 to 45 range that the KEGG and OMA references favour. % mapNewGenesToOld : logical -% determines how to match genes if not looking at only 1-1 orthologs. -% Either map the new genes to the old or old genes to new. The default -% is to map the new genes (default true). +% determines how to match genes when strictness is 2: true maps the new +% genes to the old, false maps the old genes to the new (default true). % complexPolicy : char -% what happens to an "and"-linked subunit without an ortholog: -% "flag" keeps the reaction with the subunit renamed -% OLD_MODELID_geneName, for a curator to review; "keep" drops just that -% subunit; "drop" drops the whole reaction (default "flag"). +% what happens to an "and"-linked subunit without an ortholog (case +% insensitive): "flag" keeps the reaction with the subunit renamed +% OLD_MODELID_geneName, for review; "keep" drops that subunit; +% "drop" drops the whole reaction (default "flag"). % keepGeneFree : logical % also transfer the template reactions that have no grRule (spontaneous, % transport, exchange, artificial). By default they are dropped, because -% nothing supports them in the new organism. Set it when the template is -% a curated reference whose gene-free reactions belong in every derived -% model. With several templates, a gene-free reaction comes in once per -% template (default false). +% no gene supports them in the new organism. When the template is a +% curated reference whose gene-free reactions belong in every derived +% model, true transfers them. With several templates, a gene-free +% reaction comes in once per template (default false). % preserveNotes : logical -% keep each transferred reaction's rxnNotes, rxnReferences and -% rxnConfidenceScores from the template. By default the notes say the -% reaction was included by getModelFromHomology and the confidence score -% is 2 (default false). +% keep rxnNotes and rxnConfidenceScores of each transferred reaction from +% the template. By default the notes say the reaction was included by +% getModelFromHomology and the confidence score is 2. The rxnReferences of +% the template reactions are always kept (default false). % % Returns % ------- % draftModel : struct % a model structure for the new organism. % hitGenes : struct -% collect the old and new genes. +% structure with the fields oldGenes (the template genes that were +% mapped) and newGenes (the corresponding genes of the new organism). % % Examples % -------- @@ -88,10 +90,10 @@ % % Notes % ----- -% The models in the "models" structure should have named the metabolites in -% the same manner, have their reversible reactions in the same direction -% (run sortModel), and use the same compartment names. To avoid keeping -% unneccesary old genes, the models should not have "or"-relations in their +% The models in the "models" structure should name the metabolites in the +% same manner, have their reversible reactions in the same direction (run +% sortModel), and use the same compartment names. To avoid keeping +% unnecessary old genes, the models should not have "or"-relations in their % grRules (use expandModel). % % The gene ids of the new organism in blastStructure become its gene ids in @@ -102,12 +104,14 @@ % The resulting draft model contains only reactions associated with % orthologous genes, plus the gene-free ones with keepGeneFree. With % complexPolicy "flag", the old (original) genes involved in "and" relations -% in grRules without any orthologs are still included in the draft model as +% in grRules without any orthologs are included in the draft model as % OLD_MODELID_geneName. A gene that several template genes map to appears % once in a grRule. The draft does not keep the version, date or % descriptive annotation of the templates. % -% "to" and "from" means relative to the new organism. +% "to" and "from" are relative to the new organism: "to" is a BLAST search from +% a template organism to the new organism, "from" is a search from the new +% organism to a template organism. % % The hits can also come from SuppOrth consensus orthologs (hitsFromSuppOrth, % runSuppOrth). Their e-value, identity and alignment length are diff --git a/src/reconstruction/homology/hitsFromSuppOrth.m b/src/reconstruction/homology/hitsFromSuppOrth.m index bc414d49..37fb1e2f 100644 --- a/src/reconstruction/homology/hitsFromSuppOrth.m +++ b/src/reconstruction/homology/hitsFromSuppOrth.m @@ -16,7 +16,7 @@ % organismID : char % SuppOrth species label of the organism to build a model for: the % FASTA file name without extension, or the --labels value. It becomes -% fromId/toId unless speciesToModel maps it to another id. +% fromId and toId unless speciesToModel maps it to another id. % % Name-Value Arguments % -------------------- diff --git a/src/reconstruction/homology/makeFakeBlastStructure.m b/src/reconstruction/homology/makeFakeBlastStructure.m index 734c70c5..78bdd77b 100755 --- a/src/reconstruction/homology/makeFakeBlastStructure.m +++ b/src/reconstruction/homology/makeFakeBlastStructure.m @@ -1,12 +1,11 @@ function blastStructure=makeFakeBlastStructure(orthologList,sourceModelID,getModelFor) % makeFakeBlastStructure Make a fake blastStructure from an ortholog list. % -% This is a structure that would normally be generated by getBlast. It -% allows to feed a predefined list of orthologs to getModelFromHomology -% while retaining the further use of that function. For this function to -% work, it is crucial that the orthologList is a cell array where the first -% column contains the genes from the source organism, and the second column -% contains the genes from the target organism. +% The structure has the format that getBlast generates. It feeds a +% predefined list of orthologs to getModelFromHomology. The orthologList +% is a cell array with at least 10 rows and two columns: the first column +% contains the genes from the source organism and the second column the +% genes from the target organism. % % Parameters % ---------- @@ -24,9 +23,10 @@ % Returns % ------- % blastStructure : struct -% a fake blastStructure, where the evalue, identity and aligLen are -% set at extreme values, such that all orthologous pairs will pass the -% filter when running getModelFromHomology. +% a fake blastStructure with one element per direction. For every pair, +% evalue is 0, identity is 100, aligLen is 1000, bitscore is 100 and +% ppos is 100. All orthologous pairs pass the filters of +% getModelFromHomology. % % Examples % -------- diff --git a/src/reconstruction/kegg/buildGlobalGPR.m b/src/reconstruction/kegg/buildGlobalGPR.m index 9c88b6c6..d27a128b 100644 --- a/src/reconstruction/kegg/buildGlobalGPR.m +++ b/src/reconstruction/kegg/buildGlobalGPR.m @@ -1,11 +1,11 @@ function [genes,rxnGeneMat]=buildGlobalGPR(rxns,koReaction,organismGeneKO) -% buildGlobalGPR Join every KEGG organism's genes onto the reference -% reactions through their shared KO (KEGG Orthology) ids. +% buildGlobalGPR Join the genes of all KEGG organisms to reference reactions by KO id. % -% Builds two sparse gene-KO / KO-reaction incidence matrices sharing one -% KO axis, and multiplies them, rather than expanding the join row by -% row --- organismGeneKO can carry millions of rows (every gene of every -% KEGG organism), so a per-row loop is not viable. +% Joins every KEGG organism's genes onto the reference reactions through their +% shared KO (KEGG Orthology) ids. The function builds two sparse incidence +% matrices (gene to KO and KO to reaction) that share one KO axis, and +% multiplies them. The join is not expanded row by row, because organismGeneKO +% can contain millions of rows (every gene of every KEGG organism). % % Parameters % ---------- @@ -22,7 +22,7 @@ % genes : cell array % sorted unique 'organism:gene' identifiers. % rxnGeneMat : sparse double -% numel(rxns) x numel(genes); rxnGeneMat(i,j)=1 when gene j shares at +% numel(rxns) by numel(genes); rxnGeneMat(i,j)=1 when gene j shares at % least one KO with reaction i. %Map each ko_reaction row onto the reference model's own reaction order; diff --git a/src/reconstruction/kegg/buildGlobalKEGGModel.m b/src/reconstruction/kegg/buildGlobalKEGGModel.m index f31b248a..b5417ec0 100644 --- a/src/reconstruction/kegg/buildGlobalKEGGModel.m +++ b/src/reconstruction/kegg/buildGlobalKEGGModel.m @@ -2,10 +2,10 @@ % buildGlobalKEGGModel Download and assemble the global KEGG model. % % Fetches the toolbox-assets _core.tar.gz bundle (the -% gene-free reference model plus the ko_reaction / organism_gene_ko / -% rxn_flags relational tables), then joins every organism's -% genes onto the reference reactions through their shared KO ids. The -% KEGG version is read from keggDataVersion, not hardcoded here. +% gene-free reference model plus the ko_reaction, organism_gene_ko and +% rxn_flags relational tables), then joins every organism's genes onto +% the reference reactions through their shared KO ids. The KEGG version +% is read from keggDataVersion. % % Parameters % ---------- @@ -15,9 +15,9 @@ % Returns % ------- % model : struct -% the full global KEGG model (all reactions/metabolites, genes and -% rxnGeneMat spanning every KEGG organism). Callers narrow this down -% to one organism (see getKEGGModelForOrganism). +% the full global KEGG model (all reactions and metabolites, genes and +% rxnGeneMat spanning every KEGG organism). getKEGGModelForOrganism +% narrows it to one organism. % KOModel : struct % a minimal model struct whose rxns are the KO ids linked to at % least one reaction (getKEGGModelForOrganism's HMM-search path uses diff --git a/src/reconstruction/kegg/getKEGGModelForOrganism.m b/src/reconstruction/kegg/getKEGGModelForOrganism.m index 18330504..319e7a72 100755 --- a/src/reconstruction/kegg/getKEGGModelForOrganism.m +++ b/src/reconstruction/kegg/getKEGGModelForOrganism.m @@ -3,84 +3,83 @@ % % Reconstructs a genome-scale metabolic model based on protein homology to % the orthologies in KEGG. If the target species is not available in KEGG, -% the user must select a closely related species. It is also possible to -% circumvent protein homology search (see fastaFile parameter for more -% details). +% the function stops with an error; give a closely related species as +% organismID. Without a fastaFile, the +% protein homology search is skipped (see the fastaFile parameter). % % Parameters % ---------- % organismID : char % three or four letter abbreviation of the organism (as used in KEGG). -% If not available, use a closely related species. This is used for -% determing the phylogenetic distance. Use "eukaryotes" or "prokaryotes" -% to get a model for the whole domain. Only applicable if fastaFile is -% empty, i.e. no homology search should be performed. +% If the organism is not available, a closely related species can be +% given. The abbreviation determines the genes that are kept when +% fastaFile is empty. "eukaryotes" or "prokaryotes" gives a model for +% the whole domain. Only applicable if fastaFile is empty, that is when +% no homology search is performed. % % Name-Value Arguments % -------------------- % fastaFile : char % a FASTA file that contains the protein sequences of the organism for % which to reconstruct a model. If no FASTA file is supplied then a -% model is reconstructed based only on the organism abbreviation. This -% option ignores all settings except for keepSpontaneous, -% keepUndefinedStoich, keepIncomplete and keepGeneral. +% model is reconstructed based only on the organism abbreviation. In this +% case, cutOff, minScoreRatioKO, minScoreRatioG and dataDir are not +% used (default []). % dataDir : char -% directory for which to retrieve the input data, styled as -% kegg118_prokaryotes or kegg118_eukaryotes, indicating the KEGG version -% and whether the HMMs were trained on pro- or eukaryotic sequences. The -% directory name matches the published HMM library it is paired with. The -% prebuilt concatenated KO HMM library (dataDir.hmm) is downloaded here -% from the corresponding toolbox-assets release if not already present. This -% parameter should ALWAYS be provided. +% directory of the HMM library, named kegg118_prokaryotes or +% kegg118_eukaryotes. The name gives the KEGG version and whether the +% HMMs were trained on pro- or eukaryotic sequences, and matches the +% published HMM library it is paired with. The prebuilt concatenated KO +% HMM library (dataDir.hmm) is downloaded from the corresponding +% toolbox-assets release if it is not already present. Required when +% fastaFile is supplied (default []). % outDir : char -% directory to save the results from the quering of the hidden Markov -% models. The output is specific for the input sequences and the -% settings used. It is stored in this manner so that the function can -% continue if interrupted or if it should run in parallel. Be careful -% not to leave output files from different organisms or runs with -% different settings in the same folder. They will not be overwritten -% (default is a temporary dir where all *.out files are deleted before -% and after doing the reconstruction). +% directory that is created if it does not exist when fastaFile is +% supplied. The function writes no result files to it (default is the +% temporary directory, where all *.out files are deleted before the +% reconstruction). % keepSpontaneous : logical -% include reactions labeled as "spontaneous" (default true). +% include reactions labelled as "spontaneous" (default true). % keepUndefinedStoich : logical -% include reactions in the form n A <=> n+1 A. These will be dealt with -% as two separate metabolites (default true). +% include reactions in the form n A <=> n+1 A. These are treated as +% two separate metabolites (default true). % keepIncomplete : logical % include reactions which have been labelled as "incomplete", % "erroneous" or "unclear" (default true). % keepGeneral : logical % include reactions which have been labelled as "general reaction". -% These are reactions on the form "an aldehyde <=> an alcohol", and are -% therefore unsuited for modelling purposes. Note that not all reactions -% have this type of annotation, and the script will therefore not be -% able to remove all such reactions (default false). +% These are reactions of the form "an aldehyde <=> an alcohol". Not all +% such reactions have this label, so the function cannot remove all of +% them (default false). % cutOff : double -% significance score from HMMer needed to assign genes to a KO (default -% 10^-30). +% maximum full-sequence E-value from HMMER for assigning a gene to a KO +% (default 10^-30). % minScoreRatioKO : double -% ignore genes in a KO if their score is =log(score)/log(best score) for that gene. This is to prevent that a -% gene which clearly belongs to one KO is assigned also to KOs with much -% lower scores (default 0.9, lower is less strict). +% a gene is only assigned to KOs for which log(score)/log(best score) +% is at least this value, where best score is the lowest E-value of +% that gene over its KOs after the minScoreRatioKO step. This removes the +% assignment of a gene to KOs with a much higher E-value than its best KO +% (default 0.9, lower is less strict). % maxPhylDist : double -% -1 to only use sequences from the same domain (Prokaryota, Eukaryota); -% any other (positive) value to only use sequences for organisms where -% the phylogenetic distance is at the most this large (as calculated in -% getPhylDist). Only used when reconstructing from KEGG annotations (no -% fastaFile) (default Inf, which means that all sequences will be used). +% only used when fastaFile is empty and organismID is "eukaryotes" or +% "prokaryotes". -1 keeps genes from organisms in the same domain +% (Prokaryota, Eukaryota) as H. sapiens ("hsa") or E. coli ("eco"), +% respectively. Any other value, including Inf, does not restrict the +% domain, and the value is not compared with the distances calculated +% in getPhylDist (default Inf). % globalModel : struct % structure containing both model and KOModel structures as generated -% by getModelFromKEGG. These will otherwise be loaded by via -% getModelFromKEGG. Providing globalKEGGmodel can speed up model -% generation if getKEGGModelForOrganism is run multiple times for +% by getModelFromKEGG. When empty, the global model is loaded with +% getModelFromKEGG. Providing globalModel avoids reloading it when +% getKEGGModelForOrganism is run several times, for example for % different strains. Example: % [globalModel.model,globalModel.KOModel] = getModelFromKEGG (default -% empty, global model is loaded by getModelFromKEGG). +% []). % % Returns % ------- @@ -93,19 +92,18 @@ % % 1. From KEGG annotations (no fastaFile supplied). The reactions % associated with organismID in the local KEGG database are kept; -% maxPhylDist controls which organism annotations are considered. +% maxPhylDist applies to organismID "eukaryotes" and "prokaryotes". % 2. From protein homology (fastaFile supplied). The query proteome is % searched, in a single hmmsearch, against a prebuilt KEGG-version- and -% domain-specific concatenated KO HMM library (e.g. kegg118_eukaryotes), -% downloaded from the corresponding RAVEN release if not already present -% in dataDir. Hits are filtered by cutOff and the minScoreRatioKO / -% minScoreRatioG ratios into a KO-gene matrix, from which the model is -% built (spontaneous reactions without genes are kept if keepSpontaneous -% is true). +% domain-specific concatenated KO HMM library (for example +% kegg118_eukaryotes), downloaded from the corresponding toolbox-assets +% release if not already present in dataDir. Hits are filtered by cutOff, +% minScoreRatioKO and minScoreRatioG into a KO-gene matrix, from which the +% model is built (spontaneous reactions without genes are kept if +% keepSpontaneous is true). % % The global KEGG model (reaction-KO relationships) is either supplied via -% globalModel, built from a local KEGG FTP dump in dataDir\keggdb (which -% requires a KEGG FTP license), or loaded from the internal RAVEN version. +% globalModel or loaded with getModelFromKEGG. % % Examples % -------- diff --git a/src/reconstruction/kegg/getModelFromKEGG.m b/src/reconstruction/kegg/getModelFromKEGG.m index f619df61..1b853173 100755 --- a/src/reconstruction/kegg/getModelFromKEGG.m +++ b/src/reconstruction/kegg/getModelFromKEGG.m @@ -1,19 +1,18 @@ function [model,KOModel]=getModelFromKEGG(varargin) % getModelFromKEGG Load the global KEGG model. % -% Loads the global KEGG reaction/gene model from keggModel.mat. On first -% use --- when no keggModel.mat is present yet --- the underlying -% artefacts (the gene-free reference model plus the KO/reaction/organism- -% gene relational tables, published as a toolbox-assets release) are -% downloaded and assembled instead, and the result is cached to -% keggModel.mat so later calls load instantly. The first build can take -% a while (the organism-gene table covers every KEGG organism) and needs -% a few hundred MB of disk. +% Loads the global KEGG reaction and gene model from keggModel.mat. When +% keggModel.mat is not present, the underlying artefacts (the gene-free +% reference model plus the KO, reaction and organism-gene relational +% tables, published as a toolbox-assets release) are downloaded and +% assembled, and the result is saved to keggModel.mat for later calls. The +% first build takes longer because the organism-gene table covers every +% KEGG organism, and needs a few hundred MB of disk. % % Name-Value Arguments % -------------------- % keepSpontaneous : logical -% include reactions labeled as "spontaneous" (default true). +% include reactions labelled as "spontaneous" (default true). % keepUndefinedStoich : logical % include reactions in the form n A <=> n+1 A (default true). % keepIncomplete : logical @@ -32,9 +31,9 @@ % % Notes % ----- -% The model output can be used as a template for fillGaps. In that case, -% remove the genes and rxnGeneMat fields before parsing: -% model = rmfield(model, 'genes'), etc. +% The model output can be used as a template for fillGaps after the genes +% and rxnGeneMat fields are removed: +% model = rmfield(model, 'genes') and model = rmfield(model, 'rxnGeneMat'). % % See also % -------- diff --git a/src/reconstruction/kegg/getPhylDist.m b/src/reconstruction/kegg/getPhylDist.m index 57922753..f9dc8125 100755 --- a/src/reconstruction/kegg/getPhylDist.m +++ b/src/reconstruction/kegg/getPhylDist.m @@ -2,7 +2,7 @@ % getPhylDist Load the pre-built KEGG phylogenetic distance matrix. % % Loads the pre-built phylogenetic distance matrix from keggPhylDist.mat. -% The matrix ships with RAVEN; it is derived from the KEGG taxonomy, which +% The matrix ships with RAVEN and is derived from the KEGG taxonomy, which % is a toolbox-assets release asset. % % Name-Value Arguments diff --git a/src/reconstruction/kegg/keggDataVersion.m b/src/reconstruction/kegg/keggDataVersion.m index 36477332..c8ee5493 100644 --- a/src/reconstruction/kegg/keggDataVersion.m +++ b/src/reconstruction/kegg/keggDataVersion.m @@ -1,16 +1,15 @@ function version=keggDataVersion() % keggDataVersion The toolbox-assets release tag for KEGG artefacts. % -% Reads reconstruction/kegg/KEGG_VERSION.md --- the single place this -% version is recorded --- so every function that downloads a KEGG -% artefact (getModelFromKEGG, getKEGGModelForOrganism) stays in sync -% when toolbox-assets publishes a new KEGG release. The version is the -% file's first line. +% Reads the first line of src/reconstruction/kegg/KEGG_VERSION.md. The file is +% the single place where the version is recorded, so that every function that +% downloads a KEGG artefact (getModelFromKEGG, getKEGGModelForOrganism) uses +% the same release. % % Returns % ------- % version : char -% the toolbox-assets release tag, e.g. 'kegg118'. +% the toolbox-assets release tag, for example 'kegg118'. ravenPath=findRAVENroot(); versionFile=fullfile(ravenPath,'src','reconstruction','kegg','KEGG_VERSION.md'); diff --git a/src/reconstruction/kegg/readKEGGTable.m b/src/reconstruction/kegg/readKEGGTable.m index 7fce0690..76ebe6e7 100644 --- a/src/reconstruction/kegg/readKEGGTable.m +++ b/src/reconstruction/kegg/readKEGGTable.m @@ -1,13 +1,13 @@ function tbl=readKEGGTable(gzFile) -% readKEGGTable Read a gzipped KEGG relational table into a table with -% every column forced to char cellstr (matching RAVEN's own -% cell-array-of-char convention, regardless of the caller's default -% text-import type). +% readKEGGTable Read a gzipped KEGG relational table into a table of cellstr columns. +% +% Every column is a cellstr (RAVEN's cell-array-of-char convention), whatever the +% default text-import type of the caller. % % Parameters % ---------- % gzFile : char -% path to the gzipped TSV file, e.g. '.../kegg118_ko_reaction.tsv.gz'. +% path to the gzipped TSV file, for example '.../kegg118_ko_reaction.tsv.gz'. % % Returns % ------- diff --git a/src/solver/getMinNrFluxes.m b/src/solver/getMinNrFluxes.m index 655e7287..3c7a6213 100755 --- a/src/solver/getMinNrFluxes.m +++ b/src/solver/getMinNrFluxes.m @@ -12,16 +12,19 @@ % Name-Value Arguments % -------------------- % toMinimize : cell or logical or double -% either a cell array of reaction IDs, a logical vector with the same -% number of elements as reactions in the model, or a vector of indexes -% for the reactions that should be minimized (default model.rxns). +% a cell array of reaction IDs, a logical vector with the same number +% of elements as reactions in the model, or a vector of indexes for the +% reactions that should be minimised (default model.rxns). % params : struct -% *obsolete option*. +% solver parameters passed to optimizeProb. With formulation +% 'reversible', intTol (10^-9), TimeLimit (300) and Seed (26) are set +% when absent (default empty struct). % scores : double -% vector of weights for the reactions. Negative scores should not have -% flux. Positive scores are not possible in this implementation, and -% they are changed to max(scores(scores<0)), or to 0 if scores has no -% negative entry at all. Must have the same dimension as toMinimize +% vector of weights for the reactions. A reaction with a negative +% score adds the negated score to the minimised objective when it +% carries flux. Scores of 0 or above are not supported and are +% changed to max(scores(scores<0)), or to 0 if scores has no negative +% entry at all. Must have the same dimension as toMinimize % (find(toMinimize) if it is a logical vector) (default -1 for all % reactions). % formulation : char @@ -29,24 +32,26 @@ % % - 'irrev' : convert the model to irreversible format and use one % binary per irreversible reaction, so a reversible reaction that is -% minimized costs two binaries. The magnitude of the fluxes is +% minimised needs two binaries. The magnitude of the fluxes is % estimated with an LP first, which also provides a start solution. % - 'reversible' : keep the model reversible and use one binary per -% reaction, at the cost of a larger constraint matrix. Fewer binaries -% makes this the faster formulation on large problems, but it needs a -% solver that handles MILPs well and no LP estimate of the flux -% magnitudes is made, so an arbitrary large flux is assumed. +% reaction, with a larger constraint matrix. The formulation has +% fewer binaries but needs a solver that handles MILPs well. No LP +% estimate of the flux magnitudes is made, and a fixed flux of 1000 is +% assumed. % verbose : logical % if true, the MILP progression will be shown (default false). % resolveTies : logical -% if true, pin the MILP's degenerate optimum to a canonical answer instead of -% relying on Seed alone: hold the primary (scores) objective at its optimum, -% then minimize the count of "on" reactions, then, among the sparsest, minimize -% their summed reaction-id rank (lowest ids preferred). Two extra MILP solves; -% best-effort, so a phase that does not converge within TimeLimit is dropped -% with a warning rather than changing the result. Only implemented for -% formulation='reversible' -- errors if combined with 'irrev'. Matches -% raven-toolbox's resolve_ties (raven-gecko-parity#104) (default false). +% if true, select one optimum among the degenerate optima of the MILP +% instead of relying on Seed: the primary (scores) objective is held +% at its optimum, the count of "on" reactions is minimised, and among +% the sparsest solutions the summed reaction-id rank is minimised +% (lowest ids preferred). This takes two extra MILP solves. A phase +% that is not proven optimal within TimeLimit gives a warning, and the +% solution of the last phase is used when it is feasible. Only +% implemented for formulation='reversible'; combined with 'irrev' it +% gives an error. Matches resolve_ties in raven-toolbox +% (raven-gecko-parity#104) (default false). % % Returns % ------- @@ -62,8 +67,8 @@ % - -1 : no solution found, either because the problem is infeasible or % because the solver reached its time limit before finding one % - -2 : a solution was found but is not proven optimal, because the -% solver stopped before reaching optimality. It is returned, but it -% may use more fluxes than the minimum +% solver stopped before reaching optimality. It is returned and may +% use more fluxes than the minimum % % Examples % -------- @@ -71,8 +76,11 @@ % % Notes % ----- -% Uses 1000 mmol/gDW/h as an arbitary large flux. Could possibly cause -% problems if the fluxes in the model are larger than that. +% The binary of a reaction is linked to its flux through a large flux +% value. With formulation 'irrev' the value is the larger of 1000 and 5 +% times the largest flux of an LP solution; with 'reversible' it is 1000 +% (mmol/gDW/h). A flux above this value is cut off by the binary constraint +% of the reaction, so the MILP can become infeasible. p=parseRAVENargs(varargin, {'toMinimize',[]; 'params',[]; 'scores',[]; ... 'formulation','irrev'; 'verbose',false; 'resolveTies',false}); diff --git a/src/solver/optimizeProb.m b/src/solver/optimizeProb.m index 6fc3585e..f765886d 100755 --- a/src/solver/optimizeProb.m +++ b/src/solver/optimizeProb.m @@ -1,22 +1,23 @@ function res = optimizeProb(prob,varargin) -% optimizeProb Optimize an LP or MILP formulated in COBRA terms. +% optimizeProb Optimise an LP or MILP formulated in COBRA terms. % % Parameters % ---------- % prob : struct -% COBRA-style LP/MILP problem struct to be optimised. +% COBRA-style problem struct of an LP or MILP to be optimised. % % Name-Value Arguments % -------------------- % params : struct % solver-specific parameters. In addition to solver parameters, the -% field "maxRatio" can be set to a number > 1 to improve numerical -% conditioning: before solving (LP only) any column whose coefficients -% span more than maxRatio is split via auxiliary metabolites/variables, -% preserving the feasible region. See splitProbForConditioning. The -% field is consumed here and not forwarded to the solver. +% field "maxRatio" can be set to a finite number > 1 for numerical +% conditioning: before solving an LP, any column whose coefficients +% span more than maxRatio is split with auxiliary metabolites and +% variables, which preserves the feasible region. See +% splitProbForConditioning. The field is removed before the parameters +% are passed to the solver, and is ignored for MILPs. % verbose : logical -% if true MILP progress is shown (default true). +% if true, MILP progress is shown (default true). % % Returns % ------- @@ -24,16 +25,16 @@ % the output structure from the selected solver RAVENSOLVER (COBRA % style). In addition to the fields set by the solver, the logical field % "hitTimeLimit" is true if the solver stopped because it reached its -% time limit. Whatever solution is in res is then either absent or not -% proven optimal. +% time limit. The solution in res is then either absent or not proven +% optimal. % % Notes % ----- % MILPs can be solved with GLPK, either RAVEN's bundled GLPK ('glpk') or % the COBRA Toolbox's GLPK interface ('cobra' with GLPK as its MILP solver). -% A RAVEN:glpkMILP warning then recommends Gurobi, which is much faster and -% more reliable on larger MILPs (such as ftINIT or getMinNrFluxes on -% genome-scale models). +% The RAVEN:glpkMILP warning then states that GLPK works for small problems +% and that Gurobi is much faster and more reliable for larger ones (such as +% ftINIT or getMinNrFluxes on genome-scale models). % % See also % -------- diff --git a/src/solver/setRavenSolver.m b/src/solver/setRavenSolver.m index dc534e91..c8ce7f2a 100755 --- a/src/solver/setRavenSolver.m +++ b/src/solver/setRavenSolver.m @@ -10,15 +10,15 @@ function setRavenSolver(solver) % string with solver name. The following options are available: % % - glpk : uses RAVEN-provided binaries. Solves LPs and MILPs; for -% MILPs a RAVEN:glpkMILP warning recommends Gurobi, which is much -% faster and more reliable on larger problems. +% MILPs the RAVEN:glpkMILP warning states that Gurobi is much faster +% and more reliable for larger problems. % - gurobi : requires a working Gurobi installation. % - scip : requires a SCIP MEX interface. RAVEN provides a % precompiled binary for Windows only. % - cobra : requires a working COBRA Toolbox installation. RAVEN % parses the problem to COBRA and uses the solver that is set by -% changeCobraSolver. MILPs are refused when COBRA's MILP solver is -% GLPK. +% changeCobraSolver. For MILPs, the RAVEN:glpkMILP warning is issued +% when COBRA's MILP solver is GLPK. % % Examples % -------- diff --git a/src/solver/solveLP.m b/src/solver/solveLP.m index a5e96d78..ed0c4fb6 100755 --- a/src/solver/solveLP.m +++ b/src/solver/solveLP.m @@ -9,28 +9,28 @@ % Name-Value Arguments % -------------------- % minFlux : double -% determines if a second optimization should be performed in order to -% get rid of loops in the flux distribution (default 0): +% determines if a second optimisation is performed to remove loops +% from the flux distribution (default 0): % -% - 0 : no such optimization is performed -% - 1 : the sum of abs(fluxes) is minimized. This is the fastest way of -% getting rid of loops -% - 2 : *option is deprecated* -% - 3 : the number of fluxes is minimized. This can result in the flux -% distributions that are the easiest to interpret. Note that this -% optimization can be very slow +% - 0 : no second optimisation is performed +% - 1 : the sum of abs(fluxes) is minimised with an LP, which does not +% need the MILP that the minimisation of the number of fluxes requires +% - 2 : deprecated; gives an error +% - 3 : the number of fluxes is minimised with getMinNrFluxes, which +% solves a MILP and can take long. The result has the fewest active +% reactions, which can make the flux distribution easier to interpret % params : struct -% solver parameters, forwarded to optimizeProb. Mostly obsolete, but the -% field "maxRatio" (a number > 1) can be set to improve numerical -% conditioning of ill-scaled models: any reaction whose stoichiometric -% coefficients span more than maxRatio is transiently split via -% auxiliary metabolites before solving, which preserves the feasible -% region (and thus the flux distribution of the original reactions). A -% common value is 1e6 (no splitting by default). +% solver parameters, forwarded to optimizeProb. The field "maxRatio" +% (a number > 1) can be set for numerical conditioning of ill-scaled +% models: any reaction whose stoichiometric coefficients span more +% than maxRatio is split with auxiliary metabolites before solving, +% which preserves the feasible region (and thus the flux distribution +% of the original reactions). A common value is 1e6. No splitting is done +% when maxRatio is absent (default struct with relGap=0.4). % hsSol : struct -% hot-start solution for the LP solver. This can significantly speed up -% the process if many similar optimization problems are solved -% iteratively. Only used if minFlux is 0 or 1. +% hot-start solution for the LP solver, as hsSolOut from a previous +% call. It speeds up the solution when many similar optimisation +% problems are solved iteratively. A hsSol with stat<1 is ignored. % % Returns % ------- @@ -39,15 +39,16 @@ % % - f : objective value % - x : primal (flux distribution) -% - stat : exit flag (1 = optimization terminated successfully, 0 = +% - stat : exit flag (1 = optimisation terminated successfully, 0 = % solution is feasible but not necessarily optimal, -1 = no feasible -% solution found, -2 = solution found but flux minimization failed) -% - msg : string describing the status of the optimization +% solution found, -2 = solution found but flux minimisation failed) +% - msg : string describing the status of the optimisation % - sPrice : shadow price (only reported if minFlux was 0) % - rCost : reduced cost (only reported if minFlux was 0) % hsSolOut : struct % solution to be used as hot-start solution (see the input parameters). -% Only used if minFlux is 0 or 1. +% Set only if minFlux is 0 or 1, and only when the solver returns a +% basis. % % Examples % -------- diff --git a/src/solver/splitProbForConditioning.m b/src/solver/splitProbForConditioning.m index c9fb6b38..98157373 100644 --- a/src/solver/splitProbForConditioning.m +++ b/src/solver/splitProbForConditioning.m @@ -4,18 +4,20 @@ % Reformulates an LP problem to improve its numerical conditioning by % splitting columns whose coefficients span more than a given ratio. In a % metabolic model a column corresponds to a reaction, and a wide spread of -% stoichiometric coefficients within one reaction (e.g. a biomass reaction -% with both major substrates and trace cofactors) cannot be removed by the -% diagonal row/column scaling that LP solvers apply, and may lead to +% stoichiometric coefficients within one reaction (for example a biomass reaction +% with both large and small coefficients) cannot be removed by the +% diagonal row and column scaling that LP solvers apply, and may lead to % unreliable solutions or spurious infeasibility. % -% Splitting is done via auxiliary metabolites and variables that preserve +% Splitting is done with auxiliary metabolites and variables that preserve % the feasible region of the original problem exactly, so the values of all -% original variables are unchanged. +% original variables are unchanged. A column is split when its ratio +% exceeds maxRatio by more than 1 %. Columns with a single nonzero +% coefficient are never split. % -% The function is meant to be called inside optimizeProb, on the COBRA-style -% problem struct, immediately before the problem is passed to the solver. -% Auxiliary rows/columns are appended after the original ones, so the +% optimizeProb calls this function on the COBRA-style problem struct of an +% LP, immediately before the problem is passed to the solver. Auxiliary +% rows and columns are appended after the original ones, so the % augmentation can be stripped from the solver output afterwards (see % condInfo). % @@ -31,13 +33,14 @@ % Returns % ------- % prob : struct -% the (possibly) augmented problem struct. Identical to the input if no -% column needed splitting. +% the augmented problem struct. Identical to the input if no column +% needed splitting. % condInfo : struct % struct describing the augmentation, with fields: % % - applied : true if any column was split -% - nSplits : number of auxiliary metabolite/conversion pairs added +% - nSplits : number of pairs of an auxiliary metabolite and a conversion +% variable added % - nOrigRows : number of constraint rows before augmentation % - nOrigCols : number of variables (columns) before augmentation % diff --git a/src/tasks/checkTasks.m b/src/tasks/checkTasks.m index 778f4eff..69a5c1fa 100755 --- a/src/tasks/checkTasks.m +++ b/src/tasks/checkTasks.m @@ -1,12 +1,11 @@ function [taskReport, essentialRxns, taskStructure, essentialFluxes]=checkTasks(model,inputFile,varargin) % checkTasks Perform a set of simulations as defined in a task file. % -% This function is used for defining a set of tasks for a model to perform. -% The tasks are defined by defining constraints on the model, and if the -% problem is feasible, then the task is considered successful. In general, -% each row can contain one constraint on uptakes, one constraint on outputs, -% one new equation, and one change of reaction bounds. If more bounds are -% needed to define the task, then several rows can be used for each task. +% The function runs a set of tasks for a model. Each task is defined by +% constraints on the model, and the task is successful if the problem is +% feasible. Each row can contain one constraint on uptakes, one constraint on +% outputs, one new equation, and one change of reaction bounds. If more bounds +% are needed to define the task, then several rows can be used for each task. % % The task inputs and outputs are applied to the boundary metabolites of % the model. The model's own exchange reactions are not closed: they keep @@ -30,17 +29,18 @@ % true if only tasks that failed should be displayed (default false). % getEssential : logical % true if the essential reactions should be calculated for all the -% tasks. This option is used with runINIT (default false). +% tasks. This option is used by prepINITModel (ftINIT) and getINITModel +% (tINIT) (default false). % taskStructure : struct % structure with the tasks, as from parseTaskList. If this is supplied % then inputFile is ignored. % runParallel : logical -% true to evaluate tasks in parallel workers, since each task is -% independent of the others (see parallelWorkersRAVEN). Gurobi is -% automatically pinned to one thread per worker by optimizeProb, so -% this does not oversubscribe cores. Default false, matching the -% previous (serial) behaviour, since checkTasks is often called on -% small task lists where starting a pool would only add overhead. +% true to evaluate tasks in parallel workers, as each task is +% independent of the others (see parallelWorkersRAVEN). In a worker, +% optimizeProb sets Gurobi to one thread, so the workers do not use +% more cores than assigned. With false the tasks are evaluated in +% series, which avoids the overhead of starting a pool for small task +% lists (default false). % % Returns % ------- @@ -59,7 +59,9 @@ % taskStructure : struct % structure with the tasks, as from parseTaskList. % essentialFluxes : double -% the fluxes of the essential rxns - same structure as essentialRxns. +% the fluxes of the model reactions in the solution of each task (NaN +% for a task without a solution), with the same dimensions as +% essentialRxns. % % Examples % -------- diff --git a/src/tasks/parseTaskList.m b/src/tasks/parseTaskList.m index dde8b8f5..4589d6c3 100755 --- a/src/tasks/parseTaskList.m +++ b/src/tasks/parseTaskList.m @@ -5,36 +5,33 @@ % ---------- % inputFile : char % a task list in either Excel (*.xlsx, with a sheet named TASKS with -% all relevant content) or tab-delimited (*.txt) format. The old binary +% all relevant content) or tab-delimited (*.txt) format. The binary % *.xls format is not supported. The file may contain the following -% column headers (note, all rows starting with a non-empty cell are -% removed; the first row after that is considered the headers; the -% header row itself must therefore start with an empty cell): +% column headers. Rows that start with a non-empty cell are comments +% and are removed. The first row that starts with an empty cell is the +% header row, so the header row and the task rows must start with an +% empty cell: % % - ID : the only required header (string or numeric). Ids may repeat, % for example as category codes. A task starts at a row with a % non-empty id and can span multiple rows; only the first row of a task % should have an id % - DESCRIPTION : description of the task -% - IN : allowed input(s) for the task. Metabolite names should be on +% - IN : allowed input(s) for the task. Metabolite names are written in % the form "model.metName[model.comps]". Several inputs can be % delimited by ";". If so, then the same bounds are used for all % inputs. If that is not wanted, then use several rows for the task % - IN LB : lower bound for the uptake of the metabolites in the row -% (optional, default 0 which corresponds to a minimal uptake of 0 -% units) +% (optional, default 0) % - IN UB : upper bound for the uptake of the metabolites in the row -% (optional, default 1000 which corresponds to a maximal uptake of -% 1000 units) +% (optional, default 1000) % - OUT : allowed output(s) for the task (see IN) % - OUT LB : lower bound for the production of the metabolites in the -% row (optional, default 0 which corresponds to a minimal production -% of 0 units) +% row (optional, default 0) % - OUT UB : upper bound for the production of the metabolites in the -% row (optional, default 1000 which corresponds to a maximal -% production of 1000 units) -% - EQU : equation to add. The equation should be on the form -% "0.4 A + 2 B <=> (or =>) C" and the metabolites should be on the +% row (optional, default 1000) +% - EQU : equation to add. The equation is written in the form +% "0.4 A + 2 B <=> (or =>) C" and the metabolites in the % form "model.metName[model.comps]" (optional) % - EQU LB : lower bound for the equation (optional, default -1000 for % reversible and 0 for irreversible) @@ -47,13 +44,15 @@ % of the reaction is left as it is % - CHANGED UB : upper bound for the reaction. If empty, the upper bound % of the reaction is left as it is -% - SHOULD FAIL : true if the correct behavior of the model is to not +% - SHOULD FAIL : true if the correct behaviour of the model is to not % have a feasible solution given the constraints. Any non-empty value % except 0, false and no (any case) counts as true (optional, default % false) -% - PRINT FLUX : true if the function should print the corresponding -% flux distribution for a task. Can be useful for testing. Read in -% the same way as SHOULD FAIL (optional, default false) +% - PRINT FLUX : true if checkTasks should print the corresponding +% flux distribution for a task. Read in the same way as SHOULD FAIL +% (optional, default false) +% - COMMENTS : comments on the task, stored in the field comments +% (optional) % % Headers are matched ignoring case and surrounding whitespace. In the % ";"-delimited cells, items are trimmed and empty items are dropped. @@ -90,23 +89,20 @@ % % Notes % ----- -% This function is used for defining a set of tasks for a model to perform. -% The tasks are defined by defining constraints on the model, and if the -% problem is feasible, then the task is considered successful. In general, -% each row can contain one constraint on uptakes, one constraint on -% outputs, one new equation, and one change of reaction bounds. If more -% bounds are needed to define the task, then several rows can be used for -% each task. To perform the task use checkTasks or fitTasks. +% The task list defines a set of tasks for a model to perform. A task is +% defined by constraints on the model, and the task is successful if the +% problem is feasible. Each row can contain one constraint on uptakes, one +% constraint on outputs, one new equation, and one change of reaction bounds. +% If more bounds are needed to define the task, then several rows can be used +% for each task. To perform the task use checkTasks or fitTasks. % % The general metabolites "ALLMETS" and "ALLMETSIN[comps]" can be used as -% inputs or outputs in the similar manner to normal metabolites. This is a -% convenient way to, for example, allow excretion of all metabolites to -% check whether it is the synthesis of some metabolite that is limiting or -% whether it is the degradation of some byproduct. One important difference -% is that only the upper bounds are used for these general metabolites. -% That is, you can only say that uptake or excretion is allowed, not that -% it is required. This is to avoid conflicts where the constraints for the -% general metabolites overwrite those of the real ones. +% inputs or outputs in the same manner as normal metabolites. They allow, +% for example, excretion of all metabolites, to check whether the +% synthesis of some metabolite or the degradation of some byproduct is +% limiting. Only the upper bounds are used for these general metabolites, +% so uptake or excretion can be allowed but not required. A lower bound +% would overwrite the constraints of the individual metabolites. % % See also % -------- diff --git a/src/utils/convertCharArray.m b/src/utils/convertCharArray.m index ec25bcfd..e140abf0 100755 --- a/src/utils/convertCharArray.m +++ b/src/utils/convertCharArray.m @@ -1,9 +1,9 @@ function inputConverted = convertCharArray(funcInput) % convertCharArray Ensure input is a cell array of character vectors. % -% Converts input to make sure it is a cell array of character vectors. -% String arrays are transformed into character vectors. Output is always a -% cell array, also if only one character vector is given as input. +% Converts the input to a cell array of character vectors. String arrays +% are transformed into character vectors. A single character vector gives +% a cell array with one element. An empty input gives []. % % Parameters % ---------- @@ -13,7 +13,7 @@ % Returns % ------- % inputConverted : cell -% cell array of character vectors. +% cell array of character vectors, or [] if funcInput is empty. % % Examples % -------- diff --git a/src/utils/deprecationWarning.m b/src/utils/deprecationWarning.m index aa0a6f5c..8cf4b91e 100644 --- a/src/utils/deprecationWarning.m +++ b/src/utils/deprecationWarning.m @@ -2,16 +2,16 @@ function deprecationWarning(oldFunction, replacement) % deprecationWarning Warn once per session that a function is deprecated. % % Used by the wrappers in the deprecated/ folder. Each function warns the -% first time it is called in a MATLAB session and stays quiet afterwards, -% so a loop over thousands of reactions does not bury the console. Call -% "clear functions" to reset. +% first time it is called in a MATLAB session and does not warn again, also +% when it is called repeatedly in a loop. "clear functions" resets the +% record of functions that have warned. % % Parameters % ---------- % oldFunction : char % name of the deprecated function. % replacement : char -% what to call instead, written as it would be typed, e.g. +% what to call instead, written as it would be typed, for example % 'findLeakMetabolite(model,''produce'',...)'. % % Examples diff --git a/src/utils/grRuleToDNF.m b/src/utils/grRuleToDNF.m index f2ea4465..d8dca909 100644 --- a/src/utils/grRuleToDNF.m +++ b/src/utils/grRuleToDNF.m @@ -9,7 +9,7 @@ % ---------- % rule : char or string or struct % a grRule, or a tree from parseGrRule. A grRule is read by parseGrRule: -% genes joined by "and"/"or" in any case or by "&", "&&", "|", "||", +% genes joined by "and" or "or" in any case or by "&", "&&", "|", "||", % each operator separated from the gene ids by whitespace or a bracket, % with AND binding tighter than OR. % @@ -27,11 +27,11 @@ % % Gene order within a clause follows the order the genes appear in the rule. % Duplicates are not removed: "G1 and G1" stays a two-element clause, since -% collapsing it would silently rewrite the user's rule. +% collapsing it would change the rule. % % The number of clauses is the product of the OR-arities across a complex, -% so a pathological rule can expand combinatorially. Callers that build one -% object per clause should check numel(clauses) first. +% so the number of clauses can grow combinatorially with the rule. A caller +% that builds one object per clause can check numel(clauses) first. % % Examples % -------- diff --git a/src/utils/grRuleToString.m b/src/utils/grRuleToString.m index 19706872..8f9e6f57 100644 --- a/src/utils/grRuleToString.m +++ b/src/utils/grRuleToString.m @@ -1,9 +1,10 @@ function rule=grRuleToString(tree) % grRuleToString Render a grRule syntax tree back to a string. % -% Writes the standard RAVEN format: " and " / " or " as operators, with -% parentheses added only where precedence requires them, so that -% parseGrRule(grRuleToString(tree)) round-trips to the same structure. +% Writes the standard RAVEN format, with " and " and " or " as operators. An +% AND node inside an OR node and an OR node inside an AND node are placed in +% parentheses. The result parses with parseGrRule to a rule with the same +% logic. % % Parameters % ---------- diff --git a/src/utils/isDnfGrRule.m b/src/utils/isDnfGrRule.m index 343709e0..1b73bf9c 100644 --- a/src/utils/isDnfGrRule.m +++ b/src/utils/isDnfGrRule.m @@ -1,11 +1,11 @@ function tf=isDnfGrRule(rule) % isDnfGrRule Test whether a grRule is in disjunctive normal form. % -% DNF ("OR of AND-complexes") means no AND operator has an OR anywhere -% beneath it, i.e. the rule is a single gene, a pure AND-complex, or an OR -% of those. Standard RAVEN format requires DNF: subunits that must all be -% present are ANDed into a complex, and the reaction is catalysed by at -% least one such complex. +% A rule in disjunctive normal form (DNF, an OR of AND-complexes) has no OR +% operator beneath an AND operator. It is a single gene, an AND-complex, or an +% OR of genes and AND-complexes. The standard RAVEN format requires DNF: the +% subunits that must all be present are joined by AND into a complex, and the +% reaction is catalysed by at least one such complex. % % Parameters % ---------- diff --git a/src/utils/legacyMethodNotice.m b/src/utils/legacyMethodNotice.m index cfe1cdff..17734a69 100644 --- a/src/utils/legacyMethodNotice.m +++ b/src/utils/legacyMethodNotice.m @@ -1,29 +1,28 @@ function legacyMethodNotice(oldFunction, preferred) -% legacyMethodNotice Point a caller at the recommended method, once. +% legacyMethodNotice Warn once that a function has a recommended replacement. % -% For functions that remain supported but are no longer the recommended way -% to do the job. Unlike deprecationWarning, this promises no removal: the -% function keeps working and keeps being maintained. It is a signpost, not a -% countdown. +% For functions that remain supported and maintained but have a recommended +% replacement for new work. Unlike deprecationWarning, the notice announces no +% removal. % -% The notice is issued the first time the function is called in a MATLAB -% session and stays quiet afterwards, so a script that calls it in a loop -% does not bury the console. Call "clear functions" to reset. A caller that -% wants the legacy method deliberately can silence it with -% warning('off','RAVEN:legacyMethod'). +% The warning RAVEN:legacyMethod is issued at the first call of each +% oldFunction in a MATLAB session; later calls with the same oldFunction issue +% nothing. "clear functions" resets this. warning('off','RAVEN:legacyMethod') +% silences the notice. % % Parameters % ---------- % oldFunction : char -% name of the function being called. +% name of the function being called. The notice is issued once per name. % preferred : char -% what to use instead for new work, written as it would be typed. +% name of the recommended replacement for new work, written as in the +% warning text. % % Examples % -------- % legacyMethodNotice('getINITModel','ftINIT'); % -% See Also +% See also % -------- % deprecationWarning diff --git a/src/utils/parallelWorkersRAVEN.m b/src/utils/parallelWorkersRAVEN.m index dc142c8f..bd9ce7c9 100644 --- a/src/utils/parallelWorkersRAVEN.m +++ b/src/utils/parallelWorkersRAVEN.m @@ -1,29 +1,31 @@ function nW = parallelWorkersRAVEN(runParallel) % parallelWorkersRAVEN Return the parfor worker count for a given setting. % -% Returns a value for use with the parfor worker-count syntax: +% Returns the value for the maximum-workers argument of parfor: % % nW = parallelWorkersRAVEN(runParallel); % parfor (i = 1:N, nW) % ... % end % -% This avoids mutating global parallel-pool state: nW == 0 forces parfor -% to run as a plain for loop; nW == Inf lets the Parallel Computing Toolbox -% manage the pool size and auto-creation. +% The function leaves the global parallel pool unchanged. With nW == 0, parfor +% runs as a plain for loop. With nW == Inf, the Parallel Computing Toolbox +% manages the pool size and creates a pool when none exists. When +% runParallel is true but the Parallel Computing Toolbox is not installed, the +% function prints a message and returns 0. % % Parameters % ---------- % runParallel : logical -% whether the caller should use parallel workers. +% true to use parallel workers, false for sequential execution. % % Returns % ------- % nW : double -% 0 to force sequential execution (parfor behaves as a for loop, -% no pool required or created). -% Inf to use available pool workers (PCT manages pool size and -% auto-creation). +% 0 sequential execution (parfor behaves as a for loop, no pool is +% required or created). +% Inf available pool workers (the Parallel Computing Toolbox manages +% the pool size and creates a pool when none exists). if ~runParallel nW = 0; diff --git a/src/utils/parseGrRule.m b/src/utils/parseGrRule.m index 596a5694..7ece12f9 100644 --- a/src/utils/parseGrRule.m +++ b/src/utils/parseGrRule.m @@ -1,16 +1,15 @@ function tree=parseGrRule(rule) % parseGrRule Parse a grRule into a syntax tree. % -% Parses a gene-protein-reaction rule into a tree of AND/OR/gene nodes, so -% that callers can reason about its structure instead of searching the raw -% string. Gene IDs are matched as whole tokens, so IDs that contain "and" or -% "or" as a substring (RAND1), or that are prefixes of one another (10 and -% 100), are never confused with operators or with each other. +% Parses a gene-protein-reaction rule into a tree of AND, OR and gene nodes. +% Gene IDs are matched as whole tokens, so IDs that contain "and" or "or" as a +% substring (RAND1), or that are prefixes of one another (10 and 100), are +% distinct from operators and from each other. % % Parameters % ---------- % rule : char or string -% a grRule, e.g. "(G1 and G2) or G3". Both the word forms ("and", "or", +% a grRule, for example "(G1 and G2) or G3". Both the word forms ("and", "or", % any case) and the symbol forms ("&", "&&", "|", "||") are accepted. % An empty rule yields an empty tree. % @@ -19,23 +18,22 @@ % tree : struct % a scalar struct with fields: % -% - type : char — 'gene', 'and' or 'or'. -% - id : char — the gene ID; '' for 'and'/'or' nodes. -% - children : cell — child nodes; {} for 'gene' nodes. +% - type : char, 'gene', 'and' or 'or'. +% - id : char, the gene ID; '' for 'and' and 'or' nodes. +% - children : cell, the child nodes; {} for 'gene' nodes. % % An empty rule returns a 0x0 struct array, for which isempty is true. % % Notes % ----- -% The grammar is the usual precedence, with AND binding tighter than OR: +% AND binds tighter than OR. The grammar is: % % expr := term ( OR term )* % term := factor ( AND factor )* % factor := gene | '(' expr ')' % -% Malformed rules raise RAVEN:badGrRule rather than being silently -% misparsed. Callers that lint user input (findPotentialErrors) catch this -% and report it instead of failing. +% Malformed rules raise the error RAVEN:badGrRule. findPotentialErrors catches +% this error and reports the rule. % % Examples % -------- diff --git a/src/utils/parseRAVENargs.m b/src/utils/parseRAVENargs.m index 1910be9d..0a5e557e 100644 --- a/src/utils/parseRAVENargs.m +++ b/src/utils/parseRAVENargs.m @@ -1,18 +1,17 @@ function args = parseRAVENargs(rawArgs, spec) % parseRAVENargs Resolve optional arguments passed positionally or by name. % -% Helper that lets a RAVEN function accept its optional arguments either in -% the traditional positional order, as name-value pairs, or as a mix of -% leading positional values followed by name-value pairs. The required -% leading arguments stay explicit in the function signature; the optional -% tail is collected into varargin and passed here together with a -% specification of the optional parameters. +% Helper for RAVEN functions that accept optional arguments in positional +% order, as name-value pairs, or as leading positional values followed by +% name-value pairs. The required leading arguments are explicit in the function +% signature. The optional arguments are collected in varargin and passed to +% this function together with a specification of the optional parameters. % % Parameters % ---------- % rawArgs : cell -% the varargin of the function, i.e. whatever was supplied after the required -% positional arguments. +% the varargin of the function, holding the arguments supplied after the +% required positional arguments. % spec : cell % an N-by-2 or N-by-3 cell array describing the optional parameters, one % per row: @@ -20,9 +19,9 @@ % - column 1 : parameter name (char). % - column 2 : default value, used when the parameter is not supplied. % - column 3 : optional validator, a function handle that is called with -% the resolved value (including the default, when the parameter was not -% supplied) and should error on an invalid value, e.g. @mustBeNumeric. -% Use [] for no validation. +% the resolved value (the default when the parameter was not supplied) +% and should error on an invalid value, for example @mustBeNumeric. [] means no +% validation. % % Returns % ------- @@ -31,27 +30,27 @@ % % Notes % ----- -% Calling convention is resolved as follows: +% The calling convention is resolved in these steps: % % 1. If rawArgs is empty, all parameters take their defaults. -% 2. The first element in rawArgs that is a string matching a known -% parameter name marks position k. Everything before k is treated -% as positional (assigned left-to-right by the order in spec). -% Everything from k onward must form valid name-value pairs: an even -% count where every name-position element is a known parameter name. -% 3. If no such position exists (no element is a matching string), or the -% trailing section from k does not form valid name-value pairs, the -% entire rawArgs is treated as positional. +% 2. The first element in rawArgs that is a string matching a parameter +% name (case-insensitive) marks position k. The elements before k are +% positional and are assigned in the order of spec. The elements from k +% onward must form valid name-value pairs: an even count in which every +% name-position element is a parameter name. +% 3. If no element is a matching string, or the elements from k do not form +% valid name-value pairs, all of rawArgs is positional. % -% This means three styles are accepted interchangeably: +% Three styles are accepted: % % f(model, v1, v2) % purely positional % f(model, "p1", v1, "p2", v2) % purely named % f(model, v1, "p2", v2, "p3", v3) % hybrid: v1 positional, rest named % -% When a positional value could itself be a string that equals a parameter -% name, use the explicit name-value form for that argument to avoid -% ambiguity. +% A positional value that is a string equal to a parameter name is read as a +% parameter name only when the arguments from there on form valid name-value +% pairs; otherwise all arguments are positional. The name-value form passes +% such a value unambiguously. % % Examples % -------- @@ -62,7 +61,7 @@ % fileName = p.fileName; % sortIds = p.sortIds; % -% % the caller may use any of these: +% % the caller can use any of these: % exportToExcelFormat(model, "model.xlsx", true) % positional % exportToExcelFormat(model, "sortIds", true) % named % exportToExcelFormat(model, "model.xlsx", "sortIds", true) % hybrid diff --git a/src/utils/printOrange.m b/src/utils/printOrange.m index 7611e23b..a72fd7bb 100755 --- a/src/utils/printOrange.m +++ b/src/utils/printOrange.m @@ -1,14 +1,16 @@ function orangeString = printOrange(stringToPrint) % printOrange Print an orange-coloured string to the MATLAB Command Window. % -% Prints stringToPrint in orange colour to the MATLAB Command Window. Only -% works if MATLAB is open with a GUI; it does not work with command-line -% MATLAB. +% Prints stringToPrint in orange colour to the MATLAB Command Window. The +% colour requires the MATLAB desktop; in command-line MATLAB the text is +% printed without colour. When called without an output argument, the text is +% wrapped to the width of the Command Window and printed. With an output +% argument, nothing is printed. % % Parameters % ---------- % stringToPrint : char -% string that should be printed in orange colour. "\n" marks a line +% string to print in orange colour. "\n" marks a line % break, as in fprintf; no other escape sequence or format directive % is interpreted, so text containing "%" prints as-is. % diff --git a/src/utils/progressReport.m b/src/utils/progressReport.m index f02d13c0..b4d191e1 100644 --- a/src/utils/progressReport.m +++ b/src/utils/progressReport.m @@ -1,35 +1,36 @@ classdef progressReport < handle % progressReport Universal progress reporting for RAVEN functions. % -% A single progress-reporting API used across RAVEN. The rendering backend -% is selected automatically so that the same call works on an interactive -% desktop, a plain command-line session and a headless server, and is -% silenced during unit tests: +% Progress-reporting class used by RAVEN functions. The rendering backend is +% selected automatically. The same calls work on an interactive desktop, in a +% command-line session and on a headless server. During unit tests no output +% is produced. The backends are: % % - 'gui' : MATLAB waitbar (interactive desktop). % - 'cli' : animated command-line bar (interactive terminal). -% - 'log' : plain milestone lines, e.g. "Running BLAST: 30%". Contains no -% control characters, so it stays readable in redirected logs -% and on headless / -batch servers. +% - 'log' : plain milestone lines, for example "Running BLAST: 30%", printed +% every 10%. The lines contain no control characters, so they +% are readable in redirected logs and on headless and -batch +% servers. % - 'none' : no output (during tests, or when disabled). % -% The backend can be forced with setRavenProgress, e.g. setRavenProgress('log') -% on a compute cluster, or setRavenProgress('off') to silence all progress -% reporting. +% setRavenProgress sets the backend: setRavenProgress('log') selects plain +% lines, for example on a compute cluster, and setRavenProgress('off') +% disables all progress reporting. % % Usage % ----- -% For loops with one cheap-to-expensive iteration each, count one per pass: +% To register each iteration, call count once per pass: % % PB = progressReport(N, 'Task name'); % before the loop % for i = 1:N % ... % loop body % PB.count; % or count(PB), also in parfor % end -% PB.done; % optional; cleans up on early exit +% PB.done; % optional; finalises the bar on early exit % -% For very long loops over cheap iterations, set the absolute position at -% intervals to avoid per-iteration overhead: +% To avoid the per-iteration overhead in long loops of short iterations, set +% the absolute position at intervals: % % PB = progressReport(N, 'Task name'); % for i = 1:N @@ -43,7 +44,8 @@ % Parameters % ---------- % N : double -% number of iterations the loop will perform (a positive integer). +% number of iterations the loop performs (a positive integer). For any other +% value, no progress is shown. % taskName : char, optional % label shown alongside the progress (default ''). % diff --git a/src/utils/ravenList.m b/src/utils/ravenList.m index 0d5d2af3..60936b5b 100644 --- a/src/utils/ravenList.m +++ b/src/utils/ravenList.m @@ -1,20 +1,23 @@ function msg = ravenList(header, items, trim) -% ravenList Format a header and item list for warning() or error(). +% ravenList Format a header and a list of items for warning() or error(). % % Parameters % ---------- % header : char % message header string. % items : cell -% cell array of items to list below the header. +% cell array of items, listed one per line after the header. % trim : logical -% cap the list at 10 items (default true). +% true to limit the list to 10 lines (default true). When the list has more +% than 10 items, the tenth line reads '...and N more' and replaces items +% 10 to the end, with N the number of replaced items. % % Returns % ------- % msg : char -% header + newline + tab-indented items, ready to pass to warning() or -% error() as the format string. +% header, a newline, and the items separated by newlines with a tab before +% each item. If items is empty, msg is header. Passed to warning() or +% error() as an argument after the format '%s'. % % Examples % -------- diff --git a/src/utils/ravenModelFields.m b/src/utils/ravenModelFields.m index 32654108..95654c63 100644 --- a/src/utils/ravenModelFields.m +++ b/src/utils/ravenModelFields.m @@ -1,20 +1,21 @@ function reg = ravenModelFields() % ravenModelFields Declarative registry of all RAVEN model struct fields. % -% Returns a struct array. Each element describes one field: +% Returns a struct array with one element per field. The elements have the +% fields: % .name - field name (char) -% .type - entity type: 'rxn', 'met', 'gene', or 'comp' (char) +% .type - entity type: 'rxn', 'met', 'gene' or 'comp' (char) % .default - default value when padding new entries % -% Only 1-D (vector-per-entity) fields are listed here. Fields that require -% custom 2-D indexing or value-remapping are excluded and must be handled -% explicitly by callers: +% Only 1-D (vector-per-entity) fields are listed. Fields that need 2-D +% indexing or value remapping are excluded, and callers handle them +% explicitly: % % S - sparse (mets x rxns); use S(:,perm) for rxns, S(perm,:) for mets % rxnGeneMat - sparse (rxns x genes); use rxnGeneMat(perm,:) or rxnGeneMat(:,perm) % b - double (mets x N), may be 2-D; use b(perm,:) -% metComps, rxnComps, geneComps - when permuting comps, their VALUES must -% be remapped (not the arrays themselves permuted) +% metComps, rxnComps, geneComps - when the compartments are permuted, the +% values of these arrays are remapped; the arrays are not permuted rows = { % name type default diff --git a/src/utils/runRAVENtests.m b/src/utils/runRAVENtests.m index 6801e027..a93f6958 100644 --- a/src/utils/runRAVENtests.m +++ b/src/utils/runRAVENtests.m @@ -1,8 +1,9 @@ function testResults = runRAVENtests % runRAVENtests Run all RAVEN function tests. % -% Runs all unit tests found at RAVEN/test/function_tests, and prints output in -% the command window. +% Runs all unit tests found in the folder tests/function_tests of the RAVEN +% root folder, except the abstract base class RavenTestCase, and prints the +% output in the command window. % % Returns % ------- diff --git a/src/utils/setRavenProgress.m b/src/utils/setRavenProgress.m index 58b423c5..29f72174 100644 --- a/src/utils/setRavenProgress.m +++ b/src/utils/setRavenProgress.m @@ -2,25 +2,26 @@ function setRavenProgress(mode) % setRavenProgress Set the RAVEN progress-reporting backend. % % Sets the preference that progressReport uses to decide how progress is -% shown. The setting is persistent across MATLAB sessions. +% shown. The setting is stored as the RAVEN preference progressBar and +% persists across MATLAB sessions. % % Parameters % ---------- % mode : char % one of: % -% - 'auto' : choose automatically (default). GUI on an interactive -% desktop, an animated bar in an interactive terminal, plain log lines -% on a headless / -batch server, and no output during unit tests. +% - 'auto' : choose automatically (default): 'gui' on the MATLAB desktop, +% 'cli' in a terminal, 'log' on a -batch or deployed session, and no +% output during unit tests. % - 'gui' : always use a MATLAB waitbar. % - 'cli' : always use the animated command-line bar. -% - 'log' : always print plain milestone lines (safe in redirected -% logs and on headless servers). -% - 'none' / 'off' : never show progress. +% - 'log' : always print plain milestone lines, which are readable in +% redirected logs and on headless servers. +% - 'none' or 'off' : never show progress. % % Examples % -------- -% setRavenProgress('log'); % e.g. on a compute cluster +% setRavenProgress('log'); % for example on a compute cluster % setRavenProgress('off'); % silence all progress reporting % setRavenProgress('auto'); % restore the default behaviour % From 35d8717ca39a59d6a766398f0bee634561c98da9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 31 Oct 2026 15:37:47 +0000 Subject: [PATCH 39/48] doc: regenerate HTML documentation for 1b742df [skip ci] --- docs/html/INIT/INITStepDesc.html | 4 +- docs/html/INIT/ftINIT.html | 1039 ++++---- docs/html/INIT/ftINITInternalAlg.html | 1468 +++++------ docs/html/INIT/getINITSteps.html | 248 +- docs/html/INIT/groupRxnScores.html | 80 +- docs/html/INIT/mergeLinear.html | 24 +- docs/html/INIT/parseHPA.html | 182 +- docs/html/INIT/parseHPArna.html | 138 +- docs/html/INIT/prepINITModel.html | 734 +++--- docs/html/INIT/rescaleModelForINIT.html | 166 +- docs/html/INIT/scoreModel.html | 188 +- docs/html/INIT/tINIT/getINITModel.html | 942 ++++--- docs/html/INIT/tINIT/runINIT.html | 748 +++--- docs/html/analysis/FSEOF.html | 332 +-- docs/html/analysis/analyzeSampling.html | 338 ++- docs/html/analysis/compareFluxes.html | 422 +-- docs/html/analysis/findGeneDeletions.html | 290 ++- docs/html/analysis/getAllSubGraphs.html | 12 +- docs/html/analysis/getAllowedBounds.html | 48 +- docs/html/analysis/getFluxZ.html | 126 +- docs/html/analysis/getMinimalMedium.html | 106 +- docs/html/analysis/haveFlux.html | 96 +- docs/html/analysis/index.html | 2 +- docs/html/analysis/randomSampling.html | 614 ++--- docs/html/analysis/reporterMetabolites.html | 474 ++-- docs/html/analysis/runDynamicFBA.html | 232 +- docs/html/analysis/runProductionEnvelope.html | 138 +- docs/html/analysis/runRobustnessAnalysis.html | 182 +- docs/html/analysis/runSimpleOptKnock.html | 270 +- docs/html/analysis/sampleACHR.html | 290 +-- docs/html/analysis/sampleCHRR.html | 344 +-- docs/html/analysis/sampleMaxVolEllipse.html | 302 +-- docs/html/analysis/sampleWarmupPoints.html | 160 +- docs/html/analysis/traceFluxPath.html | 768 +++--- docs/html/analysis/walkFluxes.html | 418 +-- docs/html/annotation/assignSBOterms.html | 346 +-- docs/html/annotation/editMiriam.html | 258 +- docs/html/annotation/extractMiriam.html | 206 +- docs/html/annotation/index.html | 2 +- docs/html/annotation/loadDeltaGCSV.html | 104 +- docs/html/annotation/saveDeltaGCSV.html | 20 +- docs/html/biomass/fitParameters.html | 290 ++- docs/html/biomass/getBiomassFractions.html | 288 ++- docs/html/biomass/scaleBiomassFraction.html | 160 +- .../biomass/scaleBiomassPseudoreaction.html | 200 +- docs/html/biomass/setGAM.html | 158 +- .../comparison/compareMultipleModels.html | 1122 ++++---- docs/html/comparison/diffModels.html | 564 ++-- docs/html/conditions/applyCondition.html | 356 +-- docs/html/conversion/addIdentifierPrefix.html | 146 +- docs/html/conversion/ravenCobraWrapper.html | 920 +++---- .../conversion/removeIdentifierPrefix.html | 62 +- .../standardizeModelFieldOrder.html | 148 +- docs/html/curation/curateModelFromTables.html | 1480 +++++------ docs/html/curation/downloadGenomeData.html | 246 +- docs/html/curation/getGeneData.html | 536 ++-- .../curation/processProteinFastaFile.html | 302 +-- docs/html/curation/renameModelGenes.html | 496 ++-- docs/html/gapfilling/canExchange.html | 136 +- docs/html/gapfilling/checkProduction.html | 350 +-- docs/html/gapfilling/fillGaps.html | 720 +++--- docs/html/gapfilling/findLeakMetabolite.html | 98 +- docs/html/gapfilling/fitTasks.html | 96 +- docs/html/gapfilling/ftINITFillGaps.html | 246 +- docs/html/gapfilling/gapFillFastCore.html | 256 +- docs/html/gapfilling/gapFillFastLP.html | 326 +-- docs/html/gapfilling/gapFillMILP.html | 538 ++-- docs/html/gapfilling/gapFillSwiftCore.html | 72 +- docs/html/gapfilling/gapFillTopological.html | 114 +- docs/html/gapfilling/gapReport.html | 48 +- docs/html/gapfilling/index.html | 2 +- .../html/installation/addRavenToUserPath.html | 96 +- .../installation/checkFunctionUniqueness.html | 214 +- .../installation/downloadRavenBinaries.html | 228 +- docs/html/installation/fetchToolboxAsset.html | 264 +- docs/html/installation/findRAVENroot.html | 120 +- .../installation/removeRavenFromPath.html | 86 +- .../installation/updateDocumentation.html | 24 +- docs/html/io/checkFileExistence.html | 40 +- docs/html/io/cleanSheet.html | 202 +- docs/html/io/exportForGit.html | 512 ++-- docs/html/io/exportModel.html | 34 +- docs/html/io/exportToExcelFormat.html | 16 +- docs/html/io/importModel.html | 1944 +++++++------- docs/html/io/index.html | 2 +- docs/html/io/parseYAML.html | 534 ++-- docs/html/io/readYAMLmodel.html | 20 +- docs/html/io/writeYAMLmodel.html | 1216 +++++---- .../localization/defaultCompartmentMap.html | 74 +- docs/html/localization/getUniProtScores.html | 106 +- docs/html/localization/index.html | 2 +- docs/html/localization/parseScores.html | 658 +++-- .../localization/predictLocalization.html | 2294 ++++++++--------- docs/html/manipulation/addExchangeRxns.html | 252 +- docs/html/manipulation/addGenesRaven.html | 332 ++- docs/html/manipulation/addMets.html | 746 +++--- docs/html/manipulation/addRxns.html | 1490 ++++++----- docs/html/manipulation/addRxnsGenesMets.html | 402 ++- docs/html/manipulation/addTransport.html | 424 +-- docs/html/manipulation/changeGrRules.html | 162 +- docs/html/manipulation/changeRxns.html | 308 ++- docs/html/manipulation/closeModel.html | 292 ++- docs/html/manipulation/contractModel.html | 372 ++- docs/html/manipulation/convertToIrrev.html | 12 +- docs/html/manipulation/copyToComps.html | 244 +- docs/html/manipulation/expandModel.html | 50 +- docs/html/manipulation/findDuplicateRxns.html | 124 +- .../manipulation/findPotentialErrors.html | 194 +- docs/html/manipulation/generateNewIds.html | 150 +- docs/html/manipulation/index.html | 2 +- docs/html/manipulation/mergeCompartments.html | 106 +- docs/html/manipulation/mergeModels.html | 92 +- docs/html/manipulation/permuteModel.html | 2 +- docs/html/manipulation/removeBadRxns.html | 562 ++-- docs/html/manipulation/removeGenes.html | 356 +-- .../manipulation/removeLowScoreGenes.html | 746 +++--- docs/html/manipulation/removeMets.html | 242 +- docs/html/manipulation/removeReactions.html | 160 +- docs/html/manipulation/replaceMets.html | 416 +-- docs/html/manipulation/reverseRxns.html | 88 +- docs/html/manipulation/setExchangeBounds.html | 472 ++-- docs/html/manipulation/setParam.html | 286 +- docs/html/manipulation/simplifyModel.html | 692 ++--- docs/html/manipulation/sortIdentifiers.html | 8 +- docs/html/manipulation/sortModel.html | 456 ++-- .../html/manipulation/standardizeGrRules.html | 406 +-- docs/html/queries/buildEquation.html | 134 +- docs/html/queries/checkModelStruct.html | 1142 ++++---- docs/html/queries/constructEquations.html | 186 +- docs/html/queries/constructS.html | 416 +-- docs/html/queries/getAllRxnsFromGenes.html | 88 +- docs/html/queries/getElementalBalance.html | 390 +-- docs/html/queries/getExchangeRxns.html | 230 +- docs/html/queries/getGenesFromGrRules.html | 56 +- docs/html/queries/getIndexes.html | 300 +-- docs/html/queries/getRxnsInComp.html | 100 +- docs/html/queries/getTransportRxns.html | 48 +- docs/html/queries/index.html | 2 +- docs/html/queries/modelSummary.html | 302 +-- docs/html/queries/parseFormulas.html | 526 ++-- docs/html/queries/parseRxnEqu.html | 140 +- docs/html/queries/printFluxes.html | 416 +-- docs/html/queries/printModelStats.html | 376 +-- .../html/reconstruction/guessComposition.html | 338 +-- .../reconstruction/homology/getBlast.html | 386 +-- .../reconstruction/homology/getDiamond.html | 332 +-- .../homology/getModelFromHomology.html | 1716 ++++++------ .../homology/hitsFromSuppOrth.html | 4 +- .../homology/makeFakeBlastStructure.html | 70 +- .../reconstruction/kegg/buildGlobalGPR.html | 34 +- .../kegg/buildGlobalKEGGModel.html | 30 +- .../kegg/getKEGGModelForOrganism.html | 1200 +++++---- .../reconstruction/kegg/getModelFromKEGG.html | 168 +- .../html/reconstruction/kegg/getPhylDist.html | 4 +- docs/html/reconstruction/kegg/index.html | 2 +- .../reconstruction/kegg/keggDataVersion.html | 50 +- .../reconstruction/kegg/readKEGGTable.html | 24 +- docs/html/solver/getMinNrFluxes.html | 1106 ++++---- docs/html/solver/index.html | 2 +- docs/html/solver/optimizeProb.html | 884 +++---- docs/html/solver/setRavenSolver.html | 16 +- docs/html/solver/solveLP.html | 474 ++-- .../html/solver/splitProbForConditioning.html | 296 +-- docs/html/tasks/checkTasks.html | 646 ++--- docs/html/tasks/parseTaskList.html | 816 +++--- docs/html/utils/convertCharArray.html | 18 +- docs/html/utils/deprecationWarning.html | 16 +- docs/html/utils/grRuleToDNF.html | 16 +- docs/html/utils/grRuleToString.html | 110 +- docs/html/utils/index.html | 2 +- docs/html/utils/isDnfGrRule.html | 20 +- docs/html/utils/legacyMethodNotice.html | 116 +- docs/html/utils/parallelWorkersRAVEN.html | 80 +- docs/html/utils/parseGrRule.html | 362 ++- docs/html/utils/parseRAVENargs.html | 320 ++- docs/html/utils/printOrange.html | 96 +- docs/html/utils/progressReport.html | 612 ++--- docs/html/utils/ravenList.html | 82 +- docs/html/utils/ravenModelFields.html | 152 +- docs/html/utils/runRAVENtests.html | 54 +- docs/html/utils/setRavenProgress.html | 94 +- 181 files changed, 29369 insertions(+), 28900 deletions(-) diff --git a/docs/html/INIT/INITStepDesc.html b/docs/html/INIT/INITStepDesc.html index 082addf4..eb3260f7 100644 --- a/docs/html/INIT/INITStepDesc.html +++ b/docs/html/INIT/INITStepDesc.html @@ -46,7 +46,7 @@

    SUBFUNCTIONS ^SOURCE CODE ^

    0001 classdef INITStepDesc
     0002 % Describes a step in the ftINIT algorithm. A cell array of objects of this
    -0003 % class is used as input to ftINIT to specify how the algorithm should be run.
    +0003 % class is the input to ftINIT that specifies how the algorithm is run.
     0004    properties
     0005       PosRevOff %If true, positive-scored reversible reactions are removed
     0006                 %from the MILP problem (used in step 1 of ftINIT)
    @@ -67,7 +67,7 @@ 

    SOURCE CODE ^% b8 - All rxns without GPRs 0022 0023 MetsToIgnore % Structure describing mets that can be removed from the model -0024 % before running ftINIT, such as water etc. +0024 % before running ftINIT, such as water and other ubiquitous metabolites. 0025 % .simpleMets 0026 % .mets Names of metabolites to remove 0027 % .compsToKeep Compartments for which metabolites should be kept. diff --git a/docs/html/INIT/ftINIT.html b/docs/html/INIT/ftINIT.html index e05da6af..4ba1c3e0 100644 --- a/docs/html/INIT/ftINIT.html +++ b/docs/html/INIT/ftINIT.html @@ -47,519 +47,534 @@

    SOURCE CODE ^
    0001 function [model, metProduction, addedRxnsForTasks, deletedRxnsInINIT, fullMipRes] = ftINIT(prepData, tissue, celltype, hpaData, varargin)
     0002 % ftINIT  Generate a model using the ftINIT algorithm.
     0003 %
    -0004 % Main function for generating a model using the ftINIT algorithm, based on
    -0005 % proteomics and/or transcriptomics and/or metabolomics and/or metabolic tasks.
    -0006 % The algorithm is not designed for running with metabolomics only. The
    -0007 % function prepINITModel needs to be run first for the template model (such as
    -0008 % the generic Human-GEM), but only needs to be run once. This function
    -0009 % precalculates things independent of the omics to speed up the model
    -0010 % generation process, and outputs the prepData, which is input to this
    -0011 % function.
    -0012 %
    -0013 % Parameters
    -0014 % ----------
    -0015 % prepData : struct
    -0016 %     the prepData for the model.
    -0017 % tissue : char
    -0018 %     tissue to score for. Should exist in either hpaData.tissues or
    -0019 %     transcrData.tissues.
    -0020 % celltype : char
    -0021 %     cell type to score for. Should exist in either hpaData.celltypes or
    -0022 %     transcrData.celltypes for this tissue (default is to use the max values
    -0023 %     among all the cell types for the tissue).
    -0024 % hpaData : struct
    -0025 %     HPA data structure from parseHPA (optional if transcrData is supplied,
    -0026 %     default []).
    -0027 %
    -0028 % Name-Value Arguments
    -0029 % --------------------
    -0030 % transcrData : struct
    -0031 %     gene expression data structure (optional if hpaData is supplied, default
    -0032 %     []). With fields:
    -0033 %
    -0034 %     - genes : cell array with the unique gene names.
    -0035 %     - tissues : cell array with the tissue names. The list may not be unique,
    -0036 %       as there can be multiple cell types per tissue.
    -0037 %     - celltypes : cell array with the cell type names for each tissue.
    -0038 %     - levels : GENESxTISSUES array with the expression level for each gene in
    -0039 %       each tissue/celltype. NaN should be used when no measurement was
    -0040 %       performed.
    -0041 %     - threshold : a single value or a vector of gene expression thresholds,
    -0042 %       above which genes are considered to be "expressed". (default 1; by
    -0043 %       default, the mean expression levels of each gene across all tissues in
    -0044 %       transcrData will be used as the threshold values).
    -0045 %     - singleCells : binary value selecting whether to use the single-cell
    -0046 %       algorithm to identify expressed genes. If used, specify cell
    -0047 %       subpopulations in CELLTYPES (default []).
    -0048 %     - plotResults : true if single cell probability distributions should be
    -0049 %       plotted (default false).
    -0050 % metabolomicsData : cell
    -0051 %     cell array with metabolite names that the model should produce (default
    -0052 %     []).
    -0053 % INITSteps : cell
    -0054 %     specifies the steps in the algorithm. For more info, see INITStepDesc and
    -0055 %     getINITSteps (default getINITSteps(), which is the standard ftINIT).
    -0056 % removeGenes : logical
    -0057 %     if true, low-abundance genes will be removed from grRules, unless they
    -0058 %     are the only gene associated with a reaction, or a subunit of an enzyme
    -0059 %     complex (see "removeLowScoreGenes" function for details). If false,
    -0060 %     grRules will not be modified; however, genes that were associated only
    -0061 %     with removed reactions will not be present in the final model (default
    -0062 %     true).
    -0063 % useScoresForTasks : logical
    -0064 %     true if the calculated reaction scores should be used as weights when
    -0065 %     fitting to tasks (default true).
    -0066 % paramsFT : struct
    -0067 %     *obsolete option*.
    -0068 % verbose : logical
    -0069 %     if true, the MILP progression will be shown (default false).
    -0070 % resolveTies : logical
    -0071 %     if true, pin each step's degenerate MILP to a canonical answer instead
    -0072 %     of relying on the solver Seed alone: hold the score objective at its
    -0073 %     optimum, then minimise the count of removable ("on") reactions, then,
    -0074 %     among the sparsest, minimise their summed reaction-id rank. This
    -0075 %     applies to every main-extraction step and to the final gap-filling
    -0076 %     step (fitTasks' resolveTies) -- see raven-gecko-parity#104 and
    -0077 %     raven-toolbox's resolve_ties. In each main-extraction step but the
    -0078 %     last (ftINITInternalAlg) the fluxes, which set the direction and
    -0079 %     magnitude a reaction is forced at in the next step, then come from the
    -0080 %     least-total-flux solution for that on/off state and, among those, from
    -0081 %     the one with the least id-rank-weighted backward flux (a merged
    -0082 %     reaction ranked by the smallest original id in its group). The last
    -0083 %     step skips these flux phases, as no step reads its fluxes. A step
    -0084 %     whose primary solve stops at the time limit keeps its primary
    -0085 %     solution without any of these phases; the count and id-rank phases
    -0086 %     get half of the step's TimeLimit each, and a warning
    -0087 %     names each phase that does not prove optimal or finds no solution
    -0088 %     (default false).
    -0089 % proveAbsGap : double
    -0090 %     if set, prove each step to this fixed *absolute* MILP gap in a single
    -0091 %     solve, instead of the relative-gap escalation below (stp.MILPParams).
    -0092 %     The escalation's final near-zero-objective run otherwise accepts an
    -0093 %     arbitrary within-gap incumbent -- measured on genome-scale Human-GEM to
    -0094 %     return a solution 2.0-4.0 objective units below the true optimum (see
    -0095 %     raven-toolbox's prove_abs_gap and the ftINIT reproducibility study on
    -0096 %     raven-docs). 1.0 is the recommended value: tighter (down to 0.05)
    -0097 %     proves nothing further and costs substantially more runtime. Pairs
    -0098 %     naturally with resolveTies (default [], i.e. use the escalation).
    -0099 %
    -0100 % Returns
    -0101 % -------
    -0102 % model : struct
    -0103 %     the resulting model structure.
    -0104 % metProduction : double
    -0105 %     array that indicates which of the metabolites in metabolomicsData that
    -0106 %     could be produced. Note that this is before the gap-filling process to
    -0107 %     enable defined tasks. To see which metabolites that can be produced in
    -0108 %     the final model, use canExchange:
    -0109 %
    -0110 %     - -2 : metabolite name not found in model.
    -0111 %     - -1 : metabolite found, but it could not be produced.
    -0112 %     - 1 : metabolite could be produced.
    -0113 % addedRxnsForTasks : cell
    -0114 %     cell array of the reactions which were added in order to perform the
    -0115 %     tasks.
    -0116 % deletedRxnsInINIT : cell
    -0117 %     cell array of reactions deleted because they could not carry flux (INIT
    -0118 %     requires a functional input model).
    -0119 % fullMipRes : struct
    -0120 %     the solver results from the last MILP step run.
    -0121 %
    -0122 % Notes
    -0123 % -----
    -0124 % Exchange metabolites should normally not be removed from the model when using
    -0125 % this approach, since checkTasks/fitTasks rely on putting specific constraints
    -0126 % for each task. The INIT algorithm will remove exchange metabolites if any are
    -0127 % present. Use importModel(file,false) to import a model with exchange
    -0128 % metabolites remaining.
    -0129 %
    -0130 
    -0131 p=parseRAVENargs(varargin, {'transcrData',[]; 'metabolomicsData',[]; 'INITSteps',[]; 'removeGenes',[]; 'useScoresForTasks',[]; 'paramsFT',[]; 'verbose',false; 'resolveTies',false; 'proveAbsGap',[]});
    -0132 transcrData=p.transcrData;
    -0133 metabolomicsData=p.metabolomicsData;
    -0134 INITSteps=p.INITSteps;
    -0135 removeGenes=p.removeGenes;
    -0136 useScoresForTasks=p.useScoresForTasks;
    -0137 paramsFT=p.paramsFT;
    -0138 verbose=p.verbose;
    -0139 resolveTies=p.resolveTies;
    -0140 proveAbsGap=p.proveAbsGap;
    -0141 if isempty(INITSteps)
    -0142     INITSteps = getINITSteps([],'1+1');
    -0143 end
    -0144 if isempty(removeGenes)
    -0145     removeGenes = true;
    -0146 end
    -0147 if isempty(useScoresForTasks)
    -0148     useScoresForTasks = true;
    -0149 end
    -0150 %Handle detected mets:
    -0151 %Giving a bonus for secreting these metabolites does not work, since the
    -0152 %metabolite secretion and uptake can be lost when we merge linearly dependent
    -0153 %reactions. Instead, we need to figure out which reactions either produce or
    -0154 %take up the mets, and give a bonus if any of them carry flux. To simplify
    -0155 %things, we focus on reactions that produce the metabolite (since there must
    -0156 %be one such reaction). We further reason that the direction does not matter -
    -0157 %we can force one of these reactions in any direction - if it becomes a consumer, it will
    -0158 %automatically force another producer on as well (otherwise we will have a net consumption).
    -0159 
    -0160 if (~isempty(metabolomicsData))
    -0161     if length(unique(upper(metabolomicsData))) ~= length(metabolomicsData)
    -0162         error('RAVEN:badInput', '%s', 'Metabolomics contains the same metabolite multiple times');
    -0163     end
    -0164     metData = false(numel(metabolomicsData), length(prepData.minModel.rxns)); %one row per metabolite that is a boolean vector
    -0165     for i=1:numel(metabolomicsData)
    -0166         %Get the matching mets
    -0167         metSel = ismember(upper(prepData.refModel.metNames),upper(metabolomicsData{i}));
    -0168         prodRxnsSel = any(prepData.refModel.S(metSel,:) > 0,1) | ... %direct producers
    -0169                      (any(prepData.refModel.S(metSel,:) < 0,1) & prepData.refModel.rev.'); %reversible reactions that are consumers
    -0170         %convert the production rxns from refModel to minModel
    -0171         prepData.groupIds;
    -0172         [~,ia,ib] = intersect(prepData.minModel.rxns,prepData.refModel.rxns);
    -0173         grpIdsMerged = nan(length(prepData.minModel.rxns),1);
    -0174         grpIdsMerged(ia) = prepData.groupIds(ib);
    -0175         
    -0176         groupIdsPos = unique(prepData.groupIds(prodRxnsSel));%gets all group ids which includes a production rxn
    -0177         groupIdsPos = groupIdsPos(groupIdsPos ~= 0);%remove the 0 id, it means there is no group
    -0178         %the other option is that there is a direct match between the rxn id in minModel and refModel:
    -0179         posRxns = prepData.refModel.rxns(prodRxnsSel);
    -0180         directMatch = ismember(prepData.minModel.rxns, posRxns).';
    -0181         
    -0182         metData(i,:) = ismember(grpIdsMerged, groupIdsPos).' | directMatch;
    -0183     end
    -0184     metData = sparse(metData);
    -0185 else
    -0186     metData = [];
    -0187 end
    -0188 
    -0189 % Get rxn scores and adapt them to the minimized model
    -0190 origRxnScores = scoreModel(prepData.refModel,hpaData,transcrData,tissue,celltype);
    -0191 origRxnScores(origRxnScores > -0.1 & origRxnScores <= 0) = -0.1;%we don't want reaction scores that are exactly 0 (or close), this causes problems in the milp
    -0192 origRxnScores(origRxnScores < 0.1 & origRxnScores > 0) = 0.1;
    -0193 
    -0194 rxnsTurnedOn = false(length(prepData.minModel.rxns),1);
    -0195 fluxes = zeros(length(prepData.minModel.rxns),1);
    -0196 
    -0197 rxnsToIgnoreLastStep = [1;1;1;1;1;1;1;1];
    -0198 
    -0199 %We assume that all essential rxns are irrev - this is taken care of in
    -0200 %prepINITModel. We then use an initial flux "from last run" of 0.1 for all
    -0201 %reactions. This is used for knowing what flux should be forced through an
    -0202 %essential rxn.
    -0203 fluxes = ones(length(prepData.minModel.rxns), 1).*0.1;
    -0204 
    -0205 %resolveTies ranks a merged reaction by its group's smallest original id,
    -0206 %which does not depend on which member names the merged reaction.
    -0207 rankKeys = prepData.minModel.rxns;
    -0208 [inRef, refInd] = ismember(rankKeys, prepData.refModel.rxns);
    -0209 for i = find(inRef)'
    -0210     g = prepData.groupIds(refInd(i));
    -0211     if g ~= 0
    -0212         members = sort(prepData.refModel.rxns(prepData.groupIds == g));
    -0213         rankKeys{i} = members{1};
    -0214     end
    -0215 end
    -0216 
    -0217 for initStep = 1:length(INITSteps)
    -0218     disp(['ftINIT: Running step ' num2str(initStep)])
    -0219     stp = INITSteps{initStep};
    -0220     
    -0221     if any ((rxnsToIgnoreLastStep - stp.RxnsToIgnoreMask) < 0)
    -0222         error('RAVEN:badInput', '%s', 'RxnsToIgnoreMask may not cover rxns not covered in previous steps, but the other way around is fine.');
    -0223     end
    -0224     rxnsToIgnoreLastStep = stp.RxnsToIgnoreMask;
    -0225     %The fluxes of a step set the forced directions of the next one, so the
    -0226     %last step skips the flux phases of resolveTies.
    -0227     pinFluxes = initStep < numel(INITSteps);
    -0228     
    -0229     mm = prepData.minModel;
    -0230     
    -0231     if (~isempty(stp.MetsToIgnore))
    -0232         if (~isempty(stp.MetsToIgnore.simpleMets))
    -0233             %Here, we remove simple metabolites that will not really affect the milp but
    -0234             %are very common in the S matrix. For example H2O, H+, etc.
    -0235             %It is also possible to leave compartments untouched, for example the i compartment in the mitochondria (for H+).
    -0236             metsToRem = ismember(mm.metNames,stp.MetsToIgnore.simpleMets.mets);
    -0237             compsToKeep = find(ismember(mm.comps, stp.MetsToIgnore.simpleMets.compsToKeep));
    -0238             metsToRem = metsToRem & ~ismember(mm.metComps, compsToKeep);
    -0239             mm.S(metsToRem,:) = 0;
    -0240         end    
    -0241     end
    -0242 
    -0243     %Set up the reaction scores and essential rxns
    -0244     rxnsToIgnore = getRxnsFromPattern(stp.RxnsToIgnoreMask, prepData);
    -0245     rxnScores = groupRxnScores(prepData.minModel, origRxnScores, prepData.refModel.rxns, prepData.groupIds, rxnsToIgnore);
    -0246 
    -0247     essentialRxns = prepData.essentialRxns;
    -0248     toRev = false(numel(mm.rxns),1);
    -0249     %Handle the results from previous steps ("ignore", "exclude", "essential")
    -0250     if strcmp(stp.HowToUsePrevResults, 'exclude')
    -0251         rxnScores(rxnsTurnedOn) = 0; %This is not used anymore in any step setup.
    -0252     elseif strcmp(stp.HowToUsePrevResults, 'essential')
    -0253         %Make all reversible reactions turned on in previous steps reversible
    -0254         %in the direction that they were previously carrying flux
    -0255         
    -0256         %first reverse the reactions that need to be reversed
    -0257         rev = mm.rev == 1;
    -0258         toRev = rxnsTurnedOn & rev & fluxes < 0;
    -0259         mm = reverseRxns(mm, mm.rxns(toRev));
    -0260         
    -0261         %Then make them irreversible
    -0262         mm.rev(rxnsTurnedOn) = 0;
    -0263         mm.lb(rxnsTurnedOn) = 0;
    -0264 
    -0265         essentialRxns = unique([prepData.essentialRxns;mm.rxns(rxnsTurnedOn)]);
    -0266     end
    -0267 
    -0268     
    -0269     fullMipRes = [];
    -0270     lastError = [];
    -0271     if ~isempty(proveAbsGap)
    -0272         %One solve proven to this fixed absolute gap, replacing the relative-gap
    -0273         %escalation below. The escalation's final near-zero-objective run otherwise
    -0274         %accepts an arbitrary within-gap incumbent -- measured on genome-scale
    -0275         %Human-GEM to cost 2.0-4.0 objective units (raven-toolbox's prove_abs_gap,
    -0276         %raven-gecko-parity#104). Pairs naturally with resolveTies.
    -0277         if ~isempty(stp.MILPParams) && isfield(stp.MILPParams{1}, 'TimeLimit')
    -0278             params = struct('TimeLimit', stp.MILPParams{1}.TimeLimit);
    -0279         else
    -0280             params = struct('TimeLimit', 5000);
    -0281         end
    -0282         params.MIPGap = 0;
    -0283         params.MIPGapAbs = proveAbsGap;
    -0284         try
    -0285             [deletedRxnsInINIT1, metProduction,fullMipRes,rxnsTurnedOn1,fluxes1] = ftINITInternalAlg(mm,rxnScores,metData,essentialRxns,5,stp.AllowMetSecr,stp.PosRevOff,params, [], fluxes, verbose, 0.1, resolveTies, rankKeys, pinFluxes);
    -0286             fluxes1(toRev) = -fluxes1(toRev);
    -0287             success = true;
    -0288             if fullMipRes.stat ~= 1
    -0289                 %stat==2 (checkSolution.m): feasible but not proven within the time
    -0290                 %limit -- an unproven incumbent, same caveat as the escalation path
    -0291                 %below, but disclosed since proveAbsGap explicitly asks for a proof.
    -0292                 EM=['ftINIT step ' num2str(initStep) ' hit the time limit before proving proveAbsGap; its kept reactions are an unproven incumbent. Raise TimeLimit, or loosen proveAbsGap.'];
    -0293                 warning('RAVEN:warning', '%s', EM);
    -0294             end
    -0295         catch e
    -0296             success = false;
    -0297             lastError = e;
    -0298         end
    -0299     else
    -0300         mipGap = 1;
    -0301         first = true;
    -0302         success = false;
    -0303         for rn = 1:length(stp.MILPParams)
    -0304             params = stp.MILPParams{rn};
    -0305             if ~isfield(params, 'MIPGap')
    -0306                 params.MIPGap = 0.0004;
    -0307             end
    -0308 
    -0309             if ~isfield(params, 'TimeLimit')
    -0310                 params.TimeLimit = 5000;
    -0311             end
    -0312 
    -0313             if ~first
    -0314                 %There is sometimes a problem with that the objective function becomes close to zero,
    -0315                 %which leads to that a small percentage of that (which is the MIPGap sent in) is very small
    -0316                 %and the MILP hence takes a lot of time to finish. We also therefore use an absolute MIP gap,
    -0317                 %converted to a percentage using the last value of the objective function.
    -0318                 params.MIPGap = min(max(params.MIPGap, stp.AbsMIPGaps{rn}/abs(lastObjVal)),1);
    -0319                 params.seed = 1234;%use another seed, may work better
    -0320 
    -0321                 if mipGap <= params.MIPGap
    -0322                     success = true;
    -0323                     break; %we're done - this will not happen the first time
    -0324                 else
    -0325                     disp(['MipGap too high, trying with a different run. MipGap = ' num2str(mipGap) ' New MipGap Limit = ' num2str(params.MIPGap)])
    -0326                 end
    -0327             end
    -0328 
    -0329             first = false;
    -0330 
    -0331             %now run the MILP
    -0332             try
    -0333                 %The prodweight for metabolomics is set to 5. This value has not
    -0334                 %been evaluated, but is assumed in the test cases - if changed, update the test case
    -0335                 startVals = [];
    -0336                 if ~isempty(fullMipRes)
    -0337                     startVals = fullMipRes.full;
    -0338                 end
    -0339                 [deletedRxnsInINIT1, metProduction,fullMipRes,rxnsTurnedOn1,fluxes1] = ftINITInternalAlg(mm,rxnScores,metData,essentialRxns,5,stp.AllowMetSecr,stp.PosRevOff,params, startVals, fluxes, verbose, 0.1, resolveTies, rankKeys, pinFluxes);
    -0340                 %This is a bit tricky - since we reversed some reactions, those fluxes also need to be reversed
    -0341                 fluxes1(toRev) = -fluxes1(toRev);
    -0342 
    -0343                 mipGap = fullMipRes.mipgap;
    -0344                 lastObjVal = fullMipRes.obj;
    -0345             catch e
    -0346                 mipGap = Inf;
    -0347                 lastObjVal = Inf; %we need to set something here, Inf leads to that this doesn't come into play
    -0348                 lastError = e; %kept so a throw is not reported as a time-limit miss
    -0349             end
    -0350 
    -0351             success = mipGap <= params.MIPGap;
    -0352         end
    -0353     end
    -0354 
    -0355     if ~success
    -0356         if ~isempty(lastError)
    -0357             %The MILP threw rather than returning a poor gap; reporting this
    -0358             %as a time-limit miss sends users off to raise params.TimeLimit
    -0359             %for what is often a solver or input problem.
    -0360             error('RAVEN:badInput', '%s', ['Failed to find good enough solution within the time frame. The last MILP attempt failed with: ' lastError.message]);
    -0361         else
    -0362             error('RAVEN:badInput', '%s', ['Failed to find good enough solution within the time frame. MIPGap: ' num2str(mipGap)]);
    -0363         end
    -0364     end
    -0365     
    -0366     %save the reactions turned on and their fluxes for the next step
    -0367     rxnsTurnedOn = rxnsTurnedOn | rxnsTurnedOn1.';
    -0368     %The fluxes are a bit tricky - what if they change direction between the steps?
    -0369     %The fluxes are used to determine the direction in which reactions are forced on
    -0370     %(to simplify the problem it is good if they are unidirectional).
    -0371     %We use the following strategy:
    -0372     %1. Use the fluxes from the most recent step.
    -0373     %2. If any flux is very low there (i.e. basically zero), use the flux from the previous steps
    -0374     %This could in theory cause problems, but seems to work well practically
    -0375     fluxesOld = fluxes;
    -0376     fluxes = fluxes1;
    -0377 end
    -0378 
    -0379 
    -0380 %get the essential rxns
    -0381 essential = ismember(prepData.minModel.rxns,prepData.essentialRxns);
    -0382 %So, we only add reactions where the linearly merged scores are zero for all linearly dependent reactions
    -0383 % (this cannot happen by chance, taken care of in the function groupRxnScores)
    -0384 rxnsToIgn = rxnScores == 0; 
    -0385 deletedRxnsInINITSel = ~(rxnsTurnedOn | rxnsToIgn | essential);
    -0386 deletedRxnsInINIT = prepData.minModel.rxns(deletedRxnsInINITSel);
    -0387 
    -0388 %Here we need to figure out which original reactions (before the linear merge)
    -0389 %that were removed. These are all reactions with the same group ids as the removed reactions
    -0390 groupIdsRemoved = prepData.groupIds(ismember(prepData.refModel.rxns, deletedRxnsInINIT)); %can improve this slightly, use sel above
    -0391 groupIdsRemoved = groupIdsRemoved(groupIdsRemoved ~= 0);%zero means that the reaction was not grouped, all with zeros are not a group!
    -0392 rxnsToRem = union(prepData.refModel.rxns(ismember(prepData.groupIds,groupIdsRemoved)), deletedRxnsInINIT);%make a union here to include the ungrouped (unmerged) as well
    +0004 % Generates a model from proteomics, transcriptomics, metabolomics and
    +0005 % metabolic tasks, in any combination except metabolomics alone. The function
    +0006 % prepINITModel must be run first on the template model (such as the generic
    +0007 % Human-GEM). It runs once per template and precalculates the parts that are
    +0008 % independent of the omics data. Its output, prepData, is the input to this
    +0009 % function.
    +0010 %
    +0011 % Parameters
    +0012 % ----------
    +0013 % prepData : struct
    +0014 %     the prepData for the model, as returned by prepINITModel.
    +0015 % tissue : char
    +0016 %     tissue to score for. Must exist in hpaData.tissues or
    +0017 %     transcrData.tissues.
    +0018 % celltype : char
    +0019 %     cell type to score for. Must exist in hpaData.celltypes or
    +0020 %     transcrData.celltypes for this tissue. If empty, the maximum value
    +0021 %     among all the cell types of the tissue is used.
    +0022 % hpaData : struct
    +0023 %     HPA data structure from parseHPA (required if transcrData is not
    +0024 %     supplied, default []).
    +0025 %
    +0026 % Name-Value Arguments
    +0027 % --------------------
    +0028 % transcrData : struct
    +0029 %     gene expression data structure (required if hpaData is not supplied,
    +0030 %     default []). With fields:
    +0031 %
    +0032 %     - genes : cell array with the unique gene names.
    +0033 %     - tissues : cell array with the tissue names. The list can contain
    +0034 %       duplicates, as there can be multiple cell types per tissue.
    +0035 %     - celltypes : cell array with the cell type names for each tissue.
    +0036 %     - levels : GENESxTISSUES array with the expression level for each gene in
    +0037 %       each tissue and cell type. NaN marks a gene that was not measured.
    +0038 %     - threshold : a single value or a vector of gene expression thresholds,
    +0039 %       above which genes are considered to be "expressed". If omitted, the
    +0040 %       mean expression level of each gene across all tissues in transcrData
    +0041 %       is used as the threshold.
    +0042 % metabolomicsData : cell
    +0043 %     cell array with metabolite names that the model should produce (default
    +0044 %     []).
    +0045 % INITSteps : cell
    +0046 %     specifies the steps in the algorithm. For more info, see INITStepDesc and
    +0047 %     getINITSteps (default getINITSteps([],'1+1'), the standard ftINIT).
    +0048 % removeGenes : logical
    +0049 %     if true, low-score genes are removed from grRules, unless they
    +0050 %     are the only gene associated with a reaction, or a subunit of an enzyme
    +0051 %     complex (see "removeLowScoreGenes" function for details). If false,
    +0052 %     grRules are not modified; genes that were associated only with removed
    +0053 %     reactions are not present in the final model (default true).
    +0054 % useScoresForTasks : logical
    +0055 %     true if the calculated reaction scores are used as weights when
    +0056 %     fitting to tasks. Scores above -0.1 are set to -0.1 for this step
    +0057 %     (default true).
    +0058 % paramsFT : struct
    +0059 %     solver parameters for the gap-filling step that fits the tasks, passed
    +0060 %     to fitTasks as params (default []).
    +0061 % verbose : logical
    +0062 %     if true, the MILP progression is shown (default false).
    +0063 % resolveTies : logical
    +0064 %     if true, the degenerate MILP of each step is solved to a canonical
    +0065 %     answer in addition to the solver Seed: the score objective is held at
    +0066 %     its optimum, then the count of removable ("on") reactions is
    +0067 %     minimised, then, among the sparsest solutions, their summed
    +0068 %     reaction-id rank is minimised. This applies to every main-extraction
    +0069 %     step and to the final gap-filling step (resolveTies of fitTasks); see
    +0070 %     raven-gecko-parity#104 and resolve_ties in raven-toolbox. In each
    +0071 %     main-extraction step except the last (ftINITInternalAlg), the fluxes
    +0072 %     set the direction and magnitude at which a reaction is forced in the
    +0073 %     next step. They are taken from the least-total-flux solution for that
    +0074 %     on/off state and, among those, from the solution with the least
    +0075 %     id-rank-weighted backward flux (a merged reaction is ranked by the
    +0076 %     smallest original id in its group). The last step skips these flux
    +0077 %     phases, as no later step reads its fluxes. A step whose primary solve
    +0078 %     stops at the time limit keeps its primary solution without any of
    +0079 %     these phases. The count and id-rank phases get half of the step's
    +0080 %     TimeLimit each. A warning names each phase that does not prove optimal
    +0081 %     or finds no solution. Measured with raven-toolbox's ftinit, whose phase
    +0082 %     handling differs from ftINIT, on one cell line (Human-GEM v2.0.0-38,
    +0083 %     seeds 1234 and 7, time limit 1800 s): two seeds with the default
    +0084 %     escalation differ by 110 reactions and 17 predicted essential genes,
    +0085 %     and with resolveTies alone by 41 reactions and 1 gene, at about 3.6
    +0086 %     times the run time (about 14 min against 4 min) (default false).
    +0087 % proveAbsGap : double
    +0088 %     if set, each step is proven to this fixed absolute MILP gap in a single
    +0089 %     solve, replacing the relative-gap escalation in stp.MILPParams. The
    +0090 %     final near-zero-objective run of the escalation accepts an arbitrary
    +0091 %     incumbent within the gap. A warning is raised when a step reaches the
    +0092 %     time limit before the proof. The figures below were measured with
    +0093 %     raven-toolbox's ftinit, whose phase handling differs from ftINIT, on one
    +0094 %     cell line (Human-GEM v2.0.0-38, time limit 1800 s) with resolveTies on
    +0095 %     and without gap-filling, in a sweep over four seeds (1234, 7, 1 and 2).
    +0096 %     The step 1 objective of the escalation ended 4.0 to 12.0 units below the
    +0097 %     proven optimum; with seeds 1234 and 7 alone it ended 7.0 and 12.0
    +0098 %     below. A value of 5.0 stopped 2.0 below the optimum for one seed (seed
    +0099 %     2), and values from 2.0 to 0.05 reached it for all four seeds and gave
    +0100 %     the same models. Every primary solve of the steps (48 solves) ended
    +0101 %     optimal; the flux phases phase2c-minflux (3 of 4 seeds) and
    +0102 %     phase2d-direction (4 of 4 seeds) reached the time limit and were adopted
    +0103 %     as unproven incumbents. With 1.0 and resolveTies the models of two seeds
    +0104 %     differ by 1 reaction and 0 predicted essential genes, at about 26 times
    +0105 %     the run time of the default escalation (about 100 min against 4 min);
    +0106 %     the mean of a whole build was 5766 s at 0.05 and 5641 s at 1.0. The
    +0107 %     study recommends 1.0 together with resolveTies when the variation of
    +0108 %     resolveTies alone is too large and the run time is acceptable. Its
    +0109 %     full-pipeline runs used a time limit of 600 s per step and its
    +0110 %     determinism builds 1800 s (default [], which uses the escalation).
    +0111 %
    +0112 % Returns
    +0113 % -------
    +0114 % model : struct
    +0115 %     the resulting model structure.
    +0116 % metProduction : logical
    +0117 %     array from the last MILP step that indicates which of the metabolites in
    +0118 %     metabolomicsData could be produced (true) or not (false). Metabolites
    +0119 %     with a producing reaction that is essential are not part of the MILP
    +0120 %     and are not in the array. The array is determined before the
    +0121 %     gap-filling for the defined tasks. To see which metabolites can be
    +0122 %     produced in the final model, use canExchange.
    +0123 % addedRxnsForTasks : cell
    +0124 %     cell array of the reactions which were added in order to perform the
    +0125 %     tasks.
    +0126 % deletedRxnsInINIT : cell
    +0127 %     cell array of the reactions of the template model that are not in the
    +0128 %     final model, excluding exchange reactions (these are all kept). It
    +0129 %     contains the reactions removed by the MILP steps that gap-filling did
    +0130 %     not add back.
    +0131 % fullMipRes : struct
    +0132 %     the solver results from the last MILP step run.
    +0133 %
    +0134 % Notes
    +0135 % -----
    +0136 % The template model keeps its exchange metabolites,
    +0137 % since checkTasks and fitTasks put specific constraints on them for each
    +0138 % task. The INIT algorithm removes exchange metabolites if any are
    +0139 % present. Use importModel(file,false) to import a model with exchange
    +0140 % metabolites remaining.
    +0141 %
    +0142 % ftINIT passes a prodWeight of 5 (the reward for each produced metabolite of
    +0143 % metabolomicsData) to ftINITInternalAlg, whose own default is 0.5.
    +0144 %
    +0145 
    +0146 p=parseRAVENargs(varargin, {'transcrData',[]; 'metabolomicsData',[]; 'INITSteps',[]; 'removeGenes',[]; 'useScoresForTasks',[]; 'paramsFT',[]; 'verbose',false; 'resolveTies',false; 'proveAbsGap',[]});
    +0147 transcrData=p.transcrData;
    +0148 metabolomicsData=p.metabolomicsData;
    +0149 INITSteps=p.INITSteps;
    +0150 removeGenes=p.removeGenes;
    +0151 useScoresForTasks=p.useScoresForTasks;
    +0152 paramsFT=p.paramsFT;
    +0153 verbose=p.verbose;
    +0154 resolveTies=p.resolveTies;
    +0155 proveAbsGap=p.proveAbsGap;
    +0156 if isempty(INITSteps)
    +0157     INITSteps = getINITSteps([],'1+1');
    +0158 end
    +0159 if isempty(removeGenes)
    +0160     removeGenes = true;
    +0161 end
    +0162 if isempty(useScoresForTasks)
    +0163     useScoresForTasks = true;
    +0164 end
    +0165 %Handle detected mets:
    +0166 %Giving a bonus for secreting these metabolites does not work, since the
    +0167 %metabolite secretion and uptake can be lost when we merge linearly dependent
    +0168 %reactions. Instead, we need to figure out which reactions either produce or
    +0169 %take up the mets, and give a bonus if any of them carry flux. To simplify
    +0170 %things, we focus on reactions that produce the metabolite (since there must
    +0171 %be one such reaction). We further reason that the direction does not matter -
    +0172 %we can force one of these reactions in any direction - if it becomes a consumer, it will
    +0173 %automatically force another producer on as well (otherwise we will have a net consumption).
    +0174 
    +0175 if (~isempty(metabolomicsData))
    +0176     if length(unique(upper(metabolomicsData))) ~= length(metabolomicsData)
    +0177         error('RAVEN:badInput', '%s', 'Metabolomics contains the same metabolite multiple times');
    +0178     end
    +0179     metData = false(numel(metabolomicsData), length(prepData.minModel.rxns)); %one row per metabolite that is a boolean vector
    +0180     for i=1:numel(metabolomicsData)
    +0181         %Get the matching mets
    +0182         metSel = ismember(upper(prepData.refModel.metNames),upper(metabolomicsData{i}));
    +0183         prodRxnsSel = any(prepData.refModel.S(metSel,:) > 0,1) | ... %direct producers
    +0184                      (any(prepData.refModel.S(metSel,:) < 0,1) & prepData.refModel.rev.'); %reversible reactions that are consumers
    +0185         %convert the production rxns from refModel to minModel
    +0186         prepData.groupIds;
    +0187         [~,ia,ib] = intersect(prepData.minModel.rxns,prepData.refModel.rxns);
    +0188         grpIdsMerged = nan(length(prepData.minModel.rxns),1);
    +0189         grpIdsMerged(ia) = prepData.groupIds(ib);
    +0190         
    +0191         groupIdsPos = unique(prepData.groupIds(prodRxnsSel));%gets all group ids which includes a production rxn
    +0192         groupIdsPos = groupIdsPos(groupIdsPos ~= 0);%remove the 0 id, it means there is no group
    +0193         %the other option is that there is a direct match between the rxn id in minModel and refModel:
    +0194         posRxns = prepData.refModel.rxns(prodRxnsSel);
    +0195         directMatch = ismember(prepData.minModel.rxns, posRxns).';
    +0196         
    +0197         metData(i,:) = ismember(grpIdsMerged, groupIdsPos).' | directMatch;
    +0198     end
    +0199     metData = sparse(metData);
    +0200 else
    +0201     metData = [];
    +0202 end
    +0203 
    +0204 % Get rxn scores and adapt them to the minimized model
    +0205 origRxnScores = scoreModel(prepData.refModel,hpaData,transcrData,tissue,celltype);
    +0206 origRxnScores(origRxnScores > -0.1 & origRxnScores <= 0) = -0.1;%we don't want reaction scores that are exactly 0 (or close), this causes problems in the milp
    +0207 origRxnScores(origRxnScores < 0.1 & origRxnScores > 0) = 0.1;
    +0208 
    +0209 rxnsTurnedOn = false(length(prepData.minModel.rxns),1);
    +0210 fluxes = zeros(length(prepData.minModel.rxns),1);
    +0211 
    +0212 rxnsToIgnoreLastStep = [1;1;1;1;1;1;1;1];
    +0213 
    +0214 %We assume that all essential rxns are irrev - this is taken care of in
    +0215 %prepINITModel. We then use an initial flux "from last run" of 0.1 for all
    +0216 %reactions. This is used for knowing what flux should be forced through an
    +0217 %essential rxn.
    +0218 fluxes = ones(length(prepData.minModel.rxns), 1).*0.1;
    +0219 
    +0220 %resolveTies ranks a merged reaction by its group's smallest original id,
    +0221 %which does not depend on which member names the merged reaction.
    +0222 rankKeys = prepData.minModel.rxns;
    +0223 [inRef, refInd] = ismember(rankKeys, prepData.refModel.rxns);
    +0224 for i = find(inRef)'
    +0225     g = prepData.groupIds(refInd(i));
    +0226     if g ~= 0
    +0227         members = sort(prepData.refModel.rxns(prepData.groupIds == g));
    +0228         rankKeys{i} = members{1};
    +0229     end
    +0230 end
    +0231 
    +0232 for initStep = 1:length(INITSteps)
    +0233     disp(['ftINIT: Running step ' num2str(initStep)])
    +0234     stp = INITSteps{initStep};
    +0235     
    +0236     if any ((rxnsToIgnoreLastStep - stp.RxnsToIgnoreMask) < 0)
    +0237         error('RAVEN:badInput', '%s', 'RxnsToIgnoreMask may not cover rxns not covered in previous steps, but the other way around is fine.');
    +0238     end
    +0239     rxnsToIgnoreLastStep = stp.RxnsToIgnoreMask;
    +0240     %The fluxes of a step set the forced directions of the next one, so the
    +0241     %last step skips the flux phases of resolveTies.
    +0242     pinFluxes = initStep < numel(INITSteps);
    +0243     
    +0244     mm = prepData.minModel;
    +0245     
    +0246     if (~isempty(stp.MetsToIgnore))
    +0247         if (~isempty(stp.MetsToIgnore.simpleMets))
    +0248             %Here, we remove simple metabolites that will not really affect the milp but
    +0249             %are very common in the S matrix. For example H2O, H+, etc.
    +0250             %It is also possible to leave compartments untouched, for example the i compartment in the mitochondria (for H+).
    +0251             metsToRem = ismember(mm.metNames,stp.MetsToIgnore.simpleMets.mets);
    +0252             compsToKeep = find(ismember(mm.comps, stp.MetsToIgnore.simpleMets.compsToKeep));
    +0253             metsToRem = metsToRem & ~ismember(mm.metComps, compsToKeep);
    +0254             mm.S(metsToRem,:) = 0;
    +0255         end    
    +0256     end
    +0257 
    +0258     %Set up the reaction scores and essential rxns
    +0259     rxnsToIgnore = getRxnsFromPattern(stp.RxnsToIgnoreMask, prepData);
    +0260     rxnScores = groupRxnScores(prepData.minModel, origRxnScores, prepData.refModel.rxns, prepData.groupIds, rxnsToIgnore);
    +0261 
    +0262     essentialRxns = prepData.essentialRxns;
    +0263     toRev = false(numel(mm.rxns),1);
    +0264     %Handle the results from previous steps ("ignore", "exclude", "essential")
    +0265     if strcmp(stp.HowToUsePrevResults, 'exclude')
    +0266         rxnScores(rxnsTurnedOn) = 0; %This is not used anymore in any step setup.
    +0267     elseif strcmp(stp.HowToUsePrevResults, 'essential')
    +0268         %Make all reversible reactions turned on in previous steps reversible
    +0269         %in the direction that they were previously carrying flux
    +0270         
    +0271         %first reverse the reactions that need to be reversed
    +0272         rev = mm.rev == 1;
    +0273         toRev = rxnsTurnedOn & rev & fluxes < 0;
    +0274         mm = reverseRxns(mm, mm.rxns(toRev));
    +0275         
    +0276         %Then make them irreversible
    +0277         mm.rev(rxnsTurnedOn) = 0;
    +0278         mm.lb(rxnsTurnedOn) = 0;
    +0279 
    +0280         essentialRxns = unique([prepData.essentialRxns;mm.rxns(rxnsTurnedOn)]);
    +0281     end
    +0282 
    +0283     
    +0284     fullMipRes = [];
    +0285     lastError = [];
    +0286     if ~isempty(proveAbsGap)
    +0287         %One solve proven to this fixed absolute gap, replacing the relative-gap
    +0288         %escalation below. The escalation's final near-zero-objective run otherwise
    +0289         %accepts an arbitrary within-gap incumbent -- measured on genome-scale
    +0290         %Human-GEM to cost 2.0-4.0 objective units (raven-toolbox's prove_abs_gap,
    +0291         %raven-gecko-parity#104). Pairs naturally with resolveTies.
    +0292         if ~isempty(stp.MILPParams) && isfield(stp.MILPParams{1}, 'TimeLimit')
    +0293             params = struct('TimeLimit', stp.MILPParams{1}.TimeLimit);
    +0294         else
    +0295             params = struct('TimeLimit', 5000);
    +0296         end
    +0297         params.MIPGap = 0;
    +0298         params.MIPGapAbs = proveAbsGap;
    +0299         try
    +0300             [deletedRxnsInINIT1, metProduction,fullMipRes,rxnsTurnedOn1,fluxes1] = ftINITInternalAlg(mm,rxnScores,metData,essentialRxns,5,stp.AllowMetSecr,stp.PosRevOff,params, [], fluxes, verbose, 0.1, resolveTies, rankKeys, pinFluxes);
    +0301             fluxes1(toRev) = -fluxes1(toRev);
    +0302             success = true;
    +0303             if fullMipRes.stat ~= 1
    +0304                 %stat==2 (checkSolution.m): feasible but not proven within the time
    +0305                 %limit -- an unproven incumbent, same caveat as the escalation path
    +0306                 %below, but disclosed since proveAbsGap explicitly asks for a proof.
    +0307                 EM=['ftINIT step ' num2str(initStep) ' hit the time limit before proving proveAbsGap; its kept reactions are an unproven incumbent. Raise TimeLimit, or loosen proveAbsGap.'];
    +0308                 warning('RAVEN:warning', '%s', EM);
    +0309             end
    +0310         catch e
    +0311             success = false;
    +0312             lastError = e;
    +0313         end
    +0314     else
    +0315         mipGap = 1;
    +0316         first = true;
    +0317         success = false;
    +0318         for rn = 1:length(stp.MILPParams)
    +0319             params = stp.MILPParams{rn};
    +0320             if ~isfield(params, 'MIPGap')
    +0321                 params.MIPGap = 0.0004;
    +0322             end
    +0323 
    +0324             if ~isfield(params, 'TimeLimit')
    +0325                 params.TimeLimit = 5000;
    +0326             end
    +0327 
    +0328             if ~first
    +0329                 %There is sometimes a problem with that the objective function becomes close to zero,
    +0330                 %which leads to that a small percentage of that (which is the MIPGap sent in) is very small
    +0331                 %and the MILP hence takes a lot of time to finish. We also therefore use an absolute MIP gap,
    +0332                 %converted to a percentage using the last value of the objective function.
    +0333                 params.MIPGap = min(max(params.MIPGap, stp.AbsMIPGaps{rn}/abs(lastObjVal)),1);
    +0334                 params.seed = 1234;%use another seed, may work better
    +0335 
    +0336                 if mipGap <= params.MIPGap
    +0337                     success = true;
    +0338                     break; %we're done - this will not happen the first time
    +0339                 else
    +0340                     disp(['MipGap too high, trying with a different run. MipGap = ' num2str(mipGap) ' New MipGap Limit = ' num2str(params.MIPGap)])
    +0341                 end
    +0342             end
    +0343 
    +0344             first = false;
    +0345 
    +0346             %now run the MILP
    +0347             try
    +0348                 %The prodweight for metabolomics is set to 5. This value has not
    +0349                 %been evaluated, but is assumed in the test cases - if changed, update the test case
    +0350                 startVals = [];
    +0351                 if ~isempty(fullMipRes)
    +0352                     startVals = fullMipRes.full;
    +0353                 end
    +0354                 [deletedRxnsInINIT1, metProduction,fullMipRes,rxnsTurnedOn1,fluxes1] = ftINITInternalAlg(mm,rxnScores,metData,essentialRxns,5,stp.AllowMetSecr,stp.PosRevOff,params, startVals, fluxes, verbose, 0.1, resolveTies, rankKeys, pinFluxes);
    +0355                 %This is a bit tricky - since we reversed some reactions, those fluxes also need to be reversed
    +0356                 fluxes1(toRev) = -fluxes1(toRev);
    +0357 
    +0358                 mipGap = fullMipRes.mipgap;
    +0359                 lastObjVal = fullMipRes.obj;
    +0360             catch e
    +0361                 mipGap = Inf;
    +0362                 lastObjVal = Inf; %we need to set something here, Inf leads to that this doesn't come into play
    +0363                 lastError = e; %kept so a throw is not reported as a time-limit miss
    +0364             end
    +0365 
    +0366             success = mipGap <= params.MIPGap;
    +0367         end
    +0368     end
    +0369 
    +0370     if ~success
    +0371         if ~isempty(lastError)
    +0372             %The MILP threw rather than returning a poor gap; reporting this
    +0373             %as a time-limit miss sends users off to raise params.TimeLimit
    +0374             %for what is often a solver or input problem.
    +0375             error('RAVEN:badInput', '%s', ['Failed to find good enough solution within the time frame. The last MILP attempt failed with: ' lastError.message]);
    +0376         else
    +0377             error('RAVEN:badInput', '%s', ['Failed to find good enough solution within the time frame. MIPGap: ' num2str(mipGap)]);
    +0378         end
    +0379     end
    +0380     
    +0381     %save the reactions turned on and their fluxes for the next step
    +0382     rxnsTurnedOn = rxnsTurnedOn | rxnsTurnedOn1.';
    +0383     %The fluxes are a bit tricky - what if they change direction between the steps?
    +0384     %The fluxes are used to determine the direction in which reactions are forced on
    +0385     %(to simplify the problem it is good if they are unidirectional).
    +0386     %We use the following strategy:
    +0387     %1. Use the fluxes from the most recent step.
    +0388     %2. If any flux is very low there (i.e. basically zero), use the flux from the previous steps
    +0389     %This could in theory cause problems, but seems to work well practically
    +0390     fluxesOld = fluxes;
    +0391     fluxes = fluxes1;
    +0392 end
     0393 
    -0394 initModel = removeReactions(prepData.refModel,rxnsToRem,false,true);
    -0395 
    -0396 % remove metabolites separately to avoid removing those needed for tasks
    -0397 unusedMets = initModel.mets(all(initModel.S == 0,2));
    -0398 initModel = removeMets(initModel, setdiff(unusedMets, prepData.essentialMetsForTasks));
    -0399 
    -0400 %if printReport == true
    -0401 %    printScores(initModel,"INIT model statistics",hpaData,transcrData,tissue,celltype);
    -0402 %    printScores(removeReactions(cModel,setdiff(cModel.rxns,rxnsToRem),true,true),"Reactions deleted by INIT",hpaData,transcrData,tissue,celltype);
    -0403 %end
    -0404 
    -0405 %The full model has exchange reactions in it. fitTasks with gapFillMode
    -0406 %'preMerged' calls ftINITFillGaps, which automatically removes exchange
    -0407 %metabolites (because it
    -0408 %assumes that the reactions are constrained when appropriate). In this case the
    -0409 %uptakes/outputs are retrieved from the task sheet instead. To prevent
    -0410 %exchange reactions being used to fill gaps, they are deleted from the
    -0411 %reference model here.
    -0412 initModel.id = 'INITModel';
    -0413 
    -0414 %If gaps in the model should be filled using a task list
    -0415 if ~isempty(prepData.taskStruct)
    -0416     %Remove exchange reactions and reactions already included in the INIT
    -0417     %model. Exchange rxns that are essential are kept.
    -0418 
    -0419     %At this stage the model is fully connected and most of the genes with
    -0420     %good scores should have been included. The final gap-filling should
    -0421     %take the scores of the genes into account, so that "rather bad"
    -0422     %reactions are preferred to "very bad" reactions. However, reactions
    -0423     %with positive scores will be included even if they are not connected
    -0424     %in the current formulation. Therefore, such reactions will have to be
    -0425     %assigned a small negative score instead.
    -0426     exchRxns = getExchangeRxns(prepData.refModel);
    -0427     refModelNoExc = removeReactions(prepData.refModelWithBM,exchRxns,false,true);
    -0428     exchRxns = getExchangeRxns(initModel);
    -0429     initModelNoExc = removeReactions(closeModel(initModel),exchRxns,false,true);
    -0430     
    -0431     if useScoresForTasks == true
    -0432         %map the rxn scores to the model without exchange rxns
    -0433         [~,ia,ib] = intersect(refModelNoExc.rxns,prepData.refModel.rxns);
    -0434         rxnScores2nd = NaN(length(refModelNoExc.rxns),1);
    -0435         rxnScores2nd(ia) = origRxnScores(ib);
    -0436         %all(rxnScores2nd == refRxnScores);%should be the same, ok!
    -0437         [outModel,addedRxnMat] = fitTasks(initModelNoExc,refModelNoExc,[], ...
    -0438             'printOutput',true,'rxnScores',min(rxnScores2nd,-0.1), ...
    -0439             'taskStructure',prepData.taskStruct,'gapFillMode','preMerged', ...
    -0440             'params',paramsFT,'verbose',verbose,'resolveTies',resolveTies);
    -0441     else
    -0442         [outModel,addedRxnMat] = fitTasks(initModelNoExc,refModelNoExc,[], ...
    -0443             'printOutput',true,'rxnScores',[], ...
    -0444             'taskStructure',prepData.taskStruct,'gapFillMode','preMerged', ...
    -0445             'params',paramsFT,'verbose',verbose,'resolveTies',resolveTies);
    -0446     end
    -0447     %if printReport == true
    -0448     %    printScores(outModel,"Functional model statistics",hpaData,transcrData,tissue,celltype);
    -0449     %    printScores(removeReactions(outModel,intersect(outModel.rxns,initModel.rxns),true,true),"Reactions added to perform the tasks",hpaData,transcrData,tissue,celltype);
    -0450     %end
    -0451     
    -0452     addedRxnsForTasks = refModelNoExc.rxns(any(addedRxnMat,2));
    -0453 else
    -0454     outModel = initModel;
    -0455     addedRxnMat = [];
    -0456     addedRxnsForTasks = {};
    -0457 end
    -0458 
    -0459 % The model can now perform all the tasks defined in the task list.
    -0460 model = outModel;
    -0461 
    -0462 
    -0463 % At this stage the model will contain some exchange reactions but probably
    -0464 % not all (and maybe zero). This can be inconvenient, so all exchange
    -0465 % reactions from the reference model are added, except for those which
    -0466 % involve metabolites that are not in the model.
    -0467 
    -0468 %Start from the original model, and just remove the reactions that are no longer there (and keep exchange rxns). The model we got out
    -0469 %from the problem is not complete, it does not have GRPs etc.
    -0470 %The logic below is a bit complicated. We identify the reactions that should be removed from the full model as
    -0471 %reactions that have been removed in the init model except the ones that were added back. In addition, we make
    -0472 %sure that no exchange rxns are removed - they can be removed in the init model if they were linearly merged with other
    -0473 %reactions that were decided to be removed from the model. We want to keep all exchange rxns to make sure the tasks can
    -0474 %be performed also without manipulating the b vector in the model (which is what is done in the gap-filling).
    -0475 exchRxns = getExchangeRxns(prepData.refModel);
    -0476 deletedRxnsInINIT = setdiff(prepData.refModel.rxns,union(union(initModel.rxns, addedRxnsForTasks), exchRxns));
    -0477 outModel = removeReactions(prepData.refModel, deletedRxnsInINIT, true, true);
    -0478 
    -0479 % If requested, attempt to remove negative-score genes from the model,
    -0480 % depending on their role (isozyme or complex subunit) in each grRule.
    -0481 % See the "removeLowScoreGenes" function more more details, and to adjust
    -0482 % any default parameters therein.
    -0483 if ( removeGenes )
    -0484     [~, geneScores] = scoreModel(outModel,hpaData,transcrData,tissue,celltype);
    -0485     outModel = removeLowScoreGenes(outModel,geneScores);
    -0486 end
    -0487 
    -0488 
    -0489 model = outModel;
    -0490 
    -0491 end
    -0492 
    -0493 %This is for printing a summary of a model
    -0494 function [rxnS, geneS] = printScores(model,name,hpaData,transcrData,tissue,celltype)
    -0495     [a, b] = scoreModel(model,hpaData,transcrData,tissue,celltype);
    -0496     rxnS = mean(a);
    -0497     geneS = mean(b,'omitnan');
    -0498     fprintf([name ':\n']);
    -0499     fprintf(['\t' num2str(numel(model.rxns)) ' reactions, ' num2str(numel(model.genes)) ' genes\n']);
    -0500     fprintf(['\tMean reaction score: ' num2str(rxnS) '\n']);
    -0501     fprintf(['\tMean gene score: ' num2str(geneS) '\n']);
    -0502     fprintf(['\tReactions with positive scores: ' num2str(100*sum(a>0)/numel(a)) '%%\n\n']);
    -0503 end
    -0504 
    -0505 function rxnsToIgnore = getRxnsFromPattern(rxnsToIgnorePattern, prepData)
    -0506     rxnsToIgnore = false(length(prepData.toIgnoreExch),1);
    -0507     if rxnsToIgnorePattern(1) rxnsToIgnore = rxnsToIgnore | prepData.toIgnoreExch; end;
    -0508     if rxnsToIgnorePattern(2) rxnsToIgnore = rxnsToIgnore | prepData.toIgnoreImportRxns; end;
    -0509     if rxnsToIgnorePattern(3) rxnsToIgnore = rxnsToIgnore | prepData.toIgnoreSimpleTransp; end;
    -0510     if rxnsToIgnorePattern(4) rxnsToIgnore = rxnsToIgnore | prepData.toIgnoreAdvTransp; end;
    -0511     if rxnsToIgnorePattern(5) rxnsToIgnore = rxnsToIgnore | prepData.toIgnoreSpont; end;
    -0512     if rxnsToIgnorePattern(6) rxnsToIgnore = rxnsToIgnore | prepData.toIgnoreS; end;
    -0513     if rxnsToIgnorePattern(7) rxnsToIgnore = rxnsToIgnore | prepData.toIgnoreCustomRxns; end;
    -0514     if rxnsToIgnorePattern(8) rxnsToIgnore = rxnsToIgnore | prepData.toIgnoreAllWithoutGPRs; end;
    -0515 end
    -0516

    +0394 +0395 %get the essential rxns +0396 essential = ismember(prepData.minModel.rxns,prepData.essentialRxns); +0397 %So, we only add reactions where the linearly merged scores are zero for all linearly dependent reactions +0398 % (this cannot happen by chance, taken care of in the function groupRxnScores) +0399 rxnsToIgn = rxnScores == 0; +0400 deletedRxnsInINITSel = ~(rxnsTurnedOn | rxnsToIgn | essential); +0401 deletedRxnsInINIT = prepData.minModel.rxns(deletedRxnsInINITSel); +0402 +0403 %Here we need to figure out which original reactions (before the linear merge) +0404 %that were removed. These are all reactions with the same group ids as the removed reactions +0405 groupIdsRemoved = prepData.groupIds(ismember(prepData.refModel.rxns, deletedRxnsInINIT)); %can improve this slightly, use sel above +0406 groupIdsRemoved = groupIdsRemoved(groupIdsRemoved ~= 0);%zero means that the reaction was not grouped, all with zeros are not a group! +0407 rxnsToRem = union(prepData.refModel.rxns(ismember(prepData.groupIds,groupIdsRemoved)), deletedRxnsInINIT);%make a union here to include the ungrouped (unmerged) as well +0408 +0409 initModel = removeReactions(prepData.refModel,rxnsToRem,false,true); +0410 +0411 % remove metabolites separately to avoid removing those needed for tasks +0412 unusedMets = initModel.mets(all(initModel.S == 0,2)); +0413 initModel = removeMets(initModel, setdiff(unusedMets, prepData.essentialMetsForTasks)); +0414 +0415 %if printReport == true +0416 % printScores(initModel,"INIT model statistics",hpaData,transcrData,tissue,celltype); +0417 % printScores(removeReactions(cModel,setdiff(cModel.rxns,rxnsToRem),true,true),"Reactions deleted by INIT",hpaData,transcrData,tissue,celltype); +0418 %end +0419 +0420 %The full model has exchange reactions in it. fitTasks with gapFillMode +0421 %'preMerged' calls ftINITFillGaps, which automatically removes exchange +0422 %metabolites (because it +0423 %assumes that the reactions are constrained when appropriate). In this case the +0424 %uptakes/outputs are retrieved from the task sheet instead. To prevent +0425 %exchange reactions being used to fill gaps, they are deleted from the +0426 %reference model here. +0427 initModel.id = 'INITModel'; +0428 +0429 %If gaps in the model should be filled using a task list +0430 if ~isempty(prepData.taskStruct) +0431 %Remove exchange reactions and reactions already included in the INIT +0432 %model. Exchange rxns that are essential are kept. +0433 +0434 %At this stage the model is fully connected and most of the genes with +0435 %good scores should have been included. The final gap-filling should +0436 %take the scores of the genes into account, so that "rather bad" +0437 %reactions are preferred to "very bad" reactions. However, reactions +0438 %with positive scores will be included even if they are not connected +0439 %in the current formulation. Therefore, such reactions will have to be +0440 %assigned a small negative score instead. +0441 exchRxns = getExchangeRxns(prepData.refModel); +0442 refModelNoExc = removeReactions(prepData.refModelWithBM,exchRxns,false,true); +0443 exchRxns = getExchangeRxns(initModel); +0444 initModelNoExc = removeReactions(closeModel(initModel),exchRxns,false,true); +0445 +0446 if useScoresForTasks == true +0447 %map the rxn scores to the model without exchange rxns +0448 [~,ia,ib] = intersect(refModelNoExc.rxns,prepData.refModel.rxns); +0449 rxnScores2nd = NaN(length(refModelNoExc.rxns),1); +0450 rxnScores2nd(ia) = origRxnScores(ib); +0451 %all(rxnScores2nd == refRxnScores);%should be the same, ok! +0452 [outModel,addedRxnMat] = fitTasks(initModelNoExc,refModelNoExc,[], ... +0453 'printOutput',true,'rxnScores',min(rxnScores2nd,-0.1), ... +0454 'taskStructure',prepData.taskStruct,'gapFillMode','preMerged', ... +0455 'params',paramsFT,'verbose',verbose,'resolveTies',resolveTies); +0456 else +0457 [outModel,addedRxnMat] = fitTasks(initModelNoExc,refModelNoExc,[], ... +0458 'printOutput',true,'rxnScores',[], ... +0459 'taskStructure',prepData.taskStruct,'gapFillMode','preMerged', ... +0460 'params',paramsFT,'verbose',verbose,'resolveTies',resolveTies); +0461 end +0462 %if printReport == true +0463 % printScores(outModel,"Functional model statistics",hpaData,transcrData,tissue,celltype); +0464 % printScores(removeReactions(outModel,intersect(outModel.rxns,initModel.rxns),true,true),"Reactions added to perform the tasks",hpaData,transcrData,tissue,celltype); +0465 %end +0466 +0467 addedRxnsForTasks = refModelNoExc.rxns(any(addedRxnMat,2)); +0468 else +0469 outModel = initModel; +0470 addedRxnMat = []; +0471 addedRxnsForTasks = {}; +0472 end +0473 +0474 % The model can now perform all the tasks defined in the task list. +0475 model = outModel; +0476 +0477 +0478 % At this stage the model will contain some exchange reactions but probably +0479 % not all (and maybe zero). This can be inconvenient, so all exchange +0480 % reactions from the reference model are added, except for those which +0481 % involve metabolites that are not in the model. +0482 +0483 %Start from the original model, and just remove the reactions that are no longer there (and keep exchange rxns). The model we got out +0484 %from the problem is not complete, it does not have GRPs etc. +0485 %The logic below is a bit complicated. We identify the reactions that should be removed from the full model as +0486 %reactions that have been removed in the init model except the ones that were added back. In addition, we make +0487 %sure that no exchange rxns are removed - they can be removed in the init model if they were linearly merged with other +0488 %reactions that were decided to be removed from the model. We want to keep all exchange rxns to make sure the tasks can +0489 %be performed also without manipulating the b vector in the model (which is what is done in the gap-filling). +0490 exchRxns = getExchangeRxns(prepData.refModel); +0491 deletedRxnsInINIT = setdiff(prepData.refModel.rxns,union(union(initModel.rxns, addedRxnsForTasks), exchRxns)); +0492 outModel = removeReactions(prepData.refModel, deletedRxnsInINIT, true, true); +0493 +0494 % If requested, attempt to remove negative-score genes from the model, +0495 % depending on their role (isozyme or complex subunit) in each grRule. +0496 % See the "removeLowScoreGenes" function more more details, and to adjust +0497 % any default parameters therein. +0498 if ( removeGenes ) +0499 [~, geneScores] = scoreModel(outModel,hpaData,transcrData,tissue,celltype); +0500 outModel = removeLowScoreGenes(outModel,geneScores); +0501 end +0502 +0503 +0504 model = outModel; +0505 +0506 end +0507 +0508 %This is for printing a summary of a model +0509 function [rxnS, geneS] = printScores(model,name,hpaData,transcrData,tissue,celltype) +0510 [a, b] = scoreModel(model,hpaData,transcrData,tissue,celltype); +0511 rxnS = mean(a); +0512 geneS = mean(b,'omitnan'); +0513 fprintf([name ':\n']); +0514 fprintf(['\t' num2str(numel(model.rxns)) ' reactions, ' num2str(numel(model.genes)) ' genes\n']); +0515 fprintf(['\tMean reaction score: ' num2str(rxnS) '\n']); +0516 fprintf(['\tMean gene score: ' num2str(geneS) '\n']); +0517 fprintf(['\tReactions with positive scores: ' num2str(100*sum(a>0)/numel(a)) '%%\n\n']); +0518 end +0519 +0520 function rxnsToIgnore = getRxnsFromPattern(rxnsToIgnorePattern, prepData) +0521 rxnsToIgnore = false(length(prepData.toIgnoreExch),1); +0522 if rxnsToIgnorePattern(1) rxnsToIgnore = rxnsToIgnore | prepData.toIgnoreExch; end; +0523 if rxnsToIgnorePattern(2) rxnsToIgnore = rxnsToIgnore | prepData.toIgnoreImportRxns; end; +0524 if rxnsToIgnorePattern(3) rxnsToIgnore = rxnsToIgnore | prepData.toIgnoreSimpleTransp; end; +0525 if rxnsToIgnorePattern(4) rxnsToIgnore = rxnsToIgnore | prepData.toIgnoreAdvTransp; end; +0526 if rxnsToIgnorePattern(5) rxnsToIgnore = rxnsToIgnore | prepData.toIgnoreSpont; end; +0527 if rxnsToIgnorePattern(6) rxnsToIgnore = rxnsToIgnore | prepData.toIgnoreS; end; +0528 if rxnsToIgnorePattern(7) rxnsToIgnore = rxnsToIgnore | prepData.toIgnoreCustomRxns; end; +0529 if rxnsToIgnorePattern(8) rxnsToIgnore = rxnsToIgnore | prepData.toIgnoreAllWithoutGPRs; end; +0530 end +0531
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/INIT/ftINITInternalAlg.html b/docs/html/INIT/ftINITInternalAlg.html index 467463be..3089d03b 100644 --- a/docs/html/INIT/ftINITInternalAlg.html +++ b/docs/html/INIT/ftINITInternalAlg.html @@ -28,56 +28,62 @@

    SYNOPSIS ^DESCRIPTION ^

     ftINITInternalAlg
    -    This function runs the MILP for a step in ftINIT.
    +    Run the MILP for one step of ftINIT.
     
        model           a reference model structure
        rxnScores       a vector of scores for the reactions in the model.
                        Positive scores are reactions to keep and negative
    -                   scores are reactions to exclude. Rxns set to 0 are excluded
    -                   from the problem.
    +                   scores are reactions to exclude. Reactions with score 0
    +                   are not part of the problem
        metData         boolean matrix with mets as rows and rxns as columns
                        saying which reaction produces each detected met (optional, default [])
        essentialRxns   cell array of reactions that are essential and that
    -                   have to be in the resulting model. This is normally
    -                   used when fitting a model to task (see fitTasks)
    -   prodWeight      a score that determines the value of having
    -                   net-production of metabolites. This is a way of having
    -                   a more functional network as it provides a reason for
    -                   including bad reactions for connectivity reasons. This
    -                   score is for each metabolite, and the sum of these weights
    -                   and the scores for the reactions is what is optimized
    -   allowExcretion  true if excretion of all metabolites should be allowed.
    +                   have to be in the resulting model. ftINIT passes the
    +                   reactions that the tasks require (see fitTasks)
    +   prodWeight      a reward for each detected metabolite that has at least
    +                   one producing reaction turned on; net production is not
    +                   checked. The sum of these rewards and the scores for the
    +                   reactions is optimised. The reward gives a reason for
    +                   including reactions with a negative score for
    +                   connectivity reasons, which gives a more functional
    +                   network (default 0.5)
    +   allowExcretion  true if excretion of all metabolites is allowed.
                        This results in fewer reactions being considered
    -                   dead-ends, but all reactions in the resulting model may
    -                   not be able to carry flux. If this is "false" then the
    +                   dead-ends, but not every reaction in the resulting model
    +                   can carry flux. If this is "false" then the
                        equality constraints are taken from model.b. If the
    -                   input model lacks exchange reactions then this should
    -                   probably be "true", or a large proportion of the model
    -                   would be excluded for connectivity reasons
    +                   input model lacks exchange reactions, set it to true;
    +                   with false a large part of the model is excluded for
    +                   connectivity reasons
        remPosRev       If true, the positive reversible reactions are removed from the problem.
                        This is used in step 1 of ftINIT
        params          parameters for the MILP, for example MIPGap and TimeLimit
    -   startVals       Start values for the MILP, typically used when rerunning
    -                   with a higher MIPGap, to use the results from the previous
    -                   run
    -   fluxes          Fluxes from the last run.
    -   verbose         If true, the MILP progression will be shown.
    +   startVals       Start values for the MILP. ftINIT passes the result of
    +                   the previous run when it reruns the MILP with a higher
    +                   MIPGap
    +   fluxes          Fluxes from the last run. The flux of an essential
    +                   irreversible reaction is forced to at least
    +                   min(0.99*|flux|, 0.1), clamped to the reaction's capacity
    +                   in the forced direction (its upper bound, as ftINIT
    +                   passes reactions in the forward direction)
    +   verbose         If true, the MILP progression is shown.
        forceOn         minimum flux magnitude enforced through a reaction once
                        it is turned on in the MILP (optional, default 0.1)
    -   resolveTies     if true, pin the MILP's degenerate optimum to a canonical
    -                   answer instead of relying on the solver Seed alone: hold
    -                   the score objective at its optimum, then minimise the
    -                   count of "on" removable (negative-score) reactions, then,
    -                   among the sparsest, minimise their summed reaction-id
    -                   rank. Positive-score reactions carry a continuous, not a
    -                   free binary, on/off variable (pinned near 1 by the score
    -                   objective), so only the negative-score ones are a genuine
    -                   tie to resolve. The fluxes are then taken from the
    +   resolveTies     if true, the MILP's degenerate optimum is solved to a
    +                   canonical answer in addition to the solver Seed: the
    +                   score objective is held at its optimum, then the count
    +                   of "on" removable (negative-score) reactions is
    +                   minimised, then, among the sparsest solutions, their
    +                   summed reaction-id rank is minimised. Positive-score
    +                   reactions have a continuous on/off variable (pinned near
    +                   1 by the score objective) and only the negative-score
    +                   ones have a binary on/off variable, so only these are
    +                   tied. The fluxes are then taken from the
                        least-total-flux solution for that on/off state and,
                        among those, from the one with the least id-rank-weighted
                        backward flux, so the directions and forced magnitudes
    -                   the next ftINIT step reads from them are pinned too --
    -                   matches raven-toolbox's resolve_ties
    +                   the next ftINIT step reads from them are fixed too.
    +                   This follows resolve_ties in raven-toolbox
                        (raven-gecko-parity#104). The phases are skipped when
                        the primary solve stops at params.TimeLimit, and the
                        primary solution is kept. Phases 2a and 2b get half of
    @@ -99,22 +105,22 @@ 

    DESCRIPTION ^CROSS-REFERENCE INFORMATION ^

    @@ -131,709 +137,715 @@

    CROSS-REFERENCE INFORMATION ^
 <h2><a name=SOURCE CODE ^

    0001 function [deletedRxns,metProduction,res,turnedOnRxns,fluxes]=ftINITInternalAlg(model,rxnScores,metData,essentialRxns,prodWeight,allowExcretion,remPosRev,params,startVals,fluxes,verbose,forceOn,resolveTies,rankKeys,pinFluxes)
     0002 % ftINITInternalAlg
    -0003 %    This function runs the MILP for a step in ftINIT.
    +0003 %    Run the MILP for one step of ftINIT.
     0004 %
     0005 %   model           a reference model structure
     0006 %   rxnScores       a vector of scores for the reactions in the model.
     0007 %                   Positive scores are reactions to keep and negative
    -0008 %                   scores are reactions to exclude. Rxns set to 0 are excluded
    -0009 %                   from the problem.
    +0008 %                   scores are reactions to exclude. Reactions with score 0
    +0009 %                   are not part of the problem
     0010 %   metData         boolean matrix with mets as rows and rxns as columns
     0011 %                   saying which reaction produces each detected met (optional, default [])
     0012 %   essentialRxns   cell array of reactions that are essential and that
    -0013 %                   have to be in the resulting model. This is normally
    -0014 %                   used when fitting a model to task (see fitTasks)
    -0015 %   prodWeight      a score that determines the value of having
    -0016 %                   net-production of metabolites. This is a way of having
    -0017 %                   a more functional network as it provides a reason for
    -0018 %                   including bad reactions for connectivity reasons. This
    -0019 %                   score is for each metabolite, and the sum of these weights
    -0020 %                   and the scores for the reactions is what is optimized
    -0021 %   allowExcretion  true if excretion of all metabolites should be allowed.
    -0022 %                   This results in fewer reactions being considered
    -0023 %                   dead-ends, but all reactions in the resulting model may
    -0024 %                   not be able to carry flux. If this is "false" then the
    -0025 %                   equality constraints are taken from model.b. If the
    -0026 %                   input model lacks exchange reactions then this should
    -0027 %                   probably be "true", or a large proportion of the model
    -0028 %                   would be excluded for connectivity reasons
    -0029 %   remPosRev       If true, the positive reversible reactions are removed from the problem.
    -0030 %                   This is used in step 1 of ftINIT
    -0031 %   params          parameters for the MILP, for example MIPGap and TimeLimit
    -0032 %   startVals       Start values for the MILP, typically used when rerunning
    -0033 %                   with a higher MIPGap, to use the results from the previous
    -0034 %                   run
    -0035 %   fluxes          Fluxes from the last run.
    -0036 %   verbose         If true, the MILP progression will be shown.
    -0037 %   forceOn         minimum flux magnitude enforced through a reaction once
    -0038 %                   it is turned on in the MILP (optional, default 0.1)
    -0039 %   resolveTies     if true, pin the MILP's degenerate optimum to a canonical
    -0040 %                   answer instead of relying on the solver Seed alone: hold
    -0041 %                   the score objective at its optimum, then minimise the
    -0042 %                   count of "on" removable (negative-score) reactions, then,
    -0043 %                   among the sparsest, minimise their summed reaction-id
    -0044 %                   rank. Positive-score reactions carry a continuous, not a
    -0045 %                   free binary, on/off variable (pinned near 1 by the score
    -0046 %                   objective), so only the negative-score ones are a genuine
    -0047 %                   tie to resolve. The fluxes are then taken from the
    -0048 %                   least-total-flux solution for that on/off state and,
    -0049 %                   among those, from the one with the least id-rank-weighted
    -0050 %                   backward flux, so the directions and forced magnitudes
    -0051 %                   the next ftINIT step reads from them are pinned too --
    -0052 %                   matches raven-toolbox's resolve_ties
    -0053 %                   (raven-gecko-parity#104). The phases are skipped when
    -0054 %                   the primary solve stops at params.TimeLimit, and the
    -0055 %                   primary solution is kept. Phases 2a and 2b get half of
    -0056 %                   params.TimeLimit each (rounded down, at least 1 s), the
    -0057 %                   flux phases the full limit. A warning names each phase
    -0058 %                   that does not prove optimal or finds no solution; when a
    -0059 %                   flux phase finds none, the fluxes of the phase before it
    -0060 %                   are kept (optional, default false)
    -0061 %   rankKeys        cell array with, for each reaction in model.rxns, the key
    -0062 %                   that the id-rank phases of resolveTies sort it by
    -0063 %                   (optional, default model.rxns). ftINIT passes the smallest
    -0064 %                   original reaction id of each merge group, so the order
    -0065 %                   does not depend on which member names the merged reaction
    -0066 %   pinFluxes       if false, resolveTies skips phases 2c and 2d and the
    -0067 %                   fluxes and metProduction are those of phase 2b
    -0068 %                   (optional, default true).
    -0069 %                   ftINIT passes false in its last step, whose fluxes no
    -0070 %                   later step reads
    -0071 %
    -0072 %   deletedRxns     reactions which were deleted by the algorithm (only
    -0073 %                   rxns included in the problem)
    -0074 %   metProduction   array that indicates which of the
    -0075 %                   metabolites in presentMets that could be
    -0076 %                   produced
    -0077 %                   0: metabolite could not be produced
    -0078 %                   1: metabolite could be produced
    -0079 %   res             The result from the MILP
    -0080 %   turnedOnRxns    The reactions determined to be present (only
    -0081 %                   rxns included in the problem)
    -0082 %   fluxes          The fluxes from the MILP
    -0083 %
    -0084 %   This function is the actual implementation of the algorithm. See
    -0085 %   ftINIT for a higher-level function for model reconstruction.
    -0086 %
    -0087 % Usage: [deletedRxns,metProduction,res,turnedOnRxns,fluxes]=runINIT9(model,...
    -0088 %           rxnScores,presentMets,essentialRxns,prodWeight,allowExcretion,...
    -0089 %           remPosRev,params)
    -0090 
    -0091 if nargin < 12 || isempty(forceOn)
    -0092     forceOn = 0.1;
    -0093 end
    -0094 if nargin < 14 || isempty(rankKeys)
    -0095     rankKeys = model.rxns;
    -0096 end
    -0097 if nargin < 13 || isempty(resolveTies)
    -0098     resolveTies = false;
    +0013 %                   have to be in the resulting model. ftINIT passes the
    +0014 %                   reactions that the tasks require (see fitTasks)
    +0015 %   prodWeight      a reward for each detected metabolite that has at least
    +0016 %                   one producing reaction turned on; net production is not
    +0017 %                   checked. The sum of these rewards and the scores for the
    +0018 %                   reactions is optimised. The reward gives a reason for
    +0019 %                   including reactions with a negative score for
    +0020 %                   connectivity reasons, which gives a more functional
    +0021 %                   network (default 0.5)
    +0022 %   allowExcretion  true if excretion of all metabolites is allowed.
    +0023 %                   This results in fewer reactions being considered
    +0024 %                   dead-ends, but not every reaction in the resulting model
    +0025 %                   can carry flux. If this is "false" then the
    +0026 %                   equality constraints are taken from model.b. If the
    +0027 %                   input model lacks exchange reactions, set it to true;
    +0028 %                   with false a large part of the model is excluded for
    +0029 %                   connectivity reasons
    +0030 %   remPosRev       If true, the positive reversible reactions are removed from the problem.
    +0031 %                   This is used in step 1 of ftINIT
    +0032 %   params          parameters for the MILP, for example MIPGap and TimeLimit
    +0033 %   startVals       Start values for the MILP. ftINIT passes the result of
    +0034 %                   the previous run when it reruns the MILP with a higher
    +0035 %                   MIPGap
    +0036 %   fluxes          Fluxes from the last run. The flux of an essential
    +0037 %                   irreversible reaction is forced to at least
    +0038 %                   min(0.99*|flux|, 0.1), clamped to the reaction's capacity
    +0039 %                   in the forced direction (its upper bound, as ftINIT
    +0040 %                   passes reactions in the forward direction)
    +0041 %   verbose         If true, the MILP progression is shown.
    +0042 %   forceOn         minimum flux magnitude enforced through a reaction once
    +0043 %                   it is turned on in the MILP (optional, default 0.1)
    +0044 %   resolveTies     if true, the MILP's degenerate optimum is solved to a
    +0045 %                   canonical answer in addition to the solver Seed: the
    +0046 %                   score objective is held at its optimum, then the count
    +0047 %                   of "on" removable (negative-score) reactions is
    +0048 %                   minimised, then, among the sparsest solutions, their
    +0049 %                   summed reaction-id rank is minimised. Positive-score
    +0050 %                   reactions have a continuous on/off variable (pinned near
    +0051 %                   1 by the score objective) and only the negative-score
    +0052 %                   ones have a binary on/off variable, so only these are
    +0053 %                   tied. The fluxes are then taken from the
    +0054 %                   least-total-flux solution for that on/off state and,
    +0055 %                   among those, from the one with the least id-rank-weighted
    +0056 %                   backward flux, so the directions and forced magnitudes
    +0057 %                   the next ftINIT step reads from them are fixed too.
    +0058 %                   This follows resolve_ties in raven-toolbox
    +0059 %                   (raven-gecko-parity#104). The phases are skipped when
    +0060 %                   the primary solve stops at params.TimeLimit, and the
    +0061 %                   primary solution is kept. Phases 2a and 2b get half of
    +0062 %                   params.TimeLimit each (rounded down, at least 1 s), the
    +0063 %                   flux phases the full limit. A warning names each phase
    +0064 %                   that does not prove optimal or finds no solution; when a
    +0065 %                   flux phase finds none, the fluxes of the phase before it
    +0066 %                   are kept (optional, default false)
    +0067 %   rankKeys        cell array with, for each reaction in model.rxns, the key
    +0068 %                   that the id-rank phases of resolveTies sort it by
    +0069 %                   (optional, default model.rxns). ftINIT passes the smallest
    +0070 %                   original reaction id of each merge group, so the order
    +0071 %                   does not depend on which member names the merged reaction
    +0072 %   pinFluxes       if false, resolveTies skips phases 2c and 2d and the
    +0073 %                   fluxes and metProduction are those of phase 2b
    +0074 %                   (optional, default true).
    +0075 %                   ftINIT passes false in its last step, whose fluxes no
    +0076 %                   later step reads
    +0077 %
    +0078 %   deletedRxns     reactions which were deleted by the algorithm (only
    +0079 %                   rxns included in the problem)
    +0080 %   metProduction   logical array that indicates which of the metabolites in
    +0081 %                   metData could be produced (true) or not (false).
    +0082 %                   Metabolites with a producing reaction that is essential
    +0083 %                   are not part of the MILP and are not in the array
    +0084 %   res             The result from the MILP
    +0085 %   turnedOnRxns    The reactions determined to be present (only
    +0086 %                   rxns included in the problem)
    +0087 %   fluxes          The fluxes from the MILP
    +0088 %
    +0089 %   This function is the implementation of the algorithm. See
    +0090 %   ftINIT for a higher-level function for model reconstruction.
    +0091 %
    +0092 % Usage: [deletedRxns,metProduction,res,turnedOnRxns,fluxes]=ftINITInternalAlg(model,...
    +0093 %           rxnScores,metData,essentialRxns,prodWeight,allowExcretion,...
    +0094 %           remPosRev,params,startVals,fluxes,verbose,forceOn,resolveTies,...
    +0095 %           rankKeys,pinFluxes)
    +0096 
    +0097 if nargin < 12 || isempty(forceOn)
    +0098     forceOn = 0.1;
     0099 end
    -0100 if nargin < 15 || isempty(pinFluxes)
    -0101     pinFluxes = true;
    +0100 if nargin < 14 || isempty(rankKeys)
    +0101     rankKeys = model.rxns;
     0102 end
    -0103 if isempty(essentialRxns)
    -0104     essentialRxns={};
    +0103 if nargin < 13 || isempty(resolveTies)
    +0104     resolveTies = false;
     0105 end
    -0106 essentialRxns=essentialRxns(:);
    -0107 if isempty(prodWeight)
    -0108     prodWeight=0.5;
    -0109 end
    -0110 
    -0111 
    -0112 %The model should be in the reversible format and all relevant exchange
    -0113 %reactions should be open
    -0114 if isfield(model,'unconstrained')
    -0115     EM='Exchange metabolites are still present in the model. Use simplifyModel if this is not intended';
    -0116     warning('RAVEN:warning', '%s', EM);
    -0117 end
    -0118 
    -0119 
    -0120 essential = ismember(model.rxns,essentialRxns);
    -0121 
    -0122 %Some nice to have numbers
    -0123 nMets=numel(model.mets);
    -0124 nRxns=numel(model.rxns);
    -0125 nRxnsWithOnOff = nRxns;
    -0126 
    -0127 %Reactions with score 0 will just be left in the model, and will not be part of the problem (but can carry flux).
    -0128 %It is possible to set score = 0 for e.g. spontaneous reactions, exchange rxns, etc., which may not be that interesting
    -0129 %to remove.
    -0130 
    -0131 %if makeIrrev is on, we can just as well skip all positive reversible rxns - they will be turned on without carrying any flux
    -0132 %since they can form a loop within themself (fwd-rev)
    -0133 if remPosRev
    -0134     rxnScores(rxnScores > 0 & model.rev~=0) = 0;
    -0135 end
    +0106 if nargin < 15 || isempty(pinFluxes)
    +0107     pinFluxes = true;
    +0108 end
    +0109 if isempty(essentialRxns)
    +0110     essentialRxns={};
    +0111 end
    +0112 essentialRxns=essentialRxns(:);
    +0113 if isempty(prodWeight)
    +0114     prodWeight=0.5;
    +0115 end
    +0116 
    +0117 
    +0118 %The model should be in the reversible format and all relevant exchange
    +0119 %reactions should be open
    +0120 if isfield(model,'unconstrained')
    +0121     EM='Exchange metabolites are still present in the model. Use simplifyModel if this is not intended';
    +0122     warning('RAVEN:warning', '%s', EM);
    +0123 end
    +0124 
    +0125 
    +0126 essential = ismember(model.rxns,essentialRxns);
    +0127 
    +0128 %Some nice to have numbers
    +0129 nMets=numel(model.mets);
    +0130 nRxns=numel(model.rxns);
    +0131 nRxnsWithOnOff = nRxns;
    +0132 
    +0133 %Reactions with score 0 will just be left in the model, and will not be part of the problem (but can carry flux).
    +0134 %It is possible to set score = 0 for e.g. spontaneous reactions, exchange rxns, etc., which may not be that interesting
    +0135 %to remove.
     0136 
    -0137 %Handle metabolomics
    -0138 %A problem with the metabolomics is that some of the producer reactions
    -0139 %for a metabolite could be excluded from the problem. We solve this by
    -0140 %adding them with score 0. They can still be seen as positive, since they
    -0141 %either do not matter (there is another producer) or they can contribute to an
    -0142 %increased score.
    -0143 revRxns=model.rev~=0; 
    -0144 essRevRxns = find(revRxns & essential);
    -0145 essIrrevRxns = find(~revRxns & essential);
    -0146 
    -0147 if ~isempty(metData)
    -0148     %remove any metData rows that is connected to an essential rxn -
    -0149     %these will be on regardless and will cause problems below.
    -0150     containsEssential = any(metData(:,essential),2);
    -0151     metData = metData(~containsEssential,:);
    -0152 end
    -0153 
    -0154 if ~isempty(metData)
    -0155     metRxns = any(metData,1).';
    -0156     posRxns = rxnScores > 0 | ((rxnScores == 0) & metRxns);
    -0157 else
    -0158     posRxns = rxnScores > 0;
    -0159 end
    -0160 negRxns = rxnScores < 0;
    -0161 
    -0162 posRevRxns = find(posRxns & revRxns & ~essential);
    -0163 negRevRxns = find(negRxns & revRxns & ~essential);
    -0164 posIrrevRxns = find(posRxns & ~revRxns & ~essential);
    -0165 negIrrevRxns = find(negRxns & ~revRxns & ~essential);
    -0166 nPosRev = numel(posRevRxns);
    -0167 nNegRev = numel(negRevRxns);
    -0168 nPosIrrev = numel(posIrrevRxns);
    -0169 nNegIrrev = numel(negIrrevRxns);
    -0170 nEssRev = numel(essRevRxns);
    -0171 nEssIrrev = numel(essIrrevRxns); %not used, but left for symmetry
    -0172 nMetabolMets = size(metData,1);
    -0173 
    -0174 if ~isempty(metData)
    -0175     metNegRev = negRxns & revRxns & ~essential & metRxns;
    -0176     metNegIrrev = negRxns & ~revRxns & ~essential & metRxns;
    -0177     nMetNegRev = sum(metNegRev);
    -0178     nMetNegIrrev = sum(metNegIrrev);
    -0179 end
    -0180 
    -0181 
    -0182 milpModel = model;
    -0183 
    -0184 %These six categories need to be handled separately:
    -0185 %
    -0186 %PosIrrev (positive reaction score, irreversible):
    -0187 %flux >= 0.1*Yi, flux - 0.1*Yi - VPI == 0, 0 <= Yi(onoff) <= 1, 0 <= VPI <= Inf
    -0188 %The nice thing with rxns with positive score is that they do not require a boolean. The optimizer will
    -0189 %strive for maximizing the Yi here, so it can be a continuous variable, where we just force a flux if
    -0190 %the variable is on.
    -0191 %PosRev:
    -0192 % 1: Split up the flux into positive and negative: flux - vnrp + vnrn == 0, 0 <= vprp,vprn <= Inf.
    -0193 % 2. Force one of the fluxes on: vprp + vprn >= 0.1*Yi, vprp + vprn - 0.1*Yi - vprv1 == 0. 0 <= Yi(onoff) <= 1, vprv1 >= 0
    -0194 % 3. We need a bool to make sure that one of vprp and vprn are always zero:
    -0195 %    vprb E {0,1}:  vprp <= 100*vprb (if bool is 0, vprp is zero): vprp - 100*vprb + vprv2 == 0, vprv2 >= 0
    -0196 %    vprn <= (1-vprb)*100 (if bool is one, vprn is zero): vprn + 100*vprb + vprv3 == 0, -100 <= vprv3 <= inf
    -0197 %Neg Irrev:
    -0198 %For negative scores, we need to use an integer, I see no way around that.
    -0199 % flux < 100*Yi, Yi E {0,1}. Flux - 100*Yi + vni == 0
    -0200 %Neg Rev:
    -0201 %Two cases:
    -0202 %A: Without irrev model
    -0203 % 1: Split up the flux into positive and negative: flux - vnrp + vnrn == 0, 0 <= vprp,vprn <= Inf.
    -0204 % 2: Force the Yi (on/off) var on if the flux is on: vnrp + vnrn <= 0.1 * Yi, Yi E {0,1}: vnrp + vnrn - 0.1 * Yi + vnrv1 == 0, vnrv1 >= 0.
    -0205 %B: Irrev model (Not used for now)
    -0206 % 1. We now have two reactions, but still just boolean variable. We know that
    -0207 %    the flux can only be positive, so we just say that flux1 + flux2 <= 0.1 * Yi, Yi E {0,1}: flux1 + flux2 - 0.1 * Yi + vnrv1 == 0, vnrv1 >= 0.
    -0208 %    There is no need to prevent loops - there is no benefit for the objective to generate one, and it does not matter.
    -0209 %Ess Irrev (essential rxn, i.e. forced to carry flux, irreversible):
    -0210 %flux >= 0.1 - solved with lb = 0.1.
    -0211 %Ess Rev (these are not really used):
    -0212 % 1: Split up the flux into positive and negative: flux - verp + vern == 0, 0 <= verp,vern <= Inf.
    -0213 % 2. Force one of the fluxes on: verp + vern >= 0.1, verp + vern - verv1 == 0. verv1 >= 0.1
    -0214 % 3. We need a bool to make sure that one of verp and vern are always zero:
    -0215 %    verb E {0,1}:  verp <= 100*verb (if bool is 0, verp is zero): verp - 100*verb + verv2 == 0, verv2 >= 0
    -0216 %    vern <= (1-verb)*100 (if bool is one, vern is zero): vern + 100*verb + verv3 == 0, -100 <= verv3 <= inf
    -0217 
    -0218 % Now metabolomics
    -0219 % The basic idea is that the bonus is gotten if any of the producer reactions are on. We can
    -0220 % therefore use the "on" variables directly for the reactions that are included. We therefore add one variable per met, mon, which can be
    -0221 % continuous and between 0 and 1. We then say that mon <= v1 + v2 + ... + vn, i.e. that
    -0222 % mon + mv1 - v1 - v2 ... - vn == 0, 0 <= mon <= 1, 0 <= mv1 <= inf
    -0223 % mon gets -prodWeight in the c vector (i.e. objective function)
    -0224 % A tricky part is that some of the reactions producing a metabolite are left outside the problem
    -0225 % This is solved by moving them into the problem, but with the score 0. They can still be treated as positive
    -0226 % reactions. In the end, we are not interested if they are on though, but they may
    -0227 % allow for production of a metabolite, giving no benefit of turning on another producer reaction.
    -0228 % Another tricky thing is that some metabolite producers have negative score. There is
    -0229 % no automatic mechanism for forcing flux on if the variable is on - this is simply
    -0230 % not needed for negative variables - unless they are a metabolite producer. We therefore need
    -0231 % to add an extra constraint there, similar to the case of positive. It gets a bit complicated.
    -0232 % For reversible, we need a bool to make sure that one of vnrp and vnrn stays zero:
    -0233 %    vnrbm E {0,1}:  vnrp <= 100*vnrbm (if bool is 0, vnrp is zero): vnrp - 100*vnrbm + vnrvm1 == 0, vnrvm1 >= 0
    -0234 %    vnrn <= (1-vnrbm)*100 (if bool is one, vnrn is zero): vnrn + 100*vnrbm + vnrvm2 == 0, -100 <= vnrvm2 <= inf
    -0235 % We then also say that vnrp + vnrn >= 0.1*Yi, vnrp + vnrn - 0.1*Yi - vnrvm3 == 0, vnrvm3 >= 0
    -0236 % For irreversible, it is fairly straight-forward. We just say that flux >= 0.1*Yi, i.e. flux - 0.1*Yi - vnim == 0, 0 <= vnim <= Inf.
    -0237 
    -0238 
    -0239 %The total matrix then looks like this. Note that we have ordered the categories, two reactions each,
    -0240 %in the S matrix to make it easier to follow the figure. In practice, they come in random order, which
    -0241 %is why the SEye variable is used further down.
    -0242 %all categories below have two columns - the two column starts where label starts
    -0243 %since the label cannot fit in two chars, they are on multiple lines
    -0244 %
    -0245 %The met variables and constraints are not in this figure for practical reasons.
    -0246 %
    -0247 %                           vprb  vprv3 vnrn  vern  verv2 mv1
    -0248 % pi  ni  ei  Ypi Yni vpi vprn  vprv2 vnrp  verp  verb  mon
    -0249 %   pr  nr  er  Ypr Ynr vprp  vprv1 vni   vnrv1 verv1 verv3
    -0250 % SSSSSSSSSSSS0000000000000000000000000000000000000000000000
    -0251 % SSSSSSSSSSSS0000000000000000000000000000000000000000000000 S block
    -0252 % SSSSSSSSSSSS0000000000000000000000000000000000000000000000
    -0253 % 1           N       -                                      Pos irrev block
    -0254 %  1           N       -
    -0255 %   1                   - 1                                  Pos rev block 1
    -0256 %    1                   - 1
    -0257 %               N       1 1   -                              Pos rev block 2
    -0258 %                N       1 1   -
    -0259 %                       1   M   1                            Pos rev block 3
    -0260 %                        1   M   1
    -0261 %                         1 C     1                          Pos rev block 4
    -0262 %                          1 C     1
    -0263 %     1           M                 1                        Neg irrev block
    -0264 %      1           M                 1
    -0265 %       1                             - 1                    Neg rev block 1
    -0266 %        1                             - 1
    -0267 %                   M                 1 1 1                  Neg rev block 2
    -0268 %                    M                 1 1 1
    -0269 %           1                               - 1              Ess rev block 1
    -0270 %            1                               - 1
    -0271 %                                           1 1 -            Ess rev block 2
    -0272 %                                            1 1 -
    -0273 %                                           1     M 1        Ess rev block 3
    -0274 %                                            1     M 1
    -0275 %                                             1   C   1      Ess rev block 4
    -0276 %                                              1   C   1
    -0277 %             --------                                  1 1  Met block - Here, we assume that all variables support each met.
    -0278 %             --------                                   1 1             In practice, fewer of the "on" variables with -1 should be included
    -0279 %
    -0280 %
    -0281 %M = -100
    -0282 %N = -0.1
    -0283 %D = 0.1
    -0284 %C = 100
    -0285 %- = -1
    +0137 %if makeIrrev is on, we can just as well skip all positive reversible rxns - they will be turned on without carrying any flux
    +0138 %since they can form a loop within themself (fwd-rev)
    +0139 if remPosRev
    +0140     rxnScores(rxnScores > 0 & model.rev~=0) = 0;
    +0141 end
    +0142 
    +0143 %Handle metabolomics
    +0144 %A problem with the metabolomics is that some of the producer reactions
    +0145 %for a metabolite could be excluded from the problem. We solve this by
    +0146 %adding them with score 0. They can still be seen as positive, since they
    +0147 %either do not matter (there is another producer) or they can contribute to an
    +0148 %increased score.
    +0149 revRxns=model.rev~=0; 
    +0150 essRevRxns = find(revRxns & essential);
    +0151 essIrrevRxns = find(~revRxns & essential);
    +0152 
    +0153 if ~isempty(metData)
    +0154     %remove any metData rows that is connected to an essential rxn -
    +0155     %these will be on regardless and will cause problems below.
    +0156     containsEssential = any(metData(:,essential),2);
    +0157     metData = metData(~containsEssential,:);
    +0158 end
    +0159 
    +0160 if ~isempty(metData)
    +0161     metRxns = any(metData,1).';
    +0162     posRxns = rxnScores > 0 | ((rxnScores == 0) & metRxns);
    +0163 else
    +0164     posRxns = rxnScores > 0;
    +0165 end
    +0166 negRxns = rxnScores < 0;
    +0167 
    +0168 posRevRxns = find(posRxns & revRxns & ~essential);
    +0169 negRevRxns = find(negRxns & revRxns & ~essential);
    +0170 posIrrevRxns = find(posRxns & ~revRxns & ~essential);
    +0171 negIrrevRxns = find(negRxns & ~revRxns & ~essential);
    +0172 nPosRev = numel(posRevRxns);
    +0173 nNegRev = numel(negRevRxns);
    +0174 nPosIrrev = numel(posIrrevRxns);
    +0175 nNegIrrev = numel(negIrrevRxns);
    +0176 nEssRev = numel(essRevRxns);
    +0177 nEssIrrev = numel(essIrrevRxns); %not used, but left for symmetry
    +0178 nMetabolMets = size(metData,1);
    +0179 
    +0180 if ~isempty(metData)
    +0181     metNegRev = negRxns & revRxns & ~essential & metRxns;
    +0182     metNegIrrev = negRxns & ~revRxns & ~essential & metRxns;
    +0183     nMetNegRev = sum(metNegRev);
    +0184     nMetNegIrrev = sum(metNegIrrev);
    +0185 end
    +0186 
    +0187 
    +0188 milpModel = model;
    +0189 
    +0190 %These six categories need to be handled separately:
    +0191 %
    +0192 %PosIrrev (positive reaction score, irreversible):
    +0193 %flux >= 0.1*Yi, flux - 0.1*Yi - VPI == 0, 0 <= Yi(onoff) <= 1, 0 <= VPI <= Inf
    +0194 %The nice thing with rxns with positive score is that they do not require a boolean. The optimizer will
    +0195 %strive for maximizing the Yi here, so it can be a continuous variable, where we just force a flux if
    +0196 %the variable is on.
    +0197 %PosRev:
    +0198 % 1: Split up the flux into positive and negative: flux - vnrp + vnrn == 0, 0 <= vprp,vprn <= Inf.
    +0199 % 2. Force one of the fluxes on: vprp + vprn >= 0.1*Yi, vprp + vprn - 0.1*Yi - vprv1 == 0. 0 <= Yi(onoff) <= 1, vprv1 >= 0
    +0200 % 3. We need a bool to make sure that one of vprp and vprn are always zero:
    +0201 %    vprb E {0,1}:  vprp <= 100*vprb (if bool is 0, vprp is zero): vprp - 100*vprb + vprv2 == 0, vprv2 >= 0
    +0202 %    vprn <= (1-vprb)*100 (if bool is one, vprn is zero): vprn + 100*vprb + vprv3 == 0, -100 <= vprv3 <= inf
    +0203 %Neg Irrev:
    +0204 %For negative scores, we need to use an integer, I see no way around that.
    +0205 % flux < 100*Yi, Yi E {0,1}. Flux - 100*Yi + vni == 0
    +0206 %Neg Rev:
    +0207 %Two cases:
    +0208 %A: Without irrev model
    +0209 % 1: Split up the flux into positive and negative: flux - vnrp + vnrn == 0, 0 <= vprp,vprn <= Inf.
    +0210 % 2: Force the Yi (on/off) var on if the flux is on: vnrp + vnrn <= 0.1 * Yi, Yi E {0,1}: vnrp + vnrn - 0.1 * Yi + vnrv1 == 0, vnrv1 >= 0.
    +0211 %B: Irrev model (Not used for now)
    +0212 % 1. We now have two reactions, but still just boolean variable. We know that
    +0213 %    the flux can only be positive, so we just say that flux1 + flux2 <= 0.1 * Yi, Yi E {0,1}: flux1 + flux2 - 0.1 * Yi + vnrv1 == 0, vnrv1 >= 0.
    +0214 %    There is no need to prevent loops - there is no benefit for the objective to generate one, and it does not matter.
    +0215 %Ess Irrev (essential rxn, i.e. forced to carry flux, irreversible):
    +0216 %flux >= 0.1 - solved with lb = 0.1.
    +0217 %Ess Rev (these are not really used):
    +0218 % 1: Split up the flux into positive and negative: flux - verp + vern == 0, 0 <= verp,vern <= Inf.
    +0219 % 2. Force one of the fluxes on: verp + vern >= 0.1, verp + vern - verv1 == 0. verv1 >= 0.1
    +0220 % 3. We need a bool to make sure that one of verp and vern are always zero:
    +0221 %    verb E {0,1}:  verp <= 100*verb (if bool is 0, verp is zero): verp - 100*verb + verv2 == 0, verv2 >= 0
    +0222 %    vern <= (1-verb)*100 (if bool is one, vern is zero): vern + 100*verb + verv3 == 0, -100 <= verv3 <= inf
    +0223 
    +0224 % Now metabolomics
    +0225 % The basic idea is that the bonus is gotten if any of the producer reactions are on. We can
    +0226 % therefore use the "on" variables directly for the reactions that are included. We therefore add one variable per met, mon, which can be
    +0227 % continuous and between 0 and 1. We then say that mon <= v1 + v2 + ... + vn, i.e. that
    +0228 % mon + mv1 - v1 - v2 ... - vn == 0, 0 <= mon <= 1, 0 <= mv1 <= inf
    +0229 % mon gets -prodWeight in the c vector (i.e. objective function)
    +0230 % A tricky part is that some of the reactions producing a metabolite are left outside the problem
    +0231 % This is solved by moving them into the problem, but with the score 0. They can still be treated as positive
    +0232 % reactions. In the end, we are not interested if they are on though, but they may
    +0233 % allow for production of a metabolite, giving no benefit of turning on another producer reaction.
    +0234 % Another tricky thing is that some metabolite producers have negative score. There is
    +0235 % no automatic mechanism for forcing flux on if the variable is on - this is simply
    +0236 % not needed for negative variables - unless they are a metabolite producer. We therefore need
    +0237 % to add an extra constraint there, similar to the case of positive. It gets a bit complicated.
    +0238 % For reversible, we need a bool to make sure that one of vnrp and vnrn stays zero:
    +0239 %    vnrbm E {0,1}:  vnrp <= 100*vnrbm (if bool is 0, vnrp is zero): vnrp - 100*vnrbm + vnrvm1 == 0, vnrvm1 >= 0
    +0240 %    vnrn <= (1-vnrbm)*100 (if bool is one, vnrn is zero): vnrn + 100*vnrbm + vnrvm2 == 0, -100 <= vnrvm2 <= inf
    +0241 % We then also say that vnrp + vnrn >= 0.1*Yi, vnrp + vnrn - 0.1*Yi - vnrvm3 == 0, vnrvm3 >= 0
    +0242 % For irreversible, it is fairly straight-forward. We just say that flux >= 0.1*Yi, i.e. flux - 0.1*Yi - vnim == 0, 0 <= vnim <= Inf.
    +0243 
    +0244 
    +0245 %The total matrix then looks like this. Note that we have ordered the categories, two reactions each,
    +0246 %in the S matrix to make it easier to follow the figure. In practice, they come in random order, which
    +0247 %is why the SEye variable is used further down.
    +0248 %all categories below have two columns - the two column starts where label starts
    +0249 %since the label cannot fit in two chars, they are on multiple lines
    +0250 %
    +0251 %The met variables and constraints are not in this figure for practical reasons.
    +0252 %
    +0253 %                           vprb  vprv3 vnrn  vern  verv2 mv1
    +0254 % pi  ni  ei  Ypi Yni vpi vprn  vprv2 vnrp  verp  verb  mon
    +0255 %   pr  nr  er  Ypr Ynr vprp  vprv1 vni   vnrv1 verv1 verv3
    +0256 % SSSSSSSSSSSS0000000000000000000000000000000000000000000000
    +0257 % SSSSSSSSSSSS0000000000000000000000000000000000000000000000 S block
    +0258 % SSSSSSSSSSSS0000000000000000000000000000000000000000000000
    +0259 % 1           N       -                                      Pos irrev block
    +0260 %  1           N       -
    +0261 %   1                   - 1                                  Pos rev block 1
    +0262 %    1                   - 1
    +0263 %               N       1 1   -                              Pos rev block 2
    +0264 %                N       1 1   -
    +0265 %                       1   M   1                            Pos rev block 3
    +0266 %                        1   M   1
    +0267 %                         1 C     1                          Pos rev block 4
    +0268 %                          1 C     1
    +0269 %     1           M                 1                        Neg irrev block
    +0270 %      1           M                 1
    +0271 %       1                             - 1                    Neg rev block 1
    +0272 %        1                             - 1
    +0273 %                   M                 1 1 1                  Neg rev block 2
    +0274 %                    M                 1 1 1
    +0275 %           1                               - 1              Ess rev block 1
    +0276 %            1                               - 1
    +0277 %                                           1 1 -            Ess rev block 2
    +0278 %                                            1 1 -
    +0279 %                                           1     M 1        Ess rev block 3
    +0280 %                                            1     M 1
    +0281 %                                             1   C   1      Ess rev block 4
    +0282 %                                              1   C   1
    +0283 %             --------                                  1 1  Met block - Here, we assume that all variables support each met.
    +0284 %             --------                                   1 1             In practice, fewer of the "on" variables with -1 should be included
    +0285 %
     0286 %
    -0287 %It looks a bit different for the irrev model, but still quite similar
    -0288 
    -0289 %build the A matrix
    -0290 %S row
    -0291 %When forcing on essential rxns, use the flux value of the previous run (set to 0.1 the first time)
    -0292 %Do not set it above 0.1, may starve something else out. Leave a margin of 1% from the last run.
    -0293 forceOnLim = forceOn;
    -0294 forceOnLimEss=min(abs(fluxes)*0.99,0.1);
    -0295 forceOnLimEss(essIrrevRxns) = min(forceOnLimEss(essIrrevRxns), milpModel.ub(essIrrevRxns));
    -0296 
    -0297 % Per-reaction big-M: use each reaction own flux capacity (capped at 1000).
    -0298 % This replaces the fixed big-M=100, which underestimates capacity for standard
    -0299 % RAVEN models where ub=1000. Must be at least forceOnLim so the constraints are feasible.
    -0300 bigMCap = 1000;
    -0301 ubSafe = min(milpModel.ub, bigMCap); ubSafe(~isfinite(ubSafe)) = bigMCap;
    -0302 lbSafe = min(-milpModel.lb, bigMCap); lbSafe(~isfinite(lbSafe)) = bigMCap; lbSafe = max(lbSafe, 0);
    -0303 bigMPosRev   = max(max(ubSafe(posRevRxns),  lbSafe(posRevRxns)),  forceOnLim);
    -0304 bigMNegIrrev = max(ubSafe(negIrrevRxns), forceOnLim);
    -0305 bigMNegRev   = max(max(ubSafe(negRevRxns),  lbSafe(negRevRxns)),  forceOnLim);
    -0306 bigMEssRev   = max(max(ubSafe(essRevRxns),  lbSafe(essRevRxns)),  forceOnLim);
    -0307 
    -0308 varsPerNegRev = 3;
    -0309 
    -0310 
    -0311 %Figure out the number of variables needed for metabolomics
    -0312 if ~isempty(metData)
    -0313     nMetVars = 2*size(metData,1) + 4*nMetNegRev + nMetNegIrrev; %mon,mv1; vnrbm,vnrvm1,vnrvm2,vnrvm3; vnim
    -0314 else
    -0315     nMetVars = 0;
    -0316 end
    -0317 
    -0318 
    -0319 sRow = [milpModel.S sparse(nMets, nPosIrrev*2+nPosRev*7+nNegIrrev*2+nNegRev*(1+varsPerNegRev)+nEssRev*6 + nMetVars)];
    -0320 sEye = speye(nRxns);
    -0321 nYBlock = nPosIrrev+nPosRev+nNegIrrev+nNegRev;
    -0322 piRows = [sEye(posIrrevRxns,:) speye(nPosIrrev)*-forceOnLim sparse(nPosIrrev,nPosRev+nNegIrrev+nNegRev) speye(nPosIrrev)*-1 sparse(nPosIrrev,nPosRev*6+nNegIrrev+nNegRev*varsPerNegRev+nEssRev*6+nMetVars)];
    +0287 %M = -100
    +0288 %N = -0.1
    +0289 %D = 0.1
    +0290 %C = 100
    +0291 %- = -1
    +0292 %
    +0293 %It looks a bit different for the irrev model, but still quite similar
    +0294 
    +0295 %build the A matrix
    +0296 %S row
    +0297 %When forcing on essential rxns, use the flux value of the previous run (set to 0.1 the first time)
    +0298 %Do not set it above 0.1, may starve something else out. Leave a margin of 1% from the last run.
    +0299 forceOnLim = forceOn;
    +0300 forceOnLimEss=min(abs(fluxes)*0.99,0.1);
    +0301 forceOnLimEss(essIrrevRxns) = min(forceOnLimEss(essIrrevRxns), milpModel.ub(essIrrevRxns));
    +0302 
    +0303 % Per-reaction big-M: use each reaction own flux capacity (capped at 1000).
    +0304 % This replaces the fixed big-M=100, which underestimates capacity for standard
    +0305 % RAVEN models where ub=1000. Must be at least forceOnLim so the constraints are feasible.
    +0306 bigMCap = 1000;
    +0307 ubSafe = min(milpModel.ub, bigMCap); ubSafe(~isfinite(ubSafe)) = bigMCap;
    +0308 lbSafe = min(-milpModel.lb, bigMCap); lbSafe(~isfinite(lbSafe)) = bigMCap; lbSafe = max(lbSafe, 0);
    +0309 bigMPosRev   = max(max(ubSafe(posRevRxns),  lbSafe(posRevRxns)),  forceOnLim);
    +0310 bigMNegIrrev = max(ubSafe(negIrrevRxns), forceOnLim);
    +0311 bigMNegRev   = max(max(ubSafe(negRevRxns),  lbSafe(negRevRxns)),  forceOnLim);
    +0312 bigMEssRev   = max(max(ubSafe(essRevRxns),  lbSafe(essRevRxns)),  forceOnLim);
    +0313 
    +0314 varsPerNegRev = 3;
    +0315 
    +0316 
    +0317 %Figure out the number of variables needed for metabolomics
    +0318 if ~isempty(metData)
    +0319     nMetVars = 2*size(metData,1) + 4*nMetNegRev + nMetNegIrrev; %mon,mv1; vnrbm,vnrvm1,vnrvm2,vnrvm3; vnim
    +0320 else
    +0321     nMetVars = 0;
    +0322 end
     0323 
    -0324 prRows1 = [sEye(posRevRxns,:) sparse(nPosRev,nYBlock + nPosIrrev) speye(nPosRev)*-1 speye(nPosRev) sparse(nPosRev,nPosRev*4+nNegIrrev+nNegRev*varsPerNegRev+nEssRev*6+nMetVars)];
    -0325 prRows2 = [sparse(nPosRev,nRxns + nPosIrrev) speye(nPosRev)*-forceOnLim sparse(nPosRev,nNegIrrev + nNegRev + nPosIrrev) speye(nPosRev) speye(nPosRev) sparse(nPosRev,nPosRev) speye(nPosRev)*-1 sparse(nPosRev,nPosRev*2+nNegIrrev+nNegRev*varsPerNegRev+nEssRev*6+nMetVars)];
    -0326 prRows3 = [sparse(nPosRev,nRxns + nYBlock + nPosIrrev) speye(nPosRev) sparse(nPosRev,nPosRev) spdiags(-bigMPosRev,0,nPosRev,nPosRev) sparse(nPosRev,nPosRev) speye(nPosRev) sparse(nPosRev,nPosRev+nNegIrrev+nNegRev*varsPerNegRev+nEssRev*6+nMetVars)];
    -0327 prRows4 = [sparse(nPosRev,nRxns + nYBlock + nPosIrrev + nPosRev) speye(nPosRev) spdiags(bigMPosRev,0,nPosRev,nPosRev) sparse(nPosRev,nPosRev*2) speye(nPosRev) sparse(nPosRev, nNegIrrev+nNegRev*varsPerNegRev+nEssRev*6+nMetVars)];
    -0328 
    -0329 niRows = [sEye(negIrrevRxns,:) sparse(nNegIrrev,nPosIrrev + nPosRev) spdiags(-bigMNegIrrev,0,nNegIrrev,nNegIrrev) sparse(nNegIrrev,nNegRev + nPosIrrev + nPosRev*6) speye(nNegIrrev) sparse(nNegIrrev,nNegRev*varsPerNegRev + nEssRev*6+nMetVars)];
    -0330 
    -0331 nrRows1 = [sEye(negRevRxns,:) sparse(nNegRev,nYBlock + nPosIrrev + nPosRev*6 + nNegIrrev) speye(nNegRev)*-1 speye(nNegRev) sparse(nNegRev,nNegRev + nEssRev*6+nMetVars)];
    -0332 nrRows2 = [sparse(nNegRev,nRxns + nPosIrrev + nPosRev + nNegIrrev) spdiags(-bigMNegRev,0,nNegRev,nNegRev) sparse(nNegRev,nPosIrrev + nPosRev*6 + nNegIrrev) speye(nNegRev) speye(nNegRev) speye(nNegRev) sparse(nNegRev,nEssRev*6+nMetVars)];
    -0333 nrRows = [nrRows1;nrRows2];
    +0324 
    +0325 sRow = [milpModel.S sparse(nMets, nPosIrrev*2+nPosRev*7+nNegIrrev*2+nNegRev*(1+varsPerNegRev)+nEssRev*6 + nMetVars)];
    +0326 sEye = speye(nRxns);
    +0327 nYBlock = nPosIrrev+nPosRev+nNegIrrev+nNegRev;
    +0328 piRows = [sEye(posIrrevRxns,:) speye(nPosIrrev)*-forceOnLim sparse(nPosIrrev,nPosRev+nNegIrrev+nNegRev) speye(nPosIrrev)*-1 sparse(nPosIrrev,nPosRev*6+nNegIrrev+nNegRev*varsPerNegRev+nEssRev*6+nMetVars)];
    +0329 
    +0330 prRows1 = [sEye(posRevRxns,:) sparse(nPosRev,nYBlock + nPosIrrev) speye(nPosRev)*-1 speye(nPosRev) sparse(nPosRev,nPosRev*4+nNegIrrev+nNegRev*varsPerNegRev+nEssRev*6+nMetVars)];
    +0331 prRows2 = [sparse(nPosRev,nRxns + nPosIrrev) speye(nPosRev)*-forceOnLim sparse(nPosRev,nNegIrrev + nNegRev + nPosIrrev) speye(nPosRev) speye(nPosRev) sparse(nPosRev,nPosRev) speye(nPosRev)*-1 sparse(nPosRev,nPosRev*2+nNegIrrev+nNegRev*varsPerNegRev+nEssRev*6+nMetVars)];
    +0332 prRows3 = [sparse(nPosRev,nRxns + nYBlock + nPosIrrev) speye(nPosRev) sparse(nPosRev,nPosRev) spdiags(-bigMPosRev,0,nPosRev,nPosRev) sparse(nPosRev,nPosRev) speye(nPosRev) sparse(nPosRev,nPosRev+nNegIrrev+nNegRev*varsPerNegRev+nEssRev*6+nMetVars)];
    +0333 prRows4 = [sparse(nPosRev,nRxns + nYBlock + nPosIrrev + nPosRev) speye(nPosRev) spdiags(bigMPosRev,0,nPosRev,nPosRev) sparse(nPosRev,nPosRev*2) speye(nPosRev) sparse(nPosRev, nNegIrrev+nNegRev*varsPerNegRev+nEssRev*6+nMetVars)];
     0334 
    -0335 erRows1 = [sEye(essRevRxns,:) sparse(nEssRev,nYBlock + nPosIrrev + nPosRev*6 + nNegIrrev + nNegRev*varsPerNegRev) speye(nEssRev)*-1 speye(nEssRev) sparse(nEssRev, nEssRev*4+nMetVars)];
    -0336 erRows2 = [sparse(nEssRev,nRxns + nYBlock + nPosIrrev + nPosRev*6 + nNegIrrev + nNegRev*varsPerNegRev) speye(nEssRev) speye(nEssRev) speye(nEssRev)*-1 sparse(nEssRev, nEssRev*3+nMetVars)];
    -0337 erRows3 = [sparse(nEssRev,nRxns + nYBlock + nPosIrrev + nPosRev*6 + nNegIrrev + nNegRev*varsPerNegRev) speye(nEssRev) sparse(nEssRev, nEssRev*2) spdiags(-bigMEssRev,0,nEssRev,nEssRev) speye(nEssRev) sparse(nEssRev, nEssRev+nMetVars)];
    -0338 erRows4 = [sparse(nEssRev,nRxns + nYBlock + nPosIrrev + nPosRev*6 + nNegIrrev + nNegRev*varsPerNegRev + nEssRev) speye(nEssRev) sparse(nEssRev, nEssRev) spdiags(bigMEssRev,0,nEssRev,nEssRev) sparse(nEssRev, nEssRev) speye(nEssRev) sparse(nEssRev, nMetVars)];
    -0339 
    -0340 %now the mets
    -0341 if ~isempty(metData)
    -0342     %Order of rxn "on" vars: Ypi Ypr Yni Ynr. This is a bit messy, since they are not in the
    -0343     %same order as the reactions in the S matrix or metData. We therefore resort the vars in
    -0344     %metData to match the order of the vars.
    -0345     srtMetData = sparse([metData(:,posIrrevRxns) metData(:,posRevRxns) metData(:,negIrrevRxns) metData(:,negRevRxns)]);
    -0346     
    -0347     %First the setup for giving bonus if the met is included.
    -0348     %The mon vars come first followed by the mv1 vars
    -0349     metRows1 = [sparse(nMetabolMets,nRxns) -srtMetData sparse(nMetabolMets,  nPosIrrev + nPosRev*6 + nNegIrrev + nNegRev*varsPerNegRev + nEssRev*6) speye(nMetabolMets) speye(nMetabolMets) sparse(nMetabolMets, 4*nMetNegRev + nMetNegIrrev)];
    -0350     %Then negative rev:
    -0351     % Variable order: eye(vnrbm),eye(vnrvm1),eye(vnrvm2),eye(vnrvm3)
    -0352     % vnrp,vnrn are the two first variables among the neg rev vars.
    -0353     % For reversible, we need a bool to make sure that one of vnrp and vnrn stays zero:
    -0354     %    vnrbm E {0,1}:  vnrp <= 100*vnrbm (if bool is 0, vnrp is zero): vnrp - 100*vnrbm + vnrvm1 == 0, vnrvm1 >= 0 (metRows2)
    -0355     %    vnrn <= (1-vnrbm)*100 (if bool is one, vnrn is zero): vnrn + 100*vnrbm + vnrvm2 == 0, -100 <= vnrvm2 <= inf (metRows3)
    -0356     % We then also say that vnrp + vnrn >= 0.1*Yi, -0.1*Yi + vnrp + vnrn - vnrvm3 == 0, vnrvm3 >= 0 (metRows4)
    -0357     nrEye = speye(nNegRev);
    -0358     %Force a column: when no met reaction is negative-reversible, indexing a
    -0359     %scalar bigMNegRev with an all-false mask returns a 0-by-0 empty, which
    -0360     %the spdiags calls below reject (they need a 0-by-1 column).
    -0361     bigMMetNegRev = bigMNegRev(metNegRev(negRevRxns));
    -0362     bigMMetNegRev = bigMMetNegRev(:);
    -0363     %vnrp - M*vnrbm + vnrvm1 == 0
    -0364     metRows2 = [sparse(nMetNegRev,nRxns + nYBlock + nPosIrrev + nPosRev*6 + nNegIrrev) ... %zeros up to vnrp
    -0365                 nrEye(metNegRev(negRevRxns),:) ... %vnrp
    -0366                 sparse(nMetNegRev, nNegRev*(varsPerNegRev-1) + nEssRev*6 + nMetabolMets*2) ... %zeros up to vnrbm
    -0367                 spdiags(-bigMMetNegRev,0,nMetNegRev,nMetNegRev) ... %vnrbm
    -0368                 speye(nMetNegRev) ... % vnrvm1
    -0369                 sparse(nMetNegRev, nMetNegRev*2 + nMetNegIrrev)];%fill up the rest with zeros
    -0370     %vnrn + M*vnrbm + vnrvm2 == 0
    -0371     metRows3 = [sparse(nMetNegRev,nRxns + nYBlock + nPosIrrev + nPosRev*6 + nNegIrrev + nNegRev) ... %zeros up to vnrn
    -0372                 nrEye(metNegRev(negRevRxns),:) ... %vnrp
    -0373                 sparse(nMetNegRev, nNegRev*(varsPerNegRev-2) + nEssRev*6 + nMetabolMets*2) ... %zeros up to vnrbm
    -0374                 spdiags(bigMMetNegRev,0,nMetNegRev,nMetNegRev) ... %vnrbm
    -0375                 sparse(nMetNegRev, nMetNegRev) ... %zeros up to vnrvm2
    -0376                 speye(nMetNegRev) ... % vnrvm2
    -0377                 sparse(nMetNegRev, nMetNegRev + nMetNegIrrev)];%fill up the rest with zeros
    -0378     %-0.1*Yi + vnrp + vnrn - vnrvm3 == 0
    -0379     metRows4 = [sparse(nMetNegRev,nRxns + nPosIrrev + nPosRev + nNegIrrev) ... %zeros up to Yi for neg rev
    -0380                 nrEye(metNegRev(negRevRxns),:)*-0.1 ... %Yi for met neg rev
    -0381                 sparse(nMetNegRev, nPosIrrev + nPosRev*6 + nNegIrrev) ... %zeros up to vnrp
    -0382                 nrEye(metNegRev(negRevRxns),:) ... %vnrp
    -0383                 nrEye(metNegRev(negRevRxns),:) ... %vnrn
    -0384                 sparse(nMetNegRev, nNegRev*(varsPerNegRev-2) + nEssRev*6 + nMetabolMets*2 + nMetNegRev*3) ...%zeros up to vnrvm3
    -0385                 speye(nMetNegRev)*-1 ... % vnrvm3
    -0386                 sparse(nMetNegRev, nMetNegIrrev)];%fill up the rest with zeros
    -0387     
    -0388     %Then negative irrev, i.e. flux - 0.1*Yi - vnim == 0
    -0389     niEye = speye(nNegIrrev);
    -0390     metRows5 = [sEye(metNegIrrev,:) ... %flux
    -0391                 sparse(nMetNegIrrev, nPosIrrev + nPosRev) ... % zeros up to Yi for neg irrev
    -0392                 niEye(metNegIrrev(negIrrevRxns),:)*-0.1 ... %Yi for met neg irrev
    -0393                 sparse(nMetNegIrrev, nNegRev + nPosIrrev + nPosRev*6 + nNegIrrev + nNegRev*varsPerNegRev + nEssRev*6 + nMetabolMets*2 + 4*nMetNegRev) ... %zeros up to vnim
    -0394                 -speye(nMetNegIrrev) ]; %vnim
    -0395     metRows = [metRows1;metRows2;metRows3;metRows4;metRows5];
    -0396     metVarC = [ones(nMetabolMets,1)*-prodWeight;zeros(nMetVars - nMetabolMets,1)];
    -0397     %vnrbm is boolean, so between 0 and 1
    -0398     %vnrvm1 >= 0
    -0399     %-100 <= vnrvm2 <= inf
    -0400     % vnrvm3 >= 0
    -0401     %0 <= vnim <= Inf
    -0402     metLb = [zeros(nMetabolMets*2 + 2*nMetNegRev,1);-bigMMetNegRev;zeros(nMetNegRev + nMetNegIrrev,1)];
    -0403     metUb = [ones(nMetabolMets,1);inf(nMetabolMets,1);ones(nMetNegRev,1);inf(3*nMetNegRev + nMetNegIrrev,1)];
    -0404     metVartype = [repmat('C', 1, nMetabolMets*2), ...
    -0405                   repmat('B', 1, nMetNegRev), ...
    -0406                   repmat('C', 1, 3*nMetNegRev + nMetNegIrrev)];
    -0407 
    -0408 else
    -0409     metRows = [];
    -0410     metVarC = [];
    -0411     metLb = [];
    -0412     metUb = [];
    -0413     metVartype = [];
    -0414 end
    -0415 
    -0416 prob.a = [sRow;piRows;prRows1;prRows2;prRows3;prRows4;niRows;nrRows;erRows1;erRows2;erRows3;erRows4;metRows];
    -0417 prob.A = prob.a;
    -0418 prob.b = zeros(size(prob.A,1),1);
    -0419 prob.c = [zeros(nRxns,1);rxnScores(posIrrevRxns)*-1;rxnScores(posRevRxns)*-1;rxnScores(negIrrevRxns)*-1;rxnScores(negRevRxns)*-1;zeros(nPosIrrev + nPosRev*6 + nNegIrrev + nNegRev*varsPerNegRev + nEssRev*6,1);metVarC];
    -0420 prob.lb = [milpModel.lb;zeros(nYBlock + nPosIrrev + 5*nPosRev,1);-bigMPosRev;zeros(nNegIrrev+nNegRev*varsPerNegRev+nEssRev*2,1);ones(nEssRev,1)*forceOnLim;zeros(nEssRev*2,1);-bigMEssRev;metLb];
    -0421 prob.lb(essIrrevRxns) = forceOnLimEss(essIrrevRxns);%force flux for the ess irrev rxns - this is the only way these are handled
    -0422 prob.ub = [milpModel.ub;ones(nYBlock,1);inf(nPosIrrev+nPosRev*2,1);ones(nPosRev,1);inf(3*nPosRev+nNegIrrev+varsPerNegRev*nNegRev+3*nEssRev,1);ones(nEssRev,1);inf(2*nEssRev,1);metUb];
    -0423 
    -0424 prob.vartype = [repmat('C', 1, nRxns + nPosIrrev + nPosRev), ...
    -0425                 repmat('B', 1, nNegIrrev + nNegRev), ...
    -0426                 repmat('C', 1, nPosIrrev + 2*nPosRev), ... 
    -0427                 repmat('B', 1, nPosRev), ...
    -0428                 repmat('C', 1, 3*nPosRev + nNegIrrev + varsPerNegRev*nNegRev + 3*nEssRev), ...
    -0429                 repmat('B', 1, nEssRev), ...
    -0430                 repmat('C', 1, 2*nEssRev), ...
    -0431                 metVartype];
    -0432             
    -0433 onoffVarInd = (1:(nPosIrrev + nPosRev + nNegIrrev + nNegRev)) + nRxns;
    -0434 onoffPosIrrev = onoffVarInd(1:nPosIrrev);
    -0435 onoffPosRev = onoffVarInd((1:nPosRev)+nPosIrrev);
    -0436 onoffNegIrrev = onoffVarInd((1:nNegIrrev)+nPosIrrev+nPosRev);
    -0437 onoffNegRev = onoffVarInd((1:nNegRev)+nPosIrrev+nPosRev+nNegIrrev);
    -0438 
    -0439 metVarInd = (1:nMetabolMets) + (length(prob.vartype) - nMetVars);
    -0440 
    -0441 if allowExcretion
    -0442     %S*v >= 0: a metabolite may be produced in excess of what is consumed,
    -0443     %and the surplus is implicitly excreted. 'L' would say S*v <= 0, which
    -0444     %lets a metabolite be consumed without ever being produced -- free
    -0445     %uptake, the opposite of excretion, and enough for the MILP to switch a
    -0446     %reaction on by conjuring its substrates out of nothing.
    -0447     prob.csense = [repmat('G', 1, length(milpModel.mets)), ...
    -0448                   repmat('E', 1, length(prob.b) - length(milpModel.mets))];
    -0449 else
    -0450     %One character per row: optimizeProb maps csense elementwise for glpk and
    -0451     %cobra, so a scalar '=' only happens to work on gurobi.
    -0452     prob.csense = repmat('E', 1, length(prob.b));
    -0453 end
    -0454 
    -0455 params.intTol = 10^-7; %This value is very important. If set too low
    -0456                        %there is a risk that gurobi fails due to numerical
    -0457                        %issues - this happened for Gurobi v. 10.0 with TestModelL.
    -0458                        %On the other hand, it should not be too large
    -0459                        %either. With this value, fluxes of 10-7 can slip
    -0460                        %through, which should be fine. Another option if
    -0461                        %this becomes a problem is to set NumericFocus=2,
    -0462                        %which makes the solver slower but fixes the issue
    -0463                        %with TestModelL.
    -0464 
    -0465 prob.osense = 1; %minimize
    -0466 
    -0467 if ~isempty(startVals)
    -0468      prob.start = startVals; %This doesn't work...
    -0469 end
    +0335 niRows = [sEye(negIrrevRxns,:) sparse(nNegIrrev,nPosIrrev + nPosRev) spdiags(-bigMNegIrrev,0,nNegIrrev,nNegIrrev) sparse(nNegIrrev,nNegRev + nPosIrrev + nPosRev*6) speye(nNegIrrev) sparse(nNegIrrev,nNegRev*varsPerNegRev + nEssRev*6+nMetVars)];
    +0336 
    +0337 nrRows1 = [sEye(negRevRxns,:) sparse(nNegRev,nYBlock + nPosIrrev + nPosRev*6 + nNegIrrev) speye(nNegRev)*-1 speye(nNegRev) sparse(nNegRev,nNegRev + nEssRev*6+nMetVars)];
    +0338 nrRows2 = [sparse(nNegRev,nRxns + nPosIrrev + nPosRev + nNegIrrev) spdiags(-bigMNegRev,0,nNegRev,nNegRev) sparse(nNegRev,nPosIrrev + nPosRev*6 + nNegIrrev) speye(nNegRev) speye(nNegRev) speye(nNegRev) sparse(nNegRev,nEssRev*6+nMetVars)];
    +0339 nrRows = [nrRows1;nrRows2];
    +0340 
    +0341 erRows1 = [sEye(essRevRxns,:) sparse(nEssRev,nYBlock + nPosIrrev + nPosRev*6 + nNegIrrev + nNegRev*varsPerNegRev) speye(nEssRev)*-1 speye(nEssRev) sparse(nEssRev, nEssRev*4+nMetVars)];
    +0342 erRows2 = [sparse(nEssRev,nRxns + nYBlock + nPosIrrev + nPosRev*6 + nNegIrrev + nNegRev*varsPerNegRev) speye(nEssRev) speye(nEssRev) speye(nEssRev)*-1 sparse(nEssRev, nEssRev*3+nMetVars)];
    +0343 erRows3 = [sparse(nEssRev,nRxns + nYBlock + nPosIrrev + nPosRev*6 + nNegIrrev + nNegRev*varsPerNegRev) speye(nEssRev) sparse(nEssRev, nEssRev*2) spdiags(-bigMEssRev,0,nEssRev,nEssRev) speye(nEssRev) sparse(nEssRev, nEssRev+nMetVars)];
    +0344 erRows4 = [sparse(nEssRev,nRxns + nYBlock + nPosIrrev + nPosRev*6 + nNegIrrev + nNegRev*varsPerNegRev + nEssRev) speye(nEssRev) sparse(nEssRev, nEssRev) spdiags(bigMEssRev,0,nEssRev,nEssRev) sparse(nEssRev, nEssRev) speye(nEssRev) sparse(nEssRev, nMetVars)];
    +0345 
    +0346 %now the mets
    +0347 if ~isempty(metData)
    +0348     %Order of rxn "on" vars: Ypi Ypr Yni Ynr. This is a bit messy, since they are not in the
    +0349     %same order as the reactions in the S matrix or metData. We therefore resort the vars in
    +0350     %metData to match the order of the vars.
    +0351     srtMetData = sparse([metData(:,posIrrevRxns) metData(:,posRevRxns) metData(:,negIrrevRxns) metData(:,negRevRxns)]);
    +0352     
    +0353     %First the setup for giving bonus if the met is included.
    +0354     %The mon vars come first followed by the mv1 vars
    +0355     metRows1 = [sparse(nMetabolMets,nRxns) -srtMetData sparse(nMetabolMets,  nPosIrrev + nPosRev*6 + nNegIrrev + nNegRev*varsPerNegRev + nEssRev*6) speye(nMetabolMets) speye(nMetabolMets) sparse(nMetabolMets, 4*nMetNegRev + nMetNegIrrev)];
    +0356     %Then negative rev:
    +0357     % Variable order: eye(vnrbm),eye(vnrvm1),eye(vnrvm2),eye(vnrvm3)
    +0358     % vnrp,vnrn are the two first variables among the neg rev vars.
    +0359     % For reversible, we need a bool to make sure that one of vnrp and vnrn stays zero:
    +0360     %    vnrbm E {0,1}:  vnrp <= 100*vnrbm (if bool is 0, vnrp is zero): vnrp - 100*vnrbm + vnrvm1 == 0, vnrvm1 >= 0 (metRows2)
    +0361     %    vnrn <= (1-vnrbm)*100 (if bool is one, vnrn is zero): vnrn + 100*vnrbm + vnrvm2 == 0, -100 <= vnrvm2 <= inf (metRows3)
    +0362     % We then also say that vnrp + vnrn >= 0.1*Yi, -0.1*Yi + vnrp + vnrn - vnrvm3 == 0, vnrvm3 >= 0 (metRows4)
    +0363     nrEye = speye(nNegRev);
    +0364     %Force a column: when no met reaction is negative-reversible, indexing a
    +0365     %scalar bigMNegRev with an all-false mask returns a 0-by-0 empty, which
    +0366     %the spdiags calls below reject (they need a 0-by-1 column).
    +0367     bigMMetNegRev = bigMNegRev(metNegRev(negRevRxns));
    +0368     bigMMetNegRev = bigMMetNegRev(:);
    +0369     %vnrp - M*vnrbm + vnrvm1 == 0
    +0370     metRows2 = [sparse(nMetNegRev,nRxns + nYBlock + nPosIrrev + nPosRev*6 + nNegIrrev) ... %zeros up to vnrp
    +0371                 nrEye(metNegRev(negRevRxns),:) ... %vnrp
    +0372                 sparse(nMetNegRev, nNegRev*(varsPerNegRev-1) + nEssRev*6 + nMetabolMets*2) ... %zeros up to vnrbm
    +0373                 spdiags(-bigMMetNegRev,0,nMetNegRev,nMetNegRev) ... %vnrbm
    +0374                 speye(nMetNegRev) ... % vnrvm1
    +0375                 sparse(nMetNegRev, nMetNegRev*2 + nMetNegIrrev)];%fill up the rest with zeros
    +0376     %vnrn + M*vnrbm + vnrvm2 == 0
    +0377     metRows3 = [sparse(nMetNegRev,nRxns + nYBlock + nPosIrrev + nPosRev*6 + nNegIrrev + nNegRev) ... %zeros up to vnrn
    +0378                 nrEye(metNegRev(negRevRxns),:) ... %vnrp
    +0379                 sparse(nMetNegRev, nNegRev*(varsPerNegRev-2) + nEssRev*6 + nMetabolMets*2) ... %zeros up to vnrbm
    +0380                 spdiags(bigMMetNegRev,0,nMetNegRev,nMetNegRev) ... %vnrbm
    +0381                 sparse(nMetNegRev, nMetNegRev) ... %zeros up to vnrvm2
    +0382                 speye(nMetNegRev) ... % vnrvm2
    +0383                 sparse(nMetNegRev, nMetNegRev + nMetNegIrrev)];%fill up the rest with zeros
    +0384     %-0.1*Yi + vnrp + vnrn - vnrvm3 == 0
    +0385     metRows4 = [sparse(nMetNegRev,nRxns + nPosIrrev + nPosRev + nNegIrrev) ... %zeros up to Yi for neg rev
    +0386                 nrEye(metNegRev(negRevRxns),:)*-0.1 ... %Yi for met neg rev
    +0387                 sparse(nMetNegRev, nPosIrrev + nPosRev*6 + nNegIrrev) ... %zeros up to vnrp
    +0388                 nrEye(metNegRev(negRevRxns),:) ... %vnrp
    +0389                 nrEye(metNegRev(negRevRxns),:) ... %vnrn
    +0390                 sparse(nMetNegRev, nNegRev*(varsPerNegRev-2) + nEssRev*6 + nMetabolMets*2 + nMetNegRev*3) ...%zeros up to vnrvm3
    +0391                 speye(nMetNegRev)*-1 ... % vnrvm3
    +0392                 sparse(nMetNegRev, nMetNegIrrev)];%fill up the rest with zeros
    +0393     
    +0394     %Then negative irrev, i.e. flux - 0.1*Yi - vnim == 0
    +0395     niEye = speye(nNegIrrev);
    +0396     metRows5 = [sEye(metNegIrrev,:) ... %flux
    +0397                 sparse(nMetNegIrrev, nPosIrrev + nPosRev) ... % zeros up to Yi for neg irrev
    +0398                 niEye(metNegIrrev(negIrrevRxns),:)*-0.1 ... %Yi for met neg irrev
    +0399                 sparse(nMetNegIrrev, nNegRev + nPosIrrev + nPosRev*6 + nNegIrrev + nNegRev*varsPerNegRev + nEssRev*6 + nMetabolMets*2 + 4*nMetNegRev) ... %zeros up to vnim
    +0400                 -speye(nMetNegIrrev) ]; %vnim
    +0401     metRows = [metRows1;metRows2;metRows3;metRows4;metRows5];
    +0402     metVarC = [ones(nMetabolMets,1)*-prodWeight;zeros(nMetVars - nMetabolMets,1)];
    +0403     %vnrbm is boolean, so between 0 and 1
    +0404     %vnrvm1 >= 0
    +0405     %-100 <= vnrvm2 <= inf
    +0406     % vnrvm3 >= 0
    +0407     %0 <= vnim <= Inf
    +0408     metLb = [zeros(nMetabolMets*2 + 2*nMetNegRev,1);-bigMMetNegRev;zeros(nMetNegRev + nMetNegIrrev,1)];
    +0409     metUb = [ones(nMetabolMets,1);inf(nMetabolMets,1);ones(nMetNegRev,1);inf(3*nMetNegRev + nMetNegIrrev,1)];
    +0410     metVartype = [repmat('C', 1, nMetabolMets*2), ...
    +0411                   repmat('B', 1, nMetNegRev), ...
    +0412                   repmat('C', 1, 3*nMetNegRev + nMetNegIrrev)];
    +0413 
    +0414 else
    +0415     metRows = [];
    +0416     metVarC = [];
    +0417     metLb = [];
    +0418     metUb = [];
    +0419     metVartype = [];
    +0420 end
    +0421 
    +0422 prob.a = [sRow;piRows;prRows1;prRows2;prRows3;prRows4;niRows;nrRows;erRows1;erRows2;erRows3;erRows4;metRows];
    +0423 prob.A = prob.a;
    +0424 prob.b = zeros(size(prob.A,1),1);
    +0425 prob.c = [zeros(nRxns,1);rxnScores(posIrrevRxns)*-1;rxnScores(posRevRxns)*-1;rxnScores(negIrrevRxns)*-1;rxnScores(negRevRxns)*-1;zeros(nPosIrrev + nPosRev*6 + nNegIrrev + nNegRev*varsPerNegRev + nEssRev*6,1);metVarC];
    +0426 prob.lb = [milpModel.lb;zeros(nYBlock + nPosIrrev + 5*nPosRev,1);-bigMPosRev;zeros(nNegIrrev+nNegRev*varsPerNegRev+nEssRev*2,1);ones(nEssRev,1)*forceOnLim;zeros(nEssRev*2,1);-bigMEssRev;metLb];
    +0427 prob.lb(essIrrevRxns) = forceOnLimEss(essIrrevRxns);%force flux for the ess irrev rxns - this is the only way these are handled
    +0428 prob.ub = [milpModel.ub;ones(nYBlock,1);inf(nPosIrrev+nPosRev*2,1);ones(nPosRev,1);inf(3*nPosRev+nNegIrrev+varsPerNegRev*nNegRev+3*nEssRev,1);ones(nEssRev,1);inf(2*nEssRev,1);metUb];
    +0429 
    +0430 prob.vartype = [repmat('C', 1, nRxns + nPosIrrev + nPosRev), ...
    +0431                 repmat('B', 1, nNegIrrev + nNegRev), ...
    +0432                 repmat('C', 1, nPosIrrev + 2*nPosRev), ... 
    +0433                 repmat('B', 1, nPosRev), ...
    +0434                 repmat('C', 1, 3*nPosRev + nNegIrrev + varsPerNegRev*nNegRev + 3*nEssRev), ...
    +0435                 repmat('B', 1, nEssRev), ...
    +0436                 repmat('C', 1, 2*nEssRev), ...
    +0437                 metVartype];
    +0438             
    +0439 onoffVarInd = (1:(nPosIrrev + nPosRev + nNegIrrev + nNegRev)) + nRxns;
    +0440 onoffPosIrrev = onoffVarInd(1:nPosIrrev);
    +0441 onoffPosRev = onoffVarInd((1:nPosRev)+nPosIrrev);
    +0442 onoffNegIrrev = onoffVarInd((1:nNegIrrev)+nPosIrrev+nPosRev);
    +0443 onoffNegRev = onoffVarInd((1:nNegRev)+nPosIrrev+nPosRev+nNegIrrev);
    +0444 
    +0445 metVarInd = (1:nMetabolMets) + (length(prob.vartype) - nMetVars);
    +0446 
    +0447 if allowExcretion
    +0448     %S*v >= 0: a metabolite may be produced in excess of what is consumed,
    +0449     %and the surplus is implicitly excreted. 'L' would say S*v <= 0, which
    +0450     %lets a metabolite be consumed without ever being produced -- free
    +0451     %uptake, the opposite of excretion, and enough for the MILP to switch a
    +0452     %reaction on by conjuring its substrates out of nothing.
    +0453     prob.csense = [repmat('G', 1, length(milpModel.mets)), ...
    +0454                   repmat('E', 1, length(prob.b) - length(milpModel.mets))];
    +0455 else
    +0456     %One character per row: optimizeProb maps csense elementwise for glpk and
    +0457     %cobra, so a scalar '=' only happens to work on gurobi.
    +0458     prob.csense = repmat('E', 1, length(prob.b));
    +0459 end
    +0460 
    +0461 params.intTol = 10^-7; %This value is very important. If set too low
    +0462                        %there is a risk that gurobi fails due to numerical
    +0463                        %issues - this happened for Gurobi v. 10.0 with TestModelL.
    +0464                        %On the other hand, it should not be too large
    +0465                        %either. With this value, fluxes of 10-7 can slip
    +0466                        %through, which should be fine. Another option if
    +0467                        %this becomes a problem is to set NumericFocus=2,
    +0468                        %which makes the solver slower but fixes the issue
    +0469                        %with TestModelL.
     0470 
    -0471 res=optimizeProb(prob,params,verbose);
    +0471 prob.osense = 1; %minimize
     0472 
    -0473 
    -0474 if ~checkSolution(res)
    -0475     if res.hitTimeLimit
    -0476         EM='Time limit reached without finding a solution. Try increasing the TimeLimit parameter.';
    -0477     else
    -0478         EM='The problem is infeasible';
    -0479     end
    -0480     error('RAVEN:badInput', '%s', EM);
    -0481 end
    -0482 
    -0483 if resolveTies && (nNegIrrev + nNegRev) > 0 && ~res.hitTimeLimit
    -0484     %Skipped when the primary solve stopped at the time limit: the floor
    -0485     %constraint would pin an unproven incumbent, so the phases cannot select
    -0486     %among true optima.
    -0487     %This MILP is highly degenerate: many reaction subsets reach the same
    -0488     %score optimum, and the solver returns an arbitrary one -- reproducible
    -0489     %for a fixed solver build + Seed, but fragile to a solver version bump
    -0490     %(raven-gecko-parity#104). This runs a lexicographic phase 2, holding
    -0491     %the score objective at its optimum with a floor constraint: first
    -0492     %minimise the count of "on" removable reactions (the sparsest optimum),
    -0493     %then, among the sparsest, minimise their summed reaction-id rank
    -0494     %(prefer lower ids). Only the negative-score "on" variables (onoffNegIrrev,
    -0495     %onoffNegRev) are a genuine tie to resolve: positive-score ones are
    -0496     %continuous, pinned near 1 by the score objective rather than a free
    -0497     %binary choice. Ports raven-toolbox's _resolve_ties (raven-toolbox#114).
    -0498     negCols = [onoffNegIrrev, onoffNegRev];
    -0499     negRxnIdx = [negIrrevRxns; negRevRxns];
    -0500     primaryObj = full(prob.c' * res.full);
    -0501     tol = max(abs(primaryObj)*1e-7, 1e-7);
    -0502 
    -0503     floorRow = prob.c';
    -0504     tieProb = prob;
    -0505     tieProb.A = [prob.A; floorRow];
    -0506     tieProb.a = tieProb.A;
    -0507     tieProb.b = [prob.b; primaryObj+tol];
    -0508     tieProb.csense = [prob.csense 'L'];
    -0509 
    -0510     %An integer objective needs only a cheap absolute-gap proof, not a tiny
    -0511     %relative one -- mirrors raven-toolbox's own MIPGap=0/MIPGapAbs=0.4 for
    -0512     %the same two phases.
    -0513     tieParams = params;
    -0514     tieParams.MIPGap = 0;
    -0515     tieParams.MIPGapAbs = 0.4;
    -0516     %Phases 2a and 2b get half of the time limit each, as in raven-toolbox.
    -0517     if isfield(tieParams,'TimeLimit')
    -0518         tieParams.TimeLimit = max(1,floor(tieParams.TimeLimit*0.5));
    -0519     end
    -0520 
    -0521     unproven = {};
    -0522 
    -0523     %Phase 2a: fewest "on" removable reactions among the score-optimal solutions.
    -0524     countObj = zeros(size(prob.c));
    -0525     countObj(negCols) = 1;
    -0526     tieProb.c = countObj;
    -0527     res2a = optimizeProb(tieProb,tieParams,verbose);
    -0528     [isFeasible2a, isOptimal2a] = checkSolution(res2a);
    -0529     if ~isOptimal2a
    -0530         unproven{end+1} = 'phase2a-parsimony';
    -0531     end
    -0532     if isFeasible2a
    -0533         kmin = full(countObj'*res2a.full);
    -0534 
    -0535         %Phase 2b: among the sparsest, prefer lower reaction ids -- a
    -0536         %deterministic tie-break independent of solver/seed.
    -0537         countRow = sparse(1,size(tieProb.A,2));
    -0538         countRow(negCols) = 1;
    -0539         tieProb2b = tieProb;
    -0540         tieProb2b.A = [tieProb.A; countRow];
    -0541         tieProb2b.a = tieProb2b.A;
    -0542         tieProb2b.b = [tieProb.b; kmin+0.5];
    -0543         tieProb2b.csense = [tieProb.csense 'L'];
    -0544 
    -0545         [~, sortOrder] = sort(rankKeys(negRxnIdx));
    -0546         ranks = zeros(numel(negRxnIdx),1);
    -0547         ranks(sortOrder) = (1:numel(negRxnIdx))';
    -0548         idObj = zeros(size(prob.c));
    -0549         idObj(negCols) = ranks;
    -0550         tieProb2b.c = idObj;
    -0551         res2b = optimizeProb(tieProb2b,tieParams,verbose);
    -0552         [isFeasible2b, isOptimal2b] = checkSolution(res2b);
    -0553         if ~isOptimal2b
    -0554             unproven{end+1} = 'phase2b-idrank';
    -0555         end
    -0556         if isFeasible2b
    -0557             %Adopt the tie-resolved solution for the on/off read-out below.
    -0558             res = res2b;
    -0559 
    -0560             %Phases 2c and 2d change the fluxes, not the on/off state; the next
    -0561             %ftINIT step reads the fluxes, and pinFluxes is false when no step
    -0562             %follows.
    -0563             if pinFluxes
    -0564                 %Phase 2c: the least total absolute flux for this on/off state.
    -0565                 %The next step forces each turned-on reaction in the direction
    -0566                 %of its flux, at min(0.99*|flux|,0.1), so the flux read-out
    -0567                 %comes from a minimal-flux solution instead of an arbitrary
    -0568                 %vertex. A binary on/off variable is fixed at its value; a
    -0569                 %continuous one that is on keeps at least its value, one that
    -0570                 %is off at most its value. Directions of reversible scored
    -0571                 %reactions stay free, so this is still a MILP.
    -0572                 %It stops at a relative or absolute gap of 1e-6, as raven-toolbox's
    -0573                 %phase 2c.
    -0574                 nCols = numel(prob.c);
    -0575                 minProb = tieProb2b;
    -0576                 if isfield(minProb,'start')
    -0577                     minProb = rmfield(minProb,'start');
    -0578                 end
    -0579                 x = res2b.full;
    -0580                 negVal = round(x(negCols));
    -0581                 minProb.lb(negCols) = negVal;
    -0582                 minProb.ub(negCols) = negVal;
    -0583                 posCols = [onoffPosIrrev, onoffPosRev];
    -0584                 posVal = min(max(x(posCols),0),1);
    -0585                 posOn = posVal >= 0.5;
    -0586                 minProb.lb(posCols(posOn)) = posVal(posOn);
    -0587                 minProb.ub(posCols(~posOn)) = posVal(~posOn);
    -0588                 %|v| is v for a forward-only flux, -v for a backward-only one,
    -0589                 %and an added t >= v, t >= -v otherwise.
    -0590                 fwdOnly = minProb.lb(1:nRxns) >= 0;
    -0591                 bwdOnly = ~fwdOnly & minProb.ub(1:nRxns) <= 0;
    -0592                 tRxns = find(~fwdOnly & ~bwdOnly);
    -0593                 nT = numel(tRxns);
    -0594                 selV = sparse(1:nT, tRxns, 1, nT, nCols);
    -0595                 minProb.A = [minProb.A sparse(size(minProb.A,1),nT); -selV speye(nT); selV speye(nT)];
    -0596                 minProb.a = minProb.A;
    -0597                 minProb.b = [minProb.b; zeros(2*nT,1)];
    -0598                 minProb.csense = [minProb.csense repmat('G',1,2*nT)];
    -0599                 minProb.lb = [minProb.lb; zeros(nT,1)];
    -0600                 minProb.ub = [minProb.ub; inf(nT,1)];
    -0601                 minProb.vartype = [minProb.vartype repmat('C',1,nT)];
    -0602                 minProb.c = [zeros(nCols,1); ones(nT,1)];
    -0603                 minProb.c(fwdOnly) = 1;
    -0604                 minProb.c(bwdOnly) = -1;
    -0605                 minParams = params;
    -0606                 minParams.MIPGap = 1e-6;
    -0607                 minParams.MIPGapAbs = 1e-6;
    -0608                 res2c = optimizeProb(minProb,minParams,verbose);
    -0609                 [isFeasible2c, isOptimal2c] = checkSolution(res2c);
    -0610                 if ~isOptimal2c
    -0611                     unproven{end+1} = 'phase2c-minflux';
    -0612                 end
    -0613                 if isFeasible2c
    -0614                     %Only the fluxes change; the status stays that of phase 2b.
    -0615                     res.full = res2c.full(1:nCols);
    -0616 
    -0617                     %Phase 2d: the least total flux can leave a direction open
    -0618                     %when two directions carry the same total flux, for example
    -0619                     %a pair of reversible reactions closing a loop either way.
    -0620                     %Keep the on/off state of phase 2c, hold the total absolute
    -0621                     %flux at most at its phase-2c value plus
    -0622                     %max(1e-7*total, 1e-6), and minimise the backward flux
    -0623                     %max(0,-v) of the reactions whose flux can take either sign,
    -0624                     %weighted by id rank from the end: sorted by rankKeys, the
    -0625                     %first of n reactions gets weight n and the last 1, so a
    -0626                     %reaction with a lower id prefers its forward direction.
    -0627                     if nT > 0
    -0628                         total = minProb.c' * res2c.full;
    -0629                         dirProb = minProb;
    -0630                         nCols2c = numel(minProb.c);
    -0631                         [~, ord] = sort(rankKeys(tRxns));
    -0632                         w = zeros(nT,1);
    -0633                         w(ord) = (nT:-1:1)';
    -0634                         dirProb.A = [minProb.A sparse(size(minProb.A,1),nT); ...
    -0635                                      minProb.c' sparse(1,nT); ...
    -0636                                      selV sparse(nT,nT) speye(nT)];
    -0637                         dirProb.a = dirProb.A;
    -0638                         dirProb.b = [minProb.b; total + max(abs(total)*1e-7, 1e-6); zeros(nT,1)];
    -0639                         dirProb.csense = [minProb.csense 'L' repmat('G',1,nT)];
    -0640                         dirProb.lb = [minProb.lb; zeros(nT,1)];
    -0641                         dirProb.ub = [minProb.ub; inf(nT,1)];
    -0642                         dirProb.vartype = [minProb.vartype repmat('C',1,nT)];
    -0643                         dirProb.c = [zeros(nCols2c,1); w];
    -0644                         %The phase-2c solution is the MIP start: the tight cap on
    -0645                         %the total flux leaves the solver's heuristics little room
    -0646                         %to find a first solution.
    -0647                         dirProb.start = [res2c.full; max(0, -res2c.full(tRxns))];
    -0648                         res2d = optimizeProb(dirProb,minParams,verbose);
    -0649                         [isFeasible2d, isOptimal2d] = checkSolution(res2d);
    -0650                         if ~isOptimal2d
    -0651                             unproven{end+1} = 'phase2d-direction';
    -0652                         end
    -0653                         if isFeasible2d
    -0654                             res.full = res2d.full(1:nCols);
    -0655                         end
    -0656                     end
    -0657                 end
    -0658             end
    -0659         end
    -0660     end
    -0661     if ~isempty(unproven)
    -0662         EM = ['ftINITInternalAlg tie resolution did not converge (' strjoin(unproven,', ') '): the selection among equally score-optimal solutions is itself an unproven incumbent, so resolveTies has reduced but not removed the run-to-run spread. Raise params.TimeLimit for a proven tie-break.'];
    -0663         warning('RAVEN:warning', '%s', EM);
    -0664     end
    -0665 end
    -0666 
    -0667 %get the on/off vals
    -0668 onoff = ones(nRxnsWithOnOff,1);%standard is on, i.e. all reactions not included in the problem + ess is on
    -0669 onoff(posIrrevRxns) = res.full(onoffPosIrrev);
    -0670 onoff(posRevRxns) = res.full(onoffPosRev);
    -0671 onoff(negIrrevRxns) = res.full(onoffNegIrrev);
    -0672 onoff(negRevRxns) = res.full(onoffNegRev);
    -0673 
    -0674 onoff2 = zeros(nRxnsWithOnOff,1);%standard is off, i.e. all reactions not included in the problem + ess is off
    -0675 onoff2(posIrrevRxns) = res.full(onoffPosIrrev);
    -0676 onoff2(posRevRxns) = res.full(onoffPosRev);
    -0677 onoff2(negIrrevRxns) = res.full(onoffNegIrrev);
    -0678 onoff2(negRevRxns) = res.full(onoffNegRev);
    +0473 if ~isempty(startVals)
    +0474      prob.start = startVals; %This doesn't work...
    +0475 end
    +0476 
    +0477 res=optimizeProb(prob,params,verbose);
    +0478 
    +0479 
    +0480 if ~checkSolution(res)
    +0481     if res.hitTimeLimit
    +0482         EM='Time limit reached without finding a solution. Try increasing the TimeLimit parameter.';
    +0483     else
    +0484         EM='The problem is infeasible';
    +0485     end
    +0486     error('RAVEN:badInput', '%s', EM);
    +0487 end
    +0488 
    +0489 if resolveTies && (nNegIrrev + nNegRev) > 0 && ~res.hitTimeLimit
    +0490     %Skipped when the primary solve stopped at the time limit: the floor
    +0491     %constraint would pin an unproven incumbent, so the phases cannot select
    +0492     %among true optima.
    +0493     %This MILP is highly degenerate: many reaction subsets reach the same
    +0494     %score optimum, and the solver returns an arbitrary one -- reproducible
    +0495     %for a fixed solver build + Seed, but fragile to a solver version bump
    +0496     %(raven-gecko-parity#104). This runs a lexicographic phase 2, holding
    +0497     %the score objective at its optimum with a floor constraint: first
    +0498     %minimise the count of "on" removable reactions (the sparsest optimum),
    +0499     %then, among the sparsest, minimise their summed reaction-id rank
    +0500     %(prefer lower ids). Only the negative-score "on" variables (onoffNegIrrev,
    +0501     %onoffNegRev) are a genuine tie to resolve: positive-score ones are
    +0502     %continuous, pinned near 1 by the score objective rather than a free
    +0503     %binary choice. Ports raven-toolbox's _resolve_ties (raven-toolbox#114).
    +0504     negCols = [onoffNegIrrev, onoffNegRev];
    +0505     negRxnIdx = [negIrrevRxns; negRevRxns];
    +0506     primaryObj = full(prob.c' * res.full);
    +0507     tol = max(abs(primaryObj)*1e-7, 1e-7);
    +0508 
    +0509     floorRow = prob.c';
    +0510     tieProb = prob;
    +0511     tieProb.A = [prob.A; floorRow];
    +0512     tieProb.a = tieProb.A;
    +0513     tieProb.b = [prob.b; primaryObj+tol];
    +0514     tieProb.csense = [prob.csense 'L'];
    +0515 
    +0516     %An integer objective needs only a cheap absolute-gap proof, not a tiny
    +0517     %relative one -- mirrors raven-toolbox's own MIPGap=0/MIPGapAbs=0.4 for
    +0518     %the same two phases.
    +0519     tieParams = params;
    +0520     tieParams.MIPGap = 0;
    +0521     tieParams.MIPGapAbs = 0.4;
    +0522     %Phases 2a and 2b get half of the time limit each, as in raven-toolbox.
    +0523     if isfield(tieParams,'TimeLimit')
    +0524         tieParams.TimeLimit = max(1,floor(tieParams.TimeLimit*0.5));
    +0525     end
    +0526 
    +0527     unproven = {};
    +0528 
    +0529     %Phase 2a: fewest "on" removable reactions among the score-optimal solutions.
    +0530     countObj = zeros(size(prob.c));
    +0531     countObj(negCols) = 1;
    +0532     tieProb.c = countObj;
    +0533     res2a = optimizeProb(tieProb,tieParams,verbose);
    +0534     [isFeasible2a, isOptimal2a] = checkSolution(res2a);
    +0535     if ~isOptimal2a
    +0536         unproven{end+1} = 'phase2a-parsimony';
    +0537     end
    +0538     if isFeasible2a
    +0539         kmin = full(countObj'*res2a.full);
    +0540 
    +0541         %Phase 2b: among the sparsest, prefer lower reaction ids -- a
    +0542         %deterministic tie-break independent of solver/seed.
    +0543         countRow = sparse(1,size(tieProb.A,2));
    +0544         countRow(negCols) = 1;
    +0545         tieProb2b = tieProb;
    +0546         tieProb2b.A = [tieProb.A; countRow];
    +0547         tieProb2b.a = tieProb2b.A;
    +0548         tieProb2b.b = [tieProb.b; kmin+0.5];
    +0549         tieProb2b.csense = [tieProb.csense 'L'];
    +0550 
    +0551         [~, sortOrder] = sort(rankKeys(negRxnIdx));
    +0552         ranks = zeros(numel(negRxnIdx),1);
    +0553         ranks(sortOrder) = (1:numel(negRxnIdx))';
    +0554         idObj = zeros(size(prob.c));
    +0555         idObj(negCols) = ranks;
    +0556         tieProb2b.c = idObj;
    +0557         res2b = optimizeProb(tieProb2b,tieParams,verbose);
    +0558         [isFeasible2b, isOptimal2b] = checkSolution(res2b);
    +0559         if ~isOptimal2b
    +0560             unproven{end+1} = 'phase2b-idrank';
    +0561         end
    +0562         if isFeasible2b
    +0563             %Adopt the tie-resolved solution for the on/off read-out below.
    +0564             res = res2b;
    +0565 
    +0566             %Phases 2c and 2d change the fluxes, not the on/off state; the next
    +0567             %ftINIT step reads the fluxes, and pinFluxes is false when no step
    +0568             %follows.
    +0569             if pinFluxes
    +0570                 %Phase 2c: the least total absolute flux for this on/off state.
    +0571                 %The next step forces each turned-on reaction in the direction
    +0572                 %of its flux, at min(0.99*|flux|,0.1), so the flux read-out
    +0573                 %comes from a minimal-flux solution instead of an arbitrary
    +0574                 %vertex. A binary on/off variable is fixed at its value; a
    +0575                 %continuous one that is on keeps at least its value, one that
    +0576                 %is off at most its value. Directions of reversible scored
    +0577                 %reactions stay free, so this is still a MILP.
    +0578                 %It stops at a relative or absolute gap of 1e-6, as raven-toolbox's
    +0579                 %phase 2c.
    +0580                 nCols = numel(prob.c);
    +0581                 minProb = tieProb2b;
    +0582                 if isfield(minProb,'start')
    +0583                     minProb = rmfield(minProb,'start');
    +0584                 end
    +0585                 x = res2b.full;
    +0586                 negVal = round(x(negCols));
    +0587                 minProb.lb(negCols) = negVal;
    +0588                 minProb.ub(negCols) = negVal;
    +0589                 posCols = [onoffPosIrrev, onoffPosRev];
    +0590                 posVal = min(max(x(posCols),0),1);
    +0591                 posOn = posVal >= 0.5;
    +0592                 minProb.lb(posCols(posOn)) = posVal(posOn);
    +0593                 minProb.ub(posCols(~posOn)) = posVal(~posOn);
    +0594                 %|v| is v for a forward-only flux, -v for a backward-only one,
    +0595                 %and an added t >= v, t >= -v otherwise.
    +0596                 fwdOnly = minProb.lb(1:nRxns) >= 0;
    +0597                 bwdOnly = ~fwdOnly & minProb.ub(1:nRxns) <= 0;
    +0598                 tRxns = find(~fwdOnly & ~bwdOnly);
    +0599                 nT = numel(tRxns);
    +0600                 selV = sparse(1:nT, tRxns, 1, nT, nCols);
    +0601                 minProb.A = [minProb.A sparse(size(minProb.A,1),nT); -selV speye(nT); selV speye(nT)];
    +0602                 minProb.a = minProb.A;
    +0603                 minProb.b = [minProb.b; zeros(2*nT,1)];
    +0604                 minProb.csense = [minProb.csense repmat('G',1,2*nT)];
    +0605                 minProb.lb = [minProb.lb; zeros(nT,1)];
    +0606                 minProb.ub = [minProb.ub; inf(nT,1)];
    +0607                 minProb.vartype = [minProb.vartype repmat('C',1,nT)];
    +0608                 minProb.c = [zeros(nCols,1); ones(nT,1)];
    +0609                 minProb.c(fwdOnly) = 1;
    +0610                 minProb.c(bwdOnly) = -1;
    +0611                 minParams = params;
    +0612                 minParams.MIPGap = 1e-6;
    +0613                 minParams.MIPGapAbs = 1e-6;
    +0614                 res2c = optimizeProb(minProb,minParams,verbose);
    +0615                 [isFeasible2c, isOptimal2c] = checkSolution(res2c);
    +0616                 if ~isOptimal2c
    +0617                     unproven{end+1} = 'phase2c-minflux';
    +0618                 end
    +0619                 if isFeasible2c
    +0620                     %Only the fluxes change; the status stays that of phase 2b.
    +0621                     res.full = res2c.full(1:nCols);
    +0622 
    +0623                     %Phase 2d: the least total flux can leave a direction open
    +0624                     %when two directions carry the same total flux, for example
    +0625                     %a pair of reversible reactions closing a loop either way.
    +0626                     %Keep the on/off state of phase 2c, hold the total absolute
    +0627                     %flux at most at its phase-2c value plus
    +0628                     %max(1e-7*total, 1e-6), and minimise the backward flux
    +0629                     %max(0,-v) of the reactions whose flux can take either sign,
    +0630                     %weighted by id rank from the end: sorted by rankKeys, the
    +0631                     %first of n reactions gets weight n and the last 1, so a
    +0632                     %reaction with a lower id prefers its forward direction.
    +0633                     if nT > 0
    +0634                         total = minProb.c' * res2c.full;
    +0635                         dirProb = minProb;
    +0636                         nCols2c = numel(minProb.c);
    +0637                         [~, ord] = sort(rankKeys(tRxns));
    +0638                         w = zeros(nT,1);
    +0639                         w(ord) = (nT:-1:1)';
    +0640                         dirProb.A = [minProb.A sparse(size(minProb.A,1),nT); ...
    +0641                                      minProb.c' sparse(1,nT); ...
    +0642                                      selV sparse(nT,nT) speye(nT)];
    +0643                         dirProb.a = dirProb.A;
    +0644                         dirProb.b = [minProb.b; total + max(abs(total)*1e-7, 1e-6); zeros(nT,1)];
    +0645                         dirProb.csense = [minProb.csense 'L' repmat('G',1,nT)];
    +0646                         dirProb.lb = [minProb.lb; zeros(nT,1)];
    +0647                         dirProb.ub = [minProb.ub; inf(nT,1)];
    +0648                         dirProb.vartype = [minProb.vartype repmat('C',1,nT)];
    +0649                         dirProb.c = [zeros(nCols2c,1); w];
    +0650                         %The phase-2c solution is the MIP start: the tight cap on
    +0651                         %the total flux leaves the solver's heuristics little room
    +0652                         %to find a first solution.
    +0653                         dirProb.start = [res2c.full; max(0, -res2c.full(tRxns))];
    +0654                         res2d = optimizeProb(dirProb,minParams,verbose);
    +0655                         [isFeasible2d, isOptimal2d] = checkSolution(res2d);
    +0656                         if ~isOptimal2d
    +0657                             unproven{end+1} = 'phase2d-direction';
    +0658                         end
    +0659                         if isFeasible2d
    +0660                             res.full = res2d.full(1:nCols);
    +0661                         end
    +0662                     end
    +0663                 end
    +0664             end
    +0665         end
    +0666     end
    +0667     if ~isempty(unproven)
    +0668         EM = ['ftINITInternalAlg tie resolution did not converge (' strjoin(unproven,', ') '): the selection among equally score-optimal solutions is itself an unproven incumbent, so resolveTies has reduced but not removed the run-to-run spread. Raise params.TimeLimit for a proven tie-break.'];
    +0669         warning('RAVEN:warning', '%s', EM);
    +0670     end
    +0671 end
    +0672 
    +0673 %get the on/off vals
    +0674 onoff = ones(nRxnsWithOnOff,1);%standard is on, i.e. all reactions not included in the problem + ess is on
    +0675 onoff(posIrrevRxns) = res.full(onoffPosIrrev);
    +0676 onoff(posRevRxns) = res.full(onoffPosRev);
    +0677 onoff(negIrrevRxns) = res.full(onoffNegIrrev);
    +0678 onoff(negRevRxns) = res.full(onoffNegRev);
     0679 
    -0680 
    -0681 %investigate a bit - this code could be used for fault finding. We have
    -0682 %this code commented out, but it is definitely worth testing this if for example
    -0683 %the solver is changed.
    -0684 %problematic = onoff < 0.99 & onoff > 0.01 & (rxnScores ~= 0).';
    -0685 %if sum(problematic) > 0
    -0686 %    disp("There are reactions that are in between on and off in the MILP. This may be due to the MILP parameter settings, the tolerances may be too large.")
    -0687 %    disp(["No problematic reactions: ", num2str(sum(problematic))])
    -0688 %end
    -0689 
    -0690 %so, it is only positive that are problematic. Likely because the 0.1 is too large. Test to change to 0.01
    -0691 %unique(onoff(onoff < 0.99 & onoff > 0.01))
    -0692 
    -0693 %Get all reactions used in the irreversible model
    -0694 %The reaction score check is for metabolomics - we add those reactions as
    -0695 %positive even though the score is 0. And, we do not want them included in the
    -0696 %results.
    -0697 deletedRxns=(onoff < 0.5).' & (rxnScores ~= 0).'; 
    -0698 turnedOnRxns=(onoff2 >= 0.5).' & (rxnScores ~= 0).';
    -0699 
    -0700 fluxes = res.full(1:nRxns);
    -0701 
    -0702 %extract the met data
    -0703 metProduction = logical(round(res.full(metVarInd)));
    -0704 
    -0705 end
    +0680 onoff2 = zeros(nRxnsWithOnOff,1);%standard is off, i.e. all reactions not included in the problem + ess is off +0681 onoff2(posIrrevRxns) = res.full(onoffPosIrrev); +0682 onoff2(posRevRxns) = res.full(onoffPosRev); +0683 onoff2(negIrrevRxns) = res.full(onoffNegIrrev); +0684 onoff2(negRevRxns) = res.full(onoffNegRev); +0685 +0686 +0687 %investigate a bit - this code could be used for fault finding. We have +0688 %this code commented out, but it is definitely worth testing this if for example +0689 %the solver is changed. +0690 %problematic = onoff < 0.99 & onoff > 0.01 & (rxnScores ~= 0).'; +0691 %if sum(problematic) > 0 +0692 % disp("There are reactions that are in between on and off in the MILP. This may be due to the MILP parameter settings, the tolerances may be too large.") +0693 % disp(["No problematic reactions: ", num2str(sum(problematic))]) +0694 %end +0695 +0696 %so, it is only positive that are problematic. Likely because the 0.1 is too large. Test to change to 0.01 +0697 %unique(onoff(onoff < 0.99 & onoff > 0.01)) +0698 +0699 %Get all reactions used in the irreversible model +0700 %The reaction score check is for metabolomics - we add those reactions as +0701 %positive even though the score is 0. And, we do not want them included in the +0702 %results. +0703 deletedRxns=(onoff < 0.5).' & (rxnScores ~= 0).'; +0704 turnedOnRxns=(onoff2 >= 0.5).' & (rxnScores ~= 0).'; +0705 +0706 fluxes = res.full(1:nRxns); +0707 +0708 %extract the met data +0709 metProduction = logical(round(res.full(metVarInd))); +0710 +0711 end

    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/INIT/getINITSteps.html b/docs/html/INIT/getINITSteps.html index 750e07a3..265c66f4 100644 --- a/docs/html/INIT/getINITSteps.html +++ b/docs/html/INIT/getINITSteps.html @@ -29,38 +29,36 @@

    SYNOPSIS ^DESCRIPTION ^

     getINITSteps  Define the steps used as input to ftINIT.
     
    - Defines the steps in the way ftINIT is run. Note that all combinations of
    - steps will not work. Avoid "exclude" if you want to define new
    - ways to run the algorithm.
    + Defines the steps in the way ftINIT is run. Not all combinations of steps
    + work. A new series that sets the HowToUsePrevResults option to "exclude",
    + which no predefined series uses, may not work.
     
      Name-Value Arguments
      --------------------
      metsToIgnore : struct
          structure describing mets that can be removed from the model before
    -     running ftINIT, such as water etc. With fields:
    +     running ftINIT, such as water and other ubiquitous metabolites. With fields:
     
          - simpleMets.mets : names of metabolites to remove.
          - simpleMets.compsToKeep : compartments for which metabolites are kept.
     
    -     (default []).
    +     (default []). Ignored for series "full".
      series : char
          describes the way to run ftINIT (default "1+1"):
     
    -     - "1+1" : standard behavior. Step 1 and 2 described in the paper are
    +     - "1+1" : standard behaviour. Step 1 and 2 described in the paper are
            merged into 1.
          - "2+1" : the 3-step procedure described in the paper. Faster and
    -       slightly less accurate than "1+1 steps".
    -     - "1+0" : same as "1+1 steps", but skips step 3 described in the paper.
    -       This will result in a model including a lot of reactions without GPRs.
    -       It is particularly useful for structural comparison, since the
    -       reactions removed in step 3 may be a bit random and does not really add
    -       any extra information. Faster than "1+1 steps".
    -     - "2+0" : same as "2+1 steps", but skips step 3 described in the paper.
    -       Faster and slightly less accurate than "1+0 steps", but will yield
    -       similar results.
    -     - "full" : 1-step run - similar to the old tINIT version but without
    -       simplifications. Accurate, but very slow. This is mainly used for
    -       testing purposes.
    +       slightly less accurate than "1+1".
    +     - "1+0" : same as "1+1", but skips step 3 described in the paper.
    +       The model includes many reactions without GPRs. The series suits
    +       structural comparison, because the reactions removed in step 3 can
    +       be arbitrary and add no information. Faster than "1+1".
    +     - "2+0" : same as "2+1", but skips step 3 described in the paper.
    +       Faster and slightly less accurate than "1+0", with similar results.
    +     - "full" : 1-step run, similar to tINIT, without the model
    +       simplifications that ftINIT applies in the other series. Accurate, but
    +       slow. This series is used for testing.
     
      Returns
      -------
    @@ -83,128 +81,126 @@ 

    SOURCE CODE ^
    0001 function steps = getINITSteps(varargin)
     0002 % getINITSteps  Define the steps used as input to ftINIT.
     0003 %
    -0004 % Defines the steps in the way ftINIT is run. Note that all combinations of
    -0005 % steps will not work. Avoid "exclude" if you want to define new
    -0006 % ways to run the algorithm.
    +0004 % Defines the steps in the way ftINIT is run. Not all combinations of steps
    +0005 % work. A new series that sets the HowToUsePrevResults option to "exclude",
    +0006 % which no predefined series uses, may not work.
     0007 %
     0008 % Name-Value Arguments
     0009 % --------------------
     0010 % metsToIgnore : struct
     0011 %     structure describing mets that can be removed from the model before
    -0012 %     running ftINIT, such as water etc. With fields:
    +0012 %     running ftINIT, such as water and other ubiquitous metabolites. With fields:
     0013 %
     0014 %     - simpleMets.mets : names of metabolites to remove.
     0015 %     - simpleMets.compsToKeep : compartments for which metabolites are kept.
     0016 %
    -0017 %     (default []).
    +0017 %     (default []). Ignored for series "full".
     0018 % series : char
     0019 %     describes the way to run ftINIT (default "1+1"):
     0020 %
    -0021 %     - "1+1" : standard behavior. Step 1 and 2 described in the paper are
    +0021 %     - "1+1" : standard behaviour. Step 1 and 2 described in the paper are
     0022 %       merged into 1.
     0023 %     - "2+1" : the 3-step procedure described in the paper. Faster and
    -0024 %       slightly less accurate than "1+1 steps".
    -0025 %     - "1+0" : same as "1+1 steps", but skips step 3 described in the paper.
    -0026 %       This will result in a model including a lot of reactions without GPRs.
    -0027 %       It is particularly useful for structural comparison, since the
    -0028 %       reactions removed in step 3 may be a bit random and does not really add
    -0029 %       any extra information. Faster than "1+1 steps".
    -0030 %     - "2+0" : same as "2+1 steps", but skips step 3 described in the paper.
    -0031 %       Faster and slightly less accurate than "1+0 steps", but will yield
    -0032 %       similar results.
    -0033 %     - "full" : 1-step run - similar to the old tINIT version but without
    -0034 %       simplifications. Accurate, but very slow. This is mainly used for
    -0035 %       testing purposes.
    -0036 %
    -0037 % Returns
    -0038 % -------
    -0039 % steps : cell
    -0040 %     cell array of steps, used as input to ftINIT.
    -0041 p=parseRAVENargs(varargin, {'metsToIgnore',[]; 'series','1+1'});
    -0042 metsToIgnore=p.metsToIgnore;
    -0043 series=p.series;
    -0044 
    -0045 if strcmp(series,'1+1') %step 1 and 2 are joined
    -0046     params1 = struct();
    -0047     params1.MIPGap = 0.0004;
    -0048     params1.TimeLimit = 120;
    -0049     params2 = struct();
    -0050     params2.MIPGap = 0.0030;
    -0051     params2.TimeLimit = 5000;
    -0052     params = {params1;params2};
    -0053     params3 = struct();
    -0054     params3.MIPGap = 0.0004;
    -0055     params3.TimeLimit = 5;
    -0056     paramsStep3 = {params3;params1;params2};
    -0057     %The paramsStep3 involves a quick first run. The objective value is often
    -0058     %small in the third step (~800), and 0.0004 of that is a very small number
    -0059     %With this first step, the rough value of the objective function will be
    -0060     %estimated, which will generate an absolute MIPGap limit that is much larger
    -0061     %for the second iteration.
    -0062     
    -0063     steps = { ...
    -0064         INITStepDesc(false, false, 'ignore', [1,1,1,1,1,1,1,0], metsToIgnore, params, {10;20}); ...
    -0065         INITStepDesc(false, false, 'essential', [1,0,0,0,1,0,0,0], metsToIgnore, paramsStep3, {10;10;20}); ...
    -0066         };
    -0067 elseif strcmp(series,'2+1') %the 3 step process described in the ftINIT paper
    -0068     params1 = struct();
    -0069     params1.MIPGap = 0.0004;
    -0070     params1.TimeLimit = 120;
    -0071     params2 = struct();
    -0072     params2.MIPGap = 0.0030;
    -0073     params2.TimeLimit = 5000;
    -0074     params = {params1;params2};
    -0075     params3 = struct();
    -0076     params3.MIPGap = 0.0004;
    -0077     params3.TimeLimit = 5;
    -0078     paramsStep3 = {params3;params1;params2};
    -0079     %The paramsStep3 involves a quick first run. The objective value is often
    -0080     %small in the third step (~800), and 0.0004 of that is a very small number
    -0081     %With this first step, the rough value of the objective function will be
    -0082     %estimated, which will generate an absolute MIPGap limit that is much larger
    -0083     %for the second iteration.
    -0084     
    -0085     steps = { ...
    -0086         INITStepDesc(true, true, 'ignore', [1,1,1,1,1,1,1,0], metsToIgnore, params, {10;20}); ...
    -0087         INITStepDesc(false, false, 'essential', [1,1,1,1,1,1,1,0], metsToIgnore, params, {10;20}); ...
    -0088         INITStepDesc(false, false, 'essential', [1,0,0,0,1,0,0,0], metsToIgnore, paramsStep3, {10;10;20}); ...
    -0089         };
    -0090 elseif strcmp(series,'1+0') %Joins step 1 and 2, skips step 3
    -0091     params1 = struct();
    -0092     params1.MIPGap = 0.0004;
    -0093     params1.TimeLimit = 120;
    -0094     params2 = struct();
    -0095     params2.MIPGap = 0.0030;
    -0096     params2.TimeLimit = 5000;
    -0097     params = {params1;params2};
    -0098     steps = { ...
    -0099         INITStepDesc(false, false, 'ignore', [1,1,1,1,1,1,1,0], metsToIgnore, params, {10;20}); ...
    -0100         };
    -0101 elseif strcmp(series,'2+0') %Skips step 3
    -0102     params1 = struct();
    -0103     params1.MIPGap = 0.0004;
    -0104     params1.TimeLimit = 120;
    -0105     params2 = struct();
    -0106     params2.MIPGap = 0.0030;
    -0107     params2.TimeLimit = 5000;
    -0108     params = {params1;params2};
    -0109     steps = { ...
    -0110         INITStepDesc(true, true, 'ignore', [1,1,1,1,1,1,1,0], metsToIgnore, params, {10;20}); ...
    -0111         INITStepDesc(false, false, 'essential', [1,1,1,1,1,1,1,0], metsToIgnore, params, {10;20}); ...
    -0112         };
    -0113 elseif strcmp(series,'full') %Just one run, slow on large models, but this is the 'perfect' setup
    -0114     params1 = struct();
    -0115     params1.MIPGap = 0.0004;
    -0116     params1.TimeLimit = 10000;
    -0117     params = {params1};
    -0118     steps = { ...
    -0119         INITStepDesc(false, false, 'ignore', [0,0,0,0,0,0,0,0], [], params) ...
    -0120         };
    -0121 else
    -0122     error('RAVEN:badInput', '%s', ['Invalid series in getINITSteps: ' series])
    -0123 end
    -0124 
    -0125 end

    +0024 % slightly less accurate than "1+1". +0025 % - "1+0" : same as "1+1", but skips step 3 described in the paper. +0026 % The model includes many reactions without GPRs. The series suits +0027 % structural comparison, because the reactions removed in step 3 can +0028 % be arbitrary and add no information. Faster than "1+1". +0029 % - "2+0" : same as "2+1", but skips step 3 described in the paper. +0030 % Faster and slightly less accurate than "1+0", with similar results. +0031 % - "full" : 1-step run, similar to tINIT, without the model +0032 % simplifications that ftINIT applies in the other series. Accurate, but +0033 % slow. This series is used for testing. +0034 % +0035 % Returns +0036 % ------- +0037 % steps : cell +0038 % cell array of steps, used as input to ftINIT. +0039 p=parseRAVENargs(varargin, {'metsToIgnore',[]; 'series','1+1'}); +0040 metsToIgnore=p.metsToIgnore; +0041 series=p.series; +0042 +0043 if strcmp(series,'1+1') %step 1 and 2 are joined +0044 params1 = struct(); +0045 params1.MIPGap = 0.0004; +0046 params1.TimeLimit = 120; +0047 params2 = struct(); +0048 params2.MIPGap = 0.0030; +0049 params2.TimeLimit = 5000; +0050 params = {params1;params2}; +0051 params3 = struct(); +0052 params3.MIPGap = 0.0004; +0053 params3.TimeLimit = 5; +0054 paramsStep3 = {params3;params1;params2}; +0055 %The paramsStep3 involves a quick first run. The objective value is often +0056 %small in the third step (~800), and 0.0004 of that is a very small number +0057 %With this first step, the rough value of the objective function will be +0058 %estimated, which will generate an absolute MIPGap limit that is much larger +0059 %for the second iteration. +0060 +0061 steps = { ... +0062 INITStepDesc(false, false, 'ignore', [1,1,1,1,1,1,1,0], metsToIgnore, params, {10;20}); ... +0063 INITStepDesc(false, false, 'essential', [1,0,0,0,1,0,0,0], metsToIgnore, paramsStep3, {10;10;20}); ... +0064 }; +0065 elseif strcmp(series,'2+1') %the 3 step process described in the ftINIT paper +0066 params1 = struct(); +0067 params1.MIPGap = 0.0004; +0068 params1.TimeLimit = 120; +0069 params2 = struct(); +0070 params2.MIPGap = 0.0030; +0071 params2.TimeLimit = 5000; +0072 params = {params1;params2}; +0073 params3 = struct(); +0074 params3.MIPGap = 0.0004; +0075 params3.TimeLimit = 5; +0076 paramsStep3 = {params3;params1;params2}; +0077 %The paramsStep3 involves a quick first run. The objective value is often +0078 %small in the third step (~800), and 0.0004 of that is a very small number +0079 %With this first step, the rough value of the objective function will be +0080 %estimated, which will generate an absolute MIPGap limit that is much larger +0081 %for the second iteration. +0082 +0083 steps = { ... +0084 INITStepDesc(true, true, 'ignore', [1,1,1,1,1,1,1,0], metsToIgnore, params, {10;20}); ... +0085 INITStepDesc(false, false, 'essential', [1,1,1,1,1,1,1,0], metsToIgnore, params, {10;20}); ... +0086 INITStepDesc(false, false, 'essential', [1,0,0,0,1,0,0,0], metsToIgnore, paramsStep3, {10;10;20}); ... +0087 }; +0088 elseif strcmp(series,'1+0') %Joins step 1 and 2, skips step 3 +0089 params1 = struct(); +0090 params1.MIPGap = 0.0004; +0091 params1.TimeLimit = 120; +0092 params2 = struct(); +0093 params2.MIPGap = 0.0030; +0094 params2.TimeLimit = 5000; +0095 params = {params1;params2}; +0096 steps = { ... +0097 INITStepDesc(false, false, 'ignore', [1,1,1,1,1,1,1,0], metsToIgnore, params, {10;20}); ... +0098 }; +0099 elseif strcmp(series,'2+0') %Skips step 3 +0100 params1 = struct(); +0101 params1.MIPGap = 0.0004; +0102 params1.TimeLimit = 120; +0103 params2 = struct(); +0104 params2.MIPGap = 0.0030; +0105 params2.TimeLimit = 5000; +0106 params = {params1;params2}; +0107 steps = { ... +0108 INITStepDesc(true, true, 'ignore', [1,1,1,1,1,1,1,0], metsToIgnore, params, {10;20}); ... +0109 INITStepDesc(false, false, 'essential', [1,1,1,1,1,1,1,0], metsToIgnore, params, {10;20}); ... +0110 }; +0111 elseif strcmp(series,'full') %Just one run, slow on large models, but this is the 'perfect' setup +0112 params1 = struct(); +0113 params1.MIPGap = 0.0004; +0114 params1.TimeLimit = 10000; +0115 params = {params1}; +0116 steps = { ... +0117 INITStepDesc(false, false, 'ignore', [0,0,0,0,0,0,0,0], [], params) ... +0118 }; +0119 else +0120 error('RAVEN:badInput', '%s', ['Invalid series in getINITSteps: ' series]) +0121 end +0122 +0123 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/INIT/groupRxnScores.html b/docs/html/INIT/groupRxnScores.html index c18fc74a..6d52cf5e 100644 --- a/docs/html/INIT/groupRxnScores.html +++ b/docs/html/INIT/groupRxnScores.html @@ -28,9 +28,9 @@

    SYNOPSIS ^DESCRIPTION ^

     groupRxnScores
    - This function sums up the reaction scores for all reactions that were merged 
    - into one by the linear merge.
    - 
    + Sums the reaction scores of all reactions that were merged into one by the
    + linear merge.
    +
      model          The model with linearly merged rxns.
      origRxnScores  The rxnScores from the model before the linear merge.
      origRxnIds     The rxn ids of the model before the linear merge.
    @@ -39,10 +39,11 @@ 

    DESCRIPTION ^CROSS-REFERENCE INFORMATION ^

    @@ -59,8 +60,8 @@

    CROSS-REFERENCE INFORMATION ^
 <h2><a name=SOURCE CODE ^

    0001 function newRxnScores = groupRxnScores(model, origRxnScores, origRxnIds, groupIds, origRxnsToZero)
     0002 % groupRxnScores
    -0003 % This function sums up the reaction scores for all reactions that were merged
    -0004 % into one by the linear merge.
    +0003 % Sums the reaction scores of all reactions that were merged into one by the
    +0004 % linear merge.
     0005 %
     0006 % model          The model with linearly merged rxns.
     0007 % origRxnScores  The rxnScores from the model before the linear merge.
    @@ -70,39 +71,40 @@ 

    SOURCE CODE ^% the reaction was not merged with any other reaction. A 0012 % non-zero integer means the reaction was merged with all 0013 % other rxns sharing the same integer. -0014 % origRxnsToZero A logical vector saying which of the original rxns that should not +0014 % origRxnsToZero A logical vector saying which of the original rxns should not 0015 % be part of the problem; the score for these reactions is set to 0. -0016 % To keep 0 uniquely meaning "excluded", any genuine rxnScore that is -0017 % (or sums to) exactly 0 is nudged to 0.01 instead. -0018 +0016 % Since 0 means "excluded", an original score of 0 and a group +0017 % score that sums to 0 while at least one score of the group +0018 % is not 0 are set to 0.01. 0019 -0020 newRxnScores = zeros(length(model.rxns),1); -0021 [~,ia,ib] = intersect(model.rxns,origRxnIds); -0022 grpIdsMerged = nan(length(model.rxns),1); -0023 grpIdsMerged(ia) = groupIds(ib); -0024 %check if any of the original scores are 0, in that case change them to 0.01 (unlikely) -0025 origRxnScores(origRxnScores == 0) = 0.01; -0026 %Then set the rxn scores for rxns to zero to 0 -0027 origRxnScores(origRxnsToZero) = 0; -0028 -0029 %fill in original scores -0030 newRxnScores(ia) = origRxnScores(ib); -0031 -0032 for i = 1:length(model.rxns) -0033 %for reactions that are not merged with anything, just keep score as it is -0034 if grpIdsMerged(i) ~= 0 -0035 %find all original rxns in the group -0036 sel = groupIds == grpIdsMerged(i); -0037 newRxnScores(i) = sum(origRxnScores(sel)); -0038 if (newRxnScores(i) == 0 && any(origRxnScores(sel) ~= 0)) -0039 %special unfortunate case, where the reactions happen to sum to 0 while some of them are nonzero -0040 %set to 0.01 in this case (proabably pretty unusual) -0041 newRxnScores(i) = 0.01; -0042 end -0043 end -0044 end -0045 -0046 end

    +0020 +0021 newRxnScores = zeros(length(model.rxns),1); +0022 [~,ia,ib] = intersect(model.rxns,origRxnIds); +0023 grpIdsMerged = nan(length(model.rxns),1); +0024 grpIdsMerged(ia) = groupIds(ib); +0025 %check if any of the original scores are 0, in that case change them to 0.01 (unlikely) +0026 origRxnScores(origRxnScores == 0) = 0.01; +0027 %Then set the rxn scores for rxns to zero to 0 +0028 origRxnScores(origRxnsToZero) = 0; +0029 +0030 %fill in original scores +0031 newRxnScores(ia) = origRxnScores(ib); +0032 +0033 for i = 1:length(model.rxns) +0034 %for reactions that are not merged with anything, just keep score as it is +0035 if grpIdsMerged(i) ~= 0 +0036 %find all original rxns in the group +0037 sel = groupIds == grpIdsMerged(i); +0038 newRxnScores(i) = sum(origRxnScores(sel)); +0039 if (newRxnScores(i) == 0 && any(origRxnScores(sel) ~= 0)) +0040 %special unfortunate case, where the reactions happen to sum to 0 while some of them are nonzero +0041 %set to 0.01 in this case (proabably pretty unusual) +0042 newRxnScores(i) = 0.01; +0043 end +0044 end +0045 end +0046 +0047 end

    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/INIT/mergeLinear.html b/docs/html/INIT/mergeLinear.html index 03b32a75..9647dd3b 100644 --- a/docs/html/INIT/mergeLinear.html +++ b/docs/html/INIT/mergeLinear.html @@ -28,11 +28,11 @@

    SYNOPSIS ^DESCRIPTION ^

     mergeLinear
    -   Simplifies a model by merging rxns with linear dependencies. There are two
    -   differences from the function in simplifyModel:
    -   1) Here we return which reactions were merged
    -   2) Here we allow reversible reactions to be merged as well, which will 
    -      probably reduce the size of the model more.
    +   Simplifies a model by merging rxns with linear dependencies. It differs
    +   from the grouping in simplifyModel in two ways:
    +   1) It returns which reactions were merged.
    +   2) It also merges reversible reactions.
    +   The reduced model has no genes and empty grRules.
     
        model                 a model structure
        noMergeRxns           Cell array with reaction IDs that are not allowed to be
    @@ -45,7 +45,7 @@ 

    DESCRIPTION ^CROSS-REFERENCE INFORMATION ^

    @@ -64,11 +64,11 @@

    SUBFUNCTIONS ^SOURCE CODE ^

    0001 function [reducedModel,origRxnIds,groupIds,reversedRxns]=mergeLinear(model, noMergeRxns)
     0002 % mergeLinear
    -0003 %   Simplifies a model by merging rxns with linear dependencies. There are two
    -0004 %   differences from the function in simplifyModel:
    -0005 %   1) Here we return which reactions were merged
    -0006 %   2) Here we allow reversible reactions to be merged as well, which will
    -0007 %      probably reduce the size of the model more.
    +0003 %   Simplifies a model by merging rxns with linear dependencies. It differs
    +0004 %   from the grouping in simplifyModel in two ways:
    +0005 %   1) It returns which reactions were merged.
    +0006 %   2) It also merges reversible reactions.
    +0007 %   The reduced model has no genes and empty grRules.
     0008 %
     0009 %   model                 a model structure
     0010 %   noMergeRxns           Cell array with reaction IDs that are not allowed to be
    @@ -81,7 +81,7 @@ 

    SOURCE CODE ^% in the order of their first reaction in origRxnIds. 0018 % reversedRxns Vector of booleans, saying for each rxn if it is reversed in the combined rxns. 0019 % -0020 % Usage: [reducedModel,origRxnIds,groupIds]=mergeLinear(model,noMergeRxns) +0020 % Usage: [reducedModel,origRxnIds,groupIds,reversedRxns]=mergeLinear(model,noMergeRxns) 0021 0022 0023 reducedModel=model; diff --git a/docs/html/INIT/parseHPA.html b/docs/html/INIT/parseHPA.html index eebd67fb..213c7486 100644 --- a/docs/html/INIT/parseHPA.html +++ b/docs/html/INIT/parseHPA.html @@ -38,8 +38,7 @@

    DESCRIPTION ^DESCRIPTION ^SOURCE CODE ^% Name-Value Arguments 0011 % -------------------- 0012 % version : double -0013 % accepted for backward compatibility but ignored; the format is now -0014 % inferred from the column headers (default 19). -0015 % -0016 % Returns -0017 % ------- -0018 % hpaData : struct -0019 % parsed HPA data with fields: -0020 % -0021 % - genes : cell array with the unique gene IDs (Ensembl in v>=18) -0022 % - geneNames : cell array with the gene symbols (present in v>=18 only) -0023 % - tissues : cell array with the tissue names (may not be unique; one -0024 % entry per tissue-cell-type combination) -0025 % - celltypes : cell array with the cell type names for each tissue -0026 % - levels : cell array with the unique expression level labels -0027 % - types : cell array with the unique evidence types (v<18 only) -0028 % - reliabilities : cell array with the unique reliability levels -0029 % - gene2Level : sparse gene × tissue-cell-type matrix; value i,j is -0030 % the index in hpaData.levels for gene i in tissue-cell-type j -0031 % - gene2Type : sparse gene × tissue-cell-type matrix (v<18 only) -0032 % - gene2Reliability : sparse gene × tissue-cell-type matrix; value i,j -0033 % is the index in hpaData.reliabilities for gene i in tissue-cell-type j -0034 % -0035 % Examples -0036 % -------- -0037 % hpaData = parseHPA(fileName); -0038 -0039 p=parseRAVENargs(varargin, {'version',19}); %#ok<NASGU> retained for backward compat -0040 -0041 fileName=char(fileName); -0042 if ~isfile(fileName) -0043 error('HPA file %s cannot be found', string(fileName)); -0044 end -0045 -0046 %Read header line and auto-detect delimiter -0047 fid=fopen(fileName,'r'); -0048 headerLine=fgetl(fid); -0049 fclose(fid); -0050 -0051 if any(headerLine==9) % char(9) = horizontal tab -0052 delim=sprintf('\t'); -0053 else -0054 delim=','; -0055 end -0056 -0057 %Parse header and all data columns -0058 fid=fopen(fileName,'r'); -0059 headers=strsplit(fgetl(fid), delim); -0060 fmtStr=strip(repmat('%q ',1,numel(headers))); -0061 hpa=textscan(fid, fmtStr, 'Delimiter',delim); -0062 fclose(fid); -0063 -0064 %Helper: column index by name -0065 ci=@(name) find(strcmp(headers,name),1); -0066 -0067 %Validate required columns common to all versions -0068 for h={'Gene','Tissue','Cell type','Level','Reliability'} -0069 if isempty(ci(h{1})) -0070 error(['Could not find the column "' h{1} '". ' ... -0071 'Make sure the input file matches the format at https://www.proteinatlas.org/about/download']); -0072 end -0073 end -0074 -0075 hasGeneName=~isempty(ci('Gene name')); -0076 hasExprType=~isempty(ci('Expression type')); -0077 -0078 [hpaData.genes, P, I]=unique(hpa{ci('Gene')}); -0079 if hasGeneName -0080 hpaData.geneNames=hpa{ci('Gene name')}(P); -0081 end -0082 -0083 tissueCellStr=strcat(hpa{ci('Tissue')},'€',hpa{ci('Cell type')}); -0084 [~, J, K]=unique(tissueCellStr); -0085 hpaData.tissues=hpa{ci('Tissue')}(J); -0086 hpaData.celltypes=hpa{ci('Cell type')}(J); -0087 -0088 [hpaData.levels, ~, L]=unique(hpa{ci('Level')}); -0089 [hpaData.reliabilities, ~, N]=unique(hpa{ci('Reliability')}); -0090 -0091 nGenes=numel(hpaData.genes); -0092 nTissues=max(K); -0093 -0094 hpaData.gene2Level=sparse(I,K,L,nGenes,nTissues); -0095 hpaData.gene2Reliability=sparse(I,K,N,nGenes,nTissues); -0096 -0097 if hasExprType -0098 [hpaData.types, ~, M]=unique(hpa{ci('Expression type')}); -0099 hpaData.gene2Type=sparse(I,K,M,nGenes,nTissues); -0100 end -0101 end

    +0013 % ignored; the format is inferred from the column headers (default 19). +0014 % +0015 % Returns +0016 % ------- +0017 % hpaData : struct +0018 % parsed HPA data with fields: +0019 % +0020 % - genes : cell array with the unique gene IDs (Ensembl in v>=18) +0021 % - geneNames : cell array with the gene symbols (present in v>=18 only) +0022 % - tissues : cell array with the tissue names (can repeat; one +0023 % entry per tissue-cell-type combination) +0024 % - celltypes : cell array with the cell type names for each tissue +0025 % - levels : cell array with the unique expression level labels +0026 % - types : cell array with the unique evidence types (v<18 only) +0027 % - reliabilities : cell array with the unique reliability levels +0028 % - gene2Level : sparse gene × tissue-cell-type matrix; value i,j is +0029 % the index in hpaData.levels for gene i in tissue-cell-type j +0030 % - gene2Type : sparse gene × tissue-cell-type matrix (v<18 only) +0031 % - gene2Reliability : sparse gene × tissue-cell-type matrix; value i,j +0032 % is the index in hpaData.reliabilities for gene i in tissue-cell-type j +0033 % +0034 % Examples +0035 % -------- +0036 % hpaData = parseHPA(fileName); +0037 +0038 p=parseRAVENargs(varargin, {'version',19}); %#ok<NASGU> retained for backward compat +0039 +0040 fileName=char(fileName); +0041 if ~isfile(fileName) +0042 error('HPA file %s cannot be found', string(fileName)); +0043 end +0044 +0045 %Read header line and auto-detect delimiter +0046 fid=fopen(fileName,'r'); +0047 headerLine=fgetl(fid); +0048 fclose(fid); +0049 +0050 if any(headerLine==9) % char(9) = horizontal tab +0051 delim=sprintf('\t'); +0052 else +0053 delim=','; +0054 end +0055 +0056 %Parse header and all data columns +0057 fid=fopen(fileName,'r'); +0058 headers=strsplit(fgetl(fid), delim); +0059 fmtStr=strip(repmat('%q ',1,numel(headers))); +0060 hpa=textscan(fid, fmtStr, 'Delimiter',delim); +0061 fclose(fid); +0062 +0063 %Helper: column index by name +0064 ci=@(name) find(strcmp(headers,name),1); +0065 +0066 %Validate required columns common to all versions +0067 for h={'Gene','Tissue','Cell type','Level','Reliability'} +0068 if isempty(ci(h{1})) +0069 error(['Could not find the column "' h{1} '". ' ... +0070 'Make sure the input file matches the format at https://www.proteinatlas.org/about/download']); +0071 end +0072 end +0073 +0074 hasGeneName=~isempty(ci('Gene name')); +0075 hasExprType=~isempty(ci('Expression type')); +0076 +0077 [hpaData.genes, P, I]=unique(hpa{ci('Gene')}); +0078 if hasGeneName +0079 hpaData.geneNames=hpa{ci('Gene name')}(P); +0080 end +0081 +0082 tissueCellStr=strcat(hpa{ci('Tissue')},'€',hpa{ci('Cell type')}); +0083 [~, J, K]=unique(tissueCellStr); +0084 hpaData.tissues=hpa{ci('Tissue')}(J); +0085 hpaData.celltypes=hpa{ci('Cell type')}(J); +0086 +0087 [hpaData.levels, ~, L]=unique(hpa{ci('Level')}); +0088 [hpaData.reliabilities, ~, N]=unique(hpa{ci('Reliability')}); +0089 +0090 nGenes=numel(hpaData.genes); +0091 nTissues=max(K); +0092 +0093 hpaData.gene2Level=sparse(I,K,L,nGenes,nTissues); +0094 hpaData.gene2Reliability=sparse(I,K,N,nGenes,nTissues); +0095 +0096 if hasExprType +0097 [hpaData.types, ~, M]=unique(hpa{ci('Expression type')}); +0098 hpaData.gene2Type=sparse(I,K,M,nGenes,nTissues); +0099 end +0100 end

    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/INIT/parseHPArna.html b/docs/html/INIT/parseHPArna.html index 11094e4b..3008f6f8 100644 --- a/docs/html/INIT/parseHPArna.html +++ b/docs/html/INIT/parseHPArna.html @@ -38,8 +38,7 @@

    DESCRIPTION ^SOURCE CODE ^% Name-Value Arguments 0011 % -------------------- 0012 % version : double -0013 % accepted for backward compatibility but ignored; the format is now -0014 % inferred from the column headers (default 19). -0015 % -0016 % Returns -0017 % ------- -0018 % arrayData : struct -0019 % parsed HPA RNA data with fields: -0020 % -0021 % - genes : cell array with the unique Ensembl gene IDs -0022 % - geneNames : cell array with the gene symbols -0023 % - tissues : cell array with the unique tissue (or sample) names -0024 % - levels : matrix of expression values (rows = genes, cols = tissues) -0025 % -0026 % Examples -0027 % -------- -0028 % arrayData = parseHPArna(fileName); -0029 -0030 p=parseRAVENargs(varargin, {'version',19}); %#ok<NASGU> retained for backward compat -0031 -0032 fileName=char(fileName); -0033 if ~isfile(fileName) -0034 error('HPA file %s cannot be found', string(fileName)); -0035 end -0036 -0037 %Parse header and all data columns (RNA files are always TSV) -0038 fid=fopen(fileName,'r'); -0039 headers=strsplit(fgetl(fid), sprintf('\t')); -0040 fmtStr=strip(repmat('%q ',1,numel(headers))); -0041 hpa=textscan(fid, fmtStr, 'Delimiter','\t'); -0042 fclose(fid); -0043 -0044 %Helper: column index by name (returns empty if not found) -0045 ci=@(name) find(strcmp(headers,name),1); -0046 -0047 %Validate required columns -0048 for h={'Gene','Gene name'} -0049 if isempty(ci(h{1})) -0050 error(['Could not find the column "' h{1} '". ' ... -0051 'Make sure the input file matches the format at https://www.proteinatlas.org/about/download']); -0052 end -0053 end -0054 -0055 %Detect tissue column: 'Tissue' (v19) or 'Sample' (v18) -0056 tissueCol='Tissue'; -0057 if isempty(ci('Tissue')) && ~isempty(ci('Sample')) -0058 tissueCol='Sample'; -0059 elseif isempty(ci('Tissue')) -0060 error(['Could not find a tissue column ("Tissue" or "Sample"). ' ... -0061 'Make sure the input file matches the format at https://www.proteinatlas.org/about/download']); -0062 end -0063 -0064 %Detect expression value column: 'TPM' (v19) or 'Value' (v18) -0065 valueCol='TPM'; -0066 if isempty(ci('TPM')) && ~isempty(ci('Value')) -0067 valueCol='Value'; -0068 elseif isempty(ci('TPM')) -0069 error(['Could not find an expression value column ("TPM" or "Value"). ' ... -0070 'Make sure the input file matches the format at https://www.proteinatlas.org/about/download']); -0071 end -0072 -0073 [arrayData.genes, P, I]=unique(hpa{ci('Gene')}); -0074 arrayData.geneNames=hpa{ci('Gene name')}(P); -0075 [arrayData.tissues, ~, J]=unique(hpa{ci(tissueCol)}); -0076 -0077 arrayData.levels=NaN(max(I),max(J)); -0078 linearInd=sub2ind(size(arrayData.levels),I,J); -0079 arrayData.levels(linearInd)=str2double(hpa{ci(valueCol)}); -0080 end +0013 % ignored; the format is inferred from the column headers (default 19). +0014 % +0015 % Returns +0016 % ------- +0017 % arrayData : struct +0018 % parsed HPA RNA data with fields: +0019 % +0020 % - genes : cell array with the unique Ensembl gene IDs +0021 % - geneNames : cell array with the gene symbols +0022 % - tissues : cell array with the unique tissue (or sample) names +0023 % - levels : matrix of expression values (rows = genes, cols = tissues) +0024 % +0025 % Examples +0026 % -------- +0027 % arrayData = parseHPArna(fileName); +0028 +0029 p=parseRAVENargs(varargin, {'version',19}); %#ok<NASGU> retained for backward compat +0030 +0031 fileName=char(fileName); +0032 if ~isfile(fileName) +0033 error('HPA file %s cannot be found', string(fileName)); +0034 end +0035 +0036 %Parse header and all data columns (RNA files are always TSV) +0037 fid=fopen(fileName,'r'); +0038 headers=strsplit(fgetl(fid), sprintf('\t')); +0039 fmtStr=strip(repmat('%q ',1,numel(headers))); +0040 hpa=textscan(fid, fmtStr, 'Delimiter','\t'); +0041 fclose(fid); +0042 +0043 %Helper: column index by name (returns empty if not found) +0044 ci=@(name) find(strcmp(headers,name),1); +0045 +0046 %Validate required columns +0047 for h={'Gene','Gene name'} +0048 if isempty(ci(h{1})) +0049 error(['Could not find the column "' h{1} '". ' ... +0050 'Make sure the input file matches the format at https://www.proteinatlas.org/about/download']); +0051 end +0052 end +0053 +0054 %Detect tissue column: 'Tissue' (v19) or 'Sample' (v18) +0055 tissueCol='Tissue'; +0056 if isempty(ci('Tissue')) && ~isempty(ci('Sample')) +0057 tissueCol='Sample'; +0058 elseif isempty(ci('Tissue')) +0059 error(['Could not find a tissue column ("Tissue" or "Sample"). ' ... +0060 'Make sure the input file matches the format at https://www.proteinatlas.org/about/download']); +0061 end +0062 +0063 %Detect expression value column: 'TPM' (v19) or 'Value' (v18) +0064 valueCol='TPM'; +0065 if isempty(ci('TPM')) && ~isempty(ci('Value')) +0066 valueCol='Value'; +0067 elseif isempty(ci('TPM')) +0068 error(['Could not find an expression value column ("TPM" or "Value"). ' ... +0069 'Make sure the input file matches the format at https://www.proteinatlas.org/about/download']); +0070 end +0071 +0072 [arrayData.genes, P, I]=unique(hpa{ci('Gene')}); +0073 arrayData.geneNames=hpa{ci('Gene name')}(P); +0074 [arrayData.tissues, ~, J]=unique(hpa{ci(tissueCol)}); +0075 +0076 arrayData.levels=NaN(max(I),max(J)); +0077 linearInd=sub2ind(size(arrayData.levels),I,J); +0078 arrayData.levels(linearInd)=str2double(hpa{ci(valueCol)}); +0079 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/INIT/prepINITModel.html b/docs/html/INIT/prepINITModel.html index 9bc3862c..7aa72a20 100644 --- a/docs/html/INIT/prepINITModel.html +++ b/docs/html/INIT/prepINITModel.html @@ -29,16 +29,15 @@

    SYNOPSIS ^DESCRIPTION ^

     prepINITModel  Precompute omics-independent data for ftINIT.
     
    - Runs time-consuming calculation steps that are not dependent on the RNA-Seq
    - data.
    + Runs the calculation steps that do not depend on the omics data, once per
    + template model.
     
      Parameters
      ----------
      origRefModel : struct
    -     the model to use. Expected to be something such as Human-GEM, Mouse-GEM,
    -     etc.
    +     the template model, such as Human-GEM or Mouse-GEM.
      taskStruct : struct
    -     the essential tasks. Can be loaded with for example
    +     the essential tasks, for example loaded with
          taskStruct = parseTaskList('../data/metabolicTasks_Essential.txt').
          The task ids must be character vectors: a non-text id or a missing id
          field raises RAVEN:badInput. Ids may repeat (for example as category
    @@ -51,31 +50,34 @@ 

    DESCRIPTION ^SOURCE CODE ^
    0001 function prepData = prepINITModel(origRefModel, taskStruct, varargin)
     0002 % prepINITModel  Precompute omics-independent data for ftINIT.
     0003 %
    -0004 % Runs time-consuming calculation steps that are not dependent on the RNA-Seq
    -0005 % data.
    +0004 % Runs the calculation steps that do not depend on the omics data, once per
    +0005 % template model.
     0006 %
     0007 % Parameters
     0008 % ----------
     0009 % origRefModel : struct
    -0010 %     the model to use. Expected to be something such as Human-GEM, Mouse-GEM,
    -0011 %     etc.
    -0012 % taskStruct : struct
    -0013 %     the essential tasks. Can be loaded with for example
    -0014 %     taskStruct = parseTaskList('../data/metabolicTasks_Essential.txt').
    -0015 %     The task ids must be character vectors: a non-text id or a missing id
    -0016 %     field raises RAVEN:badInput. Ids may repeat (for example as category
    -0017 %     codes) and may be empty; each task is one row group of the task list,
    -0018 %     and a task that cannot be performed is removed by its position.
    -0019 %
    -0020 % Name-Value Arguments
    -0021 % --------------------
    -0022 % spontRxnNames : cell
    -0023 %     the spontaneous rxns (default {}).
    -0024 % convertGenes : logical
    -0025 %     if true the genes are converted to gene names (from ENSEMBL). This
    -0026 %     relies on translateGrRules, which is distributed with Human-GEM rather
    -0027 %     than with RAVEN, so Human-GEM has to be on the MATLAB path (default
    -0028 %     false).
    -0029 % customRxnsToIgnore : cell
    -0030 %     these reactions can be ignored in the ignore mask (specifying b7=1)
    -0031 %     (default {}).
    -0032 % extComp : char
    -0033 %     name of the external compartment, typically 's' or 'e'. This is used for
    -0034 %     identifying exch and import rxns (default 'e').
    -0035 % skipScaling : logical
    -0036 %     if true the scaling step is not run on the minimal model. The scaling is
    -0037 %     there to remove large differences between the stoichiometric coefficients
    -0038 %     within a reaction, since such differences creates numerical issues in the
    -0039 %     ftINIT algorithm due to limitations in solver resolution. However, the
    -0040 %     current scaling step is also risky and may lead to that some reactions
    -0041 %     cannot carry flux, since it changes the stoichiometry of the reactions
    -0042 %     with large differences. If you experience problems where the solution is
    -0043 %     infeasible, it may be worth trying to turn off the scaling. Note that it
    -0044 %     is only the minModel that is scaled, the scaling will not be present in
    -0045 %     the final model (default false).
    -0046 % runParallel : logical
    -0047 %     true to run the task-essentiality check in Step 3 (checkTasks) across
    -0048 %     parallel workers, since each task is independent of the others (see
    -0049 %     parallelWorkersRAVEN). Default false, matching the previous (serial)
    -0050 %     behaviour.
    -0051 %
    -0052 % Returns
    -0053 % -------
    -0054 % prepData : struct
    -0055 %     the resulting prepData structure which is used as input to ftINIT.
    -0056 
    -0057 
    -0058 p=parseRAVENargs(varargin, {'spontRxnNames',{}; 'convertGenes',false; 'customRxnsToIgnore',{}; 'extComp','e'; 'skipScaling',false; 'runParallel',false});
    -0059 spontRxnNames=p.spontRxnNames;
    -0060 convertGenes=p.convertGenes;
    -0061 customRxnsToIgnore=p.customRxnsToIgnore;
    -0062 extComp=p.extComp;
    -0063 skipScaling=p.skipScaling;
    -0064 runParallel=p.runParallel;
    -0065 
    -0066 if ~isempty(taskStruct)
    -0067     if ~isfield(taskStruct,'id')
    -0068         error('RAVEN:badInput','The task structure must have an id field');
    -0069     end
    -0070     taskIds = {taskStruct.id};
    -0071     if ~all(cellfun(@(x) ischar(x) && (isrow(x) || isempty(x)),taskIds))
    -0072         error('RAVEN:badInput','The task ids must be character vectors');
    -0073     end
    -0074 end
    -0075 
    -0076 disp('Step 1: Gene rules')
    -0077 [origRefModel.grRules, origRefModel.rxnGeneMat] = standardizeGrRules(origRefModel, true);
    -0078 
    -0079 if convertGenes %For mouse we might want to translate in the opposite direction - this has to be done before calling this function in that case.
    -0080     if ~exist('translateGrRules','file')
    -0081         EM=['convertGenes requires translateGrRules, which is distributed with ' ...
    -0082             'Human-GEM and not with RAVEN. Add Human-GEM to the MATLAB path, or ' ...
    -0083             'convert the gene identifiers before calling prepINITModel.'];
    -0084         error('RAVEN:badInput', '%s', EM);
    -0085     end
    -0086     [origRefModel.grRules, origRefModel.genes, origRefModel.rxnGeneMat] = translateGrRules(origRefModel.grRules, 'Name');
    -0087 end
    -0088 
    -0089 
    -0090 % Get list of all dead-end/inacessible/constrained reactions in the model.
    -0091 % We don't merge linear dependent reactions here, that will happen later.
    -0092 % We also don't remove reversibility here, we don't want that in the final
    -0093 % models produced. Therefore, this is done as a separate step.
    -0094 % This takes quite some time to run
    -0095 disp('Step 2: First simplification')
    -0096 [~,deletedDeadEndRxns] = simplifyModel(origRefModel,true,false,true,true,true);
    -0097 
    -0098 % get reduced model
    -0099 cModel = removeReactions(origRefModel,deletedDeadEndRxns,false,true);
    -0100 
    -0101 
    -0102 %Then, run the tasks to find out which reactions are essential. These rxns
    -0103 %will be forced to carry flux in the optimization later
    -0104 
    -0105 % determine reactions essential for tasks - takes ~10 min
    -0106 % need to add boundary mets first - but only to a temporary model, we don't
    -0107 % want those for further processing
    -0108 disp('Step 3: Check tasks (~10 min)')
    -0109 if ~isempty(taskStruct)
    -0110     bModel = closeModel(cModel);
    -0111     [taskReport, essentialRxnMat, ~, essentialFluxes] = checkTasks(bModel,[],true,false,true,taskStruct,'runParallel',runParallel);
    -0112 
    -0113     %extract the essential rxns:
    -0114     sel = sum(essentialRxnMat,2) > 0;
    -0115     selInd = find(sel);
    -0116     essentialRxns = bModel.rxns(selInd);%382 rxns for human-GEM
    -0117 
    -0118     %Find metabolites present in taskStruct. We want to avoid removing
    -0119     %these metabolites from the final model (even though they may no longer
    -0120     %participate in any reacitons) so that the final model is still able to
    -0121     %complete all of the tasks without any errors.
    -0122     taskMets = union(vertcat(taskStruct.inputs),vertcat(taskStruct.outputs));
    -0123     taskMets = union(taskMets, parseRxnEqu(vertcat(taskStruct.equations)));
    -0124     modelMets = strcat(cModel.metNames,'[',cModel.comps(cModel.metComps),']');
    -0125     [inModel,metInd] = ismember(taskMets,modelMets);
    -0126     essentialMetsForTasks = cModel.mets(metInd(inModel));%118 mets
    -0127 
    -0128     %Remove tasks that cannot be performed
    -0129     taskStruct(taskReport.ok == false) = [];
    -0130 
    -0131 else
    -0132     essentialRxns = {};
    -0133     essentialMetsForTasks = {};
    -0134     taskReport = {};
    -0135 end
    -0136 
    -0137 
    -0138 % remove metabolites separately to avoid removing those needed for tasks
    -0139 unusedMets = cModel.mets(all(cModel.S == 0,2));%1248 in Human-GEM
    -0140 cModel = removeMets(cModel, setdiff(unusedMets,essentialMetsForTasks));
    -0141 
    -0142 if ~isempty(taskStruct)
    +0010 %     the template model, such as Human-GEM or Mouse-GEM.
    +0011 % taskStruct : struct
    +0012 %     the essential tasks, for example loaded with
    +0013 %     taskStruct = parseTaskList('../data/metabolicTasks_Essential.txt').
    +0014 %     The task ids must be character vectors: a non-text id or a missing id
    +0015 %     field raises RAVEN:badInput. Ids may repeat (for example as category
    +0016 %     codes) and may be empty; each task is one row group of the task list,
    +0017 %     and a task that cannot be performed is removed by its position.
    +0018 %
    +0019 % Name-Value Arguments
    +0020 % --------------------
    +0021 % spontRxnNames : cell
    +0022 %     the spontaneous rxns (default {}).
    +0023 % convertGenes : logical
    +0024 %     if true the genes are converted to gene names (from ENSEMBL). This
    +0025 %     uses translateGrRules, which is distributed with Human-GEM and not with
    +0026 %     RAVEN, so Human-GEM must be on the MATLAB path (default false).
    +0027 % customRxnsToIgnore : cell
    +0028 %     reactions that are left outside the MILP when bit b7 of the
    +0029 %     RxnsToIgnoreMask of INITStepDesc is 1 (default {}).
    +0030 % extComp : char
    +0031 %     name of the external compartment, such as 's' or 'e'. This is used for
    +0032 %     identifying exchange and import reactions (default 'e').
    +0033 % skipScaling : logical
    +0034 %     if true the scaling step is not run on the minimal model. The scaling
    +0035 %     removes large differences between the stoichiometric coefficients
    +0036 %     within a reaction (see rescaleModelForINIT), since such differences
    +0037 %     create numerical issues in the ftINIT algorithm due to limitations in
    +0038 %     solver resolution. The scaling changes the stoichiometry of the
    +0039 %     reactions with large differences, and some reactions can become unable
    +0040 %     to carry flux, which can make the solution infeasible. If the solution
    +0041 %     is infeasible, turning the scaling off may help; the bounds then stay as in
    +0042 %     the template and the MILP caps the per-reaction big-M at 1000 (bigMCap
    +0043 %     in ftINITInternalAlg). In raven-toolbox's ftinit on Human-GEM (one cell
    +0044 %     line, big_m 1000) the pipeline without scaling was feasible but took
    +0045 %     157 s against 83 s with scaling. The bounds of the scaled model are set to 1000 where the upper bound is above 0 and
    +0046 %     to -1000 where the lower bound is below 0; zero bounds stay 0. Only the
    +0047 %     minModel is scaled; the scaling is not present in the final model
    +0048 %     (default false).
    +0049 % runParallel : logical
    +0050 %     true to run the task-essentiality check (checkTasks) across parallel
    +0051 %     workers, since each task is independent of the others (see
    +0052 %     parallelWorkersRAVEN) (default false, serial).
    +0053 %
    +0054 % Returns
    +0055 % -------
    +0056 % prepData : struct
    +0057 %     the resulting prepData structure which is used as input to ftINIT.
    +0058 
    +0059 
    +0060 p=parseRAVENargs(varargin, {'spontRxnNames',{}; 'convertGenes',false; 'customRxnsToIgnore',{}; 'extComp','e'; 'skipScaling',false; 'runParallel',false});
    +0061 spontRxnNames=p.spontRxnNames;
    +0062 convertGenes=p.convertGenes;
    +0063 customRxnsToIgnore=p.customRxnsToIgnore;
    +0064 extComp=p.extComp;
    +0065 skipScaling=p.skipScaling;
    +0066 runParallel=p.runParallel;
    +0067 
    +0068 if ~isempty(taskStruct)
    +0069     if ~isfield(taskStruct,'id')
    +0070         error('RAVEN:badInput','The task structure must have an id field');
    +0071     end
    +0072     taskIds = {taskStruct.id};
    +0073     if ~all(cellfun(@(x) ischar(x) && (isrow(x) || isempty(x)),taskIds))
    +0074         error('RAVEN:badInput','The task ids must be character vectors');
    +0075     end
    +0076 end
    +0077 
    +0078 disp('Step 1: Gene rules')
    +0079 [origRefModel.grRules, origRefModel.rxnGeneMat] = standardizeGrRules(origRefModel, true);
    +0080 
    +0081 if convertGenes %For mouse we might want to translate in the opposite direction - this has to be done before calling this function in that case.
    +0082     if ~exist('translateGrRules','file')
    +0083         EM=['convertGenes requires translateGrRules, which is distributed with ' ...
    +0084             'Human-GEM and not with RAVEN. Add Human-GEM to the MATLAB path, or ' ...
    +0085             'convert the gene identifiers before calling prepINITModel.'];
    +0086         error('RAVEN:badInput', '%s', EM);
    +0087     end
    +0088     [origRefModel.grRules, origRefModel.genes, origRefModel.rxnGeneMat] = translateGrRules(origRefModel.grRules, 'Name');
    +0089 end
    +0090 
    +0091 
    +0092 % Get list of all dead-end/inacessible/constrained reactions in the model.
    +0093 % We don't merge linear dependent reactions here, that will happen later.
    +0094 % We also don't remove reversibility here, we don't want that in the final
    +0095 % models produced. Therefore, this is done as a separate step.
    +0096 % This takes quite some time to run
    +0097 disp('Step 2: First simplification')
    +0098 [~,deletedDeadEndRxns] = simplifyModel(origRefModel,true,false,true,true,true);
    +0099 
    +0100 % get reduced model
    +0101 cModel = removeReactions(origRefModel,deletedDeadEndRxns,false,true);
    +0102 
    +0103 
    +0104 %Then, run the tasks to find out which reactions are essential. These rxns
    +0105 %will be forced to carry flux in the optimization later
    +0106 
    +0107 % determine reactions essential for tasks - takes ~10 min
    +0108 % need to add boundary mets first - but only to a temporary model, we don't
    +0109 % want those for further processing
    +0110 disp('Step 3: Check tasks (~10 min)')
    +0111 if ~isempty(taskStruct)
    +0112     bModel = closeModel(cModel);
    +0113     [taskReport, essentialRxnMat, ~, essentialFluxes] = checkTasks(bModel,[],true,false,true,taskStruct,'runParallel',runParallel);
    +0114 
    +0115     %extract the essential rxns:
    +0116     sel = sum(essentialRxnMat,2) > 0;
    +0117     selInd = find(sel);
    +0118     essentialRxns = bModel.rxns(selInd);%382 rxns for human-GEM
    +0119 
    +0120     %Find metabolites present in taskStruct. We want to avoid removing
    +0121     %these metabolites from the final model (even though they may no longer
    +0122     %participate in any reacitons) so that the final model is still able to
    +0123     %complete all of the tasks without any errors.
    +0124     taskMets = union(vertcat(taskStruct.inputs),vertcat(taskStruct.outputs));
    +0125     taskMets = union(taskMets, parseRxnEqu(vertcat(taskStruct.equations)));
    +0126     modelMets = strcat(cModel.metNames,'[',cModel.comps(cModel.metComps),']');
    +0127     [inModel,metInd] = ismember(taskMets,modelMets);
    +0128     essentialMetsForTasks = cModel.mets(metInd(inModel));%118 mets
    +0129 
    +0130     %Remove tasks that cannot be performed
    +0131     taskStruct(taskReport.ok == false) = [];
    +0132 
    +0133 else
    +0134     essentialRxns = {};
    +0135     essentialMetsForTasks = {};
    +0136     taskReport = {};
    +0137 end
    +0138 
    +0139 
    +0140 % remove metabolites separately to avoid removing those needed for tasks
    +0141 unusedMets = cModel.mets(all(cModel.S == 0,2));%1248 in Human-GEM
    +0142 cModel = removeMets(cModel, setdiff(unusedMets,essentialMetsForTasks));
     0143 
    -0144     %Here, we decide on a direction in which the essential reactions should be forced.
    -0145     essentialRevDir = false(length(essentialRxns),1);
    -0146     pp = zeros(length(essentialRxns),1);
    -0147     nn = zeros(length(essentialRxns),1);
    -0148     for i = 1:length(selInd)
    -0149         pos = sum(essentialFluxes(selInd(i),essentialRxnMat(selInd(i),:)) > 0);
    -0150         neg = sum(essentialFluxes(selInd(i),essentialRxnMat(selInd(i),:)) < 0);
    -0151         essentialRevDir(i) = pos < neg;
    -0152         pp(i) = pos;
    -0153         nn(i) = neg;
    -0154     end
    -0155 
    -0156     %sum(pp>0 & nn>0) %0, so it is not a big problem that different tasks share essential
    -0157     %reactions but in different directions, at least no such cases exist now, so we ignore this for now.
    -0158 
    -0159     %Now create a minimal model for the MILP - we want to make this as small as we can, but we
    -0160     %don't want to use this later when we create the final models - just for the first MILP.
    -0161     %We don't want boundary metabolites in here either.
    -0162 
    -0163     %We do a trick with the essential rxns here:
    -0164     %What we want is to get rid of the reversibility of the reactions, because reversible reactions require
    -0165     %an integer each in the MILP, and we can also get the case that it may be cheaper to force flux in the wrong direction, which
    -0166     %will probably not give the gap-filling of reactions that we want.
    -0167     %So, we simply change the reversible reactions to irreversible, and flip their direction if the direction
    -0168     %to force flux in is negative. We don't need to do anything else, runINIT will then handle all essential rxns
    -0169     %as irreversible (and will not add any integers etc. for them).
    -0170 
    -0171     minModel1 = cModel;
    -0172     %for the essential rxns where we should force flux in the positive direction, just make them irreversible
    -0173     %for the negative direction, we do the same, but also flip the direction of the reaction
    -0174 
    -0175     %first flip the reactions with negative direction
    -0176     %constructEquations(minModel1,minModel1.rxns(selInd(essentialRevDir))) %looks good
    -0177     minModel1 = reverseRxns(minModel1, minModel1.rxns(selInd(essentialRevDir)));
    -0178     %constructEquations(minModel2,minModel2.rxns(selInd(essentialRevDir))) %looks good
    -0179 
    -0180     %Then make them irreversible
    -0181     minModel1.rev(selInd) = 0;
    -0182     minModel1.lb(selInd) = 0;
    -0183 else
    -0184     minModel1 = cModel;
    -0185 end
    -0186 %Now, remove reversibility on any reactions that can only be run in one direction - that simplifies the problem
    -0187 %This takes a long time to run (at least an hour or so).
    -0188 %This is an important step, we go from 5533 to 3734 reversible rxns, which reduces the size of the MILP,
    -0189 %since irreversible rxns are easier to handle.
    -0190 disp('Step 4: Second simplification (~1 hour)')
    -0191 minModel2 = simplifyModel(minModel1,false,false,false,false,false,false,true);
    -0192 
    -0193 disp('Step 5: Final work')
    +0144 if ~isempty(taskStruct)
    +0145 
    +0146     %Here, we decide on a direction in which the essential reactions should be forced.
    +0147     essentialRevDir = false(length(essentialRxns),1);
    +0148     pp = zeros(length(essentialRxns),1);
    +0149     nn = zeros(length(essentialRxns),1);
    +0150     for i = 1:length(selInd)
    +0151         pos = sum(essentialFluxes(selInd(i),essentialRxnMat(selInd(i),:)) > 0);
    +0152         neg = sum(essentialFluxes(selInd(i),essentialRxnMat(selInd(i),:)) < 0);
    +0153         essentialRevDir(i) = pos < neg;
    +0154         pp(i) = pos;
    +0155         nn(i) = neg;
    +0156     end
    +0157 
    +0158     %sum(pp>0 & nn>0) %0, so it is not a big problem that different tasks share essential
    +0159     %reactions but in different directions, at least no such cases exist now, so we ignore this for now.
    +0160 
    +0161     %Now create a minimal model for the MILP - we want to make this as small as we can, but we
    +0162     %don't want to use this later when we create the final models - just for the first MILP.
    +0163     %We don't want boundary metabolites in here either.
    +0164 
    +0165     %We do a trick with the essential rxns here:
    +0166     %What we want is to get rid of the reversibility of the reactions, because reversible reactions require
    +0167     %an integer each in the MILP, and we can also get the case that it may be cheaper to force flux in the wrong direction, which
    +0168     %will probably not give the gap-filling of reactions that we want.
    +0169     %So, we simply change the reversible reactions to irreversible, and flip their direction if the direction
    +0170     %to force flux in is negative. We don't need to do anything else, runINIT will then handle all essential rxns
    +0171     %as irreversible (and will not add any integers etc. for them).
    +0172 
    +0173     minModel1 = cModel;
    +0174     %for the essential rxns where we should force flux in the positive direction, just make them irreversible
    +0175     %for the negative direction, we do the same, but also flip the direction of the reaction
    +0176 
    +0177     %first flip the reactions with negative direction
    +0178     %constructEquations(minModel1,minModel1.rxns(selInd(essentialRevDir))) %looks good
    +0179     minModel1 = reverseRxns(minModel1, minModel1.rxns(selInd(essentialRevDir)));
    +0180     %constructEquations(minModel2,minModel2.rxns(selInd(essentialRevDir))) %looks good
    +0181 
    +0182     %Then make them irreversible
    +0183     minModel1.rev(selInd) = 0;
    +0184     minModel1.lb(selInd) = 0;
    +0185 else
    +0186     minModel1 = cModel;
    +0187 end
    +0188 %Now, remove reversibility on any reactions that can only be run in one direction - that simplifies the problem
    +0189 %This takes a long time to run (at least an hour or so).
    +0190 %This is an important step, we go from 5533 to 3734 reversible rxns, which reduces the size of the MILP,
    +0191 %since irreversible rxns are easier to handle.
    +0192 disp('Step 4: Second simplification (~1 hour)')
    +0193 minModel2 = simplifyModel(minModel1,false,false,false,false,false,false,true);
     0194 
    -0195 %Now, merge all linear dependent rxns - this reduces the size of the model a lot.
    -0196 %Size goes from 11888 rxns to 7922 rxns for human-GEM, so this is an important step
    -0197 [minModel3,origRxnIds,groupIds]=mergeLinear(minModel2, {});
    -0198 
    -0199 %we now need to figure out what happened to the essential rxns in the merge step above.
    -0200 %Note that it would be ideal to run checkTasks on the result from mergeLinear, but that doesn't work.
    -0201 %The exchange rxns have in many cases been merged with other reactions, making it difficult to run
    -0202 %the tasks. So, instead we run checkTasks before, and figure out which merged reactions the
    -0203 %essential reactions belong to now
    -0204 
    -0205 if ~isempty(taskStruct)
    +0195 disp('Step 5: Final work')
    +0196 
    +0197 %Now, merge all linear dependent rxns - this reduces the size of the model a lot.
    +0198 %Size goes from 11888 rxns to 7922 rxns for human-GEM, so this is an important step
    +0199 [minModel3,origRxnIds,groupIds]=mergeLinear(minModel2, {});
    +0200 
    +0201 %we now need to figure out what happened to the essential rxns in the merge step above.
    +0202 %Note that it would be ideal to run checkTasks on the result from mergeLinear, but that doesn't work.
    +0203 %The exchange rxns have in many cases been merged with other reactions, making it difficult to run
    +0204 %the tasks. So, instead we run checkTasks before, and figure out which merged reactions the
    +0205 %essential reactions belong to now
     0206 
    -0207     %find all potential rxns
    -0208     rxnIndOrig = ismember(origRxnIds, essentialRxns);
    -0209     ids = unique(groupIds(rxnIndOrig));
    -0210     ids = ids(ids > 0);%0 means it has not been merged
    -0211     rxnCandidates = unique([essentialRxns;origRxnIds(ismember(groupIds, ids))]);%388, essentialRxns is 382
    -0212     newEssentialRxns = minModel3.rxns(ismember(minModel3.rxns,rxnCandidates));%196 in Human-GEM
    -0213     %setdiff(newEssentialRxns, essentialRxns)%only 2, that is very few
    -0214     %setdiff(essentialRxns, newEssentialRxns)%188, that is very many
    -0215     %It is probably the case that many essential reactions are linearly dependent on one another, which makes sense.
    -0216 else
    -0217     newEssentialRxns = {};
    -0218 end
    -0219 %What is left now is to remove some rxns from the problem, such as exchange rxns and spontaneous rxns.
    -0220 %We set the rxn scores of these to exactly 0, and make sure no other scores are zero.
    -0221 %runINIT will then handle this, we do >0 and <0 to get positive and negative scores, the rest
    -0222 %are not included in the problem.
    -0223 %But, this has to happen in the scoring - we identify the reactions in the origrxns here, and then fix this
    -0224 %in the scoring for each sample (the function groupRxnScores handles this and is run for each sample).
    -0225 
    -0226 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    -0227 %identify the reactions to ignore in the minModel2
    +0207 if ~isempty(taskStruct)
    +0208 
    +0209     %find all potential rxns
    +0210     rxnIndOrig = ismember(origRxnIds, essentialRxns);
    +0211     ids = unique(groupIds(rxnIndOrig));
    +0212     ids = ids(ids > 0);%0 means it has not been merged
    +0213     rxnCandidates = unique([essentialRxns;origRxnIds(ismember(groupIds, ids))]);%388, essentialRxns is 382
    +0214     newEssentialRxns = minModel3.rxns(ismember(minModel3.rxns,rxnCandidates));%196 in Human-GEM
    +0215     %setdiff(newEssentialRxns, essentialRxns)%only 2, that is very few
    +0216     %setdiff(essentialRxns, newEssentialRxns)%188, that is very many
    +0217     %It is probably the case that many essential reactions are linearly dependent on one another, which makes sense.
    +0218 else
    +0219     newEssentialRxns = {};
    +0220 end
    +0221 %What is left now is to remove some rxns from the problem, such as exchange rxns and spontaneous rxns.
    +0222 %We set the rxn scores of these to exactly 0, and make sure no other scores are zero.
    +0223 %runINIT will then handle this, we do >0 and <0 to get positive and negative scores, the rest
    +0224 %are not included in the problem.
    +0225 %But, this has to happen in the scoring - we identify the reactions in the origrxns here, and then fix this
    +0226 %in the scoring for each sample (the function groupRxnScores handles this and is run for each sample).
    +0227 
     0228 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    -0229 
    -0230 %Identify reactions that should be ignored by tINIT, i.e. tINIT will not remove these regardless of score
    +0229 %identify the reactions to ignore in the minModel2
    +0230 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
     0231 
    -0232 
    -0233 [~,exchRxnInd] = getExchangeRxns(minModel2);
    -0234 toIgnoreExch = false(numel(minModel2.rxns),1);
    -0235 toIgnoreExch(exchRxnInd) = true;
    -0236 toIgnoreImportRxns = false(numel(minModel2.rxns),1);
    -0237 toIgnoreSimpleTransp = false(numel(minModel2.rxns),1);
    -0238 toIgnoreAdvTransp = false(numel(minModel2.rxns),1);
    -0239 toIgnoreS = false(numel(minModel2.rxns),1);
    -0240 
    -0241 
    +0232 %Identify reactions that should be ignored by tINIT, i.e. tINIT will not remove these regardless of score
    +0233 
    +0234 
    +0235 [~,exchRxnInd] = getExchangeRxns(minModel2);
    +0236 toIgnoreExch = false(numel(minModel2.rxns),1);
    +0237 toIgnoreExch(exchRxnInd) = true;
    +0238 toIgnoreImportRxns = false(numel(minModel2.rxns),1);
    +0239 toIgnoreSimpleTransp = false(numel(minModel2.rxns),1);
    +0240 toIgnoreAdvTransp = false(numel(minModel2.rxns),1);
    +0241 toIgnoreS = false(numel(minModel2.rxns),1);
     0242 
    -0243 %sum(toIgnore) %1500 for Human-GEM
    -0244 %find simple transport reactions with no GPRs:
    -0245 %We really only skip the reactions with two metabolites
    -0246 %where they are the same but different compartments - only skip the transport into the cell for now
    -0247 numMets = sum(minModel2.S ~= 0, 1);
    -0248 scomp = find(strcmp(minModel2.comps,extComp));
    -0249 for i = 1:length(minModel2.rxns)
    -0250     %check that it has no GPR and that the rxn has two metabolites
    -0251     if strcmp(minModel2.grRules(i),'') && (numMets(i) == 2)
    -0252       metsComps = minModel2.metComps(minModel2.S(:,i) ~= 0);
    -0253       metNames = minModel2.metNames(minModel2.S(:,i) ~= 0);
    -0254       if metsComps(1) ~= metsComps(2) && (metsComps(1) == scomp || metsComps(2) == scomp) %different compartments, one is the extComp
    -0255           if strcmp(metNames{1}, metNames{2}) %the same metabolite, it is a transport reaction
    -0256               toIgnoreImportRxns(i) = true;
    -0257           end
    -0258       elseif metsComps(1) ~= metsComps(2) %different compartments, no check for extComp
    -0259           if strcmp(metNames{1}, metNames{2}) %the same metabolite, it is a transport reaction
    -0260               toIgnoreSimpleTransp(i) = true;
    -0261           end
    -0262       end
    -0263     else %check for advanced
    -0264         metsInd = find(minModel2.S(:,i) ~= 0);
    -0265         %check that we have an even number of mets that is larger than 2 and that we don't have a GPR
    -0266         if rem(length(metsInd),2) == 0 && length(metsInd) > 2 && isempty(minModel2.grRules{i})
    -0267             %Now check that we have pairs of metabolites that are transported
    -0268             SVals = full(minModel2.S(metsInd,i));
    -0269             metNames = minModel2.metNames(metsInd);
    -0270             comps = minModel2.metComps(metsInd);
    -0271             success = true;
    -0272             while ~isempty(metNames)
    -0273                 if length(metNames) < 2 %this should not happen
    -0274                    error('We should never arrive here!');
    -0275                 end
    -0276                 metMatch = find(strcmp(metNames(2:length(metNames)),metNames{1})) + 1;
    -0277                 if length(metMatch) ~= 1 %we want a match, and only one (if there is some other complex transport rxn, we skip that)
    -0278                    success = false;
    -0279                    break;
    -0280                 end
    -0281                 if (SVals(1) + SVals(metMatch)) ~= 0 %the stoichiometry is not right
    -0282                    success = false;
    -0283                    break;
    -0284                 end
    -0285                 if comps(1) == comps(metMatch) %they must be in different compartments
    -0286                    success = false;
    -0287                    break;
    -0288                 end
    -0289                 %now remove the pair and continue with the next
    -0290                 SVals([1;metMatch]) = [];
    -0291                 metNames([1;metMatch]) = [];
    -0292                 comps([1;metMatch]) = [];
    -0293             end
    -0294             toIgnoreAdvTransp(i) = success;
    -0295         end          
    -0296     end
    -0297 end
    -0298 
    -0299 %Spontaneous reactions:
    -0300 toIgnoreSpont = ismember(minModel2.rxns, spontRxnNames);%only 10 rxns in human-GEM
    -0301 
    -0302 %rxns without GPRs in the s compartment (outside the cell)
    -0303 sCompInd = find(strcmp(minModel2.comps, extComp));
    -0304 for i = 1:length(minModel2.rxns)
    -0305     metsInd = find(minModel2.S(:,i) ~= 0);
    -0306     %check that it has more than 1 metabolite (not exch rxn) and no GPR
    -0307     if length(metsInd) > 1 && isempty(minModel2.grRules{i})
    -0308         if  all(minModel2.metComps(metsInd) == sCompInd)
    -0309             toIgnoreS(i) = true;
    -0310         end
    -0311     end
    -0312 end
    -0313 
    -0314 toIgnoreAllWithoutGPRs = cellfun(@isempty,minModel2.grRules);
    +0243 
    +0244 
    +0245 %sum(toIgnore) %1500 for Human-GEM
    +0246 %find simple transport reactions with no GPRs:
    +0247 %We really only skip the reactions with two metabolites
    +0248 %where they are the same but different compartments - only skip the transport into the cell for now
    +0249 numMets = sum(minModel2.S ~= 0, 1);
    +0250 scomp = find(strcmp(minModel2.comps,extComp));
    +0251 for i = 1:length(minModel2.rxns)
    +0252     %check that it has no GPR and that the rxn has two metabolites
    +0253     if strcmp(minModel2.grRules(i),'') && (numMets(i) == 2)
    +0254       metsComps = minModel2.metComps(minModel2.S(:,i) ~= 0);
    +0255       metNames = minModel2.metNames(minModel2.S(:,i) ~= 0);
    +0256       if metsComps(1) ~= metsComps(2) && (metsComps(1) == scomp || metsComps(2) == scomp) %different compartments, one is the extComp
    +0257           if strcmp(metNames{1}, metNames{2}) %the same metabolite, it is a transport reaction
    +0258               toIgnoreImportRxns(i) = true;
    +0259           end
    +0260       elseif metsComps(1) ~= metsComps(2) %different compartments, no check for extComp
    +0261           if strcmp(metNames{1}, metNames{2}) %the same metabolite, it is a transport reaction
    +0262               toIgnoreSimpleTransp(i) = true;
    +0263           end
    +0264       end
    +0265     else %check for advanced
    +0266         metsInd = find(minModel2.S(:,i) ~= 0);
    +0267         %check that we have an even number of mets that is larger than 2 and that we don't have a GPR
    +0268         if rem(length(metsInd),2) == 0 && length(metsInd) > 2 && isempty(minModel2.grRules{i})
    +0269             %Now check that we have pairs of metabolites that are transported
    +0270             SVals = full(minModel2.S(metsInd,i));
    +0271             metNames = minModel2.metNames(metsInd);
    +0272             comps = minModel2.metComps(metsInd);
    +0273             success = true;
    +0274             while ~isempty(metNames)
    +0275                 if length(metNames) < 2 %this should not happen
    +0276                    error('We should never arrive here!');
    +0277                 end
    +0278                 metMatch = find(strcmp(metNames(2:length(metNames)),metNames{1})) + 1;
    +0279                 if length(metMatch) ~= 1 %we want a match, and only one (if there is some other complex transport rxn, we skip that)
    +0280                    success = false;
    +0281                    break;
    +0282                 end
    +0283                 if (SVals(1) + SVals(metMatch)) ~= 0 %the stoichiometry is not right
    +0284                    success = false;
    +0285                    break;
    +0286                 end
    +0287                 if comps(1) == comps(metMatch) %they must be in different compartments
    +0288                    success = false;
    +0289                    break;
    +0290                 end
    +0291                 %now remove the pair and continue with the next
    +0292                 SVals([1;metMatch]) = [];
    +0293                 metNames([1;metMatch]) = [];
    +0294                 comps([1;metMatch]) = [];
    +0295             end
    +0296             toIgnoreAdvTransp(i) = success;
    +0297         end          
    +0298     end
    +0299 end
    +0300 
    +0301 %Spontaneous reactions:
    +0302 toIgnoreSpont = ismember(minModel2.rxns, spontRxnNames);%only 10 rxns in human-GEM
    +0303 
    +0304 %rxns without GPRs in the s compartment (outside the cell)
    +0305 sCompInd = find(strcmp(minModel2.comps, extComp));
    +0306 for i = 1:length(minModel2.rxns)
    +0307     metsInd = find(minModel2.S(:,i) ~= 0);
    +0308     %check that it has more than 1 metabolite (not exch rxn) and no GPR
    +0309     if length(metsInd) > 1 && isempty(minModel2.grRules{i})
    +0310         if  all(minModel2.metComps(metsInd) == sCompInd)
    +0311             toIgnoreS(i) = true;
    +0312         end
    +0313     end
    +0314 end
     0315 
    -0316 
    -0317 %sum(toIgnore)%in total 2375 rxns in human-GEM
    -0318 %sum(toIgnoreAllTransp)%in total 3337 rxns in human-GEM, so 1,000 more
    -0319 
    -0320 %Now, try to scale the model to become more favorable for the solver.
    -0321 %We try to make all fluxes as similar as possible.
    -0322 %      There is room for improvement here, this function is pretty simple.
    -0323 %      It only rescales reactions, while it would be possible to rescale
    -0324 %      metabolites as well (ROS => kROS, albumin => millialbumin, etc., but
    -0325 %      scaled freely with a mathematical method). It could potentially open up
    -0326 %      for using less strict margins in the MILP, and make it run faster.
    -0327 if (skipScaling)
    -0328     scaledMinModel = minModel3;
    -0329 else
    -0330     scaledMinModel = rescaleModelForINIT(minModel3);
    -0331     scaledMinModel.ub(scaledMinModel.ub > 0) = 1000;
    -0332     scaledMinModel.lb(scaledMinModel.lb < 0) = -1000;
    -0333 end
    -0334 % create data structure with pre-processing results
    -0335 prepData.taskReport = taskReport;
    -0336 prepData.essentialRxns = newEssentialRxns; %essential rxns in the minModel
    -0337 prepData.taskStruct = taskStruct;
    -0338 prepData.refModel = cModel;
    -0339 prepData.minModel = scaledMinModel;
    -0340 prepData.refModelWithBM = closeModel(cModel); %do this here so we don't have to do this for each sample
    -0341 prepData.groupIds = groupIds;
    -0342 prepData.essentialMetsForTasks = essentialMetsForTasks;
    -0343 
    -0344 prepData.toIgnoreExch = toIgnoreExch;
    -0345 prepData.toIgnoreImportRxns = toIgnoreImportRxns;
    -0346 prepData.toIgnoreSimpleTransp = toIgnoreSimpleTransp;
    -0347 prepData.toIgnoreAdvTransp = toIgnoreAdvTransp;
    -0348 prepData.toIgnoreSpont = toIgnoreSpont;
    -0349 prepData.toIgnoreS = toIgnoreS;
    -0350 prepData.toIgnoreCustomRxns = ismember(minModel2.rxns, customRxnsToIgnore);
    -0351 prepData.toIgnoreAllWithoutGPRs = toIgnoreAllWithoutGPRs;
    -0352 
    -0353 end

    +0316 toIgnoreAllWithoutGPRs = cellfun(@isempty,minModel2.grRules); +0317 +0318 +0319 %sum(toIgnore)%in total 2375 rxns in human-GEM +0320 %sum(toIgnoreAllTransp)%in total 3337 rxns in human-GEM, so 1,000 more +0321 +0322 %Now, try to scale the model to become more favorable for the solver. +0323 %We try to make all fluxes as similar as possible. +0324 % There is room for improvement here, this function is pretty simple. +0325 % It only rescales reactions, while it would be possible to rescale +0326 % metabolites as well (ROS => kROS, albumin => millialbumin, etc., but +0327 % scaled freely with a mathematical method). It could potentially open up +0328 % for using less strict margins in the MILP, and make it run faster. +0329 if (skipScaling) +0330 scaledMinModel = minModel3; +0331 else +0332 scaledMinModel = rescaleModelForINIT(minModel3); +0333 scaledMinModel.ub(scaledMinModel.ub > 0) = 1000; +0334 scaledMinModel.lb(scaledMinModel.lb < 0) = -1000; +0335 end +0336 % create data structure with pre-processing results +0337 prepData.taskReport = taskReport; +0338 prepData.essentialRxns = newEssentialRxns; %essential rxns in the minModel +0339 prepData.taskStruct = taskStruct; +0340 prepData.refModel = cModel; +0341 prepData.minModel = scaledMinModel; +0342 prepData.refModelWithBM = closeModel(cModel); %do this here so we don't have to do this for each sample +0343 prepData.groupIds = groupIds; +0344 prepData.essentialMetsForTasks = essentialMetsForTasks; +0345 +0346 prepData.toIgnoreExch = toIgnoreExch; +0347 prepData.toIgnoreImportRxns = toIgnoreImportRxns; +0348 prepData.toIgnoreSimpleTransp = toIgnoreSimpleTransp; +0349 prepData.toIgnoreAdvTransp = toIgnoreAdvTransp; +0350 prepData.toIgnoreSpont = toIgnoreSpont; +0351 prepData.toIgnoreS = toIgnoreS; +0352 prepData.toIgnoreCustomRxns = ismember(minModel2.rxns, customRxnsToIgnore); +0353 prepData.toIgnoreAllWithoutGPRs = toIgnoreAllWithoutGPRs; +0354 +0355 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/INIT/rescaleModelForINIT.html b/docs/html/INIT/rescaleModelForINIT.html index 8c9a7af3..d39cf456 100644 --- a/docs/html/INIT/rescaleModelForINIT.html +++ b/docs/html/INIT/rescaleModelForINIT.html @@ -29,21 +29,24 @@

    SYNOPSIS ^DESCRIPTION ^

     rescaleModelForINIT  Rescale a model to avoid large stoichiometric differences.
     
    - Rescales the MILP problem in ftINIT to avoid large differences in flux
    - magnitudes between reactions. Such differences cause among other things
    - difficulties regarding tolerances for integer variables. For now it just
    - scales down all reactions with high stoichiometric coefficients.
    + Rescales the stoichiometric matrix for the MILP problem in ftINIT to reduce
    + differences in flux magnitudes between reactions. Such differences cause,
    + among other effects, difficulties with the tolerances for integer variables.
    + In each reaction, each coefficient with an absolute value above
    + maxStoichDiff times the smallest absolute coefficient of the reaction is
    + set to that limit (keeping its sign). The reaction is then multiplied by a
    + factor that sets the mean absolute coefficient to 1.
     
      Parameters
      ----------
      model : struct
    -     the model to be modified (input and output).
    +     a model structure.
     
      Name-Value Arguments
      --------------------
      maxStoichDiff : double
    -     all reactions with a stoichiometric coefficient higher than this will be
    -     scaled down (default 25).
    +     largest allowed ratio between the largest and the smallest absolute
    +     stoichiometric coefficient in a reaction (default 25).
     
      Returns
      -------
    @@ -66,81 +69,84 @@ 

    SOURCE CODE ^
    0001 function model=rescaleModelForINIT(model, varargin)
     0002 % rescaleModelForINIT  Rescale a model to avoid large stoichiometric differences.
     0003 %
    -0004 % Rescales the MILP problem in ftINIT to avoid large differences in flux
    -0005 % magnitudes between reactions. Such differences cause among other things
    -0006 % difficulties regarding tolerances for integer variables. For now it just
    -0007 % scales down all reactions with high stoichiometric coefficients.
    -0008 %
    -0009 % Parameters
    -0010 % ----------
    -0011 % model : struct
    -0012 %     the model to be modified (input and output).
    -0013 %
    -0014 % Name-Value Arguments
    -0015 % --------------------
    -0016 % maxStoichDiff : double
    -0017 %     all reactions with a stoichiometric coefficient higher than this will be
    -0018 %     scaled down (default 25).
    -0019 %
    -0020 % Returns
    -0021 % -------
    -0022 % model : struct
    -0023 %     the rescaled model structure.
    -0024 
    -0025 p=parseRAVENargs(varargin, {'maxStoichDiff',25});
    -0026 maxStoichDiff=p.maxStoichDiff;
    +0004 % Rescales the stoichiometric matrix for the MILP problem in ftINIT to reduce
    +0005 % differences in flux magnitudes between reactions. Such differences cause,
    +0006 % among other effects, difficulties with the tolerances for integer variables.
    +0007 % In each reaction, each coefficient with an absolute value above
    +0008 % maxStoichDiff times the smallest absolute coefficient of the reaction is
    +0009 % set to that limit (keeping its sign). The reaction is then multiplied by a
    +0010 % factor that sets the mean absolute coefficient to 1.
    +0011 %
    +0012 % Parameters
    +0013 % ----------
    +0014 % model : struct
    +0015 %     a model structure.
    +0016 %
    +0017 % Name-Value Arguments
    +0018 % --------------------
    +0019 % maxStoichDiff : double
    +0020 %     largest allowed ratio between the largest and the smallest absolute
    +0021 %     stoichiometric coefficient in a reaction (default 25).
    +0022 %
    +0023 % Returns
    +0024 % -------
    +0025 % model : struct
    +0026 %     the rescaled model structure.
     0027 
    -0028 %Define maxMinRatio to be the ratio between the largest and smallest
    -0029 %stoichiometric coefficients in each reaction.
    -0030 %Scale all rxns with maxMinRatio > maxStoichDiff - just set all coeffs that are
    -0031 %larger than maxStoichDiff*minVal to that value, and center the mean coeff to 1 for all rxns.
    -0032 SAbs = abs(model.S);
    -0033 for i = 1:numel(model.rxns)
    -0034     tmp = SAbs(:,i);
    -0035     tmp = tmp(tmp ~= 0);
    -0036     mn = min(tmp);
    -0037     %modify matrix
    -0038     sign = ones(numel(model.mets),1);
    -0039     sign(model.S(:,i) < 0) = -1;
    -0040     sel = SAbs(:,i) > maxStoichDiff*mn;
    -0041     model.S(sel,i) = sign(sel) .* maxStoichDiff*mn;
    -0042 end
    -0043 %center around 1
    -0044 boolMat = model.S ~= 0;
    -0045 absMat = abs(model.S);
    -0046 rxnScales = sum(boolMat,1)./sum(absMat,1);
    -0047 
    -0048 model.S = model.S .* rxnScales;
    -0049 
    -0050 %{
    -0051 if (nargin < 2)
    -0052     maxStoichVal = 250;
    -0053 end
    -0054 
    -0055 
    -0056 %find all reactions containing high stoichiometry coefficients
    -0057 maxCoeffs = max(model.S, [], 1);
    -0058 rxnsToCheck = maxCoeffs > maxStoichVal;
    -0059 
    -0060 %for debugging:
    -0061 %constructEquations(model, model.rxns(rxnsToCheck))
    +0028 p=parseRAVENargs(varargin, {'maxStoichDiff',25});
    +0029 maxStoichDiff=p.maxStoichDiff;
    +0030 
    +0031 %Define maxMinRatio to be the ratio between the largest and smallest
    +0032 %stoichiometric coefficients in each reaction.
    +0033 %Scale all rxns with maxMinRatio > maxStoichDiff - just set all coeffs that are
    +0034 %larger than maxStoichDiff*minVal to that value, and center the mean coeff to 1 for all rxns.
    +0035 SAbs = abs(model.S);
    +0036 for i = 1:numel(model.rxns)
    +0037     tmp = SAbs(:,i);
    +0038     tmp = tmp(tmp ~= 0);
    +0039     mn = min(tmp);
    +0040     %modify matrix
    +0041     sign = ones(numel(model.mets),1);
    +0042     sign(model.S(:,i) < 0) = -1;
    +0043     sel = SAbs(:,i) > maxStoichDiff*mn;
    +0044     model.S(sel,i) = sign(sel) .* maxStoichDiff*mn;
    +0045 end
    +0046 %center around 1
    +0047 boolMat = model.S ~= 0;
    +0048 absMat = abs(model.S);
    +0049 rxnScales = sum(boolMat,1)./sum(absMat,1);
    +0050 
    +0051 model.S = model.S .* rxnScales;
    +0052 
    +0053 %{
    +0054 if (nargin < 2)
    +0055     maxStoichVal = 250;
    +0056 end
    +0057 
    +0058 
    +0059 %find all reactions containing high stoichiometry coefficients
    +0060 maxCoeffs = max(model.S, [], 1);
    +0061 rxnsToCheck = maxCoeffs > maxStoichVal;
     0062 
    -0063 rxnsToCheckInd = find(rxnsToCheck);
    -0064 
    -0065 %We just scale all reactions so they don't have a coefficient > maxStoichVal for now
    -0066 
    -0067 %do this with a loop, doesn't really matter
    -0068 for rxnInd = rxnsToCheckInd
    -0069     %find the lowest index which is not a pointless metabolite, such as H+, H2O etc.
    -0070     rxnS = model.S(:,rxnInd);
    -0071     %metInd = find(rxnS ~= 0);
    -0072     
    -0073     largestCoeff = max(abs(rxnS));
    -0074     scaleFactor = maxStoichVal/largestCoeff;
    -0075     model.S(:,rxnInd) = model.S(:,rxnInd) .* scaleFactor;
    -0076 end
    -0077 %}
    -0078 end

    +0063 %for debugging: +0064 %constructEquations(model, model.rxns(rxnsToCheck)) +0065 +0066 rxnsToCheckInd = find(rxnsToCheck); +0067 +0068 %We just scale all reactions so they don't have a coefficient > maxStoichVal for now +0069 +0070 %do this with a loop, doesn't really matter +0071 for rxnInd = rxnsToCheckInd +0072 %find the lowest index which is not a pointless metabolite, such as H+, H2O etc. +0073 rxnS = model.S(:,rxnInd); +0074 %metInd = find(rxnS ~= 0); +0075 +0076 largestCoeff = max(abs(rxnS)); +0077 scaleFactor = maxStoichVal/largestCoeff; +0078 model.S(:,rxnInd) = model.S(:,rxnInd) .* scaleFactor; +0079 end +0080 %} +0081 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/INIT/scoreModel.html b/docs/html/INIT/scoreModel.html index 56019e56..42792c3c 100644 --- a/docs/html/INIT/scoreModel.html +++ b/docs/html/INIT/scoreModel.html @@ -30,11 +30,8 @@

    DESCRIPTION ^
     scoreModel  Score reactions and genes in a model with complex gene rules.
     
      Scores the reactions and genes in a model containing complex gene rules
    - based on expression data from HPA and/or gene arrays.
    -
    - It is highly recommended that the model grRules are "cleaned" and verified by
    - the "cleanModelGeneRules" function prior to scoring the model with
    - scoreModel.
    + based on expression data from HPA, gene arrays or both. The model grRules
    + should be standardised with standardizeGrRules before scoring.
     
      Parameters
      ----------
    @@ -48,26 +45,27 @@ 

    DESCRIPTION ^DESCRIPTION ^DESCRIPTION ^SOURCE CODE ^% scoreModel Score reactions and genes in a model with complex gene rules. 0003 % 0004 % Scores the reactions and genes in a model containing complex gene rules -0005 % based on expression data from HPA and/or gene arrays. -0006 % -0007 % It is highly recommended that the model grRules are "cleaned" and verified by -0008 % the "cleanModelGeneRules" function prior to scoring the model with -0009 % scoreModel. -0010 % -0011 % Parameters -0012 % ---------- -0013 % model : struct -0014 % a model structure. -0015 % hpaData : struct -0016 % HPA data structure from parseHPA (optional if arrayData is supplied, -0017 % default []). -0018 % arrayData : struct -0019 % gene expression data structure (optional if hpaData is supplied, default -0020 % []). With fields: -0021 % -0022 % - genes : cell array with the unique gene names. -0023 % - tissues : cell array with the tissue names. The list may not be unique, -0024 % as there can be multiple cell types per tissue. -0025 % - celltypes : cell array with the cell type names for each tissue. -0026 % - levels : GENESxTISSUES array with the expression level for each gene in -0027 % each tissue/celltype. NaN should be used when no measurement was -0028 % performed. -0029 % - threshold : a single value or a vector of gene expression thresholds, -0030 % above which genes are considered to be "expressed". (optional, by -0031 % default, the mean expression levels of each gene across all tissues in -0032 % arrayData will be used as the threshold values). -0033 % tissue : char -0034 % tissue to score for. Should exist in either hpaData.tissues or -0035 % arrayData.tissues. -0036 % -0037 % Name-Value Arguments -0038 % -------------------- -0039 % celltype : char -0040 % cell type to score for. Should exist in either hpaData.celltypes or -0041 % arrayData.celltypes for this tissue (default is to use the max values -0042 % among all the cell types for the tissue). -0043 % noGeneScore : double -0044 % score for reactions without genes (default -2). -0045 % isozymeScoring : char -0046 % determines how scores are calculated for reactions with multiple genes -0047 % joined by "OR" expression(s) ("min", "max", "median", "average") -0048 % (default "max"). -0049 % complexScoring : char -0050 % determines how scores are calculated for reactions with multiple genes -0051 % joined by "AND" expression(s) ("min", "max", "median", "average") -0052 % (default "min"). -0053 % multipleCellScoring : char -0054 % determines how scores are calculated when several cell types are used -0055 % ("max" or "average") (default "max"). -0056 % hpaLevelScores : struct -0057 % structure with numerical scores for the expression level categories from -0058 % HPA. The structure should have a "names" and a "scores" field (default, -0059 % see code for default scores). -0060 % dataPrecedence : char -0061 % how hpaData takes precedence over arrayData when both are supplied -0062 % (default "gene"): -0063 % -0064 % - "gene" : per gene. A gene with HPA data is scored from it, a gene -0065 % without is scored from arrayData, and a rule can mix the two. -0066 % - "reaction" : per rule. As soon as any one gene in a rule has HPA data, -0067 % the whole rule is scored from HPA alone and its array-only genes count -0068 % as unmeasured. -0069 % -0070 % Returns -0071 % ------- -0072 % rxnScores : double -0073 % scores for each of the reactions in model. -0074 % geneScores : double -0075 % scores for each of the genes in model. Genes which are not in the -0076 % dataset(s) have NaN as scores, which removeLowScoreGenes reads as "no -0077 % evidence" and leaves in place, where a negative score would be pruned. +0005 % based on expression data from HPA, gene arrays or both. The model grRules +0006 % should be standardised with standardizeGrRules before scoring. +0007 % +0008 % Parameters +0009 % ---------- +0010 % model : struct +0011 % a model structure. +0012 % hpaData : struct +0013 % HPA data structure from parseHPA (optional if arrayData is supplied, +0014 % default []). +0015 % arrayData : struct +0016 % gene expression data structure (optional if hpaData is supplied, default +0017 % []). With fields: +0018 % +0019 % - genes : cell array with the unique gene names. +0020 % - tissues : cell array with the tissue names. The list can contain +0021 % duplicates, as there can be multiple cell types per tissue. +0022 % - celltypes : cell array with the cell type names for each tissue. +0023 % - levels : GENESxTISSUES array with the expression level for each gene in +0024 % each tissue and cell type. NaN marks a gene that was not measured. +0025 % - threshold : a single value or a vector of gene expression thresholds, +0026 % above which genes are considered to be "expressed". If omitted, the +0027 % mean expression level of each gene across all tissues in arrayData +0028 % is used as the threshold. +0029 % tissue : char +0030 % tissue to score for. Must exist in either hpaData.tissues or +0031 % arrayData.tissues (matched without regard to case); otherwise an error +0032 % is raised. +0033 % +0034 % Name-Value Arguments +0035 % -------------------- +0036 % celltype : char +0037 % cell type to score for. Must exist in either hpaData.celltypes or +0038 % arrayData.celltypes (matched without regard to case); otherwise an error +0039 % is raised (default [], which uses all cell types of the tissue, +0040 % combined as set by multipleCellScoring). +0041 % noGeneScore : double +0042 % score for reactions without genes (default -2). +0043 % isozymeScoring : char +0044 % determines how scores are calculated for reactions with multiple genes +0045 % joined by "OR" expression(s) ("min", "max", "median", "average") +0046 % (default "max"). +0047 % complexScoring : char +0048 % determines how scores are calculated for reactions with multiple genes +0049 % joined by "AND" expression(s) ("min", "max", "median", "average") +0050 % (default "min"). +0051 % multipleCellScoring : char +0052 % determines how scores are calculated when several cell types are used +0053 % ("max" or "average") (default "max"). +0054 % hpaLevelScores : struct +0055 % structure with numerical scores for the expression level categories from +0056 % HPA. The structure has a "names" and a "scores" field. The default names +0057 % are High, Medium, Low, None, Strong, Moderate, Weak, Negative and Not +0058 % detected, with the scores 20, 15, 10, -8, 20, 15, 10, -8 and -8. +0059 % dataPrecedence : char +0060 % how hpaData takes precedence over arrayData when both are supplied +0061 % (default "gene"): +0062 % +0063 % - "gene" : per gene. A gene with HPA data is scored from it, a gene +0064 % without is scored from arrayData, and a rule can mix the two. +0065 % - "reaction" : per rule. As soon as any one gene in a rule has HPA data, +0066 % the whole rule is scored from HPA alone and its array-only genes count +0067 % as unmeasured. +0068 % +0069 % Returns +0070 % ------- +0071 % rxnScores : double +0072 % scores for each of the reactions in model. +0073 % geneScores : double +0074 % scores for each of the genes in model. Genes which are not in the +0075 % dataset(s) have NaN as scores. removeLowScoreGenes reads NaN as "no +0076 % evidence" and leaves the gene in place, and prunes genes with a +0077 % negative score. 0078 % hpaScores : double 0079 % scores for each of the genes in model if only taking hpaData into 0080 % account. Genes which are not in the dataset(s) have -Inf as scores. diff --git a/docs/html/INIT/tINIT/getINITModel.html b/docs/html/INIT/tINIT/getINITModel.html index c7b0392b..0da8a679 100644 --- a/docs/html/INIT/tINIT/getINITModel.html +++ b/docs/html/INIT/tINIT/getINITModel.html @@ -47,36 +47,36 @@

    SOURCE CODE ^
    0001 function [model, metProduction, essentialRxnsForTasks, addedRxnsForTasks, deletedDeadEndRxns, deletedRxnsInINIT, taskReport]=getINITModel(refModel, tissue, varargin)
     0002 % getINITModel  Generate a model using the original tINIT algorithm.
     0003 %
    -0004 % Generates a model using the tINIT algorithm, based on proteomics and/or
    -0005 % transcriptomics and/or metabolomics and/or metabolic tasks (PLoS Comput Biol.
    -0006 % 2012;8(5):e1002518, Mol Syst Biol. 2014;10:721).
    +0004 % Generates a model using the tINIT algorithm, from proteomics,
    +0005 % transcriptomics, metabolomics and metabolic tasks in any combination (PLoS
    +0006 % Comput Biol. 2012;8(5):e1002518, Mol Syst Biol. 2014;10:721).
     0007 %
    -0008 % ftINIT is the recommended method for context-specific model extraction. Use
    -0009 % this function to reproduce models built with the original tINIT. It remains
    -0010 % supported, and issues a RAVEN:legacyMethod notice once per session, which
    +0008 % ftINIT replaces this function for context-specific model extraction. This
    +0009 % function is kept to reproduce models built with the original tINIT. It
    +0010 % issues a RAVEN:legacyMethod notice once per session, which
     0011 % warning('off','RAVEN:legacyMethod') silences.
     0012 %
    -0013 % Not all settings are reachable through this function; runINIT and fitTasks
    -0014 % can be called individually for the ones that are not.
    +0013 % Not all settings are available through this function; runINIT and fitTasks
    +0014 % can be called individually to set the others.
     0015 %
     0016 % Parameters
     0017 % ----------
     0018 % refModel : struct
    -0019 %     a model structure. The model should be in the closed form (no exchange
    -0020 %     reactions open). Import using import(filename,false). If the model is not
    -0021 %     loaded using importModel, it might be that there is no "unconstrained"
    -0022 %     field. In that case, manually add the field like:
    +0019 %     a model structure in the closed form (no exchange reactions open),
    +0020 %     imported with importModel(filename,false). A model that is not loaded
    +0021 %     with importModel can lack the "unconstrained" field, and the function
    +0022 %     then raises RAVEN:badInput. The field can be added with
     0023 %     model.unconstrained=false(numel(model.mets),1).
     0024 % tissue : char
    -0025 %     tissue to score for. Should exist in either hpaData.tissues or
    -0026 %     arrayData.tissues.
    +0025 %     tissue to score for. Must exist in either hpaData.tissues or
    +0026 %     arrayData.tissues (scoreModel raises an error otherwise).
     0027 %
     0028 % Name-Value Arguments
     0029 % --------------------
     0030 % celltype : char
    -0031 %     cell type to score for. Should exist in either hpaData.celltypes or
    -0032 %     arrayData.celltypes for this tissue (default is to use the best values
    -0033 %     among all the cell types for the tissue).
    +0031 %     cell type to score for. Must exist in either hpaData.celltypes or
    +0032 %     arrayData.celltypes (scoreModel raises an error otherwise). The default
    +0033 %     is to use the maximum values among all the cell types for the tissue.
     0034 % hpaData : struct
     0035 %     HPA data structure from parseHPA (optional if arrayData is supplied,
     0036 %     default []).
    @@ -85,465 +85,463 @@ 

    SOURCE CODE ^% []). With fields: 0040 % 0041 % - genes : cell array with the unique gene names. -0042 % - tissues : cell array with the tissue names. The list may not be unique, -0043 % as there can be multiple cell types per tissue. +0042 % - tissues : cell array with the tissue names. The list can contain +0043 % duplicates, as there can be multiple cell types per tissue. 0044 % - celltypes : cell array with the cell type names for each tissue. 0045 % - levels : GENESxTISSUES array with the expression level for each gene in -0046 % each tissue/celltype. NaN should be used when no measurement was -0047 % performed. -0048 % - threshold : a single value or a vector of gene expression thresholds, -0049 % above which genes are considered to be "expressed". (default, by -0050 % default the mean expression levels of each gene across all tissues in -0051 % arrayData will be used as the threshold values). -0052 % - singleCells : binary value selecting whether to use the single-cell -0053 % algorithm to identify expressed genes. If used, specify cell -0054 % subpopulations in CELLTYPES (default []). -0055 % - plotResults : true if single cell probability distributions should be -0056 % plotted (default false). -0057 % metabolomicsData : cell -0058 % cell array with metabolite names that the model should produce (default -0059 % []). -0060 % taskFile : char -0061 % a task list in Excel format. See parseTaskList for details (default []). -0062 % useScoresForTasks : logical -0063 % true if the calculated reaction scores should be used as weights in the -0064 % fitting to tasks (default true). -0065 % printReport : logical -0066 % true if a report should be printed to the screen (default true). -0067 % taskStructure : struct -0068 % task structure as from parseTaskList. Can be used as an alternative way -0069 % to define tasks when Excel sheets are not suitable. Overrides taskFile -0070 % (default []). -0071 % params : struct -0072 % parameter structure for use by the INIT solver (default []). -0073 % paramsFT : struct -0074 % parameter structure for the fitTasks step (default []). -0075 % seed : double -0076 % seed for the random number generator used to break ties among -0077 % genes with equally uninformative (-Inf) scores, so which gene is -0078 % kept is reproducible across runs (default: current rng state is -0079 % left untouched). -0080 % -0081 % Returns -0082 % ------- -0083 % model : struct -0084 % the resulting model structure. -0085 % metProduction : double -0086 % array that indicates which of the metabolites in metabolomicsData that -0087 % could be produced. Note that this is before the gap-filling process to -0088 % enable defined tasks. To see which metabolites that can be produced in -0089 % the final model, use canExchange: -0090 % -0091 % - -2 : metabolite name not found in model. -0092 % - -1 : metabolite found, but it could not be produced. -0093 % - 1 : metabolite could be produced. -0094 % essentialRxnsForTasks : cell -0095 % cell array of the reactions which were essential to perform the tasks. -0096 % addedRxnsForTasks : cell -0097 % cell array of the reactions which were added in order to perform the -0098 % tasks. -0099 % deletedDeadEndRxns : cell -0100 % cell array of reactions deleted because they could not carry flux (INIT -0101 % requires a functional input model). -0102 % deletedRxnsInINIT : cell -0103 % cell array of the reactions which were deleted by the INIT algorithm. -0104 % taskReport : struct -0105 % structure with the results for each task. With fields: -0106 % -0107 % - id : cell array with the id of the task. -0108 % - description : cell array with the description of the task. -0109 % - ok : boolean array with true if the task was successful. -0110 % - essential : cell array with cell arrays of essential reactions for the -0111 % task. -0112 % - gapfill : cell array of cell arrays of reactions included in the -0113 % gap-filling for the task. -0114 % -0115 % Notes -0116 % ----- -0117 % Exchange metabolites should normally not be removed from the model when using -0118 % this approach, since checkTasks/fitTasks rely on putting specific constraints -0119 % for each task. The INIT algorithm will remove exchange metabolites if any are -0120 % present. Use importModel(file,false) to import a model with exchange -0121 % metabolites remaining. -0122 % -0123 % See Also -0124 % -------- -0125 % ftINIT, prepINITModel, runINIT, scoreModel +0046 % each tissue and cell type. NaN marks a gene that was not measured. +0047 % - threshold : a single value or a vector of gene expression thresholds, +0048 % above which genes are considered to be "expressed". If omitted, the +0049 % mean expression level of each gene across all tissues in arrayData +0050 % is used as the threshold. +0051 % - singleCells : binary value selecting whether to use the single-cell +0052 % algorithm to identify expressed genes. If used, the cell +0053 % subpopulations are specified in celltypes (default []). +0054 % - plotResults : true if single cell probability distributions are +0055 % plotted (default false). +0056 % metabolomicsData : cell +0057 % cell array with metabolite names that the model should produce (default +0058 % []). +0059 % taskFile : char +0060 % a task list in Excel format. See parseTaskList for details (default []). +0061 % useScoresForTasks : logical +0062 % true if the calculated reaction scores are used as weights in the +0063 % fitting to tasks. Scores above -0.1 are set to -0.1 (default true). +0064 % printReport : logical +0065 % true if a report is printed to the screen (default true). +0066 % taskStructure : struct +0067 % task structure as returned by parseTaskList. It defines the tasks when +0068 % Excel sheets are not used and overrides taskFile (default []). +0069 % params : struct +0070 % parameter structure for use by the INIT solver (default []). +0071 % paramsFT : struct +0072 % parameter structure for the fitTasks step (default []). +0073 % seed : double +0074 % seed for the random number generator used to break ties among genes +0075 % with equal uninformative (-Inf) scores, which makes the choice of the +0076 % kept gene reproducible across runs (default [], which leaves the rng +0077 % state unchanged). +0078 % +0079 % Returns +0080 % ------- +0081 % model : struct +0082 % the resulting model structure. +0083 % metProduction : double +0084 % array that indicates which of the metabolites in metabolomicsData could +0085 % be produced. The array is determined before the gap-filling for the +0086 % defined tasks. To see which metabolites can be produced in the final +0087 % model, use canExchange: +0088 % +0089 % - -2 : metabolite name not found in model. +0090 % - -1 : metabolite found, but it could not be produced. +0091 % - 1 : metabolite could be produced. +0092 % essentialRxnsForTasks : cell +0093 % cell array of the reactions which were essential to perform the tasks. +0094 % addedRxnsForTasks : cell +0095 % cell array of the reactions which were added in order to perform the +0096 % tasks. +0097 % deletedDeadEndRxns : cell +0098 % cell array of reactions deleted because they could not carry flux (INIT +0099 % requires a functional input model). +0100 % deletedRxnsInINIT : cell +0101 % cell array of the reactions which were deleted by the INIT algorithm. +0102 % taskReport : struct +0103 % structure with the results for each task. With fields: +0104 % +0105 % - id : cell array with the id of the task. +0106 % - description : cell array with the description of the task. +0107 % - ok : boolean array with true if the task was successful. +0108 % - essential : cell array with cell arrays of essential reactions for the +0109 % task. +0110 % - gapfill : cell array of cell arrays of reactions included in the +0111 % gap-filling for the task. +0112 % +0113 % Notes +0114 % ----- +0115 % The model keeps its exchange metabolites, since checkTasks +0116 % and fitTasks put specific constraints on them for each task. The INIT +0117 % algorithm removes exchange metabolites if any are present. Use +0118 % importModel(file,false) to import a model with exchange metabolites +0119 % remaining. +0120 % +0121 % See also +0122 % -------- +0123 % ftINIT, prepINITModel, runINIT, scoreModel +0124 +0125 legacyMethodNotice('getINITModel','ftINIT'); 0126 -0127 legacyMethodNotice('getINITModel','ftINIT'); -0128 -0129 p=parseRAVENargs(varargin, {'celltype',[]; 'hpaData',[]; 'arrayData',[]; 'metabolomicsData',[]; 'taskFile',[]; 'useScoresForTasks',[]; 'printReport',[]; 'taskStructure',[]; 'params',[]; 'paramsFT',[]; 'seed',[]}); -0130 if ~isempty(p.seed) -0131 rng(p.seed); -0132 end -0133 celltype=p.celltype; -0134 hpaData=p.hpaData; -0135 arrayData=p.arrayData; -0136 metabolomicsData=p.metabolomicsData; -0137 taskFile=p.taskFile; -0138 useScoresForTasks=p.useScoresForTasks; -0139 printReport=p.printReport; -0140 taskStructure=p.taskStructure; -0141 params=p.params; -0142 paramsFT=p.paramsFT; -0143 if ~isempty(celltype) -0144 celltype=char(celltype); -0145 end -0146 if ~isempty(taskFile) -0147 taskFile=char(taskFile); -0148 end -0149 if isempty(useScoresForTasks) -0150 useScoresForTasks=true; -0151 end -0152 if isempty(printReport) -0153 printReport=true; -0154 end -0155 -0156 %Check that the model is in the closed form -0157 if ~isfield(refModel,'unconstrained') -0158 EM='Exchange metabolites should normally not be removed from the model when using getINITModel. Use importModel(file,false) to import a model with exchange metabolites remaining (see the documentation for details)'; -0159 error('RAVEN:badInput', '%s', EM); -0160 end -0161 -0162 %Create the task structure if not supplied -0163 if any(taskFile) && isempty(taskStructure) -0164 taskStructure=parseTaskList(taskFile); -0165 end -0166 -0167 -0168 % sc-tINIT to identify confidence levels of gene expression -0169 if ~isempty(arrayData) && isfield(arrayData,'singleCells') -0170 if arrayData.singleCells == 1 -0171 % Check to ensure cell type is defined -0172 if ~isfield(arrayData,'celltypes') -0173 warning('RAVEN:warning', '%s', 'arrayData must contain cell type information if sc-tINIT is to be used'); -0174 end -0175 if ~ismember(upper(celltype),upper(arrayData.celltypes)) -0176 error('RAVEN:badInput', '%s', 'The cell type name does not match'); -0177 end -0178 -0179 % Analyze only cell type of interest -0180 J= strcmpi(arrayData.celltypes,celltype); -0181 -0182 % Analyze only genes included in the reference model -0183 I=ismember(arrayData.genes,refModel.genes); -0184 -0185 % Convert expression data to population fractions -0186 binary_levels = arrayData.levels(I,J)~=0; % Classify each gene as detected (1) or not (0) in each cell -0187 cell_count_levels = sum(binary_levels,2); % Number of cells expressing each transcript -0188 cell_frac_levels = cell_count_levels/size(binary_levels,2); % Number of cells expressing each transcript -0189 -0190 % Bin cell_frac_counts manually -0191 x = 0:.01:1; -0192 for(i = 1:length(x)) -0193 cell_frac_count(i) = sum(round(cell_frac_levels,2)==x(i)); -0194 end -0195 -0196 % Fit four beta distributions -0197 cell_frac_count(cell_frac_count==0) = NaN; % Remove zeros from optimization -0198 cell_frac_count(1) = NaN; % Remove non-expressed genes from optimization -0199 x_lim = 1; % Somewhat arbitrary parameter to fit left tail of distr. -0200 myfun = @(par) sum((cell_frac_count(1:find(x>=x_lim,1)) - ... -0201 abs(par(1))*betapdf(x(1:find(x>=x_lim,1)),abs(par(2)),abs(par(3))) - ... -0202 abs(par(4))*betapdf(x(1:find(x>=x_lim,1)),abs(par(5)),abs(par(6))) - ... -0203 abs(par(7))*betapdf(x(1:find(x>=x_lim,1)),abs(par(8)),abs(par(9))) - ... -0204 abs(par(10))*betapdf(x(1:find(x>=x_lim,1)),abs(par(11)),abs(par(12)))).^2, 'omitnan'); -0205 -0206 par0 = [4,2,100,7,2,30,7,5,20,5,15,20]; -0207 opts = optimset('Display','off'); -0208 [par,f_val] = fminsearch(myfun,par0,opts); -0209 par = abs(par); -0210 -0211 % Plot results -0212 if (isfield(arrayData,'plotResults')) -0213 if arrayData.plotResults == true -0214 figure(); hold on; plot(x,cell_frac_count,'ko','MarkerSize',5); -0215 plot(x,abs(par(1))*betapdf(x,abs(par(2)),abs(par(3))),'b-','LineWidth',1) -0216 plot(x,abs(par(4))*betapdf(x,abs(par(5)),abs(par(6))),'b-','LineWidth',1) -0217 plot(x,abs(par(7))*betapdf(x,abs(par(8)),abs(par(9))),'b-','LineWidth',1) -0218 plot(x,abs(par(10))*betapdf(x,abs(par(11)),abs(par(12))),'b-','LineWidth',1) -0219 plot(x,abs(par(1))*betapdf(x,abs(par(2)),abs(par(3))) + ... -0220 abs(par(4))*betapdf(x,abs(par(5)),abs(par(6))) + ... -0221 abs(par(7))*betapdf(x,abs(par(8)),abs(par(9))) + ... -0222 abs(par(10))*betapdf(x,abs(par(11)),abs(par(12))),'-','Color',[.5 .5 .5],'LineWidth',2) -0223 xlabel('Expression Probability');ylabel('# of genes');set(gca,'FontSize',14,'LineWidth',1.25); -0224 title('Expression prediction','FontSize',18,'FontWeight','bold') -0225 end -0226 end -0227 -0228 % Score genes based on population expression (p = .05) -0229 exprs_cutoff_1 = find(cdf('beta',x,par(2),par(3)) >.95,1)-1; % Find index of no confidence genes -0230 exprs_cutoff_2 = find(cdf('beta',x,par(5),par(6)) >.95,1)-1; % Find index of low confidence genes -0231 exprs_cutoff_3 = find(cdf('beta',x,par(8),par(9)) >.95,1)-1; % Find index of low confidence genes -0232 exprs_cutoffs = sort([exprs_cutoff_1,exprs_cutoff_2,exprs_cutoff_3]); -0233 gene_scores = cell_frac_levels*0; -0234 gene_scores(cell_frac_levels <= x(exprs_cutoffs(1))) = 4; % Not detected -0235 gene_scores(logical((cell_frac_levels >= x(exprs_cutoffs(1))).*(cell_frac_levels < x(exprs_cutoffs(2))))) = 3; % Low detection -0236 gene_scores(logical((cell_frac_levels >= x(exprs_cutoffs(2))).*(cell_frac_levels < x(exprs_cutoffs(3))))) = 2; % Medium detection -0237 gene_scores(cell_frac_levels > x(exprs_cutoffs(3))) = 1; % High detection -0238 -0239 % Replace hpaData with singleCellData -0240 if printReport==true -0241 %warning()'s '%s' passes the message through literally, so its -0242 %"\n" must be resolved to a real newline beforehand, not left -0243 %to print as a literal backslash-n. -0244 warning('RAVEN:warning', '%s', sprintf('Single cell data is not currently compatible with HPA data. \n Replacing hpaData with single cell-based scoring.')); -0245 end -0246 hpaData.genes = arrayData.genes; -0247 hpaData.tissues = arrayData.tissues; -0248 hpaData.celltypes = arrayData.celltypes; -0249 hpaData.levels = [{'High'},{'Medium'},{'Low'},{'None'}]; -0250 hpaData.gene2Level = zeros(length(arrayData.genes),length(arrayData.celltypes)); -0251 for i = 1:length(find(J)) -0252 find_var = find(J,i); -0253 hpaData.gene2Level(I,find_var(end)) = gene_scores; -0254 end -0255 -0256 % Remove arrayData from the analysis -0257 clear arrayData -0258 arrayData=[]; -0259 end -0260 end -0261 -0262 -0263 if printReport==true -0264 if any(celltype) -0265 fprintf(['***Generating model for: ' tissue ' - ' celltype '\n']); -0266 else -0267 fprintf(['***Generating model for: ' tissue '\n']); -0268 end -0269 fprintf('-Using the legacy tINIT algorithm; ftINIT is the recommended method\n'); -0270 if ~isempty(hpaData) -0271 fprintf('-Using HPA data\n'); -0272 end -0273 if ~isempty(arrayData) -0274 fprintf('-Using array data\n'); -0275 end -0276 if ~isempty(metabolomicsData) -0277 fprintf('-Using metabolomics data\n'); -0278 end -0279 if ~isempty(taskFile) || ~isempty(taskStructure) -0280 fprintf('-Using metabolic tasks\n'); -0281 end -0282 fprintf('\n'); -0283 -0284 printScores(refModel,'Reference model statistics',hpaData,arrayData,tissue,celltype); -0285 end -0286 -0287 %Remove dead-end reactions to speed up the optimization and to -0288 %differentiate between reactions removed by INIT and those that are -0289 %dead-end -0290 [~, deletedDeadEndRxns]=simplifyModel(refModel,true,false,true,true,true); -0291 cModel=removeReactions(refModel,deletedDeadEndRxns,false,true); -0292 -0293 %Store the connected model like this to keep track of stuff -0294 if printReport==true -0295 printScores(cModel,'Pruned model statistics',hpaData,arrayData,tissue,celltype); -0296 end -0297 -0298 %If tasks have been defined, then go through them and get essential -0299 %reactions -0300 if ~isempty(taskStructure) -0301 [taskReport, essentialRxnMat]=checkTasks(cModel,[],printReport,true,true,taskStructure); +0127 p=parseRAVENargs(varargin, {'celltype',[]; 'hpaData',[]; 'arrayData',[]; 'metabolomicsData',[]; 'taskFile',[]; 'useScoresForTasks',[]; 'printReport',[]; 'taskStructure',[]; 'params',[]; 'paramsFT',[]; 'seed',[]}); +0128 if ~isempty(p.seed) +0129 rng(p.seed); +0130 end +0131 celltype=p.celltype; +0132 hpaData=p.hpaData; +0133 arrayData=p.arrayData; +0134 metabolomicsData=p.metabolomicsData; +0135 taskFile=p.taskFile; +0136 useScoresForTasks=p.useScoresForTasks; +0137 printReport=p.printReport; +0138 taskStructure=p.taskStructure; +0139 params=p.params; +0140 paramsFT=p.paramsFT; +0141 if ~isempty(celltype) +0142 celltype=char(celltype); +0143 end +0144 if ~isempty(taskFile) +0145 taskFile=char(taskFile); +0146 end +0147 if isempty(useScoresForTasks) +0148 useScoresForTasks=true; +0149 end +0150 if isempty(printReport) +0151 printReport=true; +0152 end +0153 +0154 %Check that the model is in the closed form +0155 if ~isfield(refModel,'unconstrained') +0156 EM='Exchange metabolites should normally not be removed from the model when using getINITModel. Use importModel(file,false) to import a model with exchange metabolites remaining (see the documentation for details)'; +0157 error('RAVEN:badInput', '%s', EM); +0158 end +0159 +0160 %Create the task structure if not supplied +0161 if any(taskFile) && isempty(taskStructure) +0162 taskStructure=parseTaskList(taskFile); +0163 end +0164 +0165 +0166 % sc-tINIT to identify confidence levels of gene expression +0167 if ~isempty(arrayData) && isfield(arrayData,'singleCells') +0168 if arrayData.singleCells == 1 +0169 % Check to ensure cell type is defined +0170 if ~isfield(arrayData,'celltypes') +0171 warning('RAVEN:warning', '%s', 'arrayData must contain cell type information if sc-tINIT is to be used'); +0172 end +0173 if ~ismember(upper(celltype),upper(arrayData.celltypes)) +0174 error('RAVEN:badInput', '%s', 'The cell type name does not match'); +0175 end +0176 +0177 % Analyze only cell type of interest +0178 J= strcmpi(arrayData.celltypes,celltype); +0179 +0180 % Analyze only genes included in the reference model +0181 I=ismember(arrayData.genes,refModel.genes); +0182 +0183 % Convert expression data to population fractions +0184 binary_levels = arrayData.levels(I,J)~=0; % Classify each gene as detected (1) or not (0) in each cell +0185 cell_count_levels = sum(binary_levels,2); % Number of cells expressing each transcript +0186 cell_frac_levels = cell_count_levels/size(binary_levels,2); % Number of cells expressing each transcript +0187 +0188 % Bin cell_frac_counts manually +0189 x = 0:.01:1; +0190 for(i = 1:length(x)) +0191 cell_frac_count(i) = sum(round(cell_frac_levels,2)==x(i)); +0192 end +0193 +0194 % Fit four beta distributions +0195 cell_frac_count(cell_frac_count==0) = NaN; % Remove zeros from optimization +0196 cell_frac_count(1) = NaN; % Remove non-expressed genes from optimization +0197 x_lim = 1; % Somewhat arbitrary parameter to fit left tail of distr. +0198 myfun = @(par) sum((cell_frac_count(1:find(x>=x_lim,1)) - ... +0199 abs(par(1))*betapdf(x(1:find(x>=x_lim,1)),abs(par(2)),abs(par(3))) - ... +0200 abs(par(4))*betapdf(x(1:find(x>=x_lim,1)),abs(par(5)),abs(par(6))) - ... +0201 abs(par(7))*betapdf(x(1:find(x>=x_lim,1)),abs(par(8)),abs(par(9))) - ... +0202 abs(par(10))*betapdf(x(1:find(x>=x_lim,1)),abs(par(11)),abs(par(12)))).^2, 'omitnan'); +0203 +0204 par0 = [4,2,100,7,2,30,7,5,20,5,15,20]; +0205 opts = optimset('Display','off'); +0206 [par,f_val] = fminsearch(myfun,par0,opts); +0207 par = abs(par); +0208 +0209 % Plot results +0210 if (isfield(arrayData,'plotResults')) +0211 if arrayData.plotResults == true +0212 figure(); hold on; plot(x,cell_frac_count,'ko','MarkerSize',5); +0213 plot(x,abs(par(1))*betapdf(x,abs(par(2)),abs(par(3))),'b-','LineWidth',1) +0214 plot(x,abs(par(4))*betapdf(x,abs(par(5)),abs(par(6))),'b-','LineWidth',1) +0215 plot(x,abs(par(7))*betapdf(x,abs(par(8)),abs(par(9))),'b-','LineWidth',1) +0216 plot(x,abs(par(10))*betapdf(x,abs(par(11)),abs(par(12))),'b-','LineWidth',1) +0217 plot(x,abs(par(1))*betapdf(x,abs(par(2)),abs(par(3))) + ... +0218 abs(par(4))*betapdf(x,abs(par(5)),abs(par(6))) + ... +0219 abs(par(7))*betapdf(x,abs(par(8)),abs(par(9))) + ... +0220 abs(par(10))*betapdf(x,abs(par(11)),abs(par(12))),'-','Color',[.5 .5 .5],'LineWidth',2) +0221 xlabel('Expression Probability');ylabel('# of genes');set(gca,'FontSize',14,'LineWidth',1.25); +0222 title('Expression prediction','FontSize',18,'FontWeight','bold') +0223 end +0224 end +0225 +0226 % Score genes based on population expression (p = .05) +0227 exprs_cutoff_1 = find(cdf('beta',x,par(2),par(3)) >.95,1)-1; % Find index of no confidence genes +0228 exprs_cutoff_2 = find(cdf('beta',x,par(5),par(6)) >.95,1)-1; % Find index of low confidence genes +0229 exprs_cutoff_3 = find(cdf('beta',x,par(8),par(9)) >.95,1)-1; % Find index of low confidence genes +0230 exprs_cutoffs = sort([exprs_cutoff_1,exprs_cutoff_2,exprs_cutoff_3]); +0231 gene_scores = cell_frac_levels*0; +0232 gene_scores(cell_frac_levels <= x(exprs_cutoffs(1))) = 4; % Not detected +0233 gene_scores(logical((cell_frac_levels >= x(exprs_cutoffs(1))).*(cell_frac_levels < x(exprs_cutoffs(2))))) = 3; % Low detection +0234 gene_scores(logical((cell_frac_levels >= x(exprs_cutoffs(2))).*(cell_frac_levels < x(exprs_cutoffs(3))))) = 2; % Medium detection +0235 gene_scores(cell_frac_levels > x(exprs_cutoffs(3))) = 1; % High detection +0236 +0237 % Replace hpaData with singleCellData +0238 if printReport==true +0239 %warning()'s '%s' passes the message through literally, so its +0240 %"\n" must be resolved to a real newline beforehand, not left +0241 %to print as a literal backslash-n. +0242 warning('RAVEN:warning', '%s', sprintf('Single cell data is not currently compatible with HPA data. \n Replacing hpaData with single cell-based scoring.')); +0243 end +0244 hpaData.genes = arrayData.genes; +0245 hpaData.tissues = arrayData.tissues; +0246 hpaData.celltypes = arrayData.celltypes; +0247 hpaData.levels = [{'High'},{'Medium'},{'Low'},{'None'}]; +0248 hpaData.gene2Level = zeros(length(arrayData.genes),length(arrayData.celltypes)); +0249 for i = 1:length(find(J)) +0250 find_var = find(J,i); +0251 hpaData.gene2Level(I,find_var(end)) = gene_scores; +0252 end +0253 +0254 % Remove arrayData from the analysis +0255 clear arrayData +0256 arrayData=[]; +0257 end +0258 end +0259 +0260 +0261 if printReport==true +0262 if any(celltype) +0263 fprintf(['***Generating model for: ' tissue ' - ' celltype '\n']); +0264 else +0265 fprintf(['***Generating model for: ' tissue '\n']); +0266 end +0267 fprintf('-Using the legacy tINIT algorithm; ftINIT is the recommended method\n'); +0268 if ~isempty(hpaData) +0269 fprintf('-Using HPA data\n'); +0270 end +0271 if ~isempty(arrayData) +0272 fprintf('-Using array data\n'); +0273 end +0274 if ~isempty(metabolomicsData) +0275 fprintf('-Using metabolomics data\n'); +0276 end +0277 if ~isempty(taskFile) || ~isempty(taskStructure) +0278 fprintf('-Using metabolic tasks\n'); +0279 end +0280 fprintf('\n'); +0281 +0282 printScores(refModel,'Reference model statistics',hpaData,arrayData,tissue,celltype); +0283 end +0284 +0285 %Remove dead-end reactions to speed up the optimization and to +0286 %differentiate between reactions removed by INIT and those that are +0287 %dead-end +0288 [~, deletedDeadEndRxns]=simplifyModel(refModel,true,false,true,true,true); +0289 cModel=removeReactions(refModel,deletedDeadEndRxns,false,true); +0290 +0291 %Store the connected model like this to keep track of stuff +0292 if printReport==true +0293 printScores(cModel,'Pruned model statistics',hpaData,arrayData,tissue,celltype); +0294 end +0295 +0296 %If tasks have been defined, then go through them and get essential +0297 %reactions +0298 if ~isempty(taskStructure) +0299 [taskReport, essentialRxnMat]=checkTasks(cModel,[],printReport,true,true,taskStructure); +0300 +0301 essentialRxnsForTasks=cModel.rxns(any(essentialRxnMat,2)); 0302 -0303 essentialRxnsForTasks=cModel.rxns(any(essentialRxnMat,2)); -0304 -0305 %Remove tasks that cannot be performed -0306 taskStructure(taskReport.ok==false)=[]; -0307 if printReport==true -0308 printScores(removeReactions(cModel,setdiff(cModel.rxns,essentialRxnsForTasks),true,true),'Reactions essential for tasks',hpaData,arrayData,tissue,celltype); -0309 end -0310 else -0311 essentialRxnsForTasks={}; -0312 end -0313 -0314 %Score the connected model. isozymeScoring/complexScoring use one operator -0315 %for both and/or (a rule reduces to a single statistic over its genes) and -0316 %dataPrecedence is per-reaction, matching the original tINIT algorithm. -0317 [rxnScores, geneScores]=scoreModel(cModel,hpaData,arrayData,tissue,celltype, ... -0318 'isozymeScoring','max','complexScoring','max','dataPrecedence','reaction'); -0319 %scoreModel marks an unmeasured gene NaN (removeLowScoreGenes reads that as -0320 %"no evidence" and leaves it be); this algorithm instead needs -Inf, so an -0321 %unmeasured gene never ranks equal-or-above a measured one further down. -0322 geneScores(isnan(geneScores))=-Inf; -0323 -0324 %Run the INIT algorithm. The exchange reactions that are used in the final -0325 %reactions will be open, which does not fit with the last step. Therefore -0326 %delete reactions from the original model instead of taking the output. The -0327 %default implementation does not constrain reversible reactions to only -0328 %carry flux in one direction. Runs without the constraints on reversibility -0329 %and with all output allowed. This is to reduce the complexity of the -0330 %problem. -0331 [~, deletedRxnsInINIT, metProduction]=runINIT(simplifyModel(cModel),rxnScores,metabolomicsData,essentialRxnsForTasks,0,true,false,params); -0332 initModel=removeReactions(cModel,deletedRxnsInINIT,true,true); -0333 if printReport==true -0334 printScores(initModel,'INIT model statistics',hpaData,arrayData,tissue,celltype); -0335 printScores(removeReactions(cModel,setdiff(cModel.rxns,deletedRxnsInINIT),true,true),'Reactions deleted by INIT',hpaData,arrayData,tissue,celltype); -0336 end -0337 -0338 %The full model has exchange reactions in it. fitTasks calls on fillGaps, -0339 %which automatically removes exchange metabolites (because it assumes that -0340 %the reactions are constrained when appropriate). In this case the -0341 %uptakes/outputs are retrieved from the task sheet instead. To prevent -0342 %exchange reactions being used to fill gaps, they are delete from the -0343 %reference model here. -0344 initModel.id='INITModel'; -0345 -0346 %If gaps in the model should be filled using a task list -0347 if ~isempty(taskStructure) -0348 %Remove exchange reactions and reactions already included in the INIT -0349 %model -0350 refModelNoExc=removeReactions(refModel,union(initModel.rxns,getExchangeRxns(refModel)),true,true); -0351 -0352 %At this stage the model is fully connected and most of the genes with -0353 %good scores should have been included. The final gap-filling should -0354 %take the scores of the genes into account, so that "rather bad" -0355 %reactions are preferred to "very bad" reactions. However, reactions -0356 %with positive scores will be included even if they are not connected -0357 %in the current formulation. Therefore, such reactions will have to be -0358 %assigned a small negative score instead. -0359 if useScoresForTasks==true -0360 refRxnScores=scoreModel(refModelNoExc,hpaData,arrayData,tissue,celltype, ... -0361 'isozymeScoring','max','complexScoring','max','dataPrecedence','reaction'); -0362 [outModel, addedRxnMat]=fitTasks(initModel,refModelNoExc,[],true,min(refRxnScores,-0.1),taskStructure,paramsFT); -0363 else -0364 [outModel, addedRxnMat]=fitTasks(initModel,refModelNoExc,[],true,[],taskStructure,paramsFT); -0365 end -0366 if printReport==true -0367 printScores(outModel,'Functional model statistics',hpaData,arrayData,tissue,celltype); -0368 printScores(removeReactions(outModel,intersect(outModel.rxns,initModel.rxns),true,true),'Reactions added to perform the tasks',hpaData,arrayData,tissue,celltype); -0369 end -0370 -0371 addedRxnsForTasks=refModelNoExc.rxns(any(addedRxnMat,2)); -0372 else -0373 outModel=initModel; -0374 addedRxnMat=[]; -0375 addedRxnsForTasks={}; -0376 end -0377 -0378 %The model can now perform all the tasks defined in the task list. The -0379 %algorithm cannot deal with gene-complexes at the moment. It is therefore -0380 %ok to remove bad genes from a reaction (as long as at least one gene is -0381 %kept) -0382 model=outModel; -0383 -0384 [~, I]=ismember(model.genes,cModel.genes); %All should be found -0385 %This is a little weird way to make sure that only one bad gene is included -0386 %if there are no good ones (since all -Inf==max(-Inf)) -0387 geneScores(isinf(geneScores))=-1000+rand(sum(isinf(geneScores)),1); -0388 -0389 model.grRules(:)={''}; -0390 for i=1:numel(model.rxns) -0391 ids=find(model.rxnGeneMat(i,:)); -0392 if numel(ids)>1 -0393 scores=geneScores(I(ids)); -0394 %Only keep the positive ones if possible -0395 model.rxnGeneMat(i,ids(~(scores>0 | scores==max(scores))))=0; -0396 end -0397 %Rewrite the grRules to be only OR -0398 if isfield(model,'grRules') -0399 J=find(model.rxnGeneMat(i,:)); -0400 for j=1:numel(J) -0401 model.grRules{i}=[model.grRules{i} '(' model.genes{J(j)} ')']; -0402 if j<numel(J) -0403 model.grRules{i}=[model.grRules{i} ' or ']; -0404 end -0405 end -0406 end -0407 end -0408 -0409 %Find all genes that are not used and delete them -0410 I=sum(model.rxnGeneMat)==0; -0411 model.genes(I)=[]; -0412 model.rxnGeneMat(:,I)=[]; -0413 if isfield(model,'geneShortNames') -0414 model.geneShortNames(I)=[]; -0415 end -0416 if isfield(model,'proteins') -0417 model.proteins(I)=[]; -0418 end -0419 if isfield(model,'geneMiriams') -0420 model.geneMiriams(I)=[]; -0421 end -0422 if isfield(model,'geneFrom') -0423 model.geneFrom(I)=[]; -0424 end -0425 if isfield(model,'geneComps') -0426 model.geneComps(I)=[]; -0427 end -0428 -0429 %At this stage the model will contain some exchange reactions but probably -0430 %not all (and maybe zero). This can be inconvenient, so all exchange -0431 %reactions from the reference model are added, except for those which -0432 %involve metabolites that are not in the model. -0433 -0434 %First delete and included exchange reactions in order to prevent the order -0435 %from changing -0436 model=removeReactions(model,getExchangeRxns(model)); -0437 -0438 %Create a model with only the exchange reactions in refModel -0439 excModel=removeReactions(refModel,setdiff(refModel.rxns,getExchangeRxns(refModel)),true,true); -0440 -0441 %Find the metabolites there which are not exchange metabolites and which do -0442 %not exist in the output model -0443 I=~ismember(excModel.mets,model.mets) & excModel.unconstrained==0; -0444 -0445 %Then find those reactions and delete them -0446 [~, J]=find(excModel.S(I,:)); -0447 excModel=removeReactions(excModel,J,true,true); -0448 -0449 %Merge with the output model -0450 model=mergeModels({model;excModel},'metNames'); -0451 model.id='INITModel'; -0452 model.name=['Automatically generated model for ' tissue]; -0453 if any(celltype) -0454 model.name=[model.name ' - ' celltype]; -0455 end -0456 -0457 if printReport==true -0458 printScores(model,'Final model statistics',hpaData,arrayData,tissue,celltype); -0459 end -0460 -0461 %Add information about essential reactions and reactions included for -0462 %gap-filling and return a taskReport -0463 if ~isempty(taskStructure) -0464 I=find(taskReport.ok); %Ignore failed tasks -0465 for i=1:numel(I) -0466 taskReport.essential{I(i),1}=cModel.rxns(essentialRxnMat(:,I(i))); -0467 taskReport.gapfill{I(i),1}=refModelNoExc.rxns(addedRxnMat(:,i)); -0468 end -0469 else -0470 taskReport=[]; -0471 end -0472 -0473 %Fix grRules and reconstruct rxnGeneMat -0474 [grRules,rxnGeneMat] = standardizeGrRules(model,true); -0475 model.grRules = grRules; -0476 model.rxnGeneMat = rxnGeneMat; -0477 end -0478 -0479 %This is for printing a summary of a model -0480 function [rxnS, geneS]=printScores(model,name,hpaData,arrayData,tissue,celltype) -0481 [a, b]=scoreModel(model,hpaData,arrayData,tissue,celltype, ... -0482 'isozymeScoring','max','complexScoring','max','dataPrecedence','reaction'); -0483 b(isnan(b))=-Inf; -0484 rxnS=mean(a); -0485 geneS=mean(b(~isinf(b))); -0486 fprintf([name ':\n']); -0487 fprintf(['\t' num2str(numel(model.rxns)) ' reactions, ' num2str(numel(model.genes)) ' genes\n']); -0488 fprintf(['\tMean reaction score: ' num2str(rxnS) '\n']); -0489 fprintf(['\tMean gene score: ' num2str(geneS) '\n']); -0490 fprintf(['\tReactions with positive scores: ' num2str(100*sum(a>0)/numel(a)) '%%\n\n']); -0491 end -0492 -0493 %Local beta probability density function, equivalent to the Statistics and -0494 %Machine Learning Toolbox betapdf but using only base MATLAB (beta). This -0495 %avoids a dependency on that toolbox. The density is zero outside [0,1]. -0496 function y=betapdf(x,a,b) -0497 y=zeros(size(x)); -0498 k=x>=0 & x<=1; -0499 y(k)=(x(k).^(a-1)).*((1-x(k)).^(b-1))/beta(a,b); -0500 end

    +0303 %Remove tasks that cannot be performed +0304 taskStructure(taskReport.ok==false)=[]; +0305 if printReport==true +0306 printScores(removeReactions(cModel,setdiff(cModel.rxns,essentialRxnsForTasks),true,true),'Reactions essential for tasks',hpaData,arrayData,tissue,celltype); +0307 end +0308 else +0309 essentialRxnsForTasks={}; +0310 end +0311 +0312 %Score the connected model. isozymeScoring/complexScoring use one operator +0313 %for both and/or (a rule reduces to a single statistic over its genes) and +0314 %dataPrecedence is per-reaction, matching the original tINIT algorithm. +0315 [rxnScores, geneScores]=scoreModel(cModel,hpaData,arrayData,tissue,celltype, ... +0316 'isozymeScoring','max','complexScoring','max','dataPrecedence','reaction'); +0317 %scoreModel marks an unmeasured gene NaN (removeLowScoreGenes reads that as +0318 %"no evidence" and leaves it be); this algorithm instead needs -Inf, so an +0319 %unmeasured gene never ranks equal-or-above a measured one further down. +0320 geneScores(isnan(geneScores))=-Inf; +0321 +0322 %Run the INIT algorithm. The exchange reactions that are used in the final +0323 %reactions will be open, which does not fit with the last step. Therefore +0324 %delete reactions from the original model instead of taking the output. The +0325 %default implementation does not constrain reversible reactions to only +0326 %carry flux in one direction. Runs without the constraints on reversibility +0327 %and with all output allowed. This is to reduce the complexity of the +0328 %problem. +0329 [~, deletedRxnsInINIT, metProduction]=runINIT(simplifyModel(cModel),rxnScores,metabolomicsData,essentialRxnsForTasks,0,true,false,params); +0330 initModel=removeReactions(cModel,deletedRxnsInINIT,true,true); +0331 if printReport==true +0332 printScores(initModel,'INIT model statistics',hpaData,arrayData,tissue,celltype); +0333 printScores(removeReactions(cModel,setdiff(cModel.rxns,deletedRxnsInINIT),true,true),'Reactions deleted by INIT',hpaData,arrayData,tissue,celltype); +0334 end +0335 +0336 %The full model has exchange reactions in it. fitTasks calls on fillGaps, +0337 %which automatically removes exchange metabolites (because it assumes that +0338 %the reactions are constrained when appropriate). In this case the +0339 %uptakes/outputs are retrieved from the task sheet instead. To prevent +0340 %exchange reactions being used to fill gaps, they are delete from the +0341 %reference model here. +0342 initModel.id='INITModel'; +0343 +0344 %If gaps in the model should be filled using a task list +0345 if ~isempty(taskStructure) +0346 %Remove exchange reactions and reactions already included in the INIT +0347 %model +0348 refModelNoExc=removeReactions(refModel,union(initModel.rxns,getExchangeRxns(refModel)),true,true); +0349 +0350 %At this stage the model is fully connected and most of the genes with +0351 %good scores should have been included. The final gap-filling should +0352 %take the scores of the genes into account, so that "rather bad" +0353 %reactions are preferred to "very bad" reactions. However, reactions +0354 %with positive scores will be included even if they are not connected +0355 %in the current formulation. Therefore, such reactions will have to be +0356 %assigned a small negative score instead. +0357 if useScoresForTasks==true +0358 refRxnScores=scoreModel(refModelNoExc,hpaData,arrayData,tissue,celltype, ... +0359 'isozymeScoring','max','complexScoring','max','dataPrecedence','reaction'); +0360 [outModel, addedRxnMat]=fitTasks(initModel,refModelNoExc,[],true,min(refRxnScores,-0.1),taskStructure,paramsFT); +0361 else +0362 [outModel, addedRxnMat]=fitTasks(initModel,refModelNoExc,[],true,[],taskStructure,paramsFT); +0363 end +0364 if printReport==true +0365 printScores(outModel,'Functional model statistics',hpaData,arrayData,tissue,celltype); +0366 printScores(removeReactions(outModel,intersect(outModel.rxns,initModel.rxns),true,true),'Reactions added to perform the tasks',hpaData,arrayData,tissue,celltype); +0367 end +0368 +0369 addedRxnsForTasks=refModelNoExc.rxns(any(addedRxnMat,2)); +0370 else +0371 outModel=initModel; +0372 addedRxnMat=[]; +0373 addedRxnsForTasks={}; +0374 end +0375 +0376 %The model can now perform all the tasks defined in the task list. The +0377 %algorithm cannot deal with gene-complexes at the moment. It is therefore +0378 %ok to remove bad genes from a reaction (as long as at least one gene is +0379 %kept) +0380 model=outModel; +0381 +0382 [~, I]=ismember(model.genes,cModel.genes); %All should be found +0383 %This is a little weird way to make sure that only one bad gene is included +0384 %if there are no good ones (since all -Inf==max(-Inf)) +0385 geneScores(isinf(geneScores))=-1000+rand(sum(isinf(geneScores)),1); +0386 +0387 model.grRules(:)={''}; +0388 for i=1:numel(model.rxns) +0389 ids=find(model.rxnGeneMat(i,:)); +0390 if numel(ids)>1 +0391 scores=geneScores(I(ids)); +0392 %Only keep the positive ones if possible +0393 model.rxnGeneMat(i,ids(~(scores>0 | scores==max(scores))))=0; +0394 end +0395 %Rewrite the grRules to be only OR +0396 if isfield(model,'grRules') +0397 J=find(model.rxnGeneMat(i,:)); +0398 for j=1:numel(J) +0399 model.grRules{i}=[model.grRules{i} '(' model.genes{J(j)} ')']; +0400 if j<numel(J) +0401 model.grRules{i}=[model.grRules{i} ' or ']; +0402 end +0403 end +0404 end +0405 end +0406 +0407 %Find all genes that are not used and delete them +0408 I=sum(model.rxnGeneMat)==0; +0409 model.genes(I)=[]; +0410 model.rxnGeneMat(:,I)=[]; +0411 if isfield(model,'geneShortNames') +0412 model.geneShortNames(I)=[]; +0413 end +0414 if isfield(model,'proteins') +0415 model.proteins(I)=[]; +0416 end +0417 if isfield(model,'geneMiriams') +0418 model.geneMiriams(I)=[]; +0419 end +0420 if isfield(model,'geneFrom') +0421 model.geneFrom(I)=[]; +0422 end +0423 if isfield(model,'geneComps') +0424 model.geneComps(I)=[]; +0425 end +0426 +0427 %At this stage the model will contain some exchange reactions but probably +0428 %not all (and maybe zero). This can be inconvenient, so all exchange +0429 %reactions from the reference model are added, except for those which +0430 %involve metabolites that are not in the model. +0431 +0432 %First delete and included exchange reactions in order to prevent the order +0433 %from changing +0434 model=removeReactions(model,getExchangeRxns(model)); +0435 +0436 %Create a model with only the exchange reactions in refModel +0437 excModel=removeReactions(refModel,setdiff(refModel.rxns,getExchangeRxns(refModel)),true,true); +0438 +0439 %Find the metabolites there which are not exchange metabolites and which do +0440 %not exist in the output model +0441 I=~ismember(excModel.mets,model.mets) & excModel.unconstrained==0; +0442 +0443 %Then find those reactions and delete them +0444 [~, J]=find(excModel.S(I,:)); +0445 excModel=removeReactions(excModel,J,true,true); +0446 +0447 %Merge with the output model +0448 model=mergeModels({model;excModel},'metNames'); +0449 model.id='INITModel'; +0450 model.name=['Automatically generated model for ' tissue]; +0451 if any(celltype) +0452 model.name=[model.name ' - ' celltype]; +0453 end +0454 +0455 if printReport==true +0456 printScores(model,'Final model statistics',hpaData,arrayData,tissue,celltype); +0457 end +0458 +0459 %Add information about essential reactions and reactions included for +0460 %gap-filling and return a taskReport +0461 if ~isempty(taskStructure) +0462 I=find(taskReport.ok); %Ignore failed tasks +0463 for i=1:numel(I) +0464 taskReport.essential{I(i),1}=cModel.rxns(essentialRxnMat(:,I(i))); +0465 taskReport.gapfill{I(i),1}=refModelNoExc.rxns(addedRxnMat(:,i)); +0466 end +0467 else +0468 taskReport=[]; +0469 end +0470 +0471 %Fix grRules and reconstruct rxnGeneMat +0472 [grRules,rxnGeneMat] = standardizeGrRules(model,true); +0473 model.grRules = grRules; +0474 model.rxnGeneMat = rxnGeneMat; +0475 end +0476 +0477 %This is for printing a summary of a model +0478 function [rxnS, geneS]=printScores(model,name,hpaData,arrayData,tissue,celltype) +0479 [a, b]=scoreModel(model,hpaData,arrayData,tissue,celltype, ... +0480 'isozymeScoring','max','complexScoring','max','dataPrecedence','reaction'); +0481 b(isnan(b))=-Inf; +0482 rxnS=mean(a); +0483 geneS=mean(b(~isinf(b))); +0484 fprintf([name ':\n']); +0485 fprintf(['\t' num2str(numel(model.rxns)) ' reactions, ' num2str(numel(model.genes)) ' genes\n']); +0486 fprintf(['\tMean reaction score: ' num2str(rxnS) '\n']); +0487 fprintf(['\tMean gene score: ' num2str(geneS) '\n']); +0488 fprintf(['\tReactions with positive scores: ' num2str(100*sum(a>0)/numel(a)) '%%\n\n']); +0489 end +0490 +0491 %Local beta probability density function, equivalent to the Statistics and +0492 %Machine Learning Toolbox betapdf but using only base MATLAB (beta). This +0493 %avoids a dependency on that toolbox. The density is zero outside [0,1]. +0494 function y=betapdf(x,a,b) +0495 y=zeros(size(x)); +0496 k=x>=0 & x<=1; +0497 y(k)=(x(k).^(a-1)).*((1-x(k)).^(b-1))/beta(a,b); +0498 end


    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/INIT/tINIT/runINIT.html b/docs/html/INIT/tINIT/runINIT.html index 3f574415..f2890576 100644 --- a/docs/html/INIT/tINIT/runINIT.html +++ b/docs/html/INIT/tINIT/runINIT.html @@ -29,15 +29,16 @@

    SYNOPSIS ^DESCRIPTION ^

     runINIT  Solve the MILP of the original INIT algorithm.
     
    - Generates a model using the INIT algorithm, based on proteomics and/or
    - transcriptomics and/or metabolomics and/or metabolic tasks. This function is
    - the actual implementation of the algorithm; see getINITModel for the
    + Generates a model using the INIT algorithm, from proteomics,
    + transcriptomics, metabolomics and metabolic tasks in any combination. This
    + function is the implementation of the algorithm; see getINITModel for the
      higher-level reconstruction function, and PLoS Comput Biol. 2012;8(5):e1002518
      for the formulation.
     
    - ftINIT is the recommended method for context-specific model extraction, and
    - solves a different MILP in ftINITInternalAlg. This function remains
    - supported, and issues a RAVEN:legacyMethod notice once per session, which
    + ftINIT replaces this function for context-specific model extraction and
    + solves a different MILP in ftINITInternalAlg. This function is kept to
    + reproduce models built with the original INIT. It issues a
    + RAVEN:legacyMethod notice once per session, which
      warning('off','RAVEN:legacyMethod') silences.
     
      Parameters
    @@ -56,31 +57,35 @@ 

    DESCRIPTION ^DESCRIPTION ^getINITModel, ftINITInternalAlg, ftINIT

    @@ -119,364 +124,369 @@

    SOURCE CODE ^
    0001 function [outModel, deletedRxns, metProduction, fValue]=runINIT(model,varargin)
     0002 % runINIT  Solve the MILP of the original INIT algorithm.
     0003 %
    -0004 % Generates a model using the INIT algorithm, based on proteomics and/or
    -0005 % transcriptomics and/or metabolomics and/or metabolic tasks. This function is
    -0006 % the actual implementation of the algorithm; see getINITModel for the
    +0004 % Generates a model using the INIT algorithm, from proteomics,
    +0005 % transcriptomics, metabolomics and metabolic tasks in any combination. This
    +0006 % function is the implementation of the algorithm; see getINITModel for the
     0007 % higher-level reconstruction function, and PLoS Comput Biol. 2012;8(5):e1002518
     0008 % for the formulation.
     0009 %
    -0010 % ftINIT is the recommended method for context-specific model extraction, and
    -0011 % solves a different MILP in ftINITInternalAlg. This function remains
    -0012 % supported, and issues a RAVEN:legacyMethod notice once per session, which
    -0013 % warning('off','RAVEN:legacyMethod') silences.
    -0014 %
    -0015 % Parameters
    -0016 % ----------
    -0017 % model : struct
    -0018 %     a reference model structure.
    -0019 %
    -0020 % Name-Value Arguments
    -0021 % --------------------
    -0022 % rxnScores : double
    -0023 %     a vector of scores for the reactions in the model. Positive scores are
    -0024 %     reactions to keep and negative scores are reactions to exclude (default
    -0025 %     all 0.0).
    -0026 % presentMets : cell
    -0027 %     cell array with unique metabolite names that the model should produce
    -0028 %     (default []).
    -0029 % essentialRxns : cell
    -0030 %     cell array of reactions that are essential and that have to be in the
    -0031 %     resulting model. This is normally used when fitting a model to task (see
    -0032 %     fitTasks) (default []).
    -0033 % prodWeight : double
    -0034 %     a score that determines the value of having net-production of
    -0035 %     metabolites. This is a way of having a more functional network as it
    -0036 %     provides a reason for including bad reactions for connectivity reasons.
    -0037 %     This score is for each metabolite, and the sum of these weights and the
    -0038 %     scores for the reactions is what is optimized (default 0.5).
    -0039 % allowExcretion : logical
    -0040 %     true if excretion of all metabolites should be allowed. This results in
    -0041 %     fewer reactions being considered dead-ends, but all reactions in the
    -0042 %     resulting model may not be able to carry flux. If this is "false" then
    -0043 %     the equality constraints are taken from model.b. If the input model
    -0044 %     lacks exchange reactions then this should probably be "true", or a large
    -0045 %     proportion of the model would be excluded for connectivity reasons
    -0046 %     (default false).
    -0047 % noRevLoops : logical
    -0048 %     true if reversible reactions should be constrained to only carry flux in
    -0049 %     one direction. This prevents reversible reactions from being wrongly
    -0050 %     assigned as connected (the forward and backward reactions can form a loop
    -0051 %     and therefore appear connected), but it makes the problem significantly
    -0052 %     more computationally intensive to solve (two more integer constraints per
    -0053 %     reversible reaction) (default false).
    -0054 % params : struct
    -0055 %     parameter structure for use by optimizeProb.
    -0056 %
    -0057 % Returns
    -0058 % -------
    -0059 % outModel : struct
    -0060 %     the resulting model structure.
    -0061 % deletedRxns : cell
    -0062 %     reactions which were deleted by the algorithm.
    -0063 % metProduction : double
    -0064 %     array that indicates which of the metabolites in presentMets that could
    -0065 %     be produced:
    -0066 %
    -0067 %     - -2 : metabolite name not found in model.
    -0068 %     - -1 : metabolite found, but it could not be produced.
    -0069 %     - 1 : metabolite could be produced.
    -0070 % fValue : double
    -0071 %     objective value (sum of (the negative of) reaction scores for the
    -0072 %     included reactions and prodWeight*number of produced metabolites).
    -0073 %
    -0074 % See Also
    -0075 % --------
    -0076 % getINITModel, ftINITInternalAlg, ftINIT
    -0077 
    -0078 legacyMethodNotice('runINIT','ftINIT');
    -0079 
    -0080 p=parseRAVENargs(varargin, {'rxnScores',[]; 'presentMets',[]; 'essentialRxns',[]; 'prodWeight',[]; 'allowExcretion',false; 'noRevLoops',false; 'params',[]; 'eps',1});
    -0081 rxnScores=p.rxnScores;
    -0082 presentMets=p.presentMets;
    -0083 essentialRxns=p.essentialRxns;
    -0084 prodWeight=p.prodWeight;
    -0085 allowExcretion=p.allowExcretion;
    -0086 noRevLoops=p.noRevLoops;
    -0087 params=p.params;
    -0088 initEps=p.eps;
    -0089 if isempty(rxnScores)
    -0090     rxnScores=zeros(numel(model.rxns),1);
    -0091 end
    -0092 if isempty(presentMets)
    -0093     presentMets={};
    -0094 else
    -0095     presentMets=convertCharArray(presentMets);
    +0010 % ftINIT replaces this function for context-specific model extraction and
    +0011 % solves a different MILP in ftINITInternalAlg. This function is kept to
    +0012 % reproduce models built with the original INIT. It issues a
    +0013 % RAVEN:legacyMethod notice once per session, which
    +0014 % warning('off','RAVEN:legacyMethod') silences.
    +0015 %
    +0016 % Parameters
    +0017 % ----------
    +0018 % model : struct
    +0019 %     a reference model structure.
    +0020 %
    +0021 % Name-Value Arguments
    +0022 % --------------------
    +0023 % rxnScores : double
    +0024 %     a vector of scores for the reactions in the model. Positive scores are
    +0025 %     reactions to keep and negative scores are reactions to exclude (default
    +0026 %     all 0.0).
    +0027 % presentMets : cell
    +0028 %     cell array with unique metabolite names that the model should produce
    +0029 %     (default []).
    +0030 % essentialRxns : cell
    +0031 %     cell array of reactions that are essential and that have to be in the
    +0032 %     resulting model. fitTasks passes the reactions that the tasks require
    +0033 %     (default []).
    +0034 % prodWeight : double
    +0035 %     a score for each metabolite that has net production. The sum of these
    +0036 %     weights and the scores for the reactions is optimised. Net production
    +0037 %     of metabolites gives a reason for including reactions with a negative
    +0038 %     score for connectivity reasons, which gives a more functional network
    +0039 %     (default 0.5).
    +0040 % allowExcretion : logical
    +0041 %     true if excretion of all metabolites is allowed. This results in
    +0042 %     fewer reactions being considered dead-ends, but not every reaction in the
    +0043 %     resulting model can carry flux. If this is "false" then
    +0044 %     the equality constraints are taken from model.b. If the input model
    +0045 %     lacks exchange reactions, set it to true; with false a large part of
    +0046 %     the model is excluded for connectivity reasons
    +0047 %     (default false).
    +0048 % noRevLoops : logical
    +0049 %     true if reversible reactions are constrained to carry flux in only one
    +0050 %     direction. This prevents reversible reactions from being assigned as
    +0051 %     connected wrongly (the forward and backward reactions can form a loop
    +0052 %     and therefore appear connected). It adds two binary variables per
    +0053 %     reversible reaction to the problem, which makes the MILP larger and more
    +0054 %     computationally intensive (default false).
    +0055 % params : struct
    +0056 %     parameter structure for use by optimizeProb.
    +0057 % eps : double
    +0058 %     the lower flux bound of a reaction that is turned on, which also
    +0059 %     applies to the essential reactions (capped by their upper bound)
    +0060 %     (default 1).
    +0061 %
    +0062 % Returns
    +0063 % -------
    +0064 % outModel : struct
    +0065 %     the resulting model structure.
    +0066 % deletedRxns : cell
    +0067 %     reactions which were deleted by the algorithm.
    +0068 % metProduction : double
    +0069 %     array that indicates which of the metabolites in presentMets that could
    +0070 %     be produced:
    +0071 %
    +0072 %     - -2 : metabolite name not found in model.
    +0073 %     - -1 : metabolite found, but it could not be produced.
    +0074 %     - 1 : metabolite could be produced.
    +0075 % fValue : double
    +0076 %     objective value (sum of (the negative of) reaction scores for the
    +0077 %     included reactions and prodWeight*number of produced metabolites).
    +0078 %
    +0079 % See also
    +0080 % --------
    +0081 % getINITModel, ftINITInternalAlg, ftINIT
    +0082 
    +0083 legacyMethodNotice('runINIT','ftINIT');
    +0084 
    +0085 p=parseRAVENargs(varargin, {'rxnScores',[]; 'presentMets',[]; 'essentialRxns',[]; 'prodWeight',[]; 'allowExcretion',false; 'noRevLoops',false; 'params',[]; 'eps',1});
    +0086 rxnScores=p.rxnScores;
    +0087 presentMets=p.presentMets;
    +0088 essentialRxns=p.essentialRxns;
    +0089 prodWeight=p.prodWeight;
    +0090 allowExcretion=p.allowExcretion;
    +0091 noRevLoops=p.noRevLoops;
    +0092 params=p.params;
    +0093 initEps=p.eps;
    +0094 if isempty(rxnScores)
    +0095     rxnScores=zeros(numel(model.rxns),1);
     0096 end
    -0097 if isempty(essentialRxns)
    -0098     essentialRxns={};
    +0097 if isempty(presentMets)
    +0098     presentMets={};
     0099 else
    -0100     essentialRxns=convertCharArray(essentialRxns);
    +0100     presentMets=convertCharArray(presentMets);
     0101 end
    -0102 if isempty(prodWeight)
    -0103     prodWeight=0.5;
    -0104 end
    -0105 
    -0106 if numel(presentMets)~=numel(unique(presentMets))
    -0107     EM='Duplicate metabolite names in presentMets';
    -0108     error('RAVEN:badInput', '%s', EM);
    +0102 if isempty(essentialRxns)
    +0103     essentialRxns={};
    +0104 else
    +0105     essentialRxns=convertCharArray(essentialRxns);
    +0106 end
    +0107 if isempty(prodWeight)
    +0108     prodWeight=0.5;
     0109 end
     0110 
    -0111 %Default is that the metabolites cannot be produced
    -0112 if ~isempty(presentMets)
    -0113     metProduction=ones(numel(presentMets),1)*-2;
    -0114     presentMets=upper(presentMets);
    -0115     pmIndexes=find(ismember(presentMets,upper(model.metNames)));
    -0116     metProduction(pmIndexes)=-1; %Then set that they are at least found
    -0117 else
    -0118     metProduction=[];
    -0119     pmIndexes=[];
    -0120 end
    -0121 
    -0122 %The model should be in the reversible format and all relevant exchange
    -0123 %reactions should be open
    -0124 if isfield(model,'unconstrained')
    -0125     EM='Exchange metabolites are still present in the model. Use simplifyModel if this is not intended';
    -0126     warning('RAVEN:warning', '%s', EM);
    -0127 end
    -0128 
    -0129 %The irreversible reactions that are essential must have a flux and are
    -0130 %therefore not optimized for using MILP, which reduces the problem size.
    -0131 %However, reversible reactions must have a flux in one direction, so they
    -0132 %have to stay in the problem. The essentiality constraint on reversible
    -0133 %reactions is implemented in the same manner as for reversible reactions
    -0134 %when noRevLoops==true, but with the additional constraint that C ub=-1.
    -0135 %This forces one of the directions to be active.
    -0136 revRxns=find(model.rev~=0);
    -0137 essentialReversible=find(ismember(model.rxns(revRxns),essentialRxns));
    -0138 essentialRxns=intersect(essentialRxns,model.rxns(model.rev==0));
    -0139 
    -0140 %Convert the model to irreversible
    -0141 irrevModel=convertToIrrev(model);
    -0142 rxnScores=[rxnScores;rxnScores(model.rev==1)];
    -0143 %These are used if noRevLoops is true
    -0144 if noRevLoops==true
    -0145     forwardIndexes=find(model.rev~=0);
    -0146     backwardIndexes=(numel(model.rxns)+1:numel(irrevModel.rxns))';
    -0147 else
    -0148     %Then they should only be used for essential reversible reactions
    -0149     forwardIndexes=revRxns(essentialReversible);
    -0150     backwardIndexes=essentialReversible+numel(model.rxns);
    -0151 end
    -0152 
    -0153 %Get the indexes of the essential reactions and remove them from the
    -0154 %scoring vector
    -0155 essentialIndex=find(ismember(irrevModel.rxns,essentialRxns));
    -0156 rxnScores(essentialIndex)=[];
    +0111 if numel(presentMets)~=numel(unique(presentMets))
    +0112     EM='Duplicate metabolite names in presentMets';
    +0113     error('RAVEN:badInput', '%s', EM);
    +0114 end
    +0115 
    +0116 %Default is that the metabolites cannot be produced
    +0117 if ~isempty(presentMets)
    +0118     metProduction=ones(numel(presentMets),1)*-2;
    +0119     presentMets=upper(presentMets);
    +0120     pmIndexes=find(ismember(presentMets,upper(model.metNames)));
    +0121     metProduction(pmIndexes)=-1; %Then set that they are at least found
    +0122 else
    +0123     metProduction=[];
    +0124     pmIndexes=[];
    +0125 end
    +0126 
    +0127 %The model should be in the reversible format and all relevant exchange
    +0128 %reactions should be open
    +0129 if isfield(model,'unconstrained')
    +0130     EM='Exchange metabolites are still present in the model. Use simplifyModel if this is not intended';
    +0131     warning('RAVEN:warning', '%s', EM);
    +0132 end
    +0133 
    +0134 %The irreversible reactions that are essential must have a flux and are
    +0135 %therefore not optimized for using MILP, which reduces the problem size.
    +0136 %However, reversible reactions must have a flux in one direction, so they
    +0137 %have to stay in the problem. The essentiality constraint on reversible
    +0138 %reactions is implemented in the same manner as for reversible reactions
    +0139 %when noRevLoops==true, but with the additional constraint that C ub=-1.
    +0140 %This forces one of the directions to be active.
    +0141 revRxns=find(model.rev~=0);
    +0142 essentialReversible=find(ismember(model.rxns(revRxns),essentialRxns));
    +0143 essentialRxns=intersect(essentialRxns,model.rxns(model.rev==0));
    +0144 
    +0145 %Convert the model to irreversible
    +0146 irrevModel=convertToIrrev(model);
    +0147 rxnScores=[rxnScores;rxnScores(model.rev==1)];
    +0148 %These are used if noRevLoops is true
    +0149 if noRevLoops==true
    +0150     forwardIndexes=find(model.rev~=0);
    +0151     backwardIndexes=(numel(model.rxns)+1:numel(irrevModel.rxns))';
    +0152 else
    +0153     %Then they should only be used for essential reversible reactions
    +0154     forwardIndexes=revRxns(essentialReversible);
    +0155     backwardIndexes=essentialReversible+numel(model.rxns);
    +0156 end
     0157 
    -0158 %Go through each of the presentMets (if they exist) and modify the S matrix
    -0159 %so that each reaction which produces any of them also produces a
    -0160 %corresponding fake metabolite and the opposite in the reverse direction.
    -0161 
    -0162 %This is to deal with the fact that there is no compartment info regarding
    -0163 %the presentMets. This modifies the irrevModel structure, but that is fine
    -0164 %since it is the model structure that is returned.
    -0165 if any(pmIndexes)
    -0166     irrevModel.metNames=upper(irrevModel.metNames);
    -0167     % num2str on the whole column right-aligns every row to a common width,
    -0168     % embedding padding spaces once the indices span more than one digit
    -0169     % count; format each one independently.
    -0170     metsToAdd.mets=arrayfun(@(x) sprintf('FAKEFORPM%d',x), pmIndexes, 'UniformOutput', false);
    -0171     metsToAdd.metNames=metsToAdd.mets;
    -0172     metsToAdd.compartments=irrevModel.comps{1};
    -0173     
    -0174     %There is no constraints on the metabolites yet, since maybe not all of
    -0175     %them could be produced
    -0176     irrevModel=addMets(irrevModel,metsToAdd);
    -0177 end
    -0178 
    -0179 %Modify the matrix
    -0180 for i=1:numel(pmIndexes)
    -0181     %Get the matching mets
    -0182     I=ismember(irrevModel.metNames,presentMets(pmIndexes(i)));
    -0183     
    -0184     %Find the reactions where any of them are used.
    -0185     [~, K, L]=find(irrevModel.S(I,:));
    -0186     
    -0187     %This ugly loop is to avoid problems if a metabolite occurs several
    -0188     %times in one reaction
    -0189     KK=unique(K);
    -0190     LL=zeros(numel(KK),1);
    -0191     for j=1:numel(KK)
    -0192         LL(j)=sum(L(K==KK(j)));
    -0193     end
    -0194     irrevModel.S(numel(irrevModel.mets)-numel(pmIndexes)+i,KK)=LL;
    -0195 end
    -0196 
    -0197 %Some nice to have numbers
    -0198 nMets=numel(irrevModel.mets);
    -0199 nRxns=numel(irrevModel.rxns);
    -0200 nEssential=numel(essentialIndex);
    -0201 nNonEssential=nRxns-nEssential;
    -0202 nonEssentialIndex=setdiff(1:nRxns,essentialIndex);
    -0203 % Per-reaction big-M: use each reaction upper bound, capped at 1000.
    -0204 % Must be at least initEps so the inclusion constraint is feasible.
    -0205 nonEssUBs = min(irrevModel.ub(nonEssentialIndex), 1000);
    -0206 nonEssUBs = max(nonEssUBs(:), initEps);
    -0207 S=irrevModel.S;
    -0208 
    -0209 %Add so that each non-essential reaction produces one unit of a fake
    -0210 %metabolite
    -0211 temp=sparse(1:nRxns,1:nRxns,1);
    -0212 temp(essentialIndex,:)=[];
    -0213 S=[S;temp];
    -0214 
    -0215 %Add another set of reactions (will be binary) which also produce these
    -0216 %fake metabolites, but with a stoichiometry of 1000
    -0217 temp=sparse(1:nNonEssential,1:nNonEssential,nonEssUBs);
    -0218 temp=[sparse(nMets,nNonEssential);temp];
    -0219 S=[S temp];
    -0220 
    -0221 %Add reactions for net-production of (real) metabolites
    -0222 if prodWeight~=0
    -0223     temp=[speye(nMets-numel(pmIndexes))*-1;sparse(nNonEssential+numel(pmIndexes),nMets-numel(pmIndexes))];
    -0224     S=[S temp];
    -0225     %To keep the number of reactions added like this
    -0226     nNetProd=nMets-numel(pmIndexes);
    -0227 else
    -0228     nNetProd=0;
    -0229 end
    -0230 
    -0231 %Add constraints so that reversible reactions can only be used in one
    -0232 %direction. This is done by adding the fake metabolites A, B, C for each
    -0233 %reversible reaction in the following manner
    -0234 % forward: A + .. => ... backwards: B + ... => ... int1: C => 1000 A int2:
    -0235 % C => 1000 B A ub=999.9 B ub=999.9 C lb=-1 int1 and int2 are binary
    -0236 if any(forwardIndexes)
    -0237     nRevBounds=numel(forwardIndexes);
    -0238     
    -0239     %Add the A metabolites for the forward reactions and the B metabolites
    -0240     %for the reverse reactions
    -0241     I=speye(numel(irrevModel.rxns))*-1;
    -0242     temp=[I(forwardIndexes,:);I(backwardIndexes,:)];
    +0158 %Get the indexes of the essential reactions and remove them from the
    +0159 %scoring vector
    +0160 essentialIndex=find(ismember(irrevModel.rxns,essentialRxns));
    +0161 rxnScores(essentialIndex)=[];
    +0162 
    +0163 %Go through each of the presentMets (if they exist) and modify the S matrix
    +0164 %so that each reaction which produces any of them also produces a
    +0165 %corresponding fake metabolite and the opposite in the reverse direction.
    +0166 
    +0167 %This is to deal with the fact that there is no compartment info regarding
    +0168 %the presentMets. This modifies the irrevModel structure, but that is fine
    +0169 %since it is the model structure that is returned.
    +0170 if any(pmIndexes)
    +0171     irrevModel.metNames=upper(irrevModel.metNames);
    +0172     % num2str on the whole column right-aligns every row to a common width,
    +0173     % embedding padding spaces once the indices span more than one digit
    +0174     % count; format each one independently.
    +0175     metsToAdd.mets=arrayfun(@(x) sprintf('FAKEFORPM%d',x), pmIndexes, 'UniformOutput', false);
    +0176     metsToAdd.metNames=metsToAdd.mets;
    +0177     metsToAdd.compartments=irrevModel.comps{1};
    +0178     
    +0179     %There is no constraints on the metabolites yet, since maybe not all of
    +0180     %them could be produced
    +0181     irrevModel=addMets(irrevModel,metsToAdd);
    +0182 end
    +0183 
    +0184 %Modify the matrix
    +0185 for i=1:numel(pmIndexes)
    +0186     %Get the matching mets
    +0187     I=ismember(irrevModel.metNames,presentMets(pmIndexes(i)));
    +0188     
    +0189     %Find the reactions where any of them are used.
    +0190     [~, K, L]=find(irrevModel.S(I,:));
    +0191     
    +0192     %This ugly loop is to avoid problems if a metabolite occurs several
    +0193     %times in one reaction
    +0194     KK=unique(K);
    +0195     LL=zeros(numel(KK),1);
    +0196     for j=1:numel(KK)
    +0197         LL(j)=sum(L(K==KK(j)));
    +0198     end
    +0199     irrevModel.S(numel(irrevModel.mets)-numel(pmIndexes)+i,KK)=LL;
    +0200 end
    +0201 
    +0202 %Some nice to have numbers
    +0203 nMets=numel(irrevModel.mets);
    +0204 nRxns=numel(irrevModel.rxns);
    +0205 nEssential=numel(essentialIndex);
    +0206 nNonEssential=nRxns-nEssential;
    +0207 nonEssentialIndex=setdiff(1:nRxns,essentialIndex);
    +0208 % Per-reaction big-M: use each reaction upper bound, capped at 1000.
    +0209 % Must be at least initEps so the inclusion constraint is feasible.
    +0210 nonEssUBs = min(irrevModel.ub(nonEssentialIndex), 1000);
    +0211 nonEssUBs = max(nonEssUBs(:), initEps);
    +0212 S=irrevModel.S;
    +0213 
    +0214 %Add so that each non-essential reaction produces one unit of a fake
    +0215 %metabolite
    +0216 temp=sparse(1:nRxns,1:nRxns,1);
    +0217 temp(essentialIndex,:)=[];
    +0218 S=[S;temp];
    +0219 
    +0220 %Add another set of reactions (will be binary) which also produce these
    +0221 %fake metabolites, but with a stoichiometry of 1000
    +0222 temp=sparse(1:nNonEssential,1:nNonEssential,nonEssUBs);
    +0223 temp=[sparse(nMets,nNonEssential);temp];
    +0224 S=[S temp];
    +0225 
    +0226 %Add reactions for net-production of (real) metabolites
    +0227 if prodWeight~=0
    +0228     temp=[speye(nMets-numel(pmIndexes))*-1;sparse(nNonEssential+numel(pmIndexes),nMets-numel(pmIndexes))];
    +0229     S=[S temp];
    +0230     %To keep the number of reactions added like this
    +0231     nNetProd=nMets-numel(pmIndexes);
    +0232 else
    +0233     nNetProd=0;
    +0234 end
    +0235 
    +0236 %Add constraints so that reversible reactions can only be used in one
    +0237 %direction. This is done by adding the fake metabolites A, B, C for each
    +0238 %reversible reaction in the following manner
    +0239 % forward: A + .. => ... backwards: B + ... => ... int1: C => 1000 A int2:
    +0240 % C => 1000 B A ub=999.9 B ub=999.9 C lb=-1 int1 and int2 are binary
    +0241 if any(forwardIndexes)
    +0242     nRevBounds=numel(forwardIndexes);
     0243     
    -0244     %Padding
    -0245     temp=[temp sparse(size(temp,1),size(S,2)-numel(irrevModel.rxns))];
    -0246     
    -0247     %Add the int1 & int2 reactions that produce A and B
    -0248     temp=[temp speye(nRevBounds*2)*1000];
    -0249     
    -0250     %And add that they also consume C
    -0251     temp=[temp;[sparse(nRevBounds,size(S,2)) speye(nRevBounds)*-1 speye(nRevBounds)*-1]];
    -0252     
    -0253     %Add the new reactions and metabolites
    -0254     S=[S sparse(size(S,1),nRevBounds*2)];
    -0255     S=[S;temp];
    -0256 else
    -0257     nRevBounds=0;
    -0258 end
    -0259 
    -0260 %Add so that the essential reactions must have a small flux and that the
    -0261 %binary ones (and net-production reactions) may have zero flux. The integer
    -0262 %reactions for reversible reactions have [0 1]
    -0263 prob.blx=[irrevModel.lb;zeros(nNonEssential+nNetProd+nRevBounds*2,1)];
    -0264 prob.blx(essentialIndex)=min(max(initEps,prob.blx(essentialIndex)),irrevModel.ub(essentialIndex));
    -0265 
    -0266 %Add so that the binary ones and net-production reactions can have at the
    -0267 %most flux 1.0
    -0268 prob.bux=[irrevModel.ub;ones(nNonEssential+nNetProd+nRevBounds*2,1)];
    -0269 
    -0270 %Add that the fake metabolites must be produced in a small amount and that
    -0271 %the A and B metabolites for reversible reactions can be [0 999.9] and C
    -0272 %metabolites [-1 0]
    -0273 prob.blc=[irrevModel.b(:,1);ones(nNonEssential,1)*initEps;zeros(nRevBounds*2,1);ones(nRevBounds,1)*-1];
    +0244     %Add the A metabolites for the forward reactions and the B metabolites
    +0245     %for the reverse reactions
    +0246     I=speye(numel(irrevModel.rxns))*-1;
    +0247     temp=[I(forwardIndexes,:);I(backwardIndexes,:)];
    +0248     
    +0249     %Padding
    +0250     temp=[temp sparse(size(temp,1),size(S,2)-numel(irrevModel.rxns))];
    +0251     
    +0252     %Add the int1 & int2 reactions that produce A and B
    +0253     temp=[temp speye(nRevBounds*2)*1000];
    +0254     
    +0255     %And add that they also consume C
    +0256     temp=[temp;[sparse(nRevBounds,size(S,2)) speye(nRevBounds)*-1 speye(nRevBounds)*-1]];
    +0257     
    +0258     %Add the new reactions and metabolites
    +0259     S=[S sparse(size(S,1),nRevBounds*2)];
    +0260     S=[S;temp];
    +0261 else
    +0262     nRevBounds=0;
    +0263 end
    +0264 
    +0265 %Add so that the essential reactions must have a small flux and that the
    +0266 %binary ones (and net-production reactions) may have zero flux. The integer
    +0267 %reactions for reversible reactions have [0 1]
    +0268 prob.blx=[irrevModel.lb;zeros(nNonEssential+nNetProd+nRevBounds*2,1)];
    +0269 prob.blx(essentialIndex)=min(max(initEps,prob.blx(essentialIndex)),irrevModel.ub(essentialIndex));
    +0270 
    +0271 %Add so that the binary ones and net-production reactions can have at the
    +0272 %most flux 1.0
    +0273 prob.bux=[irrevModel.ub;ones(nNonEssential+nNetProd+nRevBounds*2,1)];
     0274 
    -0275 %Add that normal metabolites can be freely excreted if
    -0276 %allowExcretion==true, and that the fake ones can be excreted 1000 units at
    -0277 %most. C metabolites for essential reversible reactions should have an
    -0278 %upper bound of -1. If noRevLoops is false, then add this constraint for
    -0279 %all the reactions instead.
    -0280 if noRevLoops==true
    -0281     revUB=zeros(nRevBounds,1);
    -0282     revUB(essentialReversible)=-1;
    -0283 else
    -0284     revUB=ones(nRevBounds,1)*-1;
    -0285 end
    -0286 if allowExcretion==true
    -0287     metUB=inf(nMets,1);
    +0275 %Add that the fake metabolites must be produced in a small amount and that
    +0276 %the A and B metabolites for reversible reactions can be [0 999.9] and C
    +0277 %metabolites [-1 0]
    +0278 prob.blc=[irrevModel.b(:,1);ones(nNonEssential,1)*initEps;zeros(nRevBounds*2,1);ones(nRevBounds,1)*-1];
    +0279 
    +0280 %Add that normal metabolites can be freely excreted if
    +0281 %allowExcretion==true, and that the fake ones can be excreted 1000 units at
    +0282 %most. C metabolites for essential reversible reactions should have an
    +0283 %upper bound of -1. If noRevLoops is false, then add this constraint for
    +0284 %all the reactions instead.
    +0285 if noRevLoops==true
    +0286     revUB=zeros(nRevBounds,1);
    +0287     revUB(essentialReversible)=-1;
     0288 else
    -0289     metUB=irrevModel.b(:,min(size(irrevModel.b,2),2));
    +0289     revUB=ones(nRevBounds,1)*-1;
     0290 end
    -0291 prob.buc=[metUB;nonEssUBs;ones(nRevBounds*2,1)*999.9;revUB];
    -0292 
    -0293 %Add objective coefficients for the binary reactions. The negative is used
    -0294 %since we are minimizing. The negative is taken for the prodWeight as well,
    -0295 %to be consistent with the syntax that positive scores are good
    -0296 prob.c=[zeros(nRxns,1);rxnScores;ones(nNetProd,1)*prodWeight*-1;zeros(nRevBounds*2,1)];
    -0297 prob.a=S;
    -0298 
    -0299 % adapt problem structure for cobra-style solver
    -0300 prob.c=[prob.c;zeros(size(prob.a,1),1)];
    -0301 prob.A=[prob.a -speye(size(prob.a,1))];
    -0302 prob.b=zeros(size(prob.a,1), 1);
    -0303 prob.ub=[prob.bux; prob.buc];
    -0304 prob.osense=1;
    -0305 prob.csense=char(zeros(1,size(prob.a,1)));
    -0306 prob.csense(:)='E';
    -0307 
    -0308 %Add that the binary reactions may only take integer values. This has to be
    -0309 %declared before the presentMets loop below: optimizeProb reads vartype on
    -0310 %every call, and the producibility test is only meaningful against the same
    -0311 %integer problem that is solved at the end.
    -0312 prob.vartype = repmat('C', 1, size(prob.A, 2));
    -0313 allInt=[(nRxns+1):(nRxns+nNonEssential) size(S,2)-nRevBounds*2+1:size(S,2)];
    -0314 prob.vartype(allInt) = 'B';
    -0315 
    -0316 %We still do not know which of the presentMets that can be produced. Go
    -0317 %through them, force production, and see if the problem can be solved
    -0318 for i=1:numel(pmIndexes)
    -0319     prob.blc(numel(irrevModel.mets)-numel(pmIndexes)+i)=1;
    -0320     prob.lb=[prob.blx; prob.blc];
    -0321     res=optimizeProb(prob,params);
    -0322     isFeasible=checkSolution(res);
    -0323     if ~isFeasible
    -0324         %Reset the constraint again
    -0325         prob.blc(numel(irrevModel.mets)-numel(pmIndexes)+i)=0;
    -0326     else
    -0327         %Metabolite produced
    -0328         metProduction(pmIndexes(i))=1;
    -0329     end
    -0330 end
    -0331 prob.lb=[prob.blx; prob.blc];
    -0332 
    -0333 % solve problem
    -0334 res=optimizeProb(prob,params);
    -0335 
    -0336 %Problem should not be infeasible, but it is possible that the time limit
    -0337 %was reached before finding any solutions.
    -0338 if ~checkSolution(res)
    -0339     if res.hitTimeLimit
    -0340         EM='Time limit reached without finding a solution. Try increasing the TimeLimit parameter.';
    -0341     else
    -0342         EM='The problem is infeasible';
    -0343     end
    -0344     error('RAVEN:badInput', '%s', EM);
    -0345 end
    -0346 
    -0347 fValue=res.obj;
    -0348 
    -0349 %Get all reactions used in the irreversible model
    -0350 usedRxns=(nonEssentialIndex(res.full(nRxns+1:nRxns+nNonEssential)<0.1))';
    +0291 if allowExcretion==true
    +0292     metUB=inf(nMets,1);
    +0293 else
    +0294     metUB=irrevModel.b(:,min(size(irrevModel.b,2),2));
    +0295 end
    +0296 prob.buc=[metUB;nonEssUBs;ones(nRevBounds*2,1)*999.9;revUB];
    +0297 
    +0298 %Add objective coefficients for the binary reactions. The negative is used
    +0299 %since we are minimizing. The negative is taken for the prodWeight as well,
    +0300 %to be consistent with the syntax that positive scores are good
    +0301 prob.c=[zeros(nRxns,1);rxnScores;ones(nNetProd,1)*prodWeight*-1;zeros(nRevBounds*2,1)];
    +0302 prob.a=S;
    +0303 
    +0304 % adapt problem structure for cobra-style solver
    +0305 prob.c=[prob.c;zeros(size(prob.a,1),1)];
    +0306 prob.A=[prob.a -speye(size(prob.a,1))];
    +0307 prob.b=zeros(size(prob.a,1), 1);
    +0308 prob.ub=[prob.bux; prob.buc];
    +0309 prob.osense=1;
    +0310 prob.csense=char(zeros(1,size(prob.a,1)));
    +0311 prob.csense(:)='E';
    +0312 
    +0313 %Add that the binary reactions may only take integer values. This has to be
    +0314 %declared before the presentMets loop below: optimizeProb reads vartype on
    +0315 %every call, and the producibility test is only meaningful against the same
    +0316 %integer problem that is solved at the end.
    +0317 prob.vartype = repmat('C', 1, size(prob.A, 2));
    +0318 allInt=[(nRxns+1):(nRxns+nNonEssential) size(S,2)-nRevBounds*2+1:size(S,2)];
    +0319 prob.vartype(allInt) = 'B';
    +0320 
    +0321 %We still do not know which of the presentMets that can be produced. Go
    +0322 %through them, force production, and see if the problem can be solved
    +0323 for i=1:numel(pmIndexes)
    +0324     prob.blc(numel(irrevModel.mets)-numel(pmIndexes)+i)=1;
    +0325     prob.lb=[prob.blx; prob.blc];
    +0326     res=optimizeProb(prob,params);
    +0327     isFeasible=checkSolution(res);
    +0328     if ~isFeasible
    +0329         %Reset the constraint again
    +0330         prob.blc(numel(irrevModel.mets)-numel(pmIndexes)+i)=0;
    +0331     else
    +0332         %Metabolite produced
    +0333         metProduction(pmIndexes(i))=1;
    +0334     end
    +0335 end
    +0336 prob.lb=[prob.blx; prob.blc];
    +0337 
    +0338 % solve problem
    +0339 res=optimizeProb(prob,params);
    +0340 
    +0341 %Problem should not be infeasible, but it is possible that the time limit
    +0342 %was reached before finding any solutions.
    +0343 if ~checkSolution(res)
    +0344     if res.hitTimeLimit
    +0345         EM='Time limit reached without finding a solution. Try increasing the TimeLimit parameter.';
    +0346     else
    +0347         EM='The problem is infeasible';
    +0348     end
    +0349     error('RAVEN:badInput', '%s', EM);
    +0350 end
     0351 
    -0352 %Map to reversible model IDs
    -0353 usedRxns=[usedRxns(usedRxns<=numel(model.rxns));revRxns(usedRxns(usedRxns>numel(model.rxns))-numel(model.rxns))];
    -0354 
    -0355 %Then get the ones that are not used in either direction or is essential
    -0356 I=true(numel(model.rxns),1);
    -0357 I(usedRxns)=false;
    -0358 I(essentialIndex)=false;
    -0359 deletedRxns=model.rxns(I);
    -0360 outModel=removeReactions(model,I,true,true);
    -0361 end
    +0352 fValue=res.obj; +0353 +0354 %Get all reactions used in the irreversible model +0355 usedRxns=(nonEssentialIndex(res.full(nRxns+1:nRxns+nNonEssential)<0.1))'; +0356 +0357 %Map to reversible model IDs +0358 usedRxns=[usedRxns(usedRxns<=numel(model.rxns));revRxns(usedRxns(usedRxns>numel(model.rxns))-numel(model.rxns))]; +0359 +0360 %Then get the ones that are not used in either direction or is essential +0361 I=true(numel(model.rxns),1); +0362 I(usedRxns)=false; +0363 I(essentialIndex)=false; +0364 deletedRxns=model.rxns(I); +0365 outModel=removeReactions(model,I,true,true); +0366 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/analysis/FSEOF.html b/docs/html/analysis/FSEOF.html index 3a198a6d..9158b629 100644 --- a/docs/html/analysis/FSEOF.html +++ b/docs/html/analysis/FSEOF.html @@ -31,10 +31,10 @@

    DESCRIPTION ^DESCRIPTION ^SOURCE CODE ^% 0004 % Implements the Flux Scanning based on Enforced Objective Flux algorithm. 0005 % This function writes a tab-delimited file or prints to the command -0006 % window. If an output has been specified (targets), it will also generate -0007 % a structure indicating for each model reaction whether it is identified -0008 % by FSEOF as a target and the slope of the reaction when switching from -0009 % biomass formation to product formation. +0006 % window. If an output is specified (targets), it also returns a structure +0007 % indicating for each model reaction whether FSEOF identifies it as a target +0008 % and the slope of the reaction when switching from biomass formation to +0009 % product formation. 0010 % 0011 % Parameters 0012 % ---------- @@ -116,150 +118,152 @@

    SOURCE CODE ^% Name-Value Arguments 0021 % -------------------- 0022 % iterations : double -0023 % number of iterations (default 10). -0024 % coefficient : double -0025 % ratio of optimal target reaction flux, must be less than 1 -0026 % (default 0.9). -0027 % outputFile : char -0028 % output filename (default prints to command window). -0029 % corrThreshold : double -0030 % minimum absolute Pearson correlation coefficient between a -0031 % reaction absolute flux and the enforced product flux for the -0032 % reaction to be selected as a target. Values in [0, 1]; higher -0033 % values require a more linear response and suppress LP alternative- -0034 % optima noise (default 0.9). -0035 % -0036 % Returns -0037 % ------- -0038 % targets : struct -0039 % structure with information for identified targets, with fields: -0040 % -0041 % - logical : logical array, true for amplification targets (backward -0042 % compatible alias for amplify). -0043 % - amplify : logical array, reactions whose absolute flux increases -0044 % with enforced product flux. -0045 % - knockdown : logical array, reactions driven toward but not to zero. -0046 % - knockout : logical array, reactions driven to zero at max product. -0047 % - slope : numeric array with per-reaction regression slopes of -0048 % absolute flux versus enforced product flux (all reactions, not -0049 % just targets). -0050 % -0051 % Examples -0052 % -------- -0053 % targets = FSEOF(model, biomassRxn, targetRxn, iterations, ... -0054 % coefficient, outputFile); -0055 -0056 biomassRxn=char(biomassRxn); -0057 targetRxn=char(targetRxn); -0058 -0059 p=parseRAVENargs(varargin, {'iterations',10; 'coefficient',0.9; 'outputFile',[]; 'corrThreshold',0.9}); -0060 iterations=p.iterations; -0061 coefficient=p.coefficient; -0062 outputFile=p.outputFile; -0063 corrThreshold=p.corrThreshold; -0064 -0065 %Find out the maximum theoretical yield of target reaction -0066 model=setParam(model,'obj',targetRxn,1); -0067 sol=solveLP(model,1); -0068 targetMax=sol.f*coefficient; -0069 -0070 model=setParam(model,'obj',biomassRxn,1); +0023 % number of enforced target flux levels, equally spaced up to coefficient +0024 % times the maximum target flux (default 10). +0025 % coefficient : double +0026 % ratio of the maximum target reaction flux that is enforced at the last +0027 % iteration, less than 1 (default 0.9). +0028 % outputFile : char +0029 % output filename (default prints to command window). +0030 % corrThreshold : double +0031 % minimum absolute Pearson correlation coefficient between the absolute +0032 % flux of a reaction and the enforced product flux for the reaction to be +0033 % selected as a target. Values are in [0, 1]. The filter keeps reactions +0034 % whose flux is close to linear in the enforced product flux, and a higher +0035 % value keeps fewer reactions (default 0.9). +0036 % +0037 % Returns +0038 % ------- +0039 % targets : struct +0040 % structure with information for identified targets, with fields: +0041 % +0042 % - logical : logical array, true for amplification targets (identical to +0043 % amplify). +0044 % - amplify : logical array, reactions whose absolute flux increases +0045 % with enforced product flux. +0046 % - knockdown : logical array, reactions whose absolute flux decreases but +0047 % stays above zero at the highest enforced product flux. +0048 % - knockout : logical array, reactions whose absolute flux decreases to +0049 % zero at the highest enforced product flux. +0050 % - slope : numeric array with per-reaction regression slopes of +0051 % absolute flux versus enforced product flux (for all reactions). +0052 % +0053 % Examples +0054 % -------- +0055 % targets = FSEOF(model, biomassRxn, targetRxn, iterations, ... +0056 % coefficient, outputFile); +0057 +0058 biomassRxn=char(biomassRxn); +0059 targetRxn=char(targetRxn); +0060 +0061 p=parseRAVENargs(varargin, {'iterations',10; 'coefficient',0.9; 'outputFile',[]; 'corrThreshold',0.9}); +0062 iterations=p.iterations; +0063 coefficient=p.coefficient; +0064 outputFile=p.outputFile; +0065 corrThreshold=p.corrThreshold; +0066 +0067 %Find out the maximum theoretical yield of target reaction +0068 model=setParam(model,'obj',targetRxn,1); +0069 sol=solveLP(model,1); +0070 targetMax=sol.f*coefficient; 0071 -0072 nRxns=length(model.rxns); -0073 results=zeros(nRxns,iterations); -0074 enforcedFlux=(1:iterations)*targetMax/iterations; -0075 -0076 %Run pFBA at each enforced target flux level -0077 for i=1:iterations -0078 model=setParam(model,'lb',targetRxn,enforcedFlux(i)); -0079 sol=solveLP(model,1); -0080 results(:,i)=sol.x; -0081 end -0082 -0083 %Compute per-reaction regression slope (FS4) and Pearson correlation (FS1) -0084 %on absolute flux values to treat forward/reverse symmetrically. -0085 absResults=abs(results); -0086 slope=zeros(nRxns,1); -0087 rCoeff=zeros(nRxns,1); -0088 hasVariation = iterations > 1 && std(enforcedFlux) > 0; -0089 if hasVariation -0090 for num=1:nRxns -0091 fluxTrack=absResults(num,:); -0092 if any(fluxTrack > 1e-10) && std(fluxTrack) > 0 -0093 p_fit=polyfit(enforcedFlux,fluxTrack,1); -0094 slope(num)=p_fit(1); -0095 r=corrcoef(enforcedFlux,fluxTrack); -0096 rCoeff(num)=r(1,2); -0097 end -0098 end -0099 end -0100 -0101 %Classify targets: select reactions whose absolute flux is sufficiently -0102 %correlated with the enforced product flux (FS1), then split by direction -0103 %and whether any flux remains at maximum enforcement (FS2). -0104 isCorrelated = abs(rCoeff) >= corrThreshold; -0105 isAmplify = isCorrelated & slope > 0; -0106 isKnockdown = isCorrelated & slope < 0 & absResults(:,end) > 1e-8; -0107 isKnockout = isCorrelated & slope < 0 & absResults(:,end) <= 1e-8; -0108 -0109 rxnDirection=sign(results(:,1)); +0072 model=setParam(model,'obj',biomassRxn,1); +0073 +0074 nRxns=length(model.rxns); +0075 results=zeros(nRxns,iterations); +0076 enforcedFlux=(1:iterations)*targetMax/iterations; +0077 +0078 %Run pFBA at each enforced target flux level +0079 for i=1:iterations +0080 model=setParam(model,'lb',targetRxn,enforcedFlux(i)); +0081 sol=solveLP(model,1); +0082 results(:,i)=sol.x; +0083 end +0084 +0085 %Compute per-reaction regression slope (FS4) and Pearson correlation (FS1) +0086 %on absolute flux values to treat forward/reverse symmetrically. +0087 absResults=abs(results); +0088 slope=zeros(nRxns,1); +0089 rCoeff=zeros(nRxns,1); +0090 hasVariation = iterations > 1 && std(enforcedFlux) > 0; +0091 if hasVariation +0092 for num=1:nRxns +0093 fluxTrack=absResults(num,:); +0094 if any(fluxTrack > 1e-10) && std(fluxTrack) > 0 +0095 p_fit=polyfit(enforcedFlux,fluxTrack,1); +0096 slope(num)=p_fit(1); +0097 r=corrcoef(enforcedFlux,fluxTrack); +0098 rCoeff(num)=r(1,2); +0099 end +0100 end +0101 end +0102 +0103 %Classify targets: select reactions whose absolute flux is sufficiently +0104 %correlated with the enforced product flux (FS1), then split by direction +0105 %and whether any flux remains at maximum enforcement (FS2). +0106 isCorrelated = abs(rCoeff) >= corrThreshold; +0107 isAmplify = isCorrelated & slope > 0; +0108 isKnockdown = isCorrelated & slope < 0 & absResults(:,end) > 1e-8; +0109 isKnockout = isCorrelated & slope < 0 & absResults(:,end) <= 1e-8; 0110 -0111 %Generating output -0112 formatSpec='%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n'; -0113 if ~isempty(outputFile) -0114 outputFile=char(outputFile); -0115 fid=fopen(outputFile,'w'); -0116 fprintf(fid,formatSpec,'Type','Slope','rowID','Enzyme ID','Enzyme Name','Subsystems','Direction','Gr Rule'); -0117 else -0118 fid=-1; -0119 fprintf(formatSpec,'Type','Slope','rowID','Enzyme ID','Enzyme Name','Subsystems','Direction','Gr Rule'); -0120 end -0121 -0122 targetTypes = {'Amplify','Knockdown','Knockout'}; -0123 targetArrays = {isAmplify, isKnockdown, isKnockout}; -0124 -0125 for t=1:3 -0126 flag=targetArrays{t}; -0127 for num=1:nRxns -0128 if flag(num) -0129 A_type=targetTypes{t}; -0130 A0=num2str(slope(num)); -0131 A1=num2str(num); -0132 A2=char(model.rxns(num)); -0133 A3=char(model.rxnNames(num)); -0134 if isfield(model,'subSystems') && ~isempty(model.subSystems{num}) -0135 if ~any(cellfun(@(x) iscell(x), model.subSystems)) -0136 subSys=cellfun(@(x) {x}, model.subSystems, 'uni', 0); -0137 else -0138 subSys=model.subSystems; -0139 end -0140 A4=char(strjoin(subSys{num},';')); -0141 else -0142 A4=''; -0143 end -0144 A5=num2str(model.rev(num)*rxnDirection(num)); -0145 A6=char(model.grRules(num)); -0146 if fid == -1 -0147 fprintf(formatSpec,A_type,A0,A1,A2,A3,A4,A5,A6); -0148 else -0149 fprintf(fid,formatSpec,A_type,A0,A1,A2,A3,A4,A5,A6); -0150 end -0151 end -0152 end -0153 end -0154 -0155 if fid ~= -1 -0156 fclose(fid); -0157 end -0158 -0159 if nargout == 1 -0160 targets.logical = isAmplify; -0161 targets.amplify = isAmplify; -0162 targets.knockdown = isKnockdown; -0163 targets.knockout = isKnockout; -0164 targets.slope = slope; -0165 end -0166 end +0111 rxnDirection=sign(results(:,1)); +0112 +0113 %Generating output +0114 formatSpec='%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n'; +0115 if ~isempty(outputFile) +0116 outputFile=char(outputFile); +0117 fid=fopen(outputFile,'w'); +0118 fprintf(fid,formatSpec,'Type','Slope','rowID','Enzyme ID','Enzyme Name','Subsystems','Direction','Gr Rule'); +0119 else +0120 fid=-1; +0121 fprintf(formatSpec,'Type','Slope','rowID','Enzyme ID','Enzyme Name','Subsystems','Direction','Gr Rule'); +0122 end +0123 +0124 targetTypes = {'Amplify','Knockdown','Knockout'}; +0125 targetArrays = {isAmplify, isKnockdown, isKnockout}; +0126 +0127 for t=1:3 +0128 flag=targetArrays{t}; +0129 for num=1:nRxns +0130 if flag(num) +0131 A_type=targetTypes{t}; +0132 A0=num2str(slope(num)); +0133 A1=num2str(num); +0134 A2=char(model.rxns(num)); +0135 A3=char(model.rxnNames(num)); +0136 if isfield(model,'subSystems') && ~isempty(model.subSystems{num}) +0137 if ~any(cellfun(@(x) iscell(x), model.subSystems)) +0138 subSys=cellfun(@(x) {x}, model.subSystems, 'uni', 0); +0139 else +0140 subSys=model.subSystems; +0141 end +0142 A4=char(strjoin(subSys{num},';')); +0143 else +0144 A4=''; +0145 end +0146 A5=num2str(model.rev(num)*rxnDirection(num)); +0147 A6=char(model.grRules(num)); +0148 if fid == -1 +0149 fprintf(formatSpec,A_type,A0,A1,A2,A3,A4,A5,A6); +0150 else +0151 fprintf(fid,formatSpec,A_type,A0,A1,A2,A3,A4,A5,A6); +0152 end +0153 end +0154 end +0155 end +0156 +0157 if fid ~= -1 +0158 fclose(fid); +0159 end +0160 +0161 if nargout == 1 +0162 targets.logical = isAmplify; +0163 targets.amplify = isAmplify; +0164 targets.knockdown = isKnockdown; +0165 targets.knockout = isKnockout; +0166 targets.slope = slope; +0167 end +0168 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/analysis/analyzeSampling.html b/docs/html/analysis/analyzeSampling.html index 75424dc5..62463b26 100644 --- a/docs/html/analysis/analyzeSampling.html +++ b/docs/html/analysis/analyzeSampling.html @@ -36,14 +36,12 @@

    DESCRIPTION ^DESCRIPTION ^SOURCE CODE ^% ---------- 0009 % Tex : double 0010 % a vector of t-scores for the change in gene expression for each -0011 % reaction. This score could be the Student t between the two -0012 % conditions, or you can calculate it from a p-value (by computing the -0013 % inverse of the so called error function). If you choose the second -0014 % alternative you should be aware that the transcripts that increased -0015 % in expression level should have positive values and those who -0016 % decreased in expression level should have negative values (the -0017 % p-values only tell you if the fluxes changed or not but not in which -0018 % direction). -0019 % df : double -0020 % the degrees of freedom in the t-test. -0021 % solutionsA : double -0022 % random solutions for the reference condition (as generated by -0023 % randomSampling). -0024 % solutionsB : double -0025 % random solutions for the test condition (as generated by -0026 % randomSampling). -0027 % -0028 % Name-Value Arguments -0029 % -------------------- -0030 % printResults : logical -0031 % prints the most significant reactions in each category -0032 % (default false). -0033 % -0034 % Returns -0035 % ------- -0036 % scores : double -0037 % a Nx3 column matrix with the probabilities of a reaction: -0038 % -0039 % 1. changing both in flux and expression in the same direction -0040 % 2. changing in expression but not in flux -0041 % 3. changing in flux but not in expression or changing in opposed -0042 % directions in flux and expression -0043 % -0044 % Examples -0045 % -------- -0046 % scores = analyzeSampling(Tex, df, solutionsA, solutionsB, ... -0047 % printResults); -0048 -0049 p=parseRAVENargs(varargin, {'printResults',false}); -0050 printResults=p.printResults; -0051 -0052 nRxns=numel(Tex); -0053 pM=zeros(nRxns,1); -0054 pH=zeros(nRxns,1); -0055 pR=zeros(nRxns,1); -0056 -0057 %Check that the number of reactions is the same in both expression and flux -0058 if nRxns~=size(solutionsA,1) -0059 EM='The number of reactions must be the same in Tex as in solutionsA'; -0060 error('RAVEN:badInput', '%s', EM); -0061 end -0062 -0063 %Get the Z-score and mean for the solutions -0064 mA=mean(solutionsA,2); -0065 mB=mean(solutionsB,2); -0066 Zf=getFluxZ(solutionsA, solutionsB); -0067 -0068 %Clear up the tex if there are elements that are NaN or +/- Inf. -0069 I=isnan(Tex) | isinf(Tex); -0070 if any(I) -0071 EM='There are t-scores that are NaN or +/- Inf. These values are changed to 0.0'; -0072 warning('RAVEN:warning', '%s', EM); -0073 end -0074 Tex(I)=0; -0075 -0076 for i=1:nRxns -0077 %Check how the flux has changed. The means are needed because to -0078 %differentiate between a positive flux changing to a smaller flux and a -0079 %negative flux changing to a more negative flux (which is a larger -0080 %flux) -0081 I=mB(i)/mA(i); -0082 if I<0 -0083 pM(i)=erf(abs(Zf(i))); -0084 pH(i)=(1-pM(i))*(2*tcdf(abs(Tex(i)),df)-1); -0085 pR(i)=0; -0086 else -0087 if mB(i)<0 -0088 Zf(i)=Zf(i)*-1; -0089 end -0090 end -0091 -0092 I=Zf(i)/Tex(i); -0093 if I<0 -0094 pM(i)=erf(abs(Zf(i))); -0095 pH(i)=(1-pM(i))*(2*tcdf(abs(Tex(i)),df)-1); -0096 pR(i)=0; -0097 else -0098 pR(i)=erf(abs(Zf(i)))*(2*tcdf(abs(Tex(i)),df)-1); -0099 pH(i)=(2*tcdf(abs(Tex(i)),df)-1)*(1-erf(abs(Zf(i)))); -0100 pM(i)=erf(abs(Zf(i)))*(1-(2*tcdf(abs(Tex(i)),df)-1)); -0101 end -0102 end +0011 % reaction. The score is the Student t between the two conditions, or +0012 % is calculated from a p-value by the inverse of the error function. +0013 % Transcripts that increased in expression level have positive values +0014 % and transcripts that decreased have negative values. A p-value does +0015 % not give the direction of the change, so a score calculated from a +0016 % p-value takes its sign from the direction of the expression change. +0017 % df : double +0018 % the degrees of freedom in the t-test. +0019 % solutionsA : double +0020 % random solutions for the reference condition (as generated by +0021 % randomSampling). +0022 % solutionsB : double +0023 % random solutions for the test condition (as generated by +0024 % randomSampling). +0025 % +0026 % Name-Value Arguments +0027 % -------------------- +0028 % printResults : logical +0029 % true to print the 10 reactions with the highest probability in each +0030 % category (default false). +0031 % +0032 % Returns +0033 % ------- +0034 % scores : double +0035 % a Nx3 column matrix with the probabilities of a reaction: +0036 % +0037 % 1. changing both in flux and expression in the same direction +0038 % 2. changing in expression but not in flux +0039 % 3. changing in flux but not in expression or changing in opposed +0040 % directions in flux and expression +0041 % +0042 % Examples +0043 % -------- +0044 % scores = analyzeSampling(Tex, df, solutionsA, solutionsB, ... +0045 % printResults); +0046 +0047 p=parseRAVENargs(varargin, {'printResults',false}); +0048 printResults=p.printResults; +0049 +0050 nRxns=numel(Tex); +0051 pM=zeros(nRxns,1); +0052 pH=zeros(nRxns,1); +0053 pR=zeros(nRxns,1); +0054 +0055 %Check that the number of reactions is the same in both expression and flux +0056 if nRxns~=size(solutionsA,1) +0057 EM='The number of reactions must be the same in Tex as in solutionsA'; +0058 error('RAVEN:badInput', '%s', EM); +0059 end +0060 +0061 %Get the Z-score and mean for the solutions +0062 mA=mean(solutionsA,2); +0063 mB=mean(solutionsB,2); +0064 Zf=getFluxZ(solutionsA, solutionsB); +0065 +0066 %Clear up the tex if there are elements that are NaN or +/- Inf. +0067 I=isnan(Tex) | isinf(Tex); +0068 if any(I) +0069 EM='There are t-scores that are NaN or +/- Inf. These values are changed to 0.0'; +0070 warning('RAVEN:warning', '%s', EM); +0071 end +0072 Tex(I)=0; +0073 +0074 for i=1:nRxns +0075 %Check how the flux has changed. The means are needed because to +0076 %differentiate between a positive flux changing to a smaller flux and a +0077 %negative flux changing to a more negative flux (which is a larger +0078 %flux) +0079 I=mB(i)/mA(i); +0080 if I<0 +0081 pM(i)=erf(abs(Zf(i))); +0082 pH(i)=(1-pM(i))*(2*tcdf(abs(Tex(i)),df)-1); +0083 pR(i)=0; +0084 else +0085 if mB(i)<0 +0086 Zf(i)=Zf(i)*-1; +0087 end +0088 end +0089 +0090 I=Zf(i)/Tex(i); +0091 if I<0 +0092 pM(i)=erf(abs(Zf(i))); +0093 pH(i)=(1-pM(i))*(2*tcdf(abs(Tex(i)),df)-1); +0094 pR(i)=0; +0095 else +0096 pR(i)=erf(abs(Zf(i)))*(2*tcdf(abs(Tex(i)),df)-1); +0097 pH(i)=(2*tcdf(abs(Tex(i)),df)-1)*(1-erf(abs(Zf(i)))); +0098 pM(i)=erf(abs(Zf(i)))*(1-(2*tcdf(abs(Tex(i)),df)-1)); +0099 end +0100 end +0101 +0102 scores=[pR pH pM]; 0103 -0104 scores=[pR pH pM]; -0105 -0106 if printResults==true -0107 fprintf('TOP SCORING REACTIONS\n\n'); -0108 %The top 10 hits in the first category -0109 [I, J]=sort(pR,'descend'); -0110 fprintf('Reactions which change both in flux and expression in the same direction\nReaction\tProbability\n'); -0111 for i=1:10 -0112 fprintf([num2str(J(i)) '\t' num2str(I(i)) '\n']); -0113 end -0114 -0115 %The top 10 hits in the first category -0116 [I, J]=sort(pH,'descend'); -0117 fprintf('\nReactions which change in expression but not in flux\nReaction\tProbability\n'); -0118 for i=1:10 -0119 fprintf([num2str(J(i)) '\t' num2str(I(i)) '\n']); -0120 end -0121 -0122 %The top 10 hits in the first category -0123 [I, J]=sort(pM,'descend'); -0124 fprintf('\nReactions which change in flux but not in expression, or in opposed directions in flux and expression\nReaction\tProbability\n'); -0125 for i=1:10 -0126 fprintf([num2str(J(i)) '\t' num2str(I(i)) '\n']); -0127 end -0128 end -0129 end -0130 -0131 function p = tcdf(t,v,uflag) -0132 % TCDF Student T cumulative distribution function (cdf). -0133 % -0134 % P = TCDF(X,V) computes the cdf for Student T distribution -0135 % with V degrees of freedom, at the values in X. V must be a -0136 % scalar or have the same size as T. -0137 % -0138 % P = TCDF(X,V,"upper") computes it for the upper tail instead -0139 % of the lower tail. -0140 % -0141 % Alternative to the TCDF function in the Matlab statistics toolbox, for use -0142 % when that toolbox is not installed. Adapted from -0143 % https://github.com/fieldtrip/fieldtrip/blob/master/external/stats/tcdf.m, -0144 % originally by Gordon Smyth, University of Queensland. +0104 if printResults==true +0105 fprintf('TOP SCORING REACTIONS\n\n'); +0106 %The top 10 hits in the first category +0107 [I, J]=sort(pR,'descend'); +0108 fprintf('Reactions which change both in flux and expression in the same direction\nReaction\tProbability\n'); +0109 for i=1:10 +0110 fprintf([num2str(J(i)) '\t' num2str(I(i)) '\n']); +0111 end +0112 +0113 %The top 10 hits in the first category +0114 [I, J]=sort(pH,'descend'); +0115 fprintf('\nReactions which change in expression but not in flux\nReaction\tProbability\n'); +0116 for i=1:10 +0117 fprintf([num2str(J(i)) '\t' num2str(I(i)) '\n']); +0118 end +0119 +0120 %The top 10 hits in the first category +0121 [I, J]=sort(pM,'descend'); +0122 fprintf('\nReactions which change in flux but not in expression, or in opposed directions in flux and expression\nReaction\tProbability\n'); +0123 for i=1:10 +0124 fprintf([num2str(J(i)) '\t' num2str(I(i)) '\n']); +0125 end +0126 end +0127 end +0128 +0129 function p = tcdf(t,v,uflag) +0130 % TCDF Student T cumulative distribution function (cdf). +0131 % +0132 % P = TCDF(X,V) computes the cdf for Student T distribution +0133 % with V degrees of freedom, at the values in X. V must be a +0134 % scalar or have the same size as T. +0135 % +0136 % P = TCDF(X,V,"upper") computes it for the upper tail instead +0137 % of the lower tail. +0138 % +0139 % Alternative to the TCDF function in the Matlab statistics toolbox, for use +0140 % when that toolbox is not installed. Adapted from +0141 % https://github.com/fieldtrip/fieldtrip/blob/master/external/stats/tcdf.m, +0142 % originally by Gordon Smyth, University of Queensland. +0143 +0144 if v <= 0, error('Degrees of freedom must be positive.'); end 0145 -0146 if v <= 0, error('Degrees of freedom must be positive.'); end -0147 -0148 % resize v if necessary -0149 if all(size(v)==1) -0150 v = ones(size(t))*v; -0151 end -0152 -0153 %check for NaNs - do not do calculations on them, give those out as NaNs -0154 if any( not(isfinite(t(:))) | not(isfinite(v(:))) ) -0155 sel = find(isfinite(t) & isfinite(v)); -0156 x=nan(size(t)); -0157 p=nan(size(t)); -0158 x(sel) = t(sel).^2 ./ (v(sel) + t(sel).^2) ; -0159 p(sel) = 0.5 .* ( 1 + sign(t(sel)) .* betainc( x(sel), 0.5, 0.5*v(sel) ) ); -0160 else -0161 x = t.^2 ./ (v + t.^2) ; -0162 p = 0.5 .* ( 1 + sign(t) .* betainc( x, 0.5, 0.5*v ) ); -0163 end -0164 -0165 if nargin==3 -0166 if strcmp(uflag, 'upper') -0167 % compute it for the upper tail instead of the lower tail -0168 p = 1-p; -0169 else -0170 error('incorrect specification of uflag'); -0171 end -0172 end -0173 end +0146 % resize v if necessary +0147 if all(size(v)==1) +0148 v = ones(size(t))*v; +0149 end +0150 +0151 %check for NaNs - do not do calculations on them, give those out as NaNs +0152 if any( not(isfinite(t(:))) | not(isfinite(v(:))) ) +0153 sel = find(isfinite(t) & isfinite(v)); +0154 x=nan(size(t)); +0155 p=nan(size(t)); +0156 x(sel) = t(sel).^2 ./ (v(sel) + t(sel).^2) ; +0157 p(sel) = 0.5 .* ( 1 + sign(t(sel)) .* betainc( x(sel), 0.5, 0.5*v(sel) ) ); +0158 else +0159 x = t.^2 ./ (v + t.^2) ; +0160 p = 0.5 .* ( 1 + sign(t) .* betainc( x, 0.5, 0.5*v ) ); +0161 end +0162 +0163 if nargin==3 +0164 if strcmp(uflag, 'upper') +0165 % compute it for the upper tail instead of the lower tail +0166 p = 1-p; +0167 else +0168 error('incorrect specification of uflag'); +0169 end +0170 end +0171 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/analysis/compareFluxes.html b/docs/html/analysis/compareFluxes.html index c6448840..85235416 100644 --- a/docs/html/analysis/compareFluxes.html +++ b/docs/html/analysis/compareFluxes.html @@ -4,7 +4,7 @@ Description of compareFluxes - + @@ -21,13 +21,13 @@

    compareFluxes

    PURPOSE ^

    -
    compareFluxes Find the most significant flux changes between two conditions.
    +
    compareFluxes Find the reactions whose flux differs between two flux vectors.

    SYNOPSIS ^

    function result = compareFluxes(model, fluxes1, fluxes2, varargin)

    DESCRIPTION ^

    -
     compareFluxes  Find the most significant flux changes between two conditions.
    +
     compareFluxes  Find the reactions whose flux differs between two flux vectors.
     
      Compares two flux vectors from the same model and returns all reactions
      that changed, sorted by absolute flux difference. Classifies reactions as
    @@ -36,22 +36,23 @@ 

    DESCRIPTION ^DESCRIPTION ^DESCRIPTION ^walkFluxes, traceFluxPath, modelSummary

    + -------- + walkFluxes, traceFluxPath, modelSummary

    CROSS-REFERENCE INFORMATION ^

    @@ -101,7 +110,7 @@

    SUBFUNCTIONS ^SOURCE CODE ^

    0001 function result = compareFluxes(model, fluxes1, fluxes2, varargin)
    -0002 % compareFluxes  Find the most significant flux changes between two conditions.
    +0002 % compareFluxes  Find the reactions whose flux differs between two flux vectors.
     0003 %
     0004 % Compares two flux vectors from the same model and returns all reactions
     0005 % that changed, sorted by absolute flux difference. Classifies reactions as
    @@ -110,194 +119,203 @@ 

    SOURCE CODE ^% Parameters 0009 % ---------- 0010 % model : struct -0011 % RAVEN model structure. +0011 % a model structure. 0012 % fluxes1 : double -0013 % reference flux vector (condition A, e.g. wild-type). +0013 % reference flux vector (condition A, such as wild-type). 0014 % fluxes2 : double -0015 % comparison flux vector (condition B, e.g. mutant or knockin). +0015 % comparison flux vector (condition B, such as a mutant or knock-in). 0016 % 0017 % Name-Value Arguments 0018 % -------------------- 0019 % cutoff : double -0020 % minimum |flux| to consider a reaction active (default 1e-8). -0021 % metaboliteList : cell -0022 % cell array of metabolite names. Only reactions involving at least one -0023 % of these metabolites are compared; every other reaction is left out of -0024 % the result entirely. Matching is against model.metNames and is case -0025 % insensitive. A name that matches no metabolite is reported as a warning -0026 % rather than an error (default all reactions). -0027 % nMax : double -0028 % maximum rows printed in the summary table (default 20). -0029 % verbose : logical -0030 % print the summary table (default true). -0031 % -0032 % Returns -0033 % ------- -0034 % result : struct -0035 % .turnedOn — cell, rxn IDs newly active in fluxes2 -0036 % .turnedOff — cell, rxn IDs silenced in fluxes2 -0037 % .flipped — cell, rxn IDs that reversed sign between conditions -0038 % .changed — struct with fields (all reactions with |dflux|>cutoff, -0039 % sorted by |dflux| descending): -0040 % .rxn — cell of reaction IDs -0041 % .flux1 — double, flux in condition A -0042 % .flux2 — double, flux in condition B -0043 % .absDelta — double, |flux2 - flux1| -0044 % .relChange — double, (flux2-flux1)/|flux1|; NaN when -0045 % flux1 is near-zero (reaction emerged) -0046 % .type — cell, 'on'|'off'|'flip'|'' per reaction -0047 % -0048 % Examples -0049 % -------- -0050 % solA = solveLP(model); -0051 % modelAna = setParam(model, 'ub', 'O2exchange', 0); -0052 % solB = solveLP(modelAna); -0053 % result = compareFluxes(model, solA.x, solB.x) -0054 % result = compareFluxes(model, solA.x, solB.x, 'nMax', 50) -0055 % result = compareFluxes(model, solA.x, solB.x, 'metaboliteList', {'ATP','NADH'}) -0056 % -0057 % See also -0058 % walkFluxes, traceFluxPath, modelSummary -0059 -0060 p = parseRAVENargs(varargin, {'cutoff',1e-8; 'metaboliteList',[]; 'nMax',20; 'verbose',true}); -0061 cutoff = p.cutoff; -0062 metaboliteList = p.metaboliteList; -0063 nMax = p.nMax; -0064 verbose = p.verbose; -0065 -0066 f1 = fluxes1(:); -0067 f2 = fluxes2(:); -0068 nRxns = numel(model.rxns); -0069 -0070 if numel(f1) ~= nRxns || numel(f2) ~= nRxns -0071 error('RAVEN:badInput', ... -0072 'Flux vectors must have %d elements (numel(model.rxns)).', nRxns); -0073 end +0020 % minimum |flux| for a reaction to be active, and minimum |flux2 - flux1| +0021 % for a reaction to be changed (default 1e-8). +0022 % metaboliteList : cell +0023 % cell array of metabolite names. Only reactions involving at least one +0024 % of these metabolites are compared; all other reactions are excluded +0025 % from every field of the result. Matching is against model.metNames and +0026 % is case insensitive. A name that matches no metabolite is reported as +0027 % a warning (default all reactions). +0028 % nMax : double +0029 % maximum rows printed in the summary table (default 20). +0030 % verbose : logical +0031 % print the summary table (default true). +0032 % +0033 % Returns +0034 % ------- +0035 % result : struct +0036 % structure with fields: +0037 % +0038 % - turnedOn : cell array with the IDs of reactions that are active in +0039 % fluxes2 and not in fluxes1. +0040 % - turnedOff : cell array with the IDs of reactions that are active in +0041 % fluxes1 and not in fluxes2. +0042 % - flipped : cell array with the IDs of reactions that are active in +0043 % both conditions and change sign. +0044 % - changed : struct with fields for all reactions with |flux2 - flux1| +0045 % above cutoff, sorted by |flux2 - flux1| descending: +0046 % +0047 % - rxn : cell array with the reaction IDs. +0048 % - flux1 : double array with the flux in condition A. +0049 % - flux2 : double array with the flux in condition B. +0050 % - absDelta : double array with |flux2 - flux1|. +0051 % - relChange : double array with (flux2 - flux1)/|flux1|. NaN when +0052 % |flux1| is below cutoff. +0053 % - type : cell array with 'on', 'off', 'flip' or '' (changed +0054 % magnitude) for each reaction. +0055 % +0056 % Examples +0057 % -------- +0058 % solA = solveLP(model); +0059 % modelAna = setParam(model, 'ub', 'O2exchange', 0); +0060 % solB = solveLP(modelAna); +0061 % result = compareFluxes(model, solA.x, solB.x) +0062 % result = compareFluxes(model, solA.x, solB.x, 'nMax', 50) +0063 % result = compareFluxes(model, solA.x, solB.x, 'metaboliteList', {'ATP','NADH'}) +0064 % +0065 % See also +0066 % -------- +0067 % walkFluxes, traceFluxPath, modelSummary +0068 +0069 p = parseRAVENargs(varargin, {'cutoff',1e-8; 'metaboliteList',[]; 'nMax',20; 'verbose',true}); +0070 cutoff = p.cutoff; +0071 metaboliteList = p.metaboliteList; +0072 nMax = p.nMax; +0073 verbose = p.verbose; 0074 -0075 active1 = abs(f1) > cutoff; -0076 active2 = abs(f2) > cutoff; -0077 delta = f2 - f1; +0075 f1 = fluxes1(:); +0076 f2 = fluxes2(:); +0077 nRxns = numel(model.rxns); 0078 -0079 % Restrict to reactions involving one of metaboliteList, if given. Reactions -0080 % left out are treated as unchanged, so they appear in no output field. -0081 inList = true(nRxns, 1); -0082 if ~isempty(metaboliteList) -0083 metaboliteList = convertCharArray(metaboliteList); -0084 inList = false(nRxns, 1); -0085 notFound = {}; -0086 for i = 1:numel(metaboliteList) -0087 metIdx = find(strcmpi(metaboliteList{i}, model.metNames)); -0088 if isempty(metIdx) -0089 notFound{end+1} = metaboliteList{i}; %#ok<AGROW> -0090 else -0091 inList(any(model.S(metIdx,:) ~= 0, 1)) = true; -0092 end -0093 end -0094 if ~isempty(notFound) -0095 warning('RAVEN:warning', '%s', ... -0096 ravenList('No metabolite in the model is named:', notFound)); -0097 end -0098 active1(~inList) = false; -0099 active2(~inList) = false; -0100 delta(~inList) = 0; -0101 end -0102 -0103 onMask = ~active1 & active2; -0104 offMask = active1 & ~active2; -0105 flipMask = active1 & active2 & sign(f1) ~= sign(f2); -0106 -0107 result.turnedOn = model.rxns(onMask); -0108 result.turnedOff = model.rxns(offMask); -0109 result.flipped = model.rxns(flipMask); -0110 -0111 % All changed reactions sorted by |delta| descending -0112 changedMask = abs(delta) > cutoff; -0113 [~, si] = sort(abs(delta(changedMask)), 'descend'); -0114 idx = find(changedMask); -0115 idx = idx(si); -0116 -0117 % Relative change: (f2-f1)/|f1|; NaN when reference flux is near-zero -0118 denom = abs(f1(idx)); -0119 denom(denom < cutoff) = NaN; -0120 relChange = delta(idx) ./ denom; -0121 -0122 % Type label per changed reaction -0123 changedTypes = repmat({''}, numel(idx), 1); -0124 for k = 1:numel(idx) -0125 ri = idx(k); -0126 if onMask(ri) -0127 changedTypes{k} = 'on'; -0128 elseif offMask(ri) -0129 changedTypes{k} = 'off'; -0130 elseif flipMask(ri) -0131 changedTypes{k} = 'flip'; -0132 end -0133 end -0134 -0135 result.changed.rxn = model.rxns(idx); -0136 result.changed.flux1 = f1(idx); -0137 result.changed.flux2 = f2(idx); -0138 result.changed.absDelta = abs(delta(idx)); -0139 result.changed.relChange = relChange; -0140 result.changed.type = changedTypes; -0141 -0142 if verbose -0143 printTable_(model, result, nMax, nnz(inList)); -0144 end -0145 end -0146 -0147 %-------------------------------------------------------------------------- -0148 function printTable_(model, r, nMax, nConsidered) -0149 W = 72; -0150 bar = repmat('=', 1, W); -0151 sep = repmat('-', 1, W); -0152 nC = numel(r.changed.rxn); -0153 -0154 fprintf('\n%s\n', bar); -0155 fprintf(' Flux comparison: %d of %d reactions changed\n', nC, nConsidered); -0156 nOn = numel(r.turnedOn); -0157 nOff = numel(r.turnedOff); -0158 nFlp = numel(r.flipped); -0159 if nOn + nOff + nFlp > 0 -0160 fprintf(' (on: %d | off: %d | reversed: %d)\n', nOn, nOff, nFlp); -0161 end +0079 if numel(f1) ~= nRxns || numel(f2) ~= nRxns +0080 error('RAVEN:badInput', ... +0081 'Flux vectors must have %d elements (numel(model.rxns)).', nRxns); +0082 end +0083 +0084 active1 = abs(f1) > cutoff; +0085 active2 = abs(f2) > cutoff; +0086 delta = f2 - f1; +0087 +0088 % Restrict to reactions involving one of metaboliteList, if given. Reactions +0089 % left out are treated as unchanged, so they appear in no output field. +0090 inList = true(nRxns, 1); +0091 if ~isempty(metaboliteList) +0092 metaboliteList = convertCharArray(metaboliteList); +0093 inList = false(nRxns, 1); +0094 notFound = {}; +0095 for i = 1:numel(metaboliteList) +0096 metIdx = find(strcmpi(metaboliteList{i}, model.metNames)); +0097 if isempty(metIdx) +0098 notFound{end+1} = metaboliteList{i}; %#ok<AGROW> +0099 else +0100 inList(any(model.S(metIdx,:) ~= 0, 1)) = true; +0101 end +0102 end +0103 if ~isempty(notFound) +0104 warning('RAVEN:warning', '%s', ... +0105 ravenList('No metabolite in the model is named:', notFound)); +0106 end +0107 active1(~inList) = false; +0108 active2(~inList) = false; +0109 delta(~inList) = 0; +0110 end +0111 +0112 onMask = ~active1 & active2; +0113 offMask = active1 & ~active2; +0114 flipMask = active1 & active2 & sign(f1) ~= sign(f2); +0115 +0116 result.turnedOn = model.rxns(onMask); +0117 result.turnedOff = model.rxns(offMask); +0118 result.flipped = model.rxns(flipMask); +0119 +0120 % All changed reactions sorted by |delta| descending +0121 changedMask = abs(delta) > cutoff; +0122 [~, si] = sort(abs(delta(changedMask)), 'descend'); +0123 idx = find(changedMask); +0124 idx = idx(si); +0125 +0126 % Relative change: (f2-f1)/|f1|; NaN when reference flux is near-zero +0127 denom = abs(f1(idx)); +0128 denom(denom < cutoff) = NaN; +0129 relChange = delta(idx) ./ denom; +0130 +0131 % Type label per changed reaction +0132 changedTypes = repmat({''}, numel(idx), 1); +0133 for k = 1:numel(idx) +0134 ri = idx(k); +0135 if onMask(ri) +0136 changedTypes{k} = 'on'; +0137 elseif offMask(ri) +0138 changedTypes{k} = 'off'; +0139 elseif flipMask(ri) +0140 changedTypes{k} = 'flip'; +0141 end +0142 end +0143 +0144 result.changed.rxn = model.rxns(idx); +0145 result.changed.flux1 = f1(idx); +0146 result.changed.flux2 = f2(idx); +0147 result.changed.absDelta = abs(delta(idx)); +0148 result.changed.relChange = relChange; +0149 result.changed.type = changedTypes; +0150 +0151 if verbose +0152 printTable_(model, result, nMax, nnz(inList)); +0153 end +0154 end +0155 +0156 %-------------------------------------------------------------------------- +0157 function printTable_(model, r, nMax, nConsidered) +0158 W = 72; +0159 bar = repmat('=', 1, W); +0160 sep = repmat('-', 1, W); +0161 nC = numel(r.changed.rxn); 0162 -0163 if nC == 0 -0164 fprintf('%s\n\n', bar); -0165 return -0166 end -0167 -0168 nShow = min(nC, nMax); -0169 showRxns = r.changed.rxn(1:nShow); -0170 eqns = constructEquations(model, showRxns); +0163 fprintf('\n%s\n', bar); +0164 fprintf(' Flux comparison: %d of %d reactions changed\n', nC, nConsidered); +0165 nOn = numel(r.turnedOn); +0166 nOff = numel(r.turnedOff); +0167 nFlp = numel(r.flipped); +0168 if nOn + nOff + nFlp > 0 +0169 fprintf(' (on: %d | off: %d | reversed: %d)\n', nOn, nOff, nFlp); +0170 end 0171 -0172 fprintf('%s\n', sep); -0173 fprintf(' %-20s %10s %10s %10s %7s %-5s %-25s %s\n', ... -0174 'Reaction', 'Cond.1', 'Cond.2', '|dflux|', 'drel%', 'Type', 'Name', 'Equation'); -0175 fprintf(' %s\n', sep); +0172 if nC == 0 +0173 fprintf('%s\n\n', bar); +0174 return +0175 end 0176 -0177 for i = 1:nShow -0178 rel = r.changed.relChange(i); -0179 if isnan(rel) -0180 relStr = ' new'; -0181 else -0182 relStr = sprintf('%+6.0f%%', rel * 100); -0183 end -0184 ri = find(strcmp(model.rxns, showRxns{i}), 1); -0185 nm = ''; -0186 if isfield(model,'rxnNames') && ri <= numel(model.rxnNames) -0187 nm = char(model.rxnNames{ri}); -0188 end -0189 if numel(nm) > 25, nm = [nm(1:22) '...']; end -0190 fprintf(' %-20s %+10.4g %+10.4g %10.4g %7s %-5s %-25s %s\n', ... -0191 showRxns{i}, r.changed.flux1(i), r.changed.flux2(i), ... -0192 r.changed.absDelta(i), relStr, r.changed.type{i}, nm, eqns{i}); -0193 end -0194 if nC > nMax -0195 fprintf(' ... (%d more, use nMax to show all)\n', nC - nMax); -0196 end -0197 fprintf('%s\n\n', bar); -0198 end

    +0177 nShow = min(nC, nMax); +0178 showRxns = r.changed.rxn(1:nShow); +0179 eqns = constructEquations(model, showRxns); +0180 +0181 fprintf('%s\n', sep); +0182 fprintf(' %-20s %10s %10s %10s %7s %-5s %-25s %s\n', ... +0183 'Reaction', 'Cond.1', 'Cond.2', '|dflux|', 'drel%', 'Type', 'Name', 'Equation'); +0184 fprintf(' %s\n', sep); +0185 +0186 for i = 1:nShow +0187 rel = r.changed.relChange(i); +0188 if isnan(rel) +0189 relStr = ' new'; +0190 else +0191 relStr = sprintf('%+6.0f%%', rel * 100); +0192 end +0193 ri = find(strcmp(model.rxns, showRxns{i}), 1); +0194 nm = ''; +0195 if isfield(model,'rxnNames') && ri <= numel(model.rxnNames) +0196 nm = char(model.rxnNames{ri}); +0197 end +0198 if numel(nm) > 25, nm = [nm(1:22) '...']; end +0199 fprintf(' %-20s %+10.4g %+10.4g %10.4g %7s %-5s %-25s %s\n', ... +0200 showRxns{i}, r.changed.flux1(i), r.changed.flux2(i), ... +0201 r.changed.absDelta(i), relStr, r.changed.type{i}, nm, eqns{i}); +0202 end +0203 if nC > nMax +0204 fprintf(' ... (%d more, use nMax to show all)\n', nC - nMax); +0205 end +0206 fprintf('%s\n\n', bar); +0207 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/analysis/findGeneDeletions.html b/docs/html/analysis/findGeneDeletions.html index 9a894a24..d100eed3 100644 --- a/docs/html/analysis/findGeneDeletions.html +++ b/docs/html/analysis/findGeneDeletions.html @@ -29,8 +29,9 @@

    SYNOPSIS ^DESCRIPTION ^

     findGeneDeletions  Delete genes and track the resulting fluxes.
     
    - Deletes genes, optimizes the model by FBA, and keeps track of the
    - resulting fluxes. This is used for identifying gene deletion targets.
    + Deletes single genes or gene pairs, solves the model by FBA after each
    + deletion, and returns the resulting fluxes. The output identifies gene
    + deletion targets.
     
      Parameters
      ----------
    @@ -40,7 +41,7 @@ 

    DESCRIPTION ^DESCRIPTION ^DESCRIPTION ^SOURCE CODE ^
    0001 function [genes, fluxes, originalGenes, details, grRatioMuts]=findGeneDeletions(model,varargin)
     0002 % findGeneDeletions  Delete genes and track the resulting fluxes.
     0003 %
    -0004 % Deletes genes, optimizes the model by FBA, and keeps track of the
    -0005 % resulting fluxes. This is used for identifying gene deletion targets.
    -0006 %
    -0007 % Parameters
    -0008 % ----------
    -0009 % model : struct
    -0010 %     a model structure.
    -0011 %
    -0012 % Name-Value Arguments
    -0013 % --------------------
    -0014 % testType : char
    -0015 %     single/double gene deletion (default "sgd"):
    -0016 %
    -0017 %     - "sgd" : single gene deletion
    -0018 %     - "dgd" : double gene deletion
    -0019 %
    -0020 % Returns
    -0021 % -------
    -0022 % genes : double
    -0023 %     a matrix with the genes that were deleted in each optimization (the
    -0024 %     gene indexes in originalGenes). Each row corresponds to a column in
    -0025 %     fluxes.
    -0026 % fluxes : double
    -0027 %     a matrix with the resulting fluxes. Double deletions that result in
    -0028 %     an unsolvable problem have all zero flux. Single deletions that
    -0029 %     result in an unsolvable problem are indicated in details instead.
    -0030 % originalGenes : cell
    -0031 %     simply the genes in the input model. Included for simple
    -0032 %     presentation of the output.
    +0004 % Deletes single genes or gene pairs, solves the model by FBA after each
    +0005 % deletion, and returns the resulting fluxes. The output identifies gene
    +0006 % deletion targets.
    +0007 %
    +0008 % Parameters
    +0009 % ----------
    +0010 % model : struct
    +0011 %     a model structure.
    +0012 %
    +0013 % Name-Value Arguments
    +0014 % --------------------
    +0015 % testType : char
    +0016 %     type of gene deletion (default "sgd"):
    +0017 %
    +0018 %     - "sgd" : single gene deletion
    +0019 %     - "dgd" : double gene deletion
    +0020 %
    +0021 % Returns
    +0022 % -------
    +0023 % genes : double
    +0024 %     a matrix with the genes that were deleted in each simulation (the
    +0025 %     gene indexes in originalGenes). Each row corresponds to a column in
    +0026 %     fluxes.
    +0027 % fluxes : double
    +0028 %     a matrix with the resulting fluxes. Double deletions that result in
    +0029 %     an unsolvable problem have all zero flux. Single deletions that
    +0030 %     result in an unsolvable problem are indicated in details instead.
    +0031 % originalGenes : cell
    +0032 %     the genes in the input model.
     0033 % details : double
    -0034 %     not all genes will be deleted in all analyses. It is for example not
    -0035 %     necessary to delete genes for dead end reactions. This is a vector
    -0036 %     with details about each gene in originalGenes and why or why not it
    -0037 %     was deleted:
    -0038 %
    -0039 %     - 1 : was deleted, and the problem was solved (a lethal deletion
    -0040 %       with zero growth also gets 1)
    -0041 %     - 2 : the single-deletion problem had no optimal solution
    -0042 %       (infeasible or failed); fluxes and grRatioMuts are 0
    -0043 %     - 3 : redundant, no longer used
    -0044 %     - 4 : involved in dead-end reaction
    -0045 % grRatioMuts : double
    -0046 %     growth rate ratio between mutated strain and wild type, matching the
    -0047 %     originalGenes(genes) mutants. Note that this does not directly map
    -0048 %     to model.genes, as is the case for COBRA getEssentialGenes. However,
    -0049 %     this can be obtained by afterwards running:
    -0050 %
    -0051 %         grRatio=zeros(1,numel(model.genes));
    -0052 %         grRatio(genes)=grRatioMuts;
    -0053 %
    -0054 % Examples
    -0055 % --------
    -0056 %     [genes, fluxes, originalGenes, details, grRatioMuts]=...
    -0057 %         findGeneDeletions(model,testType);
    -0058 
    -0059 originalModel=model;
    -0060 p=parseRAVENargs(varargin, {'testType',[]});
    -0061 testType=p.testType;
    -0062 if isempty(testType)
    -0063     testType='sgd';
    -0064 else
    -0065     testType=char(testType);
    -0066 end
    -0067 
    -0068 %Check that the test type is correct
    -0069 if ~strcmpi(testType,'sgd') && ~strcmpi(testType,'dgd')
    -0070     EM='Incorrect test type';
    -0071     error('RAVEN:badInput', '%s', EM);
    -0072 end
    -0073 
    -0074 originalGenes=model.genes;
    -0075 details=zeros(numel(model.genes),1);
    -0076 
    -0077 %First simplify the model to reduce the size
    -0078 model=simplifyModel(model,true,false,true,true);
    -0079 model=removeReactions(model,{},true,true); %Removes unused genes
    -0080 details(~ismember(originalGenes,model.genes))=4;
    -0081 
    -0082 [~, geneMapping]=ismember(model.genes,originalGenes);
    -0083 growthWT=solveLP(model);
    -0084 growthWT=growthWT.f;
    -0085 
    -0086 %Do single deletion. This is done here since the double deletion depends
    -0087 %on which single deletions prove lethal (to reduce the size of the system)
    -0088 if strcmpi(testType,'sgd') || strcmpi(testType,'dgd')
    -0089     fluxes=zeros(numel(model.rxns),numel(model.genes));
    -0090     grRatioMuts=zeros(1,numel(model.genes));
    -0091     for i=1:numel(model.genes)
    -0092         %Constrain all reactions involving the gene to 0
    -0093         tempModel=removeGenes(model,i,false,false,false);
    -0094         sol=solveLP(tempModel);
    -0095 
    -0096         %If the optimization terminated successfully
    -0097         if sol.stat==1
    -0098             fluxes(:,i)=sol.x;
    -0099             grRatioMuts(i)=sol.f/growthWT;
    -0100             details(geneMapping(i))=1;
    -0101         else
    -0102             fluxes(:,i)=0;
    -0103             grRatioMuts(i)=0;
    -0104             details(geneMapping(i))=2;
    -0105         end
    -0106     end
    -0107     genes=geneMapping;
    -0108 end
    -0109 
    -0110 %For double deletions
    -0111 if strcmpi(testType,'dgd')
    -0112     %This is a little lazy but it is fine. Check which genes have already
    -0113     %been deleted in "sgd" analysis.
    -0114     [~, I]=ismember(originalGenes(details==1),model.genes);
    -0115     genesToModify=nchoosek(I,2);
    -0116     genes=geneMapping(genesToModify);
    -0117     grRatioMuts=zeros(1,numel(genes));
    -0118     fluxes=sparse(numel(model.rxns),size(genesToModify,1));
    -0119     for i=1:size(genesToModify,1)
    -0120         tempModel=removeGenes(model,genesToModify(i,:),false,false,false);
    -0121         sol=solveLP(tempModel);
    -0122 
    -0123         if sol.stat==1
    -0124             fluxes(:,i)=sol.x;
    -0125             grRatioMuts(i)=sol.f/growthWT;
    -0126         end
    -0127     end
    -0128 end
    -0129 
    -0130 %Map back to the old model
    -0131 [~, I]=ismember(model.rxns,originalModel.rxns);
    -0132 temp=fluxes;
    -0133 fluxes=sparse(numel(originalModel.rxns),size(temp,2));
    -0134 fluxes(I,:)=temp;
    -0135 end

    +0034 % a vector with one element per gene in originalGenes that states +0035 % whether the gene was deleted and why. Genes that only belong to +0036 % dead-end reactions, for example, are not deleted: +0037 % +0038 % - 1 : deleted, and the problem was solved (a lethal deletion +0039 % with zero growth also gets 1) +0040 % - 2 : the single-deletion problem had no optimal solution +0041 % (infeasible or failed); fluxes and grRatioMuts are 0 +0042 % - 3 : redundant (not assigned) +0043 % - 4 : involved in dead-end reaction +0044 % grRatioMuts : double +0045 % growth rate ratio between mutated strain and wild type, matching the +0046 % originalGenes(genes) mutants. The elements do not map directly to +0047 % model.genes, unlike the output of COBRA getEssentialGenes. The +0048 % mapping to model.genes is obtained with: +0049 % +0050 % grRatio=zeros(1,numel(model.genes)); +0051 % grRatio(genes)=grRatioMuts; +0052 % +0053 % Examples +0054 % -------- +0055 % [genes, fluxes, originalGenes, details, grRatioMuts]=... +0056 % findGeneDeletions(model,testType); +0057 +0058 originalModel=model; +0059 p=parseRAVENargs(varargin, {'testType',[]}); +0060 testType=p.testType; +0061 if isempty(testType) +0062 testType='sgd'; +0063 else +0064 testType=char(testType); +0065 end +0066 +0067 %Check that the test type is correct +0068 if ~strcmpi(testType,'sgd') && ~strcmpi(testType,'dgd') +0069 EM='Incorrect test type'; +0070 error('RAVEN:badInput', '%s', EM); +0071 end +0072 +0073 originalGenes=model.genes; +0074 details=zeros(numel(model.genes),1); +0075 +0076 %First simplify the model to reduce the size +0077 model=simplifyModel(model,true,false,true,true); +0078 model=removeReactions(model,{},true,true); %Removes unused genes +0079 details(~ismember(originalGenes,model.genes))=4; +0080 +0081 [~, geneMapping]=ismember(model.genes,originalGenes); +0082 growthWT=solveLP(model); +0083 growthWT=growthWT.f; +0084 +0085 %Do single deletion. This is done here since the double deletion depends +0086 %on which single deletions prove lethal (to reduce the size of the system) +0087 if strcmpi(testType,'sgd') || strcmpi(testType,'dgd') +0088 fluxes=zeros(numel(model.rxns),numel(model.genes)); +0089 grRatioMuts=zeros(1,numel(model.genes)); +0090 for i=1:numel(model.genes) +0091 %Constrain all reactions involving the gene to 0 +0092 tempModel=removeGenes(model,i,false,false,false); +0093 sol=solveLP(tempModel); +0094 +0095 %If the optimization terminated successfully +0096 if sol.stat==1 +0097 fluxes(:,i)=sol.x; +0098 grRatioMuts(i)=sol.f/growthWT; +0099 details(geneMapping(i))=1; +0100 else +0101 fluxes(:,i)=0; +0102 grRatioMuts(i)=0; +0103 details(geneMapping(i))=2; +0104 end +0105 end +0106 genes=geneMapping; +0107 end +0108 +0109 %For double deletions +0110 if strcmpi(testType,'dgd') +0111 %This is a little lazy but it is fine. Check which genes have already +0112 %been deleted in "sgd" analysis. +0113 [~, I]=ismember(originalGenes(details==1),model.genes); +0114 genesToModify=nchoosek(I,2); +0115 genes=geneMapping(genesToModify); +0116 grRatioMuts=zeros(1,numel(genes)); +0117 fluxes=sparse(numel(model.rxns),size(genesToModify,1)); +0118 for i=1:size(genesToModify,1) +0119 tempModel=removeGenes(model,genesToModify(i,:),false,false,false); +0120 sol=solveLP(tempModel); +0121 +0122 if sol.stat==1 +0123 fluxes(:,i)=sol.x; +0124 grRatioMuts(i)=sol.f/growthWT; +0125 end +0126 end +0127 end +0128 +0129 %Map back to the old model +0130 [~, I]=ismember(model.rxns,originalModel.rxns); +0131 temp=fluxes; +0132 fluxes=sparse(numel(originalModel.rxns),size(temp,2)); +0133 fluxes(I,:)=temp; +0134 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/analysis/getAllSubGraphs.html b/docs/html/analysis/getAllSubGraphs.html index c887854b..ab39fa49 100644 --- a/docs/html/analysis/getAllSubGraphs.html +++ b/docs/html/analysis/getAllSubGraphs.html @@ -39,9 +39,9 @@

    DESCRIPTION ^SOURCE CODE ^% Returns 0012 % ------- 0013 % subGraphs : logical -0014 % a boolean matrix where the rows correspond to the metabolites and -0015 % the columns to which subgraph they are assigned to. The columns are -0016 % ordered so that larger subgraphs come first. +0014 % a logical matrix with one row per metabolite and one column per +0015 % subgraph. An element is true if the metabolite belongs to the +0016 % subgraph. The columns are ordered by decreasing subgraph size. 0017 % 0018 % Examples 0019 % -------- diff --git a/docs/html/analysis/getAllowedBounds.html b/docs/html/analysis/getAllowedBounds.html index f3c75685..875148be 100644 --- a/docs/html/analysis/getAllowedBounds.html +++ b/docs/html/analysis/getAllowedBounds.html @@ -41,12 +41,11 @@

    DESCRIPTION ^DESCRIPTION ^SOURCE CODE ^% number of elements as reactions in the model, or a vector of 0014 % reaction indexes (default model.rxns). 0015 % runParallel : logical -0016 % speed up calculations by parallel processing. This is not beneficial -0017 % if allowed bounds are calculated for only a few reactions, as the -0018 % overhead of parallel processing will take longer. It requires MATLAB -0019 % Parallel Computing Toolbox. If this is not installed, the -0020 % calculations will not be parallelized, regardless of what is -0021 % indicated as runParallel (default true). -0022 % -0023 % Returns -0024 % ------- -0025 % minFluxes : double -0026 % minimal allowed fluxes. -0027 % maxFluxes : double -0028 % maximal allowed fluxes. -0029 % exitFlags : double -0030 % exit flags for min/max for each of the reactions. True if it was -0031 % possible to calculate a flux. +0016 % run the calculations in parallel (default true). Parallel processing +0017 % requires the MATLAB Parallel Computing Toolbox; without it, the +0018 % calculations run serially regardless of this setting. For a few +0019 % reactions, the overhead of parallel processing exceeds the time it +0020 % saves. +0021 % +0022 % Returns +0023 % ------- +0024 % minFluxes : double +0025 % minimal allowed fluxes. +0026 % maxFluxes : double +0027 % maximal allowed fluxes. +0028 % exitFlags : double +0029 % a matrix with one row per reaction. Column 1 holds the solver status +0030 % of the minimisation and column 2 the status of the maximisation +0031 % (1 if a flux could be calculated). 0032 % 0033 % Notes 0034 % ----- diff --git a/docs/html/analysis/getFluxZ.html b/docs/html/analysis/getFluxZ.html index d4d426f2..dbf4f9a3 100644 --- a/docs/html/analysis/getFluxZ.html +++ b/docs/html/analysis/getFluxZ.html @@ -41,9 +41,12 @@

    DESCRIPTION ^SOURCE CODE ^% Returns 0014 % ------- 0015 % Z : double -0016 % a vector with Z-scores that tells you for each reaction how likely it -0017 % is for its flux to have increased (positive sign) or decreased -0018 % (negative sign) in the second condition with respect to the first. -0019 % -0020 % Examples -0021 % -------- -0022 % Z = getFluxZ(solutionsA, solutionsB); -0023 % -0024 % See also -0025 % -------- -0026 % randomSampling -0027 -0028 nRxns=size(solutionsA,1); -0029 -0030 %Check that the number of reactions is the same in both cases -0031 if nRxns~=size(solutionsB,1) -0032 EM='The number of reactions must be the same in solutionsA as in solutionsB'; -0033 error('RAVEN:badInput', '%s', EM); -0034 end -0035 -0036 Z=zeros(nRxns,1); -0037 -0038 %Calculate the mean and standard deviation for the two cases -0039 mA=mean(solutionsA,2); -0040 mB=mean(solutionsB,2); -0041 -0042 %This can lead to OUT OF MEMORY, so do it in segments of 500 reactions -0043 varA=zeros(size(solutionsA,1),1); -0044 for i=1:500:size(solutionsA,1) -0045 varA(i:min(i+499,size(solutionsA,1)))=var(solutionsA(i:min(i+499,size(solutionsA,1)),:),0,2); -0046 end -0047 varB=zeros(size(solutionsB,1),1); -0048 for i=1:500:size(solutionsB,1) -0049 varB(i:min(i+499,size(solutionsB,1)))=var(solutionsB(i:min(i+499,size(solutionsB,1)),:),0,2); -0050 end -0051 -0052 %If the mean of both solutions are the same then the Z-score is zero -0053 toCheck=mA~=mB; +0016 % a vector with one Z-score per reaction: the difference between the +0017 % mean fluxes of solutionsB and solutionsA, divided by the square root +0018 % of the sum of their variances. A positive sign indicates an increase +0019 % of the flux in the second condition with respect to the first, a +0020 % negative sign a decrease. Values are limited to the range -100 to +0021 % 100. +0022 % +0023 % Examples +0024 % -------- +0025 % Z = getFluxZ(solutionsA, solutionsB); +0026 % +0027 % See also +0028 % -------- +0029 % randomSampling +0030 +0031 nRxns=size(solutionsA,1); +0032 +0033 %Check that the number of reactions is the same in both cases +0034 if nRxns~=size(solutionsB,1) +0035 EM='The number of reactions must be the same in solutionsA as in solutionsB'; +0036 error('RAVEN:badInput', '%s', EM); +0037 end +0038 +0039 Z=zeros(nRxns,1); +0040 +0041 %Calculate the mean and standard deviation for the two cases +0042 mA=mean(solutionsA,2); +0043 mB=mean(solutionsB,2); +0044 +0045 %This can lead to OUT OF MEMORY, so do it in segments of 500 reactions +0046 varA=zeros(size(solutionsA,1),1); +0047 for i=1:500:size(solutionsA,1) +0048 varA(i:min(i+499,size(solutionsA,1)))=var(solutionsA(i:min(i+499,size(solutionsA,1)),:),0,2); +0049 end +0050 varB=zeros(size(solutionsB,1),1); +0051 for i=1:500:size(solutionsB,1) +0052 varB(i:min(i+499,size(solutionsB,1)))=var(solutionsB(i:min(i+499,size(solutionsB,1)),:),0,2); +0053 end 0054 -0055 %If the variance is zero in both cases, then put a very large or very small -0056 %Z-score for the corresponding reactions -0057 I=find(varA==0 & varB==0 & toCheck==true); -0058 toCheck(I)=false; -0059 % J true where flux increased from A to B, matching the sign convention -0060 % the general branch below uses ((mB-mA)/... is positive when increased) -0061 J=mB(I)>mA(I); -0062 Z(I(J))=100; -0063 Z(I(~J))=-100; -0064 toCheck=find(toCheck); -0065 -0066 for i=1:numel(toCheck) -0067 Z(toCheck(i))=(mB(toCheck(i))-mA(toCheck(i)))/sqrt(varA(toCheck(i))+varB(toCheck(i))); -0068 end -0069 -0070 %Shrink very large values -0071 Z=min(Z,100); -0072 Z=max(Z,-100); -0073 end +0055 %If the mean of both solutions are the same then the Z-score is zero +0056 toCheck=mA~=mB; +0057 +0058 %If the variance is zero in both cases, then put a very large or very small +0059 %Z-score for the corresponding reactions +0060 I=find(varA==0 & varB==0 & toCheck==true); +0061 toCheck(I)=false; +0062 % J true where flux increased from A to B, matching the sign convention +0063 % the general branch below uses ((mB-mA)/... is positive when increased) +0064 J=mB(I)>mA(I); +0065 Z(I(J))=100; +0066 Z(I(~J))=-100; +0067 toCheck=find(toCheck); +0068 +0069 for i=1:numel(toCheck) +0070 Z(toCheck(i))=(mB(toCheck(i))-mA(toCheck(i)))/sqrt(varA(toCheck(i))+varB(toCheck(i))); +0071 end +0072 +0073 %Shrink very large values +0074 Z=min(Z,100); +0075 Z=max(Z,-100); +0076 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/analysis/getMinimalMedium.html b/docs/html/analysis/getMinimalMedium.html index 87c9b731..61acf538 100644 --- a/docs/html/analysis/getMinimalMedium.html +++ b/docs/html/analysis/getMinimalMedium.html @@ -30,21 +30,22 @@

    DESCRIPTION ^
     getMinimalMedium  Find the minimal set of uptake reactions for growth.
     
      Uses MILP to find the smallest set of exchange reactions that can carry
    - uptake flux (lb < 0) while still allowing the model to achieve a target
    - growth rate. Unlike iterative heuristics, the MILP formulation finds the
    - globally minimal medium.
    + uptake flux (lb < 0) while the model achieves a target growth rate. The
    + MILP formulation gives a medium with the globally minimal number of
    + uptake reactions.
     
      Parameters
      ----------
      model : struct
    -     RAVEN model structure.
    +     a model structure.
     
      Name-Value Arguments
      --------------------
      minGrowth : double
    -     minimum objective value (growth rate) the medium must support.
    -     Defaults to 10 % of the unconstrained optimum computed automatically.
    -     Set explicitly to avoid an extra FBA call (e.g. 'minGrowth', 0.1).
    +     minimum objective value (growth rate) the medium must support. If
    +     empty, 10 % of the optimum objective value under the current bounds,
    +     which requires one extra FBA call. A value given explicitly avoids
    +     that call (for example 'minGrowth', 0.1) (default empty).
      verbose : logical
          print the resulting minimal medium (default true).
     
    @@ -64,9 +65,8 @@ 

    DESCRIPTION ^DESCRIPTION ^CROSS-REFERENCE INFORMATION ^

    @@ -108,43 +108,43 @@

    SOURCE CODE ^% getMinimalMedium Find the minimal set of uptake reactions for growth. 0003 % 0004 % Uses MILP to find the smallest set of exchange reactions that can carry -0005 % uptake flux (lb < 0) while still allowing the model to achieve a target -0006 % growth rate. Unlike iterative heuristics, the MILP formulation finds the -0007 % globally minimal medium. +0005 % uptake flux (lb < 0) while the model achieves a target growth rate. The +0006 % MILP formulation gives a medium with the globally minimal number of +0007 % uptake reactions. 0008 % 0009 % Parameters 0010 % ---------- 0011 % model : struct -0012 % RAVEN model structure. +0012 % a model structure. 0013 % 0014 % Name-Value Arguments 0015 % -------------------- 0016 % minGrowth : double -0017 % minimum objective value (growth rate) the medium must support. -0018 % Defaults to 10 % of the unconstrained optimum computed automatically. -0019 % Set explicitly to avoid an extra FBA call (e.g. 'minGrowth', 0.1). -0020 % verbose : logical -0021 % print the resulting minimal medium (default true). -0022 % -0023 % Returns -0024 % ------- -0025 % medium : cell -0026 % cell array of reaction IDs that form the minimal medium. Empty if -0027 % no feasible medium can support the requested growth. -0028 % mediumIdx : double -0029 % column vector of reaction indices corresponding to medium. -0030 % -0031 % Examples -0032 % -------- -0033 % [med, idx] = getMinimalMedium(model) -0034 % [med, idx] = getMinimalMedium(model, 'minGrowth', 0.05) -0035 % [med, idx] = getMinimalMedium(model, 'verbose', false) -0036 % -0037 % Notes -0038 % ----- -0039 % Only exchange reactions with lb < 0 are considered as candidate uptake -0040 % reactions. Exchange reactions that are always secretion-only (lb >= 0) or -0041 % that are completely blocked are not included. +0017 % minimum objective value (growth rate) the medium must support. If +0018 % empty, 10 % of the optimum objective value under the current bounds, +0019 % which requires one extra FBA call. A value given explicitly avoids +0020 % that call (for example 'minGrowth', 0.1) (default empty). +0021 % verbose : logical +0022 % print the resulting minimal medium (default true). +0023 % +0024 % Returns +0025 % ------- +0026 % medium : cell +0027 % cell array of reaction IDs that form the minimal medium. Empty if +0028 % no feasible medium can support the requested growth. +0029 % mediumIdx : double +0030 % column vector of reaction indices corresponding to medium. +0031 % +0032 % Examples +0033 % -------- +0034 % [med, idx] = getMinimalMedium(model) +0035 % [med, idx] = getMinimalMedium(model, 'minGrowth', 0.05) +0036 % [med, idx] = getMinimalMedium(model, 'verbose', false) +0037 % +0038 % Notes +0039 % ----- +0040 % Only exchange reactions with lb < 0 are candidate uptake reactions. +0041 % Exchange reactions with lb >= 0 are not included. 0042 % 0043 % The MILP formulation: 0044 % minimise sum(y_i) @@ -157,15 +157,15 @@

    SOURCE CODE ^% A y_i = 0 forces reaction i to carry non-negative flux (no net uptake); 0052 % y_i = 1 allows it to carry flux as low as lb_i. 0053 % -0054 % The minimal number of uptakes is guaranteed, but the medium itself need -0055 % not be unique: when several media of the same size support the growth -0056 % rate, the solver returns one of them, and another solver or solver -0057 % version can return a different one. +0054 % The number of uptake reactions is minimal, but the medium is not +0055 % necessarily unique: when several media of the same size support the +0056 % growth rate, the solver returns one of them, and another solver or +0057 % solver version can return a different one. 0058 % 0059 % The MILP is solved with the solver set by setRavenSolver. RAVEN's bundled -0060 % GLPK solves it for small models (with a RAVEN:glpkMILP warning); for -0061 % larger models Gurobi is much faster and more reliable -0062 % (setRavenSolver('gurobi')). +0060 % GLPK solves it for small models. The RAVEN:glpkMILP warning issued with +0061 % GLPK states that GLPK works for small problems and that Gurobi is much +0062 % faster and more reliable for larger ones (setRavenSolver('gurobi')). 0063 0064 p = parseRAVENargs(varargin, {'minGrowth',[]; 'verbose',true}); 0065 minGrowth = p.minGrowth; diff --git a/docs/html/analysis/haveFlux.html b/docs/html/analysis/haveFlux.html index 0f20c00c..91f089fa 100644 --- a/docs/html/analysis/haveFlux.html +++ b/docs/html/analysis/haveFlux.html @@ -30,9 +30,10 @@

    DESCRIPTION ^
     haveFlux  Check which reactions can carry a flux.
     
      Checks which reactions can carry a (positive or negative) flux under the
    - model's current bounds; other media or bounds are not explored. Is used
    - as a faster version of getAllowedBounds if it is only interesting whether
    - the reactions can carry a flux or not.
    + model's current bounds; other media or bounds are not explored. Unlike
    + getAllowedBounds, which minimises and maximises every reaction, it skips a
    + reaction once an earlier solution has given it a flux, and minimises only
    + reversible reactions that still have none.
     
      Parameters
      ----------
    @@ -42,16 +43,16 @@ 

    DESCRIPTION ^DESCRIPTION ^SOURCE CODE ^% haveFlux Check which reactions can carry a flux. 0003 % 0004 % Checks which reactions can carry a (positive or negative) flux under the -0005 % model's current bounds; other media or bounds are not explored. Is used -0006 % as a faster version of getAllowedBounds if it is only interesting whether -0007 % the reactions can carry a flux or not. -0008 % -0009 % Parameters -0010 % ---------- -0011 % model : struct -0012 % a model structure. -0013 % -0014 % Name-Value Arguments -0015 % -------------------- -0016 % cutOff : double -0017 % the flux value that a reaction has to carry to be identified as -0018 % positive (default 10^-6). -0019 % rxns : cell or logical or double -0020 % either a cell array of IDs, a logical vector with the same number of -0021 % elements as metabolites in the model, or a vector of indexes (default -0022 % model.rxns). -0023 % seed : double -0024 % seed for the random number generator used to shuffle the order in -0025 % which reactions are tested, so that order is reproducible across -0026 % runs (default: current rng state is left untouched). -0027 % -0028 % Returns -0029 % ------- -0030 % I : logical -0031 % logical array with true if the corresponding reaction can carry a -0032 % flux under the tested bounds. -0033 % -0034 % Examples -0035 % -------- -0036 % I = haveFlux(model, cutOff, rxns); -0037 % -0038 % Notes -0039 % ----- -0040 % If a model has +/- Inf bounds then those are replaced with an arbitary -0041 % large value of +/- 10000 prior to solving. +0005 % model's current bounds; other media or bounds are not explored. Unlike +0006 % getAllowedBounds, which minimises and maximises every reaction, it skips a +0007 % reaction once an earlier solution has given it a flux, and minimises only +0008 % reversible reactions that still have none. +0009 % +0010 % Parameters +0011 % ---------- +0012 % model : struct +0013 % a model structure. +0014 % +0015 % Name-Value Arguments +0016 % -------------------- +0017 % cutOff : double +0018 % the absolute flux value that a reaction has to exceed to be identified +0019 % as carrying flux (default 10^-6). +0020 % rxns : cell or logical or double +0021 % either a cell array of IDs, a logical vector with the same number of +0022 % elements as reactions in the model, or a vector of indexes (default +0023 % model.rxns). +0024 % seed : double +0025 % seed for the random number generator that shuffles the order in +0026 % which reactions are tested. The order is then reproducible across +0027 % runs (default: the current rng state is left untouched). +0028 % +0029 % Returns +0030 % ------- +0031 % I : logical +0032 % logical array with true if the corresponding reaction can carry a +0033 % flux under the tested bounds. +0034 % +0035 % Examples +0036 % -------- +0037 % I = haveFlux(model, cutOff, rxns); +0038 % +0039 % Notes +0040 % ----- +0041 % Bounds of +/- Inf are replaced with +/- 10000 before solving. 0042 % 0043 % Dead-end reactions are removed (simplifyModel) before solving. A dead-end 0044 % reaction with a forced flux (lb > 0 or ub < 0) makes the model infeasible, diff --git a/docs/html/analysis/index.html b/docs/html/analysis/index.html index a5ae7e63..f06e4d8e 100644 --- a/docs/html/analysis/index.html +++ b/docs/html/analysis/index.html @@ -19,7 +19,7 @@

    Index for analysis

    Matlab files in this directory:

    -
     FSEOFFSEOF Flux Scanning based on Enforced Objective Flux.
     analyzeSamplinganalyzeSampling Compare flux change significance with expression change.
     compareFluxescompareFluxes Find the most significant flux changes between two conditions.
     findGeneDeletionsfindGeneDeletions Delete genes and track the resulting fluxes.
     getAllSubGraphsgetAllSubGraphs Get all metabolic subgraphs in a model.
     getAllowedBoundsgetAllowedBounds Return the minimal and maximal fluxes through reactions.
     getEssentialRxnsgetEssentialRxns Calculate the essential reactions for a solvable model.
     getFluxZgetFluxZ Calculate Z scores between two sets of random flux distributions.
     getMinimalMediumgetMinimalMedium Find the minimal set of uptake reactions for growth.
     haveFluxhaveFlux Check which reactions can carry a flux.
     randomSamplingrandomSampling Sample the flux solution space (entry point for all samplers).
     reporterMetabolitesreporterMetabolites Identify metabolites around which transcriptional changes occur.
     runDynamicFBArunDynamicFBA Perform dynamic FBA using the static optimization approach.
     runPhenotypePhasePlanerunPhenotypePhasePlane Run phenotype phase plane analysis and plot the results.
     runProductionEnveloperunProductionEnvelope Calculate the byproduct secretion envelope.
     runRobustnessAnalysisrunRobustnessAnalysis Perform robustness analysis for a reaction and objective.
     runSimpleOptKnockrunSimpleOptKnock Simple OptKnock for growth-coupled production.
     sampleACHRsampleACHR Artificially Centered Hit-and-Run flux sampling.
     sampleCHRRsampleCHRR Coordinate Hit-and-Run with Rounding flux sampling.
     sampleChebyshevCentersampleChebyshevCenter Centre of the largest ball inscribed in a polytope.
     sampleMaxVolEllipsesampleMaxVolEllipse Maximum-volume inscribed ellipsoid of a polytope.
     sampleWarmupPointssampleWarmupPoints FVA warmup vertices for ACHR sampling.
     traceFluxPathtraceFluxPath Find the highest-flux-fraction path between two reactions.
     walkFluxeswalkFluxes Interactively navigate a flux distribution reaction by reaction.
    + 
    FSEOFFSEOF Flux Scanning based on Enforced Objective Flux.  analyzeSamplinganalyzeSampling Compare flux change significance with expression change.  compareFluxescompareFluxes Find the reactions whose flux differs between two flux vectors.  findGeneDeletionsfindGeneDeletions Delete genes and track the resulting fluxes.  getAllSubGraphsgetAllSubGraphs Get all metabolic subgraphs in a model.  getAllowedBoundsgetAllowedBounds Return the minimal and maximal fluxes through reactions.  getEssentialRxnsgetEssentialRxns Calculate the essential reactions for a solvable model.  getFluxZgetFluxZ Calculate Z scores between two sets of random flux distributions.  getMinimalMediumgetMinimalMedium Find the minimal set of uptake reactions for growth.  haveFluxhaveFlux Check which reactions can carry a flux.  randomSamplingrandomSampling Sample the flux solution space (entry point for all samplers).  reporterMetabolitesreporterMetabolites Identify metabolites around which transcriptional changes occur.  runDynamicFBArunDynamicFBA Perform dynamic FBA using the static optimisation approach.  runPhenotypePhasePlanerunPhenotypePhasePlane Run phenotype phase plane analysis and plot the results.  runProductionEnveloperunProductionEnvelope Calculate the byproduct secretion envelope.  runRobustnessAnalysisrunRobustnessAnalysis Perform robustness analysis for a reaction and objective.  runSimpleOptKnockrunSimpleOptKnock Find deletions that couple metabolite production to growth.  sampleACHRsampleACHR Artificially Centered Hit-and-Run flux sampling.  sampleCHRRsampleCHRR Coordinate Hit-and-Run with Rounding flux sampling.  sampleChebyshevCentersampleChebyshevCenter Centre of the largest ball inscribed in a polytope.  sampleMaxVolEllipsesampleMaxVolEllipse Maximum-volume inscribed ellipsoid of a polytope.  sampleWarmupPointssampleWarmupPoints FVA warmup vertices for ACHR sampling.  traceFluxPathtraceFluxPath Find the highest-flux-fraction path between two reactions.  walkFluxeswalkFluxes Interactively navigate a flux distribution reaction by reaction. diff --git a/docs/html/analysis/randomSampling.html b/docs/html/analysis/randomSampling.html index d26543d6..718157df 100644 --- a/docs/html/analysis/randomSampling.html +++ b/docs/html/analysis/randomSampling.html @@ -33,18 +33,19 @@

    DESCRIPTION ^DESCRIPTION ^DESCRIPTION ^DESCRIPTION ^SOURCE CODE ^% 'achr' (default) Artificially Centered Hit-and-Run; near-uniform MCMC 0006 % sampling of the polytope interior (see sampleACHR). 0007 % 'chrr' Coordinate Hit-and-Run with Rounding; near-uniform MCMC -0008 % with maximum-volume-ellipsoid rounding, for better mixing -0009 % on thin/ill-conditioned polytopes such as enzyme- -0010 % constrained models (see sampleCHRR). +0008 % with maximum-volume-ellipsoid rounding, which improves +0009 % mixing on thin or ill-conditioned polytopes such as +0010 % enzyme-constrained models (see sampleCHRR). 0011 % 'randomObjective' the random-objective vertex method of Bordel et al. (2010) 0012 % PLoS Comput Biol (doi:10.1371/journal.pcbi.1000859): each -0013 % sample maximises a small random objective, returning a -0014 % polytope vertex. -0015 % -0016 % The 'achr'/'chrr' methods draw the (near-)uniform interior distribution; the -0017 % 'randomObjective' method draws diverse vertices. Arguments below marked -0018 % [randomObjective] apply only to that method; 'thinning'/'nBurnin' apply only -0019 % to the MCMC methods. -0020 % -0021 % Parameters -0022 % ---------- -0023 % model : struct -0024 % a model structure. -0025 % -0026 % Name-Value Arguments -0027 % -------------------- -0028 % nSamples : double -0029 % the number of solutions to return (default 1000). -0030 % method : char -0031 % sampling method: 'achr' (default), 'chrr', or 'randomObjective'. -0032 % thinning : double -0033 % MCMC steps between recorded samples for 'achr'/'chrr' (default 100). -0034 % nBurnin : double -0035 % burn-in steps discarded before the first sample for 'chrr' (default 1000). -0036 % replaceBoundsWithInf : logical -0037 % [randomObjective] -0038 % replace the largest upper bounds with Inf and the smallest lower -0039 % bounds with -Inf. This is needed to get solutions without loops if -0040 % your model has, for example, 1000/-1000 as arbitrarily large bounds. -0041 % If your model only has "biologically relevant" bounds, then set this -0042 % to false (default true). -0043 % supressErrors : logical -0044 % the program will halt if it has problems finding non-zero solutions -0045 % which are not involved in loops. This could be because the constraints -0046 % on the model are too relaxed (such as unlimited glucose uptake) or too -0047 % strict (such as too many and too narrow constraints) (default false). -0048 % runParallel : logical -0049 % speed up calculations by parallel processing. Requires the MATLAB -0050 % Parallel Computing Toolbox. If this is not installed, the calculations -0051 % will not be parallelized, regardless of what is indicated as -0052 % runParallel (default true). -0053 % goodRxns : double -0054 % vector of indexes of those reactions that are not involved in loops -0055 % and can be used as random objective functions, as generated by a -0056 % previous run of randomSampling on the same model (default empty). -0057 % minFlux : logical -0058 % determines if a second optimization should be performed for each -0059 % random sample, to minimize the number of fluxes and thereby prevent -0060 % loops. Typically, loops are averaged out when a large number of -0061 % samples are taken, but this is not always the case (default false). -0062 % nObjectives : double -0063 % number of reactions included in the random objective function per -0064 % sample iteration (default 2). -0065 % seed : double -0066 % random seed for reproducibility. If empty, the current MATLAB random -0067 % state is used (default empty). -0068 % -0069 % Returns -0070 % ------- -0071 % solutions : double -0072 % matrix with the solutions. -0073 % goodRxns : double -0074 % [randomObjective] vector of indexes of those reactions that are not -0075 % involved in loops or always carry zero flux and can be used as random -0076 % objective functions. Empty ([]) for the 'achr' and 'chrr' methods. -0077 % info : struct -0078 % [chrr] the diagnostics from sampleCHRR, including mveConverged (whether -0079 % the maximum-volume ellipsoid rounding reached tolerance). Empty ([]) -0080 % for the 'achr' and 'randomObjective' methods. -0081 % -0082 % Notes -0083 % ----- -0084 % With 'randomObjective', the solutions are generated by maximizing (with -0085 % random weights) for a random set of nObjectives reactions (default 2). -0086 % For reversible reactions it randomly chooses between maximizing and -0087 % minimizing. The default 'achr' and the 'chrr' methods use no random -0088 % objectives; they take hit-and-run steps through the interior of the -0089 % solution space. -0090 % -0091 % If the model is a GECKO v3+ ecModel, then usage_prot reactions are not -0092 % selected for sampling, instead focusing on sampling from the metabolic -0093 % aspects that form the solution space. -0094 % -0095 % Examples -0096 % -------- -0097 % % Near-uniform interior sampling (default, ACHR): -0098 % solutions = randomSampling(model, 1000); -0099 % % Coordinate hit-and-run with rounding: -0100 % solutions = randomSampling(model, 1000, 'method', 'chrr', 'seed', 1); -0101 % % Historical random-objective vertex method: -0102 % solutions = randomSampling(model, 1000, 'method', 'randomObjective'); -0103 % -0104 % See also -0105 % -------- -0106 % sampleACHR, sampleCHRR, sampleMaxVolEllipse -0107 -0108 p=parseRAVENargs(varargin, {'nSamples',[]; 'replaceBoundsWithInf',[]; 'supressErrors',[]; 'runParallel',[]; 'goodRxns',[]; 'minFlux',[]; 'nObjectives',[]; 'seed',[]; 'method',[]; 'thinning',[]; 'nBurnin',[]}); -0109 nSamples=p.nSamples; -0110 replaceBoundsWithInf=p.replaceBoundsWithInf; -0111 supressErrors=p.supressErrors; -0112 runParallel=p.runParallel; -0113 goodRxns=p.goodRxns; -0114 minFlux=p.minFlux; -0115 nObjectives=p.nObjectives; -0116 seed=p.seed; -0117 method=p.method; -0118 thinning=p.thinning; -0119 nBurnin=p.nBurnin; -0120 info=[]; %only populated by the 'chrr' method -0121 if isempty(nSamples) -0122 nSamples=1000; -0123 end -0124 if isempty(method) -0125 method='achr'; +0013 % sample optimises a random objective of nObjectives +0014 % reactions with random weights, returning a polytope +0015 % vertex. +0016 % +0017 % The 'achr' and 'chrr' methods draw from the (near-)uniform distribution +0018 % over the interior; the 'randomObjective' method draws vertices. Arguments +0019 % below marked [randomObjective] apply only to that method; 'thinning' and +0020 % 'nBurnin' apply only to the MCMC methods. +0021 % +0022 % Parameters +0023 % ---------- +0024 % model : struct +0025 % a model structure. +0026 % +0027 % Name-Value Arguments +0028 % -------------------- +0029 % nSamples : double +0030 % the number of solutions to return (default 1000). +0031 % method : char +0032 % sampling method: 'achr' (default), 'chrr', or 'randomObjective'. +0033 % thinning : double +0034 % MCMC steps between recorded samples for 'achr' and 'chrr' (default 100). +0035 % nBurnin : double +0036 % burn-in steps discarded before the first sample for 'chrr' (default 1000). +0037 % replaceBoundsWithInf : logical +0038 % [randomObjective] +0039 % replace the largest upper bounds with Inf and the smallest lower +0040 % bounds with -Inf (the latter only if the smallest lower bound is +0041 % negative). This is required to get solutions without loops if the +0042 % model has arbitrarily large bounds, for example 1000 and -1000. A +0043 % model with only biologically relevant bounds can use false +0044 % (default true). +0045 % supressErrors : logical +0046 % if false, randomSampling raises an error after 99 consecutive failed +0047 % attempts to find a non-zero solution that is not involved in loops. +0048 % Failures can result from constraints that are too relaxed (such as +0049 % unlimited glucose uptake) or too strict (such as many narrow +0050 % constraints). If true, the attempts continue without an error +0051 % (default false). +0052 % runParallel : logical +0053 % run the calculations in parallel (default true). Requires the MATLAB +0054 % Parallel Computing Toolbox; without it, the calculations run serially +0055 % regardless of this setting. +0056 % goodRxns : double +0057 % vector of indexes of those reactions that are not involved in loops +0058 % and can be used as random objective functions, as generated by a +0059 % previous run of randomSampling on the same model (default empty). +0060 % minFlux : logical +0061 % determines if a second optimisation is performed for each random +0062 % sample, which minimises the sum of absolute fluxes to prevent loops +0063 % (default false). +0064 % nObjectives : double +0065 % number of reactions included in the random objective function per +0066 % sample iteration (default 2). +0067 % seed : double +0068 % random seed for reproducibility. If empty, the current MATLAB random +0069 % state is used (default empty). +0070 % +0071 % Returns +0072 % ------- +0073 % solutions : double +0074 % matrix with the solutions. +0075 % goodRxns : double +0076 % [randomObjective] vector of indexes of those reactions that are not +0077 % involved in loops or always carry zero flux and can be used as random +0078 % objective functions. Empty ([]) for the 'achr' and 'chrr' methods. +0079 % info : struct +0080 % [chrr] the diagnostics from sampleCHRR, including mveConverged (whether +0081 % the maximum-volume ellipsoid rounding reached tolerance). Empty ([]) +0082 % for the 'achr' and 'randomObjective' methods. +0083 % +0084 % Notes +0085 % ----- +0086 % With 'randomObjective', the solutions are generated by optimising (with +0087 % random weights) a random set of nObjectives reactions (default 2). The +0088 % sign of each weight is chosen at random, which selects maximisation or +0089 % minimisation of the reaction; reactions with an upper bound below 0 always +0090 % receive a positive weight. The default 'achr' and the 'chrr' methods use +0091 % no random objectives; they take hit-and-run steps through the interior of +0092 % the solution space. +0093 % +0094 % If the model is a GECKO v3+ ecModel, then usage_prot reactions are not +0095 % selected as random objectives; the objectives are drawn from the +0096 % metabolic reactions. +0097 % +0098 % Examples +0099 % -------- +0100 % % Near-uniform interior sampling (default, ACHR): +0101 % solutions = randomSampling(model, 1000); +0102 % % Coordinate hit-and-run with rounding: +0103 % solutions = randomSampling(model, 1000, 'method', 'chrr', 'seed', 1); +0104 % % Random-objective vertex method: +0105 % solutions = randomSampling(model, 1000, 'method', 'randomObjective'); +0106 % +0107 % See also +0108 % -------- +0109 % sampleACHR, sampleCHRR, sampleMaxVolEllipse +0110 +0111 p=parseRAVENargs(varargin, {'nSamples',[]; 'replaceBoundsWithInf',[]; 'supressErrors',[]; 'runParallel',[]; 'goodRxns',[]; 'minFlux',[]; 'nObjectives',[]; 'seed',[]; 'method',[]; 'thinning',[]; 'nBurnin',[]}); +0112 nSamples=p.nSamples; +0113 replaceBoundsWithInf=p.replaceBoundsWithInf; +0114 supressErrors=p.supressErrors; +0115 runParallel=p.runParallel; +0116 goodRxns=p.goodRxns; +0117 minFlux=p.minFlux; +0118 nObjectives=p.nObjectives; +0119 seed=p.seed; +0120 method=p.method; +0121 thinning=p.thinning; +0122 nBurnin=p.nBurnin; +0123 info=[]; %only populated by the 'chrr' method +0124 if isempty(nSamples) +0125 nSamples=1000; 0126 end -0127 -0128 % ---- Dispatch to the requested sampler ---- -0129 % 'achr'/'chrr' are near-uniform MCMC samplers of the polytope interior, -0130 % implemented in sampleACHR / sampleCHRR. 'randomObjective' is the -0131 % random-objective vertex method (Bordel 2010), implemented below; goodRxns is -0132 % meaningful only for that method (empty otherwise). -0133 switch lower(method) -0134 case 'achr' -0135 solutions = sampleACHR(model, nSamples, thinning, [], seed); -0136 goodRxns = []; -0137 return; -0138 case 'chrr' -0139 [solutions, info] = sampleCHRR(model, nSamples, thinning, nBurnin, seed); -0140 goodRxns = []; -0141 return; -0142 case {'randomobjective', 'objective'} -0143 % fall through to the random-objective implementation below -0144 otherwise -0145 error('RAVEN:badInput', ... -0146 ['randomSampling: unknown method ''%s''; expected ''achr'', ' ... -0147 '''chrr'', or ''randomObjective''.'], method); -0148 end -0149 -0150 if isempty(replaceBoundsWithInf) -0151 replaceBoundsWithInf=true; -0152 end -0153 if isempty(supressErrors) -0154 supressErrors=false; +0127 if isempty(method) +0128 method='achr'; +0129 end +0130 +0131 % ---- Dispatch to the requested sampler ---- +0132 % 'achr'/'chrr' are near-uniform MCMC samplers of the polytope interior, +0133 % implemented in sampleACHR / sampleCHRR. 'randomObjective' is the +0134 % random-objective vertex method (Bordel 2010), implemented below; goodRxns is +0135 % meaningful only for that method (empty otherwise). +0136 switch lower(method) +0137 case 'achr' +0138 solutions = sampleACHR(model, nSamples, thinning, [], seed); +0139 goodRxns = []; +0140 return; +0141 case 'chrr' +0142 [solutions, info] = sampleCHRR(model, nSamples, thinning, nBurnin, seed); +0143 goodRxns = []; +0144 return; +0145 case {'randomobjective', 'objective'} +0146 % fall through to the random-objective implementation below +0147 otherwise +0148 error('RAVEN:badInput', ... +0149 ['randomSampling: unknown method ''%s''; expected ''achr'', ' ... +0150 '''chrr'', or ''randomObjective''.'], method); +0151 end +0152 +0153 if isempty(replaceBoundsWithInf) +0154 replaceBoundsWithInf=true; 0155 end -0156 if isempty(runParallel) -0157 runParallel=true; +0156 if isempty(supressErrors) +0157 supressErrors=false; 0158 end -0159 if isempty(minFlux) -0160 minFlux=false; +0159 if isempty(runParallel) +0160 runParallel=true; 0161 end -0162 if isempty(nObjectives) -0163 nObjectives=2; +0162 if isempty(minFlux) +0163 minFlux=false; 0164 end -0165 -0166 nW = parallelWorkersRAVEN(runParallel); -0167 -0168 nObjRxns=nObjectives; -0169 -0170 %Simplify the model to speed stuff up a little. Keep original mapping -0171 originalRxns=model.rxns; -0172 model=simplifyModel(model,false,false,true,true); -0173 [~, simpToOrig] = ismember(model.rxns, originalRxns); -0174 -0175 %goodRxns is part of the public API and stored as original-model indices. -0176 %Convert to simplified-model indices for use in the detection and sampling -0177 %loops below, dropping any reactions that were removed by simplification. -0178 if ~isempty(goodRxns) -0179 [tf, simpIdx] = ismember(originalRxns(goodRxns), model.rxns); -0180 goodRxns = double(simpIdx(tf)); -0181 end -0182 -0183 %Check that the model is feasible given the constraints -0184 [sol,~]=solveLP(model); -0185 if isempty(sol.x) -0186 EM='The model has no feasible solution, likely due to incompatible constraints'; -0187 error('RAVEN:badInput', '%s', EM); -0188 elseif sol.f==0 -0189 warning('The model objective function cannot reach a non-zero value. This might be intended, so randomSampling will continue, but this could indicate problems with your model') -0190 end -0191 -0192 nRxns = numel(model.rxns); -0193 %Reactions which can be involved in loops should not be optimized for. -0194 %Check which reactions reach an arbitrary high upper bound. This is done -0195 %on the original bounded model so that loop detection is meaningful -0196 %(Inf-bounded reactions would always reach the threshold). -0197 if isempty(goodRxns) -0198 [minF, maxF] = getAllowedBounds(model, 'runParallel', runParallel); -0199 goodRxns = true(nRxns, 1); -0200 % Reactions that reach the model's own highest bound are involved in -0201 % loops. The threshold has to be derived from the model, as the bound -0202 % replacement below does: hardcoding ±1000 matches nothing on a model -0203 % whose bounds are on another scale (ecModels, 100-scale bounds), so no -0204 % reaction would be excluded and loops would be sampled while the -0205 % function reports success. -0206 loopUb = max(model.ub); -0207 loopLb = min(model.lb); -0208 goodRxns(maxF == Inf | maxF > loopUb*0.999) = false; -0209 if loopLb < 0 -0210 goodRxns(minF == -Inf | minF < loopLb*0.999) = false; -0211 end -0212 % Reactions that cannot carry any non-zero flux -0213 goodRxns(~(maxF > 0 | minF < 0)) = false; -0214 % In ecModels do not sample from usage_prot reactions -0215 if isfield(model,'ec') -0216 goodRxns(startsWith(model.rxns,'usage_prot_')) = false; -0217 end -0218 goodRxns = find(goodRxns); -0219 end -0220 -0221 %Change the bounds to +/- Inf to prevent loops in the sampled solutions. -0222 %Applied after goodRxns detection so that loop-detection uses original bounds. -0223 if replaceBoundsWithInf==true -0224 model.ub(model.ub==max(model.ub))=Inf; -0225 if min(model.lb)<0 % Only negative lower bounds should be set to -Inf -0226 model.lb(model.lb==min(model.lb))=-Inf; -0227 end -0228 end -0229 -0230 %Seed the RNG here so that only the sampling loop is affected, not the -0231 %model simplification or goodRxns detection above. -0232 if ~isempty(seed) -0233 rng(seed); -0234 end -0235 -0236 %Reserve space for a solution matrix -0237 sols = zeros(numel(model.rxns),nSamples); -0238 sols = num2cell(sols,1); -0239 -0240 %Main loop -0241 if nSamples > 0 +0165 if isempty(nObjectives) +0166 nObjectives=2; +0167 end +0168 +0169 nW = parallelWorkersRAVEN(runParallel); +0170 +0171 nObjRxns=nObjectives; +0172 +0173 %Simplify the model to speed stuff up a little. Keep original mapping +0174 originalRxns=model.rxns; +0175 model=simplifyModel(model,false,false,true,true); +0176 [~, simpToOrig] = ismember(model.rxns, originalRxns); +0177 +0178 %goodRxns is part of the public API and stored as original-model indices. +0179 %Convert to simplified-model indices for use in the detection and sampling +0180 %loops below, dropping any reactions that were removed by simplification. +0181 if ~isempty(goodRxns) +0182 [tf, simpIdx] = ismember(originalRxns(goodRxns), model.rxns); +0183 goodRxns = double(simpIdx(tf)); +0184 end +0185 +0186 %Check that the model is feasible given the constraints +0187 [sol,~]=solveLP(model); +0188 if isempty(sol.x) +0189 EM='The model has no feasible solution, likely due to incompatible constraints'; +0190 error('RAVEN:badInput', '%s', EM); +0191 elseif sol.f==0 +0192 warning('The model objective function cannot reach a non-zero value. This might be intended, so randomSampling will continue, but this could indicate problems with your model') +0193 end +0194 +0195 nRxns = numel(model.rxns); +0196 %Reactions which can be involved in loops should not be optimized for. +0197 %Check which reactions reach an arbitrary high upper bound. This is done +0198 %on the original bounded model so that loop detection is meaningful +0199 %(Inf-bounded reactions would always reach the threshold). +0200 if isempty(goodRxns) +0201 [minF, maxF] = getAllowedBounds(model, 'runParallel', runParallel); +0202 goodRxns = true(nRxns, 1); +0203 % Reactions that reach the model's own highest bound are involved in +0204 % loops. The threshold has to be derived from the model, as the bound +0205 % replacement below does: hardcoding ±1000 matches nothing on a model +0206 % whose bounds are on another scale (ecModels, 100-scale bounds), so no +0207 % reaction would be excluded and loops would be sampled while the +0208 % function reports success. +0209 loopUb = max(model.ub); +0210 loopLb = min(model.lb); +0211 goodRxns(maxF == Inf | maxF > loopUb*0.999) = false; +0212 if loopLb < 0 +0213 goodRxns(minF == -Inf | minF < loopLb*0.999) = false; +0214 end +0215 % Reactions that cannot carry any non-zero flux +0216 goodRxns(~(maxF > 0 | minF < 0)) = false; +0217 % In ecModels do not sample from usage_prot reactions +0218 if isfield(model,'ec') +0219 goodRxns(startsWith(model.rxns,'usage_prot_')) = false; +0220 end +0221 goodRxns = find(goodRxns); +0222 end +0223 +0224 %Change the bounds to +/- Inf to prevent loops in the sampled solutions. +0225 %Applied after goodRxns detection so that loop-detection uses original bounds. +0226 if replaceBoundsWithInf==true +0227 model.ub(model.ub==max(model.ub))=Inf; +0228 if min(model.lb)<0 % Only negative lower bounds should be set to -Inf +0229 model.lb(model.lb==min(model.lb))=-Inf; +0230 end +0231 end +0232 +0233 %Seed the RNG here so that only the sampling loop is affected, not the +0234 %model simplification or goodRxns detection above. +0235 if ~isempty(seed) +0236 rng(seed); +0237 end +0238 +0239 %Reserve space for a solution matrix +0240 sols = zeros(numel(model.rxns),nSamples); +0241 sols = num2cell(sols,1); 0242 -0243 PB = progressReport(nSamples,'Performing random sampling'); -0244 parfor (i = 1:nSamples, nW) -0245 badSolutions = 1; -0246 tmpModel = model; -0247 while lt(0,badSolutions) -0248 rxns = goodRxns(randperm(numel(goodRxns), nObjRxns)); -0249 tmpModel.c = zeros(numel(tmpModel.rxns),1); -0250 -0251 multipliers = 2*randi(2,nObjRxns,1)-3; % random draws from {-1, 1} -0252 multipliers(tmpModel.ub(rxns)<0) = 1; +0243 %Main loop +0244 if nSamples > 0 +0245 +0246 PB = progressReport(nSamples,'Performing random sampling'); +0247 parfor (i = 1:nSamples, nW) +0248 badSolutions = 1; +0249 tmpModel = model; +0250 while lt(0,badSolutions) +0251 rxns = goodRxns(randperm(numel(goodRxns), nObjRxns)); +0252 tmpModel.c = zeros(numel(tmpModel.rxns),1); 0253 -0254 tmpModel.c(rxns) = rand(nObjRxns,1).*multipliers; -0255 -0256 if true(minFlux) -0257 sol=solveLP(tmpModel,1); -0258 else -0259 sol=solveLP(tmpModel,0); -0260 end -0261 if any(sol.x) && abs(sol.f)>10^-8 -0262 sols{i} = sol.x; -0263 badSolutions = 0; -0264 else -0265 badSolutions = badSolutions+1; -0266 %If it only finds bad solutions then throw an error. -0267 if badSolutions == 100 && supressErrors==false -0268 error('The program is having problems finding non-zero solutions (ignoring reactions that might be involved in loops). Review the constraints on your model. Set supressErrors to true to ignore this error'); -0269 end -0270 end -0271 end -0272 count(PB); -0273 end -0274 -0275 %Map to original model -0276 sols = cell2mat(sols); -0277 solutions=zeros(numel(originalRxns),nSamples); -0278 solutions(simpToOrig,:)=sols; -0279 else -0280 solutions=[]; -0281 end -0282 %Return goodRxns as original-model indices so callers can pass them back -0283 %safely across separate calls (which each re-simplify the model internally). -0284 goodRxns = double(simpToOrig(goodRxns)); -0285 end

    +0254 multipliers = 2*randi(2,nObjRxns,1)-3; % random draws from {-1, 1} +0255 multipliers(tmpModel.ub(rxns)<0) = 1; +0256 +0257 tmpModel.c(rxns) = rand(nObjRxns,1).*multipliers; +0258 +0259 if true(minFlux) +0260 sol=solveLP(tmpModel,1); +0261 else +0262 sol=solveLP(tmpModel,0); +0263 end +0264 if any(sol.x) && abs(sol.f)>10^-8 +0265 sols{i} = sol.x; +0266 badSolutions = 0; +0267 else +0268 badSolutions = badSolutions+1; +0269 %If it only finds bad solutions then throw an error. +0270 if badSolutions == 100 && supressErrors==false +0271 error('The program is having problems finding non-zero solutions (ignoring reactions that might be involved in loops). Review the constraints on your model. Set supressErrors to true to ignore this error'); +0272 end +0273 end +0274 end +0275 count(PB); +0276 end +0277 +0278 %Map to original model +0279 sols = cell2mat(sols); +0280 solutions=zeros(numel(originalRxns),nSamples); +0281 solutions(simpToOrig,:)=sols; +0282 else +0283 solutions=[]; +0284 end +0285 %Return goodRxns as original-model indices so callers can pass them back +0286 %safely across separate calls (which each re-simplify the model internally). +0287 goodRxns = double(simpToOrig(goodRxns)); +0288 end


    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/analysis/reporterMetabolites.html b/docs/html/analysis/reporterMetabolites.html index 26febd25..dc1f5dbb 100644 --- a/docs/html/analysis/reporterMetabolites.html +++ b/docs/html/analysis/reporterMetabolites.html @@ -29,15 +29,15 @@

    SYNOPSIS ^DESCRIPTION ^

     reporterMetabolites  Identify metabolites around which transcriptional changes occur.
     
    - The Reporter Metabolites algorithm for identifying metabolites around
    - which transcriptional changes occur.
    + Applies the Reporter Metabolites algorithm, which scores each metabolite
    + by the differential expression of the genes around it.
     
      Parameters
      ----------
      model : struct
          a model structure.
      genes : cell
    -     a cell array of gene names (should match with model.genes).
    +     a cell array of gene names (matching model.genes).
      genePValues : double
          P-values for differential expression of the genes. Values must lie in
          [0, 1]; NaN values are ignored, any other value raises an error.
    @@ -45,14 +45,15 @@ 

    DESCRIPTION ^DESCRIPTION ^SOURCE CODE ^
    0001 function repMets=reporterMetabolites(model,genes,genePValues,varargin)
     0002 % reporterMetabolites  Identify metabolites around which transcriptional changes occur.
     0003 %
    -0004 % The Reporter Metabolites algorithm for identifying metabolites around
    -0005 % which transcriptional changes occur.
    +0004 % Applies the Reporter Metabolites algorithm, which scores each metabolite
    +0005 % by the differential expression of the genes around it.
     0006 %
     0007 % Parameters
     0008 % ----------
     0009 % model : struct
     0010 %     a model structure.
     0011 % genes : cell
    -0012 %     a cell array of gene names (should match with model.genes).
    +0012 %     a cell array of gene names (matching model.genes).
     0013 % genePValues : double
     0014 %     P-values for differential expression of the genes. Values must lie in
     0015 %     [0, 1]; NaN values are ignored, any other value raises an error.
    @@ -123,231 +126,234 @@ 

    SOURCE CODE ^% Name-Value Arguments 0018 % -------------------- 0019 % printResults : logical -0020 % true if the top 20 Reporter Metabolites should be printed to the -0021 % screen (default false). +0020 % true to print the top 20 Reporter Metabolites to the screen +0021 % (default false). 0022 % outputFile : char 0023 % the results are printed to this file (default none). 0024 % geneFoldChanges : double -0025 % log-fold changes for the genes. If supplied, then Reporter -0026 % Metabolites are calculated for only up/down-regulated genes in -0027 % addition to the full test (default none). -0028 % -0029 % Returns -0030 % ------- -0031 % repMets : struct -0032 % an array of structures with the following fields: -0033 % -0034 % - test : a string that describes the genes used to calculate the -0035 % Reporter Metabolites ("all", "only up", or "only down"). The two -0036 % latter structures are only calculated if geneFoldChanges are -0037 % supplied. -0038 % - mets : a cell array of metabolite IDs for the metabolites for which -0039 % a score could be calculated. -0040 % - metZScores : background-corrected Z-scores for differential -0041 % expression around each metabolite in "mets". The entries of all -0042 % fields are sorted by this score, highest first. -0043 % - metPValues : one-sided (upper-tail) P-values of "metZScores". -0044 % - metNGenes : number of neighbouring genes for each metabolite in -0045 % "mets". -0046 % - meanZ : average Z-scores for the genes around each metabolite in -0047 % "mets". -0048 % - stdZ : standard deviations of the Z-scores around each metabolite -0049 % in "mets". -0050 % -0051 % Examples -0052 % -------- -0053 % repMets = reporterMetabolites(model, genes, genePValues, ... -0054 % printResults, outputFile, geneFoldChanges); -0055 % -0056 % Notes -0057 % ----- -0058 % For details about the algorithm, see Patil KR, Nielsen J, Uncovering -0059 % transcriptional regulation of metabolism by using metabolic network -0060 % topology. Proc. Natl Acad. Sci. USA 2005;102:2685-2689. -0061 % -0062 % The background correction uses the closed-form mean and standard -0063 % deviation of the aggregate Z-score of n genes drawn at random from all -0064 % scored genes, so the results are deterministic. -0065 -0066 genes=convertCharArray(genes); -0067 p=parseRAVENargs(varargin, {'printResults',false; 'outputFile',[]; 'geneFoldChanges',[]}); -0068 printResults=p.printResults; -0069 outputFile=p.outputFile; -0070 geneFoldChanges=p.geneFoldChanges; -0071 -0072 %Check some stuff -0073 if numel(genes)~=numel(genePValues) -0074 EM='The number of genes and the number of Z-scores must be the same'; -0075 error('RAVEN:badInput', '%s', EM); -0076 end -0077 if ~isfield(model,'rxnGeneMat') -0078 EM='The model structure must have a rxnGeneMat field'; -0079 error('RAVEN:badInput', '%s', EM); -0080 end -0081 -0082 %All P-values that are given must lie in [0,1]; NaN means that no P-value -0083 %is available and is removed below -0084 badP=~isnan(genePValues) & (genePValues<0 | genePValues>1); -0085 if any(badP) -0086 firstBad=find(badP,1); -0087 EM=sprintf('The P-values must lie between 0 and 1, but %d of them do not. The first is %g (gene %s)',nnz(badP),genePValues(firstBad),genes{firstBad}); -0088 error('RAVEN:badInput', '%s', EM); -0089 end -0090 -0091 %Remove the genes which are not in the model -0092 genePValues(~ismember(genes,model.genes))=[]; -0093 if any(geneFoldChanges) -0094 geneFoldChanges(~ismember(genes,model.genes))=[]; -0095 end -0096 genes(~ismember(genes,model.genes))=[]; -0097 -0098 %Remove the genes that has NA P-value -0099 genes(isnan(genePValues))=[]; -0100 if any(geneFoldChanges) -0101 geneFoldChanges(isnan(genePValues))=[]; -0102 end -0103 genePValues(isnan(genePValues))=[]; -0104 -0105 %Convert p-values to Z-scores. Uses base MATLAB erfcinv instead of the -0106 %Statistics Toolbox norminv: -norminv(p) == sqrt(2)*erfcinv(2*p). -0107 geneZScores=sqrt(2)*erfcinv(2*genePValues); -0108 -0109 %This is to prevent errors if the p-values are exactly 0 or 1 -0110 geneZScores(geneZScores==-inf)=-15; -0111 geneZScores(geneZScores==inf)=15; -0112 -0113 %For each metabolite, calculate the aggregate Z-score and keep track of the -0114 %number of neighbouring genes -0115 metZScores=nan(numel(model.mets),1); -0116 %Sums are taken on Z-scores relative to their mean (found in two passes, the -0117 %second relative to the first gene's Z-score), which keeps the rounding error -0118 %proportional to the spread of the Z-scores instead of to their size -0119 metZShifted=nan(numel(model.mets),1); -0120 zCentre=0; -0121 if ~isempty(geneZScores) -0122 zCentre=geneZScores(1)+mean(geneZScores-geneZScores(1)); -0123 end -0124 metNGenes=nan(numel(model.mets),1); -0125 meanZ=nan(numel(model.mets),1); -0126 stdZ=nan(numel(model.mets),1); -0127 %model.genes -> genes(filtered list) index, computed once rather than -0128 %re-scanning the whole "genes" list (via ismember) for every metabolite, -0129 %which is quadratic in a genome-scale model's mets x genes. -0130 [~,geneIdxInList]=ismember(model.genes,genes); -0131 for i=1:numel(model.mets) -0132 %Get the involved rxns -0133 I=model.S(i,:); -0134 -0135 %Get the involved genes -0136 [~, J]=find(model.rxnGeneMat(I~=0,:)); +0025 % log-fold changes for the genes. If supplied, Reporter Metabolites are +0026 % also calculated for the upregulated genes (fold change >= 0) alone and +0027 % for the downregulated genes (fold change < 0) alone, in addition to +0028 % the full test (default none). +0029 % +0030 % Returns +0031 % ------- +0032 % repMets : struct +0033 % an array of structures with the following fields: +0034 % +0035 % - test : a string that describes the genes used to calculate the +0036 % Reporter Metabolites ("all", "only up", or "only down"). The tests +0037 % "only up" and "only down" are only calculated if geneFoldChanges is +0038 % supplied. +0039 % - mets : a cell array of metabolite IDs for the metabolites for which +0040 % a score could be calculated. +0041 % - metNames : a cell array with the names of the metabolites in +0042 % "mets". +0043 % - metZScores : background-corrected Z-scores for differential +0044 % expression around each metabolite in "mets". The entries of all +0045 % fields are sorted by this score, highest first. +0046 % - metPValues : one-sided (upper-tail) P-values of "metZScores". +0047 % - metNGenes : number of neighbouring genes for each metabolite in +0048 % "mets". +0049 % - meanZ : average Z-scores for the genes around each metabolite in +0050 % "mets". +0051 % - stdZ : standard deviations of the Z-scores around each metabolite +0052 % in "mets". +0053 % +0054 % Examples +0055 % -------- +0056 % repMets = reporterMetabolites(model, genes, genePValues, ... +0057 % printResults, outputFile, geneFoldChanges); +0058 % +0059 % Notes +0060 % ----- +0061 % For details about the algorithm, see Patil KR, Nielsen J, Uncovering +0062 % transcriptional regulation of metabolism by using metabolic network +0063 % topology. Proc. Natl Acad. Sci. USA 2005;102:2685-2689. +0064 % +0065 % The background correction uses the closed-form mean and standard +0066 % deviation of the aggregate Z-score of n genes drawn at random from all +0067 % scored genes, so the results are deterministic. +0068 +0069 genes=convertCharArray(genes); +0070 p=parseRAVENargs(varargin, {'printResults',false; 'outputFile',[]; 'geneFoldChanges',[]}); +0071 printResults=p.printResults; +0072 outputFile=p.outputFile; +0073 geneFoldChanges=p.geneFoldChanges; +0074 +0075 %Check some stuff +0076 if numel(genes)~=numel(genePValues) +0077 EM='The number of genes and the number of Z-scores must be the same'; +0078 error('RAVEN:badInput', '%s', EM); +0079 end +0080 if ~isfield(model,'rxnGeneMat') +0081 EM='The model structure must have a rxnGeneMat field'; +0082 error('RAVEN:badInput', '%s', EM); +0083 end +0084 +0085 %All P-values that are given must lie in [0,1]; NaN means that no P-value +0086 %is available and is removed below +0087 badP=~isnan(genePValues) & (genePValues<0 | genePValues>1); +0088 if any(badP) +0089 firstBad=find(badP,1); +0090 EM=sprintf('The P-values must lie between 0 and 1, but %d of them do not. The first is %g (gene %s)',nnz(badP),genePValues(firstBad),genes{firstBad}); +0091 error('RAVEN:badInput', '%s', EM); +0092 end +0093 +0094 %Remove the genes which are not in the model +0095 genePValues(~ismember(genes,model.genes))=[]; +0096 if any(geneFoldChanges) +0097 geneFoldChanges(~ismember(genes,model.genes))=[]; +0098 end +0099 genes(~ismember(genes,model.genes))=[]; +0100 +0101 %Remove the genes that has NA P-value +0102 genes(isnan(genePValues))=[]; +0103 if any(geneFoldChanges) +0104 geneFoldChanges(isnan(genePValues))=[]; +0105 end +0106 genePValues(isnan(genePValues))=[]; +0107 +0108 %Convert p-values to Z-scores. Uses base MATLAB erfcinv instead of the +0109 %Statistics Toolbox norminv: -norminv(p) == sqrt(2)*erfcinv(2*p). +0110 geneZScores=sqrt(2)*erfcinv(2*genePValues); +0111 +0112 %This is to prevent errors if the p-values are exactly 0 or 1 +0113 geneZScores(geneZScores==-inf)=-15; +0114 geneZScores(geneZScores==inf)=15; +0115 +0116 %For each metabolite, calculate the aggregate Z-score and keep track of the +0117 %number of neighbouring genes +0118 metZScores=nan(numel(model.mets),1); +0119 %Sums are taken on Z-scores relative to their mean (found in two passes, the +0120 %second relative to the first gene's Z-score), which keeps the rounding error +0121 %proportional to the spread of the Z-scores instead of to their size +0122 metZShifted=nan(numel(model.mets),1); +0123 zCentre=0; +0124 if ~isempty(geneZScores) +0125 zCentre=geneZScores(1)+mean(geneZScores-geneZScores(1)); +0126 end +0127 metNGenes=nan(numel(model.mets),1); +0128 meanZ=nan(numel(model.mets),1); +0129 stdZ=nan(numel(model.mets),1); +0130 %model.genes -> genes(filtered list) index, computed once rather than +0131 %re-scanning the whole "genes" list (via ismember) for every metabolite, +0132 %which is quadratic in a genome-scale model's mets x genes. +0133 [~,geneIdxInList]=ismember(model.genes,genes); +0134 for i=1:numel(model.mets) +0135 %Get the involved rxns +0136 I=model.S(i,:); 0137 -0138 %Find the genes in the gene list -0139 K=unique(geneIdxInList(J)); -0140 K=K(K>0); -0141 -0142 %Calculate the aggregated Z-score for the metabolite -0143 if any(K) -0144 metZScores(i)=sum(geneZScores(K))/sqrt(numel(K)); -0145 metZShifted(i)=sum(geneZScores(K)-zCentre)/sqrt(numel(K)); -0146 meanZ(i)=mean(geneZScores(K)); -0147 stdZ(i)=std(geneZScores(K)); -0148 metNGenes(i)=numel(K); -0149 end -0150 end -0151 -0152 %Remove the metabolites which have no Z-scores, from all fields alike -0153 noScore=isnan(metZScores); -0154 mets=model.mets(~noScore); -0155 metNames=model.metNames(~noScore); -0156 metZShifted(noScore)=[]; -0157 metZScores(noScore)=[]; -0158 metNGenes(noScore)=[]; -0159 meanZ(noScore)=[]; -0160 stdZ(noScore)=[]; -0161 -0162 %Correct for background using the closed-form moments of the sampling -0163 %distribution. For n genes drawn with replacement from the scored-gene pool -0164 %(mean mu, population std sigma), the aggregate Z = sum(z)/sqrt(n) has: -0165 % E[Z] = sqrt(n)*mu and Std[Z] = sigma (independent of n) -0166 %so the corrected score is (metZ - sqrt(n)*mu) / sigma. When sigma is 0 (all -0167 %scored genes have the same Z-score) no metabolite stands out from the -0168 %background, and the corrected score is 0 (P-value 0.5). The moments are -0169 %taken on Z-scores shifted by their mean, so that mu and the numerator keep -0170 %their precision when the Z-scores are close to each other or the first one is -0171 %an outlier. Identical Z-scores then give exactly 0 for the numerator, and the -0172 %test of the spread of the Z-scores (0 up to 100*eps(max(abs(Z)))) only -0173 %prevents a division of 0 by 0. -0174 zShifted = geneZScores - zCentre; -0175 muShifted = mean(zShifted); -0176 sigma = std(zShifted,1); -0177 if max(geneZScores)-min(geneZScores) > 100*eps(max(abs(geneZScores))) -0178 metZScores = (metZShifted - sqrt(metNGenes) .* muShifted) ./ sigma; -0179 else -0180 metZScores = zeros(size(metZScores)); -0181 end -0182 -0183 %Calculate the P-values (upper-tail of the standard normal). Uses the base -0184 %MATLAB erfc so this does not depend on the Statistics Toolbox normcdf: -0185 %1 - normcdf(z) == 0.5*erfc(z/sqrt(2)). -0186 metPValues=0.5*erfc(metZScores/sqrt(2)); -0187 -0188 %Sort the results -0189 [metZScores, I]=sort(metZScores,'descend'); -0190 mets=mets(I); -0191 metNames=metNames(I); -0192 metPValues=metPValues(I); -0193 metNGenes=metNGenes(I); -0194 meanZ=meanZ(I); -0195 stdZ=stdZ(I); -0196 -0197 %Prepare the output -0198 repMets.mets=mets; -0199 repMets.metNames=metNames; -0200 repMets.metZScores=metZScores; -0201 repMets.metPValues=metPValues; -0202 repMets.metNGenes=metNGenes; -0203 repMets.meanZ=meanZ; -0204 repMets.stdZ=stdZ; -0205 -0206 %Call this function recursively if geneFoldChanges has been specified -0207 repMets(1).test='all'; -0208 if any(geneFoldChanges) -0209 repMets=[repMets;reporterMetabolites(model,genes(geneFoldChanges>=0),genePValues(geneFoldChanges>=0))]; -0210 repMets=[repMets;reporterMetabolites(model,genes(geneFoldChanges<0),genePValues(geneFoldChanges<0))]; -0211 repMets(2).test='only up'; -0212 repMets(3).test='only down'; -0213 end -0214 -0215 %This is for printing results to the screen. For printing a full list of -0216 %all scores, specify a output file -0217 %Escapes metabolite text before it is spliced into an fprintf template, -0218 %since a literal "%" in e.g. a metabolite name would otherwise be misread -0219 %as a format directive and swallow the remainder of the line. -0220 esc=@(s) strrep(s,'%','%%'); -0221 -0222 if printResults==true -0223 for i=1:numel(repMets) -0224 fprintf(['TOP 20 REPORTER METABOLITES\nTEST TYPE: ' repMets(i).test '\nID\tNAME\tP-VALUE\n']); -0225 for j=1:min(20,numel(repMets(i).mets)) -0226 fprintf([esc(repMets(i).mets{j}) '\t' esc(repMets(i).metNames{j}) '\t' num2str(repMets(i).metPValues(j)) '\n']); -0227 end -0228 fprintf('\n'); -0229 end -0230 end -0231 -0232 %Print results to file -0233 if any(outputFile) -0234 fid=fopen(outputFile,'w'); -0235 for i=1:numel(repMets) -0236 fprintf(fid,['REPORTER METABOLITES USING TEST TYPE: ' repMets(i).test '\n']); -0237 fprintf(fid,'ID\tNAME\tZ-SCORE\tP-VALUE\tNUMBER OF NEIGHBOURS\tAVERAGE Z-SCORE\tSTD Z-SCORE\n'); -0238 for j=1:numel(repMets(i).mets) -0239 fprintf(fid,[esc(repMets(i).mets{j}) '\t' esc(repMets(i).metNames{j}) '\t' num2str(repMets(i).metZScores(j)) '\t' num2str(repMets(i).metPValues(j)) '\t' num2str(repMets(i).metNGenes(j)) '\t' num2str(repMets(i).meanZ(j)) '\t' num2str(repMets(i).stdZ(j)) '\n']); -0240 end -0241 end -0242 fclose(fid); -0243 end -0244 end

    +0138 %Get the involved genes +0139 [~, J]=find(model.rxnGeneMat(I~=0,:)); +0140 +0141 %Find the genes in the gene list +0142 K=unique(geneIdxInList(J)); +0143 K=K(K>0); +0144 +0145 %Calculate the aggregated Z-score for the metabolite +0146 if any(K) +0147 metZScores(i)=sum(geneZScores(K))/sqrt(numel(K)); +0148 metZShifted(i)=sum(geneZScores(K)-zCentre)/sqrt(numel(K)); +0149 meanZ(i)=mean(geneZScores(K)); +0150 stdZ(i)=std(geneZScores(K)); +0151 metNGenes(i)=numel(K); +0152 end +0153 end +0154 +0155 %Remove the metabolites which have no Z-scores, from all fields alike +0156 noScore=isnan(metZScores); +0157 mets=model.mets(~noScore); +0158 metNames=model.metNames(~noScore); +0159 metZShifted(noScore)=[]; +0160 metZScores(noScore)=[]; +0161 metNGenes(noScore)=[]; +0162 meanZ(noScore)=[]; +0163 stdZ(noScore)=[]; +0164 +0165 %Correct for background using the closed-form moments of the sampling +0166 %distribution. For n genes drawn with replacement from the scored-gene pool +0167 %(mean mu, population std sigma), the aggregate Z = sum(z)/sqrt(n) has: +0168 % E[Z] = sqrt(n)*mu and Std[Z] = sigma (independent of n) +0169 %so the corrected score is (metZ - sqrt(n)*mu) / sigma. When sigma is 0 (all +0170 %scored genes have the same Z-score) no metabolite stands out from the +0171 %background, and the corrected score is 0 (P-value 0.5). The moments are +0172 %taken on Z-scores shifted by their mean, so that mu and the numerator keep +0173 %their precision when the Z-scores are close to each other or the first one is +0174 %an outlier. Identical Z-scores then give exactly 0 for the numerator, and the +0175 %test of the spread of the Z-scores (0 up to 100*eps(max(abs(Z)))) only +0176 %prevents a division of 0 by 0. +0177 zShifted = geneZScores - zCentre; +0178 muShifted = mean(zShifted); +0179 sigma = std(zShifted,1); +0180 if max(geneZScores)-min(geneZScores) > 100*eps(max(abs(geneZScores))) +0181 metZScores = (metZShifted - sqrt(metNGenes) .* muShifted) ./ sigma; +0182 else +0183 metZScores = zeros(size(metZScores)); +0184 end +0185 +0186 %Calculate the P-values (upper-tail of the standard normal). Uses the base +0187 %MATLAB erfc so this does not depend on the Statistics Toolbox normcdf: +0188 %1 - normcdf(z) == 0.5*erfc(z/sqrt(2)). +0189 metPValues=0.5*erfc(metZScores/sqrt(2)); +0190 +0191 %Sort the results +0192 [metZScores, I]=sort(metZScores,'descend'); +0193 mets=mets(I); +0194 metNames=metNames(I); +0195 metPValues=metPValues(I); +0196 metNGenes=metNGenes(I); +0197 meanZ=meanZ(I); +0198 stdZ=stdZ(I); +0199 +0200 %Prepare the output +0201 repMets.mets=mets; +0202 repMets.metNames=metNames; +0203 repMets.metZScores=metZScores; +0204 repMets.metPValues=metPValues; +0205 repMets.metNGenes=metNGenes; +0206 repMets.meanZ=meanZ; +0207 repMets.stdZ=stdZ; +0208 +0209 %Call this function recursively if geneFoldChanges has been specified +0210 repMets(1).test='all'; +0211 if any(geneFoldChanges) +0212 repMets=[repMets;reporterMetabolites(model,genes(geneFoldChanges>=0),genePValues(geneFoldChanges>=0))]; +0213 repMets=[repMets;reporterMetabolites(model,genes(geneFoldChanges<0),genePValues(geneFoldChanges<0))]; +0214 repMets(2).test='only up'; +0215 repMets(3).test='only down'; +0216 end +0217 +0218 %This is for printing results to the screen. For printing a full list of +0219 %all scores, specify a output file +0220 %Escapes metabolite text before it is spliced into an fprintf template, +0221 %since a literal "%" in e.g. a metabolite name would otherwise be misread +0222 %as a format directive and swallow the remainder of the line. +0223 esc=@(s) strrep(s,'%','%%'); +0224 +0225 if printResults==true +0226 for i=1:numel(repMets) +0227 fprintf(['TOP 20 REPORTER METABOLITES\nTEST TYPE: ' repMets(i).test '\nID\tNAME\tP-VALUE\n']); +0228 for j=1:min(20,numel(repMets(i).mets)) +0229 fprintf([esc(repMets(i).mets{j}) '\t' esc(repMets(i).metNames{j}) '\t' num2str(repMets(i).metPValues(j)) '\n']); +0230 end +0231 fprintf('\n'); +0232 end +0233 end +0234 +0235 %Print results to file +0236 if any(outputFile) +0237 fid=fopen(outputFile,'w'); +0238 for i=1:numel(repMets) +0239 fprintf(fid,['REPORTER METABOLITES USING TEST TYPE: ' repMets(i).test '\n']); +0240 fprintf(fid,'ID\tNAME\tZ-SCORE\tP-VALUE\tNUMBER OF NEIGHBOURS\tAVERAGE Z-SCORE\tSTD Z-SCORE\n'); +0241 for j=1:numel(repMets(i).mets) +0242 fprintf(fid,[esc(repMets(i).mets{j}) '\t' esc(repMets(i).metNames{j}) '\t' num2str(repMets(i).metZScores(j)) '\t' num2str(repMets(i).metPValues(j)) '\t' num2str(repMets(i).metNGenes(j)) '\t' num2str(repMets(i).meanZ(j)) '\t' num2str(repMets(i).stdZ(j)) '\n']); +0243 end +0244 end +0245 fclose(fid); +0246 end +0247 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/analysis/runDynamicFBA.html b/docs/html/analysis/runDynamicFBA.html index de3d3d9f..e3353ab0 100644 --- a/docs/html/analysis/runDynamicFBA.html +++ b/docs/html/analysis/runDynamicFBA.html @@ -4,7 +4,7 @@ Description of runDynamicFBA - + @@ -21,13 +21,13 @@

    runDynamicFBA

    PURPOSE ^

    -
    runDynamicFBA Perform dynamic FBA using the static optimization approach.
    +
    runDynamicFBA Perform dynamic FBA using the static optimisation approach.

    SYNOPSIS ^

    function [concentrationMatrix, excRxnNames, timeVec, biomassVec] = runDynamicFBA(model, substrateRxns, initConcentrations, initBiomass, timeStep, nSteps, plotRxns, exclUptakeRxns)

    DESCRIPTION ^

    -
     runDynamicFBA  Perform dynamic FBA using the static optimization approach.
    +
     runDynamicFBA  Perform dynamic FBA using the static optimisation approach.
     
      Parameters
      ----------
    @@ -35,8 +35,8 @@ 

    DESCRIPTION ^DESCRIPTION ^DESCRIPTION ^CROSS-REFERENCE INFORMATION ^
 
 <h2><a name=SOURCE CODE ^

    0001 function [concentrationMatrix, excRxnNames, timeVec, biomassVec] = runDynamicFBA(model, substrateRxns, initConcentrations, initBiomass, timeStep, nSteps, plotRxns, exclUptakeRxns)
    -0002 % runDynamicFBA  Perform dynamic FBA using the static optimization approach.
    +0002 % runDynamicFBA  Perform dynamic FBA using the static optimisation approach.
     0003 %
     0004 % Parameters
     0005 % ----------
    @@ -104,8 +103,8 @@ 

    SOURCE CODE ^% a model structure. 0008 % substrateRxns : cell 0009 % cell array with exchange reaction identifiers for substrates that are -0010 % initially in the media, whose concentration may change (e.g. not h2o -0011 % or co2). +0010 % initially in the media, whose concentration may change (for example +0011 % not h2o or co2). 0012 % initConcentrations : double 0013 % initial concentrations of substrates (matching substrateRxns). 0014 % initBiomass : double @@ -119,7 +118,7 @@

    SOURCE CODE ^% concentration should be plotted. 0023 % exclUptakeRxns : cell 0024 % cell array with exchange reaction identifiers for substrates whose -0025 % concentration does not change (e.g. co2, o2, h2o, h). +0025 % concentration does not change (for example co2, o2, h2o, h). 0026 % 0027 % Returns 0028 % ------- @@ -142,107 +141,106 @@

    SOURCE CODE ^% Notes 0046 % ----- 0047 % If no initial concentration is given for a substrate that has an open -0048 % uptake in the model (i.e. model.lb < 0) the concentration is assumed to -0049 % be high enough to not be limiting. If the uptake rate for a nutrient is -0050 % calculated to exceed the maximum uptake rate for that nutrient specified -0051 % in the model and the max uptake rate specified is > 0, the maximum uptake -0052 % rate specified in the model is used instead of the calculated uptake -0053 % rate. -0054 % -0055 % Modified from COBRA Toolbox dynamicFBA.m. -0056 -0057 % Find exchange rxns -0058 excRxnNames = getExchangeRxns(model); -0059 excRxnNames(ismember(excRxnNames,exclUptakeRxns))=[]; -0060 excInd = getIndexes(model,excRxnNames,'rxns'); -0061 % Figure out if substrate reactions are correct -0062 missingInd = find(~ismember(substrateRxns,excRxnNames)); -0063 if (~isempty(missingInd)) -0064 for i = 1:length(missingInd) -0065 fprintf('%s\n',substrateRxns{missingInd(i)}); -0066 end -0067 error('Invalid substrate uptake reaction!'); -0068 end -0069 -0070 % Initialize concentrations -0071 [~, substrateMatchInd] = ismember(substrateRxns,excRxnNames); -0072 concentrations = zeros(length(excRxnNames),1); -0073 concentrations(substrateMatchInd) = initConcentrations; -0074 -0075 % Deal with reactions for which there are no initial concentrations -0076 originalBound = -model.lb(excInd); -0077 noInitConcentration = (concentrations == 0 & originalBound > 0); -0078 concentrations(noInitConcentration) = 1000; -0079 -0080 biomass = initBiomass; -0081 -0082 % Initialize bounds -0083 uptakeBound = concentrations/(biomass*timeStep); -0084 -0085 % Make sure bounds are not higher than what are specified in the model -0086 aboveOriginal = (uptakeBound > originalBound) & (originalBound > 0); -0087 uptakeBound(aboveOriginal) = originalBound(aboveOriginal); -0088 model.lb(excInd) = -uptakeBound; -0089 -0090 concentrationMatrix = concentrations; -0091 biomassVec = biomass; -0092 timeVec(1) = 0; -0093 [~,hsSol]=solveLP(model,1); -0094 PB = progressReport(nSteps,'Running dynamic FBA analysis'); -0095 for stepNo = 1:nSteps -0096 % Run FBA -0097 [sol,hsSol] = solveLP(model,1,[],hsSol); -0098 mu = sol.f; -0099 if (sol.stat ~= 1 || mu == 0) -0100 fprintf('\nNo feasible solution - nutrients exhausted. Biomass:\t %f\n', biomass); -0101 break; -0102 end -0103 uptakeFlux = sol.x(excInd); -0104 biomass = biomass*exp(mu*timeStep); -0105 biomassVec(end+1) = biomass; -0106 -0107 % Update concentrations -0108 concentrations = concentrations - uptakeFlux/mu*biomass*(1-exp(mu*timeStep)); -0109 concentrations(concentrations <= 0) = 0; -0110 concentrationMatrix(:,end+1) = concentrations; -0111 -0112 % Update bounds for uptake reactions -0113 uptakeBound = concentrations/(biomass*timeStep); -0114 % This is to avoid any numerical issues -0115 uptakeBound(uptakeBound > 1000) = 1000; -0116 % Figure out if the computed bounds were above the original bounds -0117 aboveOriginal = (uptakeBound > originalBound) & (originalBound > 0); -0118 % Revert to original bounds if the rate was too high -0119 uptakeBound(aboveOriginal) = originalBound(aboveOriginal); -0120 uptakeBound(abs(uptakeBound) < 1e-9) = 0; -0121 -0122 model.lb(excInd) = -uptakeBound; -0123 timeVec(stepNo+1) = stepNo*timeStep; -0124 -0125 count(PB); -0126 end -0127 -0128 selNonZero = any(concentrationMatrix>0,2); -0129 concentrationMatrix = concentrationMatrix(selNonZero,:); -0130 excRxnNames = excRxnNames(selNonZero); -0131 selPlot = ismember(excRxnNames,plotRxns); -0132 -0133 % Plot concentrations as a function of time -0134 clf -0135 subplot(1,2,1); -0136 plot(timeVec,biomassVec); -0137 axis tight -0138 title('Biomass'); -0139 xlabel('Time (h)'); -0140 ylabel('Concentration (g/L)'); -0141 subplot(1,2,2); -0142 plot(timeVec,concentrationMatrix(selPlot,:)); -0143 axis tight -0144 title('Substrates and/or products'); -0145 xlabel('Time (h)'); -0146 ylabel('Concentration (mmol/L)'); -0147 legend(strrep(excRxnNames(selPlot),'EX_','')); -0148 end

    +0048 % uptake in the model (that is, model.lb < 0), the initial concentration is set +0049 % to 1000. If the uptake bound calculated from the concentration exceeds +0050 % the maximum uptake rate specified in the model for that nutrient, and the +0051 % maximum uptake rate specified is > 0, the maximum uptake rate specified +0052 % in the model is used as the uptake bound. +0053 % +0054 % Modified from COBRA Toolbox dynamicFBA.m. +0055 +0056 % Find exchange rxns +0057 excRxnNames = getExchangeRxns(model); +0058 excRxnNames(ismember(excRxnNames,exclUptakeRxns))=[]; +0059 excInd = getIndexes(model,excRxnNames,'rxns'); +0060 % Figure out if substrate reactions are correct +0061 missingInd = find(~ismember(substrateRxns,excRxnNames)); +0062 if (~isempty(missingInd)) +0063 for i = 1:length(missingInd) +0064 fprintf('%s\n',substrateRxns{missingInd(i)}); +0065 end +0066 error('Invalid substrate uptake reaction!'); +0067 end +0068 +0069 % Initialize concentrations +0070 [~, substrateMatchInd] = ismember(substrateRxns,excRxnNames); +0071 concentrations = zeros(length(excRxnNames),1); +0072 concentrations(substrateMatchInd) = initConcentrations; +0073 +0074 % Deal with reactions for which there are no initial concentrations +0075 originalBound = -model.lb(excInd); +0076 noInitConcentration = (concentrations == 0 & originalBound > 0); +0077 concentrations(noInitConcentration) = 1000; +0078 +0079 biomass = initBiomass; +0080 +0081 % Initialize bounds +0082 uptakeBound = concentrations/(biomass*timeStep); +0083 +0084 % Make sure bounds are not higher than what are specified in the model +0085 aboveOriginal = (uptakeBound > originalBound) & (originalBound > 0); +0086 uptakeBound(aboveOriginal) = originalBound(aboveOriginal); +0087 model.lb(excInd) = -uptakeBound; +0088 +0089 concentrationMatrix = concentrations; +0090 biomassVec = biomass; +0091 timeVec(1) = 0; +0092 [~,hsSol]=solveLP(model,1); +0093 PB = progressReport(nSteps,'Running dynamic FBA analysis'); +0094 for stepNo = 1:nSteps +0095 % Run FBA +0096 [sol,hsSol] = solveLP(model,1,[],hsSol); +0097 mu = sol.f; +0098 if (sol.stat ~= 1 || mu == 0) +0099 fprintf('\nNo feasible solution - nutrients exhausted. Biomass:\t %f\n', biomass); +0100 break; +0101 end +0102 uptakeFlux = sol.x(excInd); +0103 biomass = biomass*exp(mu*timeStep); +0104 biomassVec(end+1) = biomass; +0105 +0106 % Update concentrations +0107 concentrations = concentrations - uptakeFlux/mu*biomass*(1-exp(mu*timeStep)); +0108 concentrations(concentrations <= 0) = 0; +0109 concentrationMatrix(:,end+1) = concentrations; +0110 +0111 % Update bounds for uptake reactions +0112 uptakeBound = concentrations/(biomass*timeStep); +0113 % This is to avoid any numerical issues +0114 uptakeBound(uptakeBound > 1000) = 1000; +0115 % Figure out if the computed bounds were above the original bounds +0116 aboveOriginal = (uptakeBound > originalBound) & (originalBound > 0); +0117 % Revert to original bounds if the rate was too high +0118 uptakeBound(aboveOriginal) = originalBound(aboveOriginal); +0119 uptakeBound(abs(uptakeBound) < 1e-9) = 0; +0120 +0121 model.lb(excInd) = -uptakeBound; +0122 timeVec(stepNo+1) = stepNo*timeStep; +0123 +0124 count(PB); +0125 end +0126 +0127 selNonZero = any(concentrationMatrix>0,2); +0128 concentrationMatrix = concentrationMatrix(selNonZero,:); +0129 excRxnNames = excRxnNames(selNonZero); +0130 selPlot = ismember(excRxnNames,plotRxns); +0131 +0132 % Plot concentrations as a function of time +0133 clf +0134 subplot(1,2,1); +0135 plot(timeVec,biomassVec); +0136 axis tight +0137 title('Biomass'); +0138 xlabel('Time (h)'); +0139 ylabel('Concentration (g/L)'); +0140 subplot(1,2,2); +0141 plot(timeVec,concentrationMatrix(selPlot,:)); +0142 axis tight +0143 title('Substrates and/or products'); +0144 xlabel('Time (h)'); +0145 ylabel('Concentration (mmol/L)'); +0146 legend(strrep(excRxnNames(selPlot),'EX_','')); +0147 end

    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/analysis/runProductionEnvelope.html b/docs/html/analysis/runProductionEnvelope.html index 53e5bec4..d1c4b005 100644 --- a/docs/html/analysis/runProductionEnvelope.html +++ b/docs/html/analysis/runProductionEnvelope.html @@ -41,14 +41,18 @@

    DESCRIPTION ^SOURCE CODE ^% Name-Value Arguments 0014 % -------------------- 0015 % nPts : double -0016 % number of points in the plot (default 20). -0017 % -0018 % Returns -0019 % ------- -0020 % biomassValues : double -0021 % biomass values for plotting. -0022 % targetValues : double -0023 % target upper and lower bounds for plotting. -0024 % -0025 % Examples -0026 % -------- -0027 % [biomassValues, targetValues] = runProductionEnvelope(model, ... -0028 % targetRxn, biomassRxn, nPts); -0029 % -0030 % Notes -0031 % ----- -0032 % Modified from COBRA Toolbox productionEnvelope.m. -0033 -0034 p=parseRAVENargs(varargin, {'nPts',20}); -0035 nPts=p.nPts; -0036 -0037 % Run FBA to get upper bound for biomass -0038 model = setParam(model,'obj',biomassRxn,1); -0039 [solMax,hsSol] = solveLP(model); -0040 solMax = solMax.x(logical(model.c)); -0041 model.c = -model.c; -0042 solMin = solveLP(model,0,[],hsSol); -0043 solMin = solMin.x(logical(model.c)); -0044 -0045 % Create biomass range vector -0046 biomassValues = linspace(solMin,solMax,nPts); -0047 targetUpperBound = nan(1,numel(biomassValues)); -0048 targetLowerBound = nan(1,numel(biomassValues)); -0049 -0050 PB = progressReport(length(biomassValues),'Creating production envelope'); -0051 % Max/min for target production -0052 model = setParam(model,'obj',targetRxn,1); -0053 for i = 1:length(biomassValues) -0054 model1 = setParam(model,'eq',biomassRxn,biomassValues(i)); -0055 sol = solveLP(model1,0,[],hsSol); -0056 if (sol.stat > 0) -0057 targetUpperBound(i) = sol.x(logical(model.c)); -0058 else -0059 targetUpperBound(i) = NaN; -0060 end -0061 model1.c = -model1.c; -0062 sol = solveLP(model1,0,[],hsSol); -0063 if (sol.stat > 0) -0064 targetLowerBound(i) = sol.x(logical(model1.c)); -0065 else -0066 targetLowerBound(i) = NaN; -0067 end -0068 count(PB); -0069 end -0070 -0071 % Plot results -0072 plot([biomassValues fliplr(biomassValues)],[targetUpperBound fliplr(targetLowerBound)],'blue','LineWidth',2); -0073 axis tight; -0074 ylabel([strrep(targetRxn,'_','-') ' (mmol/gDW h)']); -0075 xlabel('Growth rate (1/h)'); -0076 targetValues = [targetLowerBound; targetUpperBound]; -0077 end

    +0016 % number of biomass values at which the target bounds are calculated +0017 % (default 20). +0018 % +0019 % Returns +0020 % ------- +0021 % biomassValues : double +0022 % nPts biomass values, evenly spaced from the minimum to the maximum +0023 % biomass flux. +0024 % targetValues : double +0025 % a matrix with two rows: the lower bounds (row 1) and the upper bounds +0026 % (row 2) of the target reaction flux at each biomass value. NaN where +0027 % the problem has no solution. +0028 % +0029 % Examples +0030 % -------- +0031 % [biomassValues, targetValues] = runProductionEnvelope(model, ... +0032 % targetRxn, biomassRxn, nPts); +0033 % +0034 % Notes +0035 % ----- +0036 % Modified from COBRA Toolbox productionEnvelope.m. +0037 +0038 p=parseRAVENargs(varargin, {'nPts',20}); +0039 nPts=p.nPts; +0040 +0041 % Run FBA to get upper bound for biomass +0042 model = setParam(model,'obj',biomassRxn,1); +0043 [solMax,hsSol] = solveLP(model); +0044 solMax = solMax.x(logical(model.c)); +0045 model.c = -model.c; +0046 solMin = solveLP(model,0,[],hsSol); +0047 solMin = solMin.x(logical(model.c)); +0048 +0049 % Create biomass range vector +0050 biomassValues = linspace(solMin,solMax,nPts); +0051 targetUpperBound = nan(1,numel(biomassValues)); +0052 targetLowerBound = nan(1,numel(biomassValues)); +0053 +0054 PB = progressReport(length(biomassValues),'Creating production envelope'); +0055 % Max/min for target production +0056 model = setParam(model,'obj',targetRxn,1); +0057 for i = 1:length(biomassValues) +0058 model1 = setParam(model,'eq',biomassRxn,biomassValues(i)); +0059 sol = solveLP(model1,0,[],hsSol); +0060 if (sol.stat > 0) +0061 targetUpperBound(i) = sol.x(logical(model.c)); +0062 else +0063 targetUpperBound(i) = NaN; +0064 end +0065 model1.c = -model1.c; +0066 sol = solveLP(model1,0,[],hsSol); +0067 if (sol.stat > 0) +0068 targetLowerBound(i) = sol.x(logical(model1.c)); +0069 else +0070 targetLowerBound(i) = NaN; +0071 end +0072 count(PB); +0073 end +0074 +0075 % Plot results +0076 plot([biomassValues fliplr(biomassValues)],[targetUpperBound fliplr(targetLowerBound)],'blue','LineWidth',2); +0077 axis tight; +0078 ylabel([strrep(targetRxn,'_','-') ' (mmol/gDW h)']); +0079 xlabel('Growth rate (1/h)'); +0080 targetValues = [targetLowerBound; targetUpperBound]; +0081 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/analysis/runRobustnessAnalysis.html b/docs/html/analysis/runRobustnessAnalysis.html index 3c95349a..7214e60a 100644 --- a/docs/html/analysis/runRobustnessAnalysis.html +++ b/docs/html/analysis/runRobustnessAnalysis.html @@ -29,8 +29,9 @@

    SYNOPSIS ^DESCRIPTION ^

     runRobustnessAnalysis  Perform robustness analysis for a reaction and objective.
     
    - Performs robustness analysis for a reaction of interest and an objective
    - of interest.
    + Varies the flux of a reaction of interest from its minimum to its maximum
    + and calculates the optimal value of an objective of interest at each
    + flux.
     
      Parameters
      ----------
    @@ -42,12 +43,13 @@ 

    DESCRIPTION ^SOURCE CODE ^
    0001 function [controlFlux, objFlux] = runRobustnessAnalysis(model, controlRxn, varargin)
     0002 % runRobustnessAnalysis  Perform robustness analysis for a reaction and objective.
     0003 %
    -0004 % Performs robustness analysis for a reaction of interest and an objective
    -0005 % of interest.
    -0006 %
    -0007 % Parameters
    -0008 % ----------
    -0009 % model : struct
    -0010 %     a model structure.
    -0011 % controlRxn : char
    -0012 %     reaction of interest whose value is to be controlled.
    -0013 %
    -0014 % Name-Value Arguments
    -0015 % --------------------
    -0016 % nPoints : double
    -0017 %     number of points to show on plot (default 20).
    -0018 % objRxn : char
    -0019 %     reaction identifier of objective to be maximized (default uses the
    -0020 %     objective defined in the model).
    -0021 % plotRedCost : logical
    -0022 %     whether reduced cost should also be plotted (default false).
    -0023 %
    -0024 % Returns
    -0025 % -------
    -0026 % controlFlux : double
    -0027 %     flux values of the reaction of interest, ranging from its minimum to
    -0028 %     its maximum value.
    -0029 % objFlux : double
    -0030 %     optimal values of objective reaction at each control reaction flux
    -0031 %     value.
    -0032 %
    -0033 % Examples
    -0034 % --------
    -0035 %     [controlFlux, objFlux] = runRobustnessAnalysis(model, controlRxn, ...
    -0036 %         nPoints, objRxn);
    -0037 %
    -0038 % Notes
    -0039 % -----
    -0040 % Modified from COBRA Toolbox robustnessAnalysis.m.
    -0041 
    -0042 p=parseRAVENargs(varargin, {'nPoints',20; 'objRxn',[]; 'plotRedCost',false});
    -0043 nPoints=p.nPoints;
    -0044 objRxn=p.objRxn;
    -0045 plotRedCost=p.plotRedCost;
    -0046 if isempty(objRxn)
    -0047     baseModel = model;
    -0048 else
    -0049     baseModel = setParam(model,'obj',objRxn,1);
    -0050 end
    -0051 
    -0052 if any(ismember(model.rxns,controlRxn))
    -0053     controlRxnIdx = getIndexes(model,controlRxn,'rxns');
    -0054     tmpModel = setParam(model,'obj',controlRxnIdx,1);
    -0055     solMax = solveLP(tmpModel);
    -0056     solMax = solMax.x(logical(tmpModel.c));
    -0057     tmpModel.c = -tmpModel.c;
    -0058     solMin = solveLP(tmpModel);
    -0059     solMin = solMin.x(logical(tmpModel.c));
    -0060 else
    -0061     error('Control reaction does not exist!');
    -0062 end
    -0063 
    -0064 objFlux = zeros(nPoints,1);
    -0065 redCost = zeros(nPoints,1);
    -0066 controlFlux = linspace(solMin,solMax,nPoints)';
    -0067 
    -0068 
    -0069 PB = progressReport(length(controlFlux),'Running robustness analysis');
    -0070 for i=1:length(controlFlux)
    -0071     modelControlled = setParam(baseModel,'eq',controlRxnIdx,controlFlux(i));
    -0072     solControlled = solveLP(modelControlled);
    -0073     objFlux(i) = solControlled.x(logical(modelControlled.c));
    -0074     redCost(i) = solControlled.rCost(controlRxnIdx);
    -0075     count(PB);
    -0076 end
    -0077 
    -0078 if plotRedCost
    -0079     yyaxis right
    -0080     plot(controlFlux,redCost)
    -0081     ylabel([strrep(controlRxn,'_','-') ' reduced cost']);
    -0082     yyaxis left
    -0083 end
    -0084 plot(controlFlux,objFlux)
    -0085 xlabel(strrep(controlRxn,'_','-'));
    -0086 ylabel('Objective');
    -0087 end

    +0004 % Varies the flux of a reaction of interest from its minimum to its maximum +0005 % and calculates the optimal value of an objective of interest at each +0006 % flux. +0007 % +0008 % Parameters +0009 % ---------- +0010 % model : struct +0011 % a model structure. +0012 % controlRxn : char +0013 % reaction of interest whose value is to be controlled. +0014 % +0015 % Name-Value Arguments +0016 % -------------------- +0017 % nPoints : double +0018 % number of control flux values (default 20). +0019 % objRxn : char +0020 % reaction identifier of the objective to be maximised (default: the +0021 % objective defined in the model). +0022 % plotRedCost : logical +0023 % whether the reduced cost of the control reaction is also plotted on +0024 % the right axis (default false). +0025 % +0026 % Returns +0027 % ------- +0028 % controlFlux : double +0029 % flux values of the reaction of interest, ranging from its minimum to +0030 % its maximum value. +0031 % objFlux : double +0032 % optimal values of objective reaction at each control reaction flux +0033 % value. +0034 % +0035 % Examples +0036 % -------- +0037 % [controlFlux, objFlux] = runRobustnessAnalysis(model, controlRxn, ... +0038 % nPoints, objRxn); +0039 % +0040 % Notes +0041 % ----- +0042 % Modified from COBRA Toolbox robustnessAnalysis.m. +0043 +0044 p=parseRAVENargs(varargin, {'nPoints',20; 'objRxn',[]; 'plotRedCost',false}); +0045 nPoints=p.nPoints; +0046 objRxn=p.objRxn; +0047 plotRedCost=p.plotRedCost; +0048 if isempty(objRxn) +0049 baseModel = model; +0050 else +0051 baseModel = setParam(model,'obj',objRxn,1); +0052 end +0053 +0054 if any(ismember(model.rxns,controlRxn)) +0055 controlRxnIdx = getIndexes(model,controlRxn,'rxns'); +0056 tmpModel = setParam(model,'obj',controlRxnIdx,1); +0057 solMax = solveLP(tmpModel); +0058 solMax = solMax.x(logical(tmpModel.c)); +0059 tmpModel.c = -tmpModel.c; +0060 solMin = solveLP(tmpModel); +0061 solMin = solMin.x(logical(tmpModel.c)); +0062 else +0063 error('Control reaction does not exist!'); +0064 end +0065 +0066 objFlux = zeros(nPoints,1); +0067 redCost = zeros(nPoints,1); +0068 controlFlux = linspace(solMin,solMax,nPoints)'; +0069 +0070 +0071 PB = progressReport(length(controlFlux),'Running robustness analysis'); +0072 for i=1:length(controlFlux) +0073 modelControlled = setParam(baseModel,'eq',controlRxnIdx,controlFlux(i)); +0074 solControlled = solveLP(modelControlled); +0075 objFlux(i) = solControlled.x(logical(modelControlled.c)); +0076 redCost(i) = solControlled.rCost(controlRxnIdx); +0077 count(PB); +0078 end +0079 +0080 if plotRedCost +0081 yyaxis right +0082 plot(controlFlux,redCost) +0083 ylabel([strrep(controlRxn,'_','-') ' reduced cost']); +0084 yyaxis left +0085 end +0086 plot(controlFlux,objFlux) +0087 xlabel(strrep(controlRxn,'_','-')); +0088 ylabel('Objective'); +0089 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/analysis/runSimpleOptKnock.html b/docs/html/analysis/runSimpleOptKnock.html index 498f7693..2479a938 100644 --- a/docs/html/analysis/runSimpleOptKnock.html +++ b/docs/html/analysis/runSimpleOptKnock.html @@ -4,7 +4,7 @@ Description of runSimpleOptKnock - + @@ -21,18 +21,22 @@

    runSimpleOptKnock

    PURPOSE ^

    -
    runSimpleOptKnock Simple OptKnock for growth-coupled production.
    +
    runSimpleOptKnock Find deletions that couple metabolite production to growth.

    SYNOPSIS ^

    function out = runSimpleOptKnock(model, targetRxn, biomassRxn, varargin)

    DESCRIPTION ^

    -
     runSimpleOptKnock  Simple OptKnock for growth-coupled production.
    +
     runSimpleOptKnock  Find deletions that couple metabolite production to growth.
     
    - Simple OptKnock algorithm that checks all gene or reaction deletions for
    - growth-coupled metabolite production, by testing all possible
    - combinations. This is not defined as MILP, and is therefore slow (but
    - simple).
    + Checks all gene or reaction deletions for growth-coupled metabolite
    + production, by testing combinations of up to maxNumKO deletions. It tries
    + a deeper deletion only after a deletion whose growth rate is above
    + minGrowth. A combination is reported if the growth rate exceeds
    + minGrowth and the product of growth rate and production rate exceeds the
    + value without the last added deletion (the wild type for the first
    + deletion) by more than 1 %. The method is not defined as a MILP and is
    + therefore slow.
     
      Parameters
      ----------
    @@ -65,6 +69,7 @@ 

    DESCRIPTION ^SUBFUNCTIONS ^SOURCE CODE ^

    0001 function out = runSimpleOptKnock(model, targetRxn, biomassRxn, varargin)
    -0002 % runSimpleOptKnock  Simple OptKnock for growth-coupled production.
    +0002 % runSimpleOptKnock  Find deletions that couple metabolite production to growth.
     0003 %
    -0004 % Simple OptKnock algorithm that checks all gene or reaction deletions for
    -0005 % growth-coupled metabolite production, by testing all possible
    -0006 % combinations. This is not defined as MILP, and is therefore slow (but
    -0007 % simple).
    -0008 %
    -0009 % Parameters
    -0010 % ----------
    -0011 % model : struct
    -0012 %     a model structure.
    -0013 % targetRxn : char
    -0014 %     identifier of target reaction.
    -0015 % biomassRxn : char
    -0016 %     identifier of biomass reaction.
    -0017 %
    -0018 % Name-Value Arguments
    -0019 % --------------------
    -0020 % deletions : cell
    -0021 %     cell array with gene or reaction identifiers that should be
    -0022 %     considered for knockout (default model.rxns).
    -0023 % genesOrRxns : char
    -0024 %     string indicating whether deletions parameter is given with 'genes'
    -0025 %     or 'rxns' identifiers (default 'rxns').
    -0026 % maxNumKO : double
    -0027 %     maximum number of simultaneous knockouts (default 1).
    -0028 % minGrowth : double
    -0029 %     minimum growth rate (default 0.05).
    -0030 %
    -0031 % Returns
    -0032 % -------
    -0033 % out : struct
    -0034 %     structure with deletion strategies that result in growth-coupled
    -0035 %     production, with fields:
    -0036 %
    -0037 %     - KO : cell array with gene(s) or reaction(s) to be deleted.
    -0038 %     - growthRate : vector with growth rates after deletion.
    -0039 %     - prodRate : vector with production rates after deletion.
    +0004 % Checks all gene or reaction deletions for growth-coupled metabolite
    +0005 % production, by testing combinations of up to maxNumKO deletions. It tries
    +0006 % a deeper deletion only after a deletion whose growth rate is above
    +0007 % minGrowth. A combination is reported if the growth rate exceeds
    +0008 % minGrowth and the product of growth rate and production rate exceeds the
    +0009 % value without the last added deletion (the wild type for the first
    +0010 % deletion) by more than 1 %. The method is not defined as a MILP and is
    +0011 % therefore slow.
    +0012 %
    +0013 % Parameters
    +0014 % ----------
    +0015 % model : struct
    +0016 %     a model structure.
    +0017 % targetRxn : char
    +0018 %     identifier of target reaction.
    +0019 % biomassRxn : char
    +0020 %     identifier of biomass reaction.
    +0021 %
    +0022 % Name-Value Arguments
    +0023 % --------------------
    +0024 % deletions : cell
    +0025 %     cell array with gene or reaction identifiers that should be
    +0026 %     considered for knockout (default model.rxns).
    +0027 % genesOrRxns : char
    +0028 %     string indicating whether deletions parameter is given with 'genes'
    +0029 %     or 'rxns' identifiers (default 'rxns').
    +0030 % maxNumKO : double
    +0031 %     maximum number of simultaneous knockouts (default 1).
    +0032 % minGrowth : double
    +0033 %     minimum growth rate (default 0.05).
    +0034 %
    +0035 % Returns
    +0036 % -------
    +0037 % out : struct
    +0038 %     structure with deletion strategies that result in growth-coupled
    +0039 %     production, with fields:
     0040 %
    -0041 % Examples
    -0042 % --------
    -0043 %     out = runSimpleOptKnock(model, targetRxn, biomassRxn, deletions, ...
    -0044 %         genesOrRxns, maxNumKO, minGrowth);
    -0045 
    -0046 p=parseRAVENargs(varargin, {'deletions',[]; 'genesOrRxns','rxns'; 'maxNumKO',1; 'minGrowth',0.05});
    -0047 deletions=p.deletions;
    -0048 genesOrRxns=p.genesOrRxns;
    -0049 maxNumKO=p.maxNumKO;
    -0050 minGrowth=p.minGrowth;
    -0051 if isempty(deletions)
    -0052     params.deletions = model.rxns;
    -0053 else
    -0054     params.deletions = deletions;
    -0055 end
    -0056 params.genesOrRxns = genesOrRxns;
    -0057 params.maxNumKO = maxNumKO;
    -0058 params.minGrowth = minGrowth;
    -0059 
    -0060 % Number of deletions
    -0061 out.KO         = cell(0,params.maxNumKO); % The KO genes/rxns
    -0062 out.growthRate = zeros(0);
    -0063 out.prodRate   = zeros(0);
    -0064 out.score      = zeros(0);
    -0065 
    -0066 params.biomassIdx  = getIndexes(model,biomassRxn,'rxns');
    -0067 params.targetIdx   = getIndexes(model,targetRxn,'rxns');
    -0068 
    -0069 model = setParam(model,'obj',params.biomassIdx,1);
    -0070 [solWT, hsSol] = solveLP(model);
    -0071 WT.minScore = solWT.x(params.targetIdx)*solWT.f;
    -0072 
    -0073 params.PB = progressReport(numel(params.deletions),'Running simple OptKnock analysis');
    -0074 KO=zeros(1,params.maxNumKO);
    -0075 [~,~,out,~] = knockoutIteration(model,params,WT,out,params.maxNumKO,KO,[],hsSol);
    -0076 
    -0077 if size(out.KO,2)>1
    -0078     singleKO = cellfun(@isempty,out.KO(:,1));
    -0079     if any(singleKO)
    -0080         singleKO = out.KO{singleKO,2};
    -0081         singleKO = strcmp(out.KO(:,1),singleKO);
    -0082         out.KO(singleKO,:) = [];
    -0083         out.growthRate(singleKO,:) = [];
    -0084         out.prodRate(singleKO,:) = [];
    -0085         out.score(singleKO,:) = [];
    -0086     end
    -0087 end
    -0088 
    -0089 
    -0090 params.PB.done;
    -0091 end
    -0092 
    -0093 function [model,iteration,out,KO,hsSol] = knockoutIteration(model,params,WT,out,iteration,KO,minScore,hsSol)
    -0094 if nargin<7 || isempty(minScore)
    -0095     minScore = WT.minScore;
    +0041 %     - KO : cell array with gene(s) or reaction(s) to be deleted.
    +0042 %     - growthRate : vector with growth rates after deletion.
    +0043 %     - prodRate : vector with production rates after deletion.
    +0044 %     - score : vector with the product of growth rate and production rate.
    +0045 %
    +0046 % Examples
    +0047 % --------
    +0048 %     out = runSimpleOptKnock(model, targetRxn, biomassRxn, deletions, ...
    +0049 %         genesOrRxns, maxNumKO, minGrowth);
    +0050 
    +0051 p=parseRAVENargs(varargin, {'deletions',[]; 'genesOrRxns','rxns'; 'maxNumKO',1; 'minGrowth',0.05});
    +0052 deletions=p.deletions;
    +0053 genesOrRxns=p.genesOrRxns;
    +0054 maxNumKO=p.maxNumKO;
    +0055 minGrowth=p.minGrowth;
    +0056 if isempty(deletions)
    +0057     params.deletions = model.rxns;
    +0058 else
    +0059     params.deletions = deletions;
    +0060 end
    +0061 params.genesOrRxns = genesOrRxns;
    +0062 params.maxNumKO = maxNumKO;
    +0063 params.minGrowth = minGrowth;
    +0064 
    +0065 % Number of deletions
    +0066 out.KO         = cell(0,params.maxNumKO); % The KO genes/rxns
    +0067 out.growthRate = zeros(0);
    +0068 out.prodRate   = zeros(0);
    +0069 out.score      = zeros(0);
    +0070 
    +0071 params.biomassIdx  = getIndexes(model,biomassRxn,'rxns');
    +0072 params.targetIdx   = getIndexes(model,targetRxn,'rxns');
    +0073 
    +0074 model = setParam(model,'obj',params.biomassIdx,1);
    +0075 [solWT, hsSol] = solveLP(model);
    +0076 WT.minScore = solWT.x(params.targetIdx)*solWT.f;
    +0077 
    +0078 params.PB = progressReport(numel(params.deletions),'Running simple OptKnock analysis');
    +0079 KO=zeros(1,params.maxNumKO);
    +0080 [~,~,out,~] = knockoutIteration(model,params,WT,out,params.maxNumKO,KO,[],hsSol);
    +0081 
    +0082 if size(out.KO,2)>1
    +0083     singleKO = cellfun(@isempty,out.KO(:,1));
    +0084     if any(singleKO)
    +0085         singleKO = out.KO{singleKO,2};
    +0086         singleKO = strcmp(out.KO(:,1),singleKO);
    +0087         out.KO(singleKO,:) = [];
    +0088         out.growthRate(singleKO,:) = [];
    +0089         out.prodRate(singleKO,:) = [];
    +0090         out.score(singleKO,:) = [];
    +0091     end
    +0092 end
    +0093 
    +0094 
    +0095 params.PB.done;
     0096 end
    -0097 iteration = iteration - 1;
    -0098 for i = 1:numel(params.deletions)
    -0099     if iteration+1==params.maxNumKO
    -0100         params.PB.update(i);
    -0101     end
    -0102     KO(iteration+1)=i;
    -0103     switch params.genesOrRxns
    -0104         case 'rxns'
    -0105             modelKO = setParam(model,'eq',params.deletions{i},0);
    -0106         case 'genes'
    -0107             modelKO = removeGenes(model,params.deletions{i},false,false,false);
    -0108     end
    -0109     solKO = solveLP(modelKO,0,[],hsSol);
    -0110     if ~isempty(solKO.f)
    -0111         growthRate = solKO.f;
    -0112         prodRate   = solKO.x(params.targetIdx);
    -0113         prodRate(prodRate<1e-10)=0; % Filter out results from solver tolerance
    -0114         if growthRate > params.minGrowth
    -0115             if growthRate*prodRate > minScore*1.01
    -0116                 out.KO(end+1,find(KO)) = transpose(params.deletions(KO(find(KO))));
    -0117                 out.growthRate(end+1,1) = growthRate;
    -0118                 out.prodRate(end+1,1)   = prodRate;
    -0119                 out.score(end+1,1)      = growthRate*prodRate;
    -0120             end
    -0121             if iteration>0
    -0122                 [~,~,out] = knockoutIteration(modelKO,params,WT,out,iteration,KO,growthRate*prodRate,hsSol);
    -0123             end
    -0124         end
    -0125     end
    -0126 end
    -0127 end
    +0097 +0098 function [model,iteration,out,KO,hsSol] = knockoutIteration(model,params,WT,out,iteration,KO,minScore,hsSol) +0099 if nargin<7 || isempty(minScore) +0100 minScore = WT.minScore; +0101 end +0102 iteration = iteration - 1; +0103 for i = 1:numel(params.deletions) +0104 if iteration+1==params.maxNumKO +0105 params.PB.update(i); +0106 end +0107 KO(iteration+1)=i; +0108 switch params.genesOrRxns +0109 case 'rxns' +0110 modelKO = setParam(model,'eq',params.deletions{i},0); +0111 case 'genes' +0112 modelKO = removeGenes(model,params.deletions{i},false,false,false); +0113 end +0114 solKO = solveLP(modelKO,0,[],hsSol); +0115 if ~isempty(solKO.f) +0116 growthRate = solKO.f; +0117 prodRate = solKO.x(params.targetIdx); +0118 prodRate(prodRate<1e-10)=0; % Filter out results from solver tolerance +0119 if growthRate > params.minGrowth +0120 if growthRate*prodRate > minScore*1.01 +0121 out.KO(end+1,find(KO)) = transpose(params.deletions(KO(find(KO)))); +0122 out.growthRate(end+1,1) = growthRate; +0123 out.prodRate(end+1,1) = prodRate; +0124 out.score(end+1,1) = growthRate*prodRate; +0125 end +0126 if iteration>0 +0127 [~,~,out] = knockoutIteration(modelKO,params,WT,out,iteration,KO,growthRate*prodRate,hsSol); +0128 end +0129 end +0130 end +0131 end +0132 end

    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/analysis/sampleACHR.html b/docs/html/analysis/sampleACHR.html index 6f280c35..2d19b234 100644 --- a/docs/html/analysis/sampleACHR.html +++ b/docs/html/analysis/sampleACHR.html @@ -31,8 +31,9 @@

    DESCRIPTION ^DESCRIPTION ^SOURCE CODE ^% 0004 % Draws nSamples flux vectors approximately uniformly from the flux polytope 0005 % {v : S*v = 0, lb <= v <= ub}, using the ACHR algorithm (Kaufman & Smith 1998). -0006 % No rounding is performed, so on strongly elongated polytopes (e.g. -0007 % enzyme-constrained models) mixing is slower than sampleCHRR; prefer CHRR there. -0008 % Reactions whose bounds or warmup values coincide never move. The remaining -0009 % reactions are corrected towards S*v = 0 every few steps and before each -0010 % recorded sample, with a sparse Cholesky factor of the row-normalised S*S' -0011 % (no dense null-space basis), so the rounding error in S*v does not grow with -0012 % the number of steps and samples stay inside the bounds. The guarantee -0013 % S*v = 0 holds for a well-conditioned S and warmup points that are feasible -0014 % for the model. When the largest residual of a recorded sample exceeds 1e-9 -0015 % times max(1, max(abs(v))) (nearly dependent rows of S, a noisy or infeasible -0016 % warmup), sampleACHR issues the warning sampleACHR:steadyStateNotRestored. -0017 % -0018 % Parameters -0019 % ---------- -0020 % model : struct -0021 % a RAVEN model structure. -0022 % nSamples : double -0023 % number of flux vectors to return (default 1000). -0024 % thinning : double -0025 % Markov-chain steps between recorded samples (default 100). -0026 % warmup : double -0027 % nPoints-by-nRxns warmup vertices (default: generated by sampleWarmupPoints). -0028 % seed : double -0029 % random seed for reproducibility (default: current RNG state). -0030 % -0031 % Returns -0032 % ------- -0033 % solutions : double -0034 % nRxns-by-nSamples matrix of sampled flux distributions. -0035 % -0036 % See also -0037 % -------- -0038 % randomSampling, sampleCHRR, sampleWarmupPoints -0039 -0040 if nargin < 2 || isempty(nSamples); nSamples = 1000; end -0041 if nargin < 3 || isempty(thinning); thinning = 100; end -0042 if nargin < 4 || isempty(warmup); warmup = sampleWarmupPoints(model); end -0043 if nargin < 5; seed = []; end -0044 if ~isempty(seed) -0045 rng(seed); -0046 end -0047 -0048 nRxns = numel(model.rxns); -0049 lb = model.lb(:); -0050 ub = model.ub(:); -0051 nW = size(warmup, 1); -0052 -0053 % Reactions that cannot move: fixed bounds, or identical value in all warmup -0054 % points. Directions and corrections act on the other reactions only. -0055 spread = (max(warmup, [], 1) - min(warmup, [], 1))'; -0056 free = (ub - lb > 1e-9) & (spread > 1e-9); -0057 proj = buildSteadyStateCorrection(model.S, free, lb, ub); -0058 corrEvery = min(thinning, 20); -0059 -0060 center = mean(warmup, 1)'; % nRxns-by-1 -0061 prev = restoreSteadyState(center, proj); -0062 center = prev; -0063 nTotal = 0; -0064 -0065 solutions = zeros(nRxns, nSamples); -0066 rec = 0; -0067 worstResidual = 0; -0068 nSteps = thinning*nSamples; -0069 -0070 for stepno = 1:nSteps -0071 pidx = randi(nW); -0072 delta = warmup(pidx, :)' - center; -0073 delta(~free) = 0; -0074 dirTol = 1e-12*max(1, max(abs(delta))); -0075 -0076 posD = delta > dirTol; -0077 negD = delta < -dirTol; -0078 ubp = ub - prev; -0079 lbp = lb - prev; -0080 % alpha range keeping lb <= prev + alpha*delta <= ub -0081 upperLims = [ubp(posD)./delta(posD); lbp(negD)./delta(negD)]; -0082 lowerLims = [lbp(posD)./delta(posD); ubp(negD)./delta(negD)]; -0083 if isempty(upperLims); tHi = 0; else; tHi = min(upperLims); end -0084 if isempty(lowerLims); tLo = 0; else; tLo = max(lowerLims); end -0085 -0086 if tHi > tLo -0087 alpha = tLo + rand*(tHi - tLo); -0088 prev = min(max(prev + alpha*delta, lb), ub); -0089 end -0090 -0091 % Running centre (quasi-Markovian ACHR: centre converges and steadies). -0092 center = (nTotal*center + prev) / (nTotal + 1); -0093 nTotal = nTotal + 1; -0094 -0095 if mod(stepno, corrEvery) == 0 || mod(stepno, thinning) == 0 -0096 prev = restoreSteadyState(prev, proj); -0097 center = restoreSteadyState(center, proj); -0098 end -0099 -0100 if mod(stepno, thinning) == 0 -0101 rec = rec + 1; -0102 solutions(:, rec) = prev; -0103 worstResidual = max(worstResidual, ... -0104 max(abs(model.S*prev))/max(1, max(abs(prev)))); -0105 end -0106 end -0107 if worstResidual > 1e-9 -0108 warning('sampleACHR:steadyStateNotRestored', ... -0109 ['The largest residual of S*v over the samples is %.3g times max(1, max(abs(v))), ' ... -0110 'above 1e-9. The rows of S are nearly dependent or the warmup points are not ' ... -0111 'feasible for the model.'], worstResidual); -0112 end +0006 % No rounding is performed, so on strongly elongated polytopes (for example +0007 % enzyme-constrained models) mixing is slower than with sampleCHRR, which +0008 % rounds the polytope. +0009 % Reactions whose bounds or warmup values coincide never move. The remaining +0010 % reactions are corrected towards S*v = 0 every few steps and before each +0011 % recorded sample, with a sparse Cholesky factor of the row-normalised S*S' +0012 % (no dense null-space basis), so the rounding error in S*v does not grow with +0013 % the number of steps and samples stay inside the bounds. The guarantee +0014 % S*v = 0 holds for a well-conditioned S and warmup points that are feasible +0015 % for the model. When the largest residual of a recorded sample exceeds 1e-9 +0016 % times max(1, max(abs(v))) (nearly dependent rows of S, a noisy or infeasible +0017 % warmup), sampleACHR issues the warning sampleACHR:steadyStateNotRestored. +0018 % +0019 % Parameters +0020 % ---------- +0021 % model : struct +0022 % a model structure. +0023 % nSamples : double +0024 % number of flux vectors to return (default 1000). +0025 % thinning : double +0026 % Markov-chain steps between recorded samples (default 100). +0027 % warmup : double +0028 % nPoints-by-nRxns warmup vertices (default: generated by sampleWarmupPoints). +0029 % seed : double +0030 % random seed for reproducibility (default: current RNG state). +0031 % +0032 % Returns +0033 % ------- +0034 % solutions : double +0035 % nRxns-by-nSamples matrix of sampled flux distributions. +0036 % +0037 % See also +0038 % -------- +0039 % randomSampling, sampleCHRR, sampleWarmupPoints +0040 +0041 if nargin < 2 || isempty(nSamples); nSamples = 1000; end +0042 if nargin < 3 || isempty(thinning); thinning = 100; end +0043 if nargin < 4 || isempty(warmup); warmup = sampleWarmupPoints(model); end +0044 if nargin < 5; seed = []; end +0045 if ~isempty(seed) +0046 rng(seed); +0047 end +0048 +0049 nRxns = numel(model.rxns); +0050 lb = model.lb(:); +0051 ub = model.ub(:); +0052 nW = size(warmup, 1); +0053 +0054 % Reactions that cannot move: fixed bounds, or identical value in all warmup +0055 % points. Directions and corrections act on the other reactions only. +0056 spread = (max(warmup, [], 1) - min(warmup, [], 1))'; +0057 free = (ub - lb > 1e-9) & (spread > 1e-9); +0058 proj = buildSteadyStateCorrection(model.S, free, lb, ub); +0059 corrEvery = min(thinning, 20); +0060 +0061 center = mean(warmup, 1)'; % nRxns-by-1 +0062 prev = restoreSteadyState(center, proj); +0063 center = prev; +0064 nTotal = 0; +0065 +0066 solutions = zeros(nRxns, nSamples); +0067 rec = 0; +0068 worstResidual = 0; +0069 nSteps = thinning*nSamples; +0070 +0071 for stepno = 1:nSteps +0072 pidx = randi(nW); +0073 delta = warmup(pidx, :)' - center; +0074 delta(~free) = 0; +0075 dirTol = 1e-12*max(1, max(abs(delta))); +0076 +0077 posD = delta > dirTol; +0078 negD = delta < -dirTol; +0079 ubp = ub - prev; +0080 lbp = lb - prev; +0081 % alpha range keeping lb <= prev + alpha*delta <= ub +0082 upperLims = [ubp(posD)./delta(posD); lbp(negD)./delta(negD)]; +0083 lowerLims = [lbp(posD)./delta(posD); ubp(negD)./delta(negD)]; +0084 if isempty(upperLims); tHi = 0; else; tHi = min(upperLims); end +0085 if isempty(lowerLims); tLo = 0; else; tLo = max(lowerLims); end +0086 +0087 if tHi > tLo +0088 alpha = tLo + rand*(tHi - tLo); +0089 prev = min(max(prev + alpha*delta, lb), ub); +0090 end +0091 +0092 % Running centre (quasi-Markovian ACHR: centre converges and steadies). +0093 center = (nTotal*center + prev) / (nTotal + 1); +0094 nTotal = nTotal + 1; +0095 +0096 if mod(stepno, corrEvery) == 0 || mod(stepno, thinning) == 0 +0097 prev = restoreSteadyState(prev, proj); +0098 center = restoreSteadyState(center, proj); +0099 end +0100 +0101 if mod(stepno, thinning) == 0 +0102 rec = rec + 1; +0103 solutions(:, rec) = prev; +0104 worstResidual = max(worstResidual, ... +0105 max(abs(model.S*prev))/max(1, max(abs(prev)))); +0106 end +0107 end +0108 if worstResidual > 1e-9 +0109 warning('sampleACHR:steadyStateNotRestored', ... +0110 ['The largest residual of S*v over the samples is %.3g times max(1, max(abs(v))), ' ... +0111 'above 1e-9. The rows of S are nearly dependent or the warmup points are not ' ... +0112 'feasible for the model.'], worstResidual); 0113 end -0114 -0115 function proj = buildSteadyStateCorrection(S, free, lb, ub) -0116 % Sparse factor of the row-normalised S(:,free)*S(:,free)' with a small ridge, -0117 % used to move a flux vector onto S*v = 0 through the free reactions only. -0118 Sf = S(:, free); -0119 rowNorm = sqrt(full(sum(Sf.^2, 2))); -0120 keep = rowNorm > 0; -0121 rowNorm = rowNorm(keep); -0122 Sn = spdiags(1./rowNorm, 0, numel(rowNorm), numel(rowNorm))*Sf(keep, :); -0123 A = Sn*Sn' + 1e-10*speye(size(Sn, 1)); -0124 [R, flag, p] = chol(A, 'vector'); -0125 if flag ~= 0 -0126 error('sampleACHR:factorization', 'The stoichiometric matrix could not be factorized.'); -0127 end -0128 proj = struct('S', S, 'free', free, 'keep', keep, 'rowNorm', rowNorm, ... -0129 'Sn', Sn, 'R', R, 'Rt', R', 'p', p, 'lb', lb, 'ub', ub); -0130 end -0131 -0132 function v = restoreSteadyState(v, proj) -0133 % Removes the residual of S*v through the free reactions (a few ridge-regularised -0134 % projection passes), then clips v to its bounds. -0135 tol = 1e-12*max(1, max(abs(v))); -0136 for it = 1:8 -0137 r = proj.S*v; -0138 if max(abs(r)) <= tol -0139 break -0140 end -0141 rn = r(proj.keep) ./ proj.rowNorm; -0142 x = zeros(size(rn)); -0143 x(proj.p) = proj.R \ (proj.Rt \ rn(proj.p)); -0144 v(proj.free) = v(proj.free) - proj.Sn'*x; -0145 end -0146 v = min(max(v, proj.lb), proj.ub); -0147 end

    +0114 end +0115 +0116 function proj = buildSteadyStateCorrection(S, free, lb, ub) +0117 % Sparse factor of the row-normalised S(:,free)*S(:,free)' with a small ridge, +0118 % used to move a flux vector onto S*v = 0 through the free reactions only. +0119 Sf = S(:, free); +0120 rowNorm = sqrt(full(sum(Sf.^2, 2))); +0121 keep = rowNorm > 0; +0122 rowNorm = rowNorm(keep); +0123 Sn = spdiags(1./rowNorm, 0, numel(rowNorm), numel(rowNorm))*Sf(keep, :); +0124 A = Sn*Sn' + 1e-10*speye(size(Sn, 1)); +0125 [R, flag, p] = chol(A, 'vector'); +0126 if flag ~= 0 +0127 error('sampleACHR:factorization', 'The stoichiometric matrix could not be factorized.'); +0128 end +0129 proj = struct('S', S, 'free', free, 'keep', keep, 'rowNorm', rowNorm, ... +0130 'Sn', Sn, 'R', R, 'Rt', R', 'p', p, 'lb', lb, 'ub', ub); +0131 end +0132 +0133 function v = restoreSteadyState(v, proj) +0134 % Removes the residual of S*v through the free reactions (a few ridge-regularised +0135 % projection passes), then clips v to its bounds. +0136 tol = 1e-12*max(1, max(abs(v))); +0137 for it = 1:8 +0138 r = proj.S*v; +0139 if max(abs(r)) <= tol +0140 break +0141 end +0142 rn = r(proj.keep) ./ proj.rowNorm; +0143 x = zeros(size(rn)); +0144 x(proj.p) = proj.R \ (proj.Rt \ rn(proj.p)); +0145 v(proj.free) = v(proj.free) - proj.Sn'*x; +0146 end +0147 v = min(max(v, proj.lb), proj.ub); +0148 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/analysis/sampleCHRR.html b/docs/html/analysis/sampleCHRR.html index a1c724bd..15b5ff11 100644 --- a/docs/html/analysis/sampleCHRR.html +++ b/docs/html/analysis/sampleCHRR.html @@ -31,17 +31,18 @@

    DESCRIPTION ^DESCRIPTION ^SOURCE CODE ^% 0004 % Draws nSamples flux vectors approximately uniformly from the flux polytope 0005 % {v : S*v = 0, lb <= v <= ub}, using the CHRR algorithm (Haraldsdottir et al. -0006 % 2017, Bioinformatics 33:1741). The polytope is rounded before sampling, so -0007 % mixing stays fast even on strongly elongated polytopes (e.g. -0008 % enzyme-constrained models), unlike sampleACHR. -0009 % -0010 % Set any constraints you want to condition on (e.g. a biomass lower bound or -0011 % measured exchange fluxes) on the model before calling. -0012 % -0013 % Parameters -0014 % ---------- -0015 % model : struct -0016 % a RAVEN model structure. -0017 % nSamples : double -0018 % number of flux vectors to return (default 1000). -0019 % thinning : double -0020 % Markov-chain steps between recorded samples (default 100). -0021 % nBurnin : double -0022 % burn-in steps discarded before the first recorded sample (default 1000). -0023 % seed : double -0024 % random seed for reproducibility (default: current RNG state). -0025 % fixedWidthTol : double -0026 % a reaction whose allowed flux range is narrower than this is treated as -0027 % stoichiometrically fixed and folded into the equality system, keeping the -0028 % reduced polytope full-dimensional (default 1e-7). -0029 % -0030 % Returns -0031 % ------- -0032 % solutions : double -0033 % nRxns-by-nSamples matrix of sampled flux distributions. -0034 % info : struct -0035 % .nDimensions — dimension of the sampled flux polytope -0036 % .mveConverged — whether MVE rounding reached tolerance -0037 % .fixedRxns — IDs of reactions folded in as fixed -0038 % -0039 % See also -0040 % -------- -0041 % randomSampling, sampleACHR, sampleMaxVolEllipse -0042 -0043 if nargin < 2 || isempty(nSamples); nSamples = 1000; end -0044 if nargin < 3 || isempty(thinning); thinning = 100; end -0045 if nargin < 4 || isempty(nBurnin); nBurnin = 1000; end -0046 if nargin < 5; seed = []; end -0047 if nargin < 6 || isempty(fixedWidthTol); fixedWidthTol = 1e-7; end -0048 if ~isempty(seed) -0049 rng(seed); -0050 end -0051 -0052 nRxns = numel(model.rxns); -0053 S = full(model.S); -0054 lb = model.lb(:); -0055 ub = model.ub(:); -0056 -0057 % ---- Fold implicitly-fixed reactions into equalities ---- -0058 % A reaction with ~zero flux range is determined by stoichiometry; keeping it as -0059 % a box constraint would make the reduced polytope lower-dimensional and the MVE -0060 % solve singular. Fix it at its (unique) value instead. -0061 [minF, maxF] = getAllowedBounds(model); -0062 minF = minF(:); maxF = maxF(:); -0063 width = maxF - minF; -0064 fixed = width < fixedWidthTol; -0065 fixedVals = (maxF + minF) / 2; -0066 fixedIdx = find(fixed); -0067 -0068 Aeq = S; -0069 beq = zeros(size(S, 1), 1); -0070 if ~isempty(fixedIdx) -0071 blk = zeros(numel(fixedIdx), nRxns); -0072 for k = 1:numel(fixedIdx) -0073 blk(k, fixedIdx(k)) = 1; -0074 end -0075 Aeq = [Aeq; blk]; -0076 beq = [beq; fixedVals(fixedIdx)]; -0077 end -0078 -0079 % Particular solution v0 and nullspace basis N: v = v0 + N*x. -0080 % v0 is ANY particular solution; the sampled flux distribution is invariant to -0081 % its choice and to the nullspace basis. -0082 % lsqminnorm rather than '\': Aeq is square whenever the number of metabolites -0083 % plus the number of folded-in fixed reactions equals the number of reactions, -0084 % and it is then rank-deficient, for which '\' returns NaN. Those NaNs travel -0085 % into the polytope and surface much later as a misleading "empty interior" -0086 % error from sampleChebyshevCenter. -0087 v0 = lsqminnorm(Aeq, beq); -0088 N = null(Aeq); -0089 d = size(N, 2); -0090 -0091 info = struct(); -0092 info.fixedRxns = model.rxns(fixedIdx); -0093 -0094 if d == 0 -0095 % Flux space is a single point — nothing to sample. -0096 solutions = repmat(v0, 1, nSamples); -0097 info.nDimensions = 0; -0098 info.mveConverged = true; -0099 return; -0100 end -0101 -0102 % ---- Build the full-dimensional polytope in x (free reactions only) ---- -0103 free = ~fixed; -0104 Nf = N(free, :); -0105 A_full = [Nf; -Nf]; -0106 b_full = [ub(free) - v0(free); v0(free) - lb(free)]; -0107 % Drop rows whose direction vanished in the nullspace (constraint is constant). -0108 keep = sqrt(sum(A_full.^2, 2)) > 1e-10; -0109 A_full = A_full(keep, :); -0110 b_full = b_full(keep); -0111 -0112 % ---- Round with the MVE ---- -0113 x0 = sampleChebyshevCenter(A_full, b_full); -0114 [center, E, converged] = sampleMaxVolEllipse(A_full, b_full, x0); -0115 info.nDimensions = d; -0116 info.mveConverged = converged; -0117 if ~converged -0118 warning('RAVEN:warning', '%s', ['The maximum-volume ellipsoid rounding ' ... -0119 'did not converge; the samples may be poorly mixed. Inspect the ' ... -0120 'mveConverged field of the second output.']); -0121 end -0122 -0123 % Rounded polytope {y : A_r*y <= b_r}, which contains the unit ball. -0124 A_r = A_full * E; -0125 b_r = b_full - A_full*center; -0126 -0127 % ---- Coordinate hit-and-run on the rounded polytope ---- -0128 y = zeros(d, 1); -0129 s = b_r - A_r*y; % maintained slack, > 0 at start -0130 solutions = zeros(nRxns, nSamples); -0131 rec = 0; -0132 total = nBurnin + thinning*nSamples; -0133 colTol = 1e-12; -0134 -0135 for stepno = 1:total -0136 j = randi(d); -0137 col = A_r(:, j); -0138 pos = col > colTol; -0139 neg = col < -colTol; -0140 ratios = s ./ col; -0141 if any(pos); tHi = min(ratios(pos)); else; tHi = inf; end -0142 if any(neg); tLo = max(ratios(neg)); else; tLo = -inf; end -0143 if ~isfinite(tHi) || ~isfinite(tLo) || tHi <= tLo -0144 continue; % unbounded/degenerate coordinate; skip -0145 end -0146 t = tLo + rand*(tHi - tLo); -0147 y(j) = y(j) + t; -0148 s = s - col*t; -0149 -0150 if stepno > nBurnin && mod(stepno - nBurnin, thinning) == 0 -0151 rec = rec + 1; -0152 x = center + E*y; -0153 solutions(:, rec) = v0 + N*x; -0154 end -0155 end -0156 -0157 if rec < nSamples -0158 if rec >= 1 -0159 solutions(:, rec+1:end) = repmat(solutions(:, rec), 1, nSamples - rec); -0160 else -0161 solutions = repmat(v0, 1, nSamples); -0162 end -0163 end -0164 end +0006 % 2017, Bioinformatics 33:1741). The polytope is rounded before sampling, +0007 % which improves mixing on strongly elongated polytopes (for example +0008 % enzyme-constrained models) in comparison with sampleACHR, which does not +0009 % round. +0010 % +0011 % Constraints to condition on (for example a biomass lower bound or measured +0012 % exchange fluxes) are set on the model before the call. +0013 % +0014 % Parameters +0015 % ---------- +0016 % model : struct +0017 % a model structure. +0018 % nSamples : double +0019 % number of flux vectors to return (default 1000). +0020 % thinning : double +0021 % Markov-chain steps between recorded samples (default 100). +0022 % nBurnin : double +0023 % burn-in steps discarded before the first recorded sample (default 1000). +0024 % seed : double +0025 % random seed for reproducibility (default: current RNG state). +0026 % fixedWidthTol : double +0027 % a reaction whose allowed flux range is narrower than this is treated as +0028 % stoichiometrically fixed and added to the equality system at the middle +0029 % of its range, which keeps the reduced polytope full-dimensional +0030 % (default 1e-7). +0031 % +0032 % Returns +0033 % ------- +0034 % solutions : double +0035 % nRxns-by-nSamples matrix of sampled flux distributions. +0036 % info : struct +0037 % .nDimensions : dimension of the sampled flux polytope +0038 % .mveConverged : whether MVE rounding reached tolerance +0039 % .fixedRxns : IDs of reactions added to the equality system as fixed +0040 % +0041 % See also +0042 % -------- +0043 % randomSampling, sampleACHR, sampleMaxVolEllipse +0044 +0045 if nargin < 2 || isempty(nSamples); nSamples = 1000; end +0046 if nargin < 3 || isempty(thinning); thinning = 100; end +0047 if nargin < 4 || isempty(nBurnin); nBurnin = 1000; end +0048 if nargin < 5; seed = []; end +0049 if nargin < 6 || isempty(fixedWidthTol); fixedWidthTol = 1e-7; end +0050 if ~isempty(seed) +0051 rng(seed); +0052 end +0053 +0054 nRxns = numel(model.rxns); +0055 S = full(model.S); +0056 lb = model.lb(:); +0057 ub = model.ub(:); +0058 +0059 % ---- Fold implicitly-fixed reactions into equalities ---- +0060 % A reaction with ~zero flux range is determined by stoichiometry; keeping it as +0061 % a box constraint would make the reduced polytope lower-dimensional and the MVE +0062 % solve singular. Fix it at its (unique) value instead. +0063 [minF, maxF] = getAllowedBounds(model); +0064 minF = minF(:); maxF = maxF(:); +0065 width = maxF - minF; +0066 fixed = width < fixedWidthTol; +0067 fixedVals = (maxF + minF) / 2; +0068 fixedIdx = find(fixed); +0069 +0070 Aeq = S; +0071 beq = zeros(size(S, 1), 1); +0072 if ~isempty(fixedIdx) +0073 blk = zeros(numel(fixedIdx), nRxns); +0074 for k = 1:numel(fixedIdx) +0075 blk(k, fixedIdx(k)) = 1; +0076 end +0077 Aeq = [Aeq; blk]; +0078 beq = [beq; fixedVals(fixedIdx)]; +0079 end +0080 +0081 % Particular solution v0 and nullspace basis N: v = v0 + N*x. +0082 % v0 is ANY particular solution; the sampled flux distribution is invariant to +0083 % its choice and to the nullspace basis. +0084 % lsqminnorm rather than '\': Aeq is square whenever the number of metabolites +0085 % plus the number of folded-in fixed reactions equals the number of reactions, +0086 % and it is then rank-deficient, for which '\' returns NaN. Those NaNs travel +0087 % into the polytope and surface much later as a misleading "empty interior" +0088 % error from sampleChebyshevCenter. +0089 v0 = lsqminnorm(Aeq, beq); +0090 N = null(Aeq); +0091 d = size(N, 2); +0092 +0093 info = struct(); +0094 info.fixedRxns = model.rxns(fixedIdx); +0095 +0096 if d == 0 +0097 % Flux space is a single point — nothing to sample. +0098 solutions = repmat(v0, 1, nSamples); +0099 info.nDimensions = 0; +0100 info.mveConverged = true; +0101 return; +0102 end +0103 +0104 % ---- Build the full-dimensional polytope in x (free reactions only) ---- +0105 free = ~fixed; +0106 Nf = N(free, :); +0107 A_full = [Nf; -Nf]; +0108 b_full = [ub(free) - v0(free); v0(free) - lb(free)]; +0109 % Drop rows whose direction vanished in the nullspace (constraint is constant). +0110 keep = sqrt(sum(A_full.^2, 2)) > 1e-10; +0111 A_full = A_full(keep, :); +0112 b_full = b_full(keep); +0113 +0114 % ---- Round with the MVE ---- +0115 x0 = sampleChebyshevCenter(A_full, b_full); +0116 [center, E, converged] = sampleMaxVolEllipse(A_full, b_full, x0); +0117 info.nDimensions = d; +0118 info.mveConverged = converged; +0119 if ~converged +0120 warning('RAVEN:warning', '%s', ['The maximum-volume ellipsoid rounding ' ... +0121 'did not converge; the samples may be poorly mixed. Inspect the ' ... +0122 'mveConverged field of the second output.']); +0123 end +0124 +0125 % Rounded polytope {y : A_r*y <= b_r}, which contains the unit ball. +0126 A_r = A_full * E; +0127 b_r = b_full - A_full*center; +0128 +0129 % ---- Coordinate hit-and-run on the rounded polytope ---- +0130 y = zeros(d, 1); +0131 s = b_r - A_r*y; % maintained slack, > 0 at start +0132 solutions = zeros(nRxns, nSamples); +0133 rec = 0; +0134 total = nBurnin + thinning*nSamples; +0135 colTol = 1e-12; +0136 +0137 for stepno = 1:total +0138 j = randi(d); +0139 col = A_r(:, j); +0140 pos = col > colTol; +0141 neg = col < -colTol; +0142 ratios = s ./ col; +0143 if any(pos); tHi = min(ratios(pos)); else; tHi = inf; end +0144 if any(neg); tLo = max(ratios(neg)); else; tLo = -inf; end +0145 if ~isfinite(tHi) || ~isfinite(tLo) || tHi <= tLo +0146 continue; % unbounded/degenerate coordinate; skip +0147 end +0148 t = tLo + rand*(tHi - tLo); +0149 y(j) = y(j) + t; +0150 s = s - col*t; +0151 +0152 if stepno > nBurnin && mod(stepno - nBurnin, thinning) == 0 +0153 rec = rec + 1; +0154 x = center + E*y; +0155 solutions(:, rec) = v0 + N*x; +0156 end +0157 end +0158 +0159 if rec < nSamples +0160 if rec >= 1 +0161 solutions(:, rec+1:end) = repmat(solutions(:, rec), 1, nSamples - rec); +0162 else +0163 solutions = repmat(v0, 1, nSamples); +0164 end +0165 end +0166 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/analysis/sampleMaxVolEllipse.html b/docs/html/analysis/sampleMaxVolEllipse.html index 6e49cb8c..e4962bb4 100644 --- a/docs/html/analysis/sampleMaxVolEllipse.html +++ b/docs/html/analysis/sampleMaxVolEllipse.html @@ -34,7 +34,7 @@

    DESCRIPTION ^DESCRIPTION ^DESCRIPTION ^SOURCE CODE ^% {z : A*z <= b}, using the Zhang & Gao (2003) primal-dual interior-point 0007 % method (the regularised variant used in COBRA's CHRR sampler). This is the 0008 % rounding step of sampleCHRR: the returned transform E maps the polytope to a -0009 % well-conditioned body in which coordinate hit-and-run mixes rapidly. +0009 % well-conditioned body in which coordinate hit-and-run samples. 0010 % 0011 % This function is pure linear algebra (no LP solver) except when x0 is empty, 0012 % in which case a Chebyshev centre is obtained via sampleChebyshevCenter. @@ -124,8 +125,8 @@

    SOURCE CODE ^% tol : double (optional) 0027 % convergence tolerance (default 1e-6). 0028 % reg : double (optional) -0029 % diagonal / Levenberg regularisation keeping the Newton solves -0030 % well-conditioned (default 1e-8). +0029 % diagonal (Levenberg) regularisation of the Newton solves +0030 % (default 1e-8). 0031 % 0032 % Returns 0033 % ------- @@ -135,146 +136,147 @@

    SOURCE CODE ^% n-by-n lower-triangular rounding transform with E*E' = E2 (the SPD 0038 % ellipsoid matrix). The rounding substitution is z = x + E*y. 0039 % converged : logical -0040 % whether tol was reached within maxiter. A false value is not fatal — -0041 % the last iterate is still a valid (if looser) rounding. -0042 % -0043 % Notes -0044 % ----- -0045 % Validated against analytic cases: a box maps to the unit ball (E = I); a -0046 % sheared box {z : -1 <= M*z <= 1} gives E*E' = inv(M)*inv(M)'; a triangle -0047 % gives its Steiner inellipse. -0048 % -0049 % Based on Y. Zhang & L. Gao (2003), SIAM J. Optim. 14:53-76; as used in -0050 % Haraldsdottir et al. (2017) Bioinformatics 33:1741 (CHRR). -0051 % -0052 % See also -0053 % -------- -0054 % sampleCHRR, sampleChebyshevCenter, randomSampling -0055 -0056 if nargin < 4 || isempty(maxiter); maxiter = 150; end -0057 if nargin < 5 || isempty(tol); tol = 1e-6; end -0058 if nargin < 6 || isempty(reg); reg = 1e-8; end -0059 -0060 [m, n] = size(A); -0061 b = b(:); -0062 % bnrm is the norm of the ORIGINAL b (before the row-normalisation below sets -0063 % b <- ones). This matches COBRA's chrrSampler reference; do not "correct" it to -0064 % sqrt(m), or convergence scaling diverges from the reference. -0065 bnrm = norm(b); -0066 minmu = 1e-8; -0067 tau0 = 0.75; -0068 -0069 if nargin < 3 || isempty(x0) -0070 x0 = sampleChebyshevCenter(A, b); -0071 end -0072 x0 = x0(:); -0073 -0074 bmAx0 = b - A*x0; -0075 if any(bmAx0 <= 0) -0076 error('RAVEN:sampling', 'sampleMaxVolEllipse: x0 is not strictly interior (A*x0 < b).'); -0077 end -0078 -0079 % Row-normalise so the constraint RHS is all ones; solve in shifted coords. -0080 A = A ./ bmAx0; -0081 b = ones(m, 1); -0082 -0083 x = zeros(n, 1); -0084 y = ones(m, 1); -0085 bmAx = b; -0086 z = zeros(m, 1); -0087 astep = 0; -0088 Adx = zeros(n, 1); -0089 converged = false; -0090 E2 = eye(n); -0091 -0092 for it = 1:maxiter -0093 if it > 1 -0094 bmAx = bmAx - astep*Adx; -0095 end -0096 -0097 AtYA = A' * (A .* y); % A' * diag(y) * A (n-by-n) -0098 E2 = inv(AtYA); %#ok<MINV> explicit inverse mirrors the reference -0099 Q = A * E2 * A'; % m-by-m -0100 h = sqrt(max(diag(Q), 0)); -0101 -0102 if it == 1 -0103 t = min(bmAx ./ h); -0104 y = y / t^2; -0105 h = t * h; -0106 z = max(1e-1, bmAx - h); -0107 Q = t^2 * Q; -0108 end -0109 -0110 yz = y .* z; -0111 yh = y .* h; -0112 gap = sum(yz) / m; -0113 rmu = max(min(0.5, gap)*gap, minmu); -0114 -0115 R1 = -A' * yh; % dual residual (n-by-1) -0116 R2 = bmAx - h - z; % primal/slack (m-by-1) -0117 R3 = rmu - yz; % complementarity (m-by-1) -0118 res = max([max(abs(R1)), max(abs(R2)), max(abs(R3))]); -0119 -0120 if res < tol*(1 + bnrm) && rmu <= minmu -0121 x = x + x0; -0122 converged = true; -0123 break; -0124 end -0125 -0126 YQ = y .* Q; % diag(y) * Q -0127 YQQY = YQ .* YQ'; % Hadamard product -0128 y2h = 2 * yh; -0129 G = YQQY + diag(max(reg, y2h .* z)); -0130 YA = y .* A; % diag(y) * A -0131 T = G \ ((h + z) .* YA); % m-by-n -0132 ATP = (y2h .* T - YA)'; % n-by-m -0133 R3Dy = R3 ./ y; -0134 R23 = R2 - R3Dy; -0135 ATP_A = ATP*A + reg*eye(n); -0136 dx = ATP_A \ (R1 + ATP*R23); -0137 Adx = A * dx; -0138 dyDy = G \ (y2h .* (Adx - R23)); -0139 dy = y .* dyDy; -0140 dz = R3Dy - z .* dyDy; -0141 -0142 ax = -1 / min([-Adx ./ bmAx; -0.5]); -0143 ay = -1 / min([ dyDy; -0.5]); -0144 az = -1 / min([ dz ./ z; -0.5]); -0145 tau = max(tau0, 1 - res); -0146 astep = tau * min([1, ax, ay, az]); -0147 -0148 x = x + astep*dx; -0149 y = y + astep*dy; -0150 z = z + astep*dz; -0151 end -0152 -0153 if ~converged -0154 x = x + x0; -0155 end -0156 -0157 E = nearestSPDchol(E2); -0158 end -0159 +0040 % whether tol was reached within maxiter. If false, the last iterate +0041 % is returned as the centre and transform, which is a valid but looser +0042 % rounding. +0043 % +0044 % Notes +0045 % ----- +0046 % Analytic cases, tested in tSampling: a box maps to the unit ball (E = I); +0047 % a sheared box {z : -1 <= M*z <= 1} gives E*E' = inv(M)*inv(M)'; a +0048 % triangle gives its Steiner inellipse. +0049 % +0050 % Based on Y. Zhang & L. Gao (2003), SIAM J. Optim. 14:53-76; as used in +0051 % Haraldsdottir et al. (2017) Bioinformatics 33:1741 (CHRR). +0052 % +0053 % See also +0054 % -------- +0055 % sampleCHRR, sampleChebyshevCenter, randomSampling +0056 +0057 if nargin < 4 || isempty(maxiter); maxiter = 150; end +0058 if nargin < 5 || isempty(tol); tol = 1e-6; end +0059 if nargin < 6 || isempty(reg); reg = 1e-8; end +0060 +0061 [m, n] = size(A); +0062 b = b(:); +0063 % bnrm is the norm of the ORIGINAL b (before the row-normalisation below sets +0064 % b <- ones). This matches COBRA's chrrSampler reference; do not "correct" it to +0065 % sqrt(m), or convergence scaling diverges from the reference. +0066 bnrm = norm(b); +0067 minmu = 1e-8; +0068 tau0 = 0.75; +0069 +0070 if nargin < 3 || isempty(x0) +0071 x0 = sampleChebyshevCenter(A, b); +0072 end +0073 x0 = x0(:); +0074 +0075 bmAx0 = b - A*x0; +0076 if any(bmAx0 <= 0) +0077 error('RAVEN:sampling', 'sampleMaxVolEllipse: x0 is not strictly interior (A*x0 < b).'); +0078 end +0079 +0080 % Row-normalise so the constraint RHS is all ones; solve in shifted coords. +0081 A = A ./ bmAx0; +0082 b = ones(m, 1); +0083 +0084 x = zeros(n, 1); +0085 y = ones(m, 1); +0086 bmAx = b; +0087 z = zeros(m, 1); +0088 astep = 0; +0089 Adx = zeros(n, 1); +0090 converged = false; +0091 E2 = eye(n); +0092 +0093 for it = 1:maxiter +0094 if it > 1 +0095 bmAx = bmAx - astep*Adx; +0096 end +0097 +0098 AtYA = A' * (A .* y); % A' * diag(y) * A (n-by-n) +0099 E2 = inv(AtYA); %#ok<MINV> explicit inverse mirrors the reference +0100 Q = A * E2 * A'; % m-by-m +0101 h = sqrt(max(diag(Q), 0)); +0102 +0103 if it == 1 +0104 t = min(bmAx ./ h); +0105 y = y / t^2; +0106 h = t * h; +0107 z = max(1e-1, bmAx - h); +0108 Q = t^2 * Q; +0109 end +0110 +0111 yz = y .* z; +0112 yh = y .* h; +0113 gap = sum(yz) / m; +0114 rmu = max(min(0.5, gap)*gap, minmu); +0115 +0116 R1 = -A' * yh; % dual residual (n-by-1) +0117 R2 = bmAx - h - z; % primal/slack (m-by-1) +0118 R3 = rmu - yz; % complementarity (m-by-1) +0119 res = max([max(abs(R1)), max(abs(R2)), max(abs(R3))]); +0120 +0121 if res < tol*(1 + bnrm) && rmu <= minmu +0122 x = x + x0; +0123 converged = true; +0124 break; +0125 end +0126 +0127 YQ = y .* Q; % diag(y) * Q +0128 YQQY = YQ .* YQ'; % Hadamard product +0129 y2h = 2 * yh; +0130 G = YQQY + diag(max(reg, y2h .* z)); +0131 YA = y .* A; % diag(y) * A +0132 T = G \ ((h + z) .* YA); % m-by-n +0133 ATP = (y2h .* T - YA)'; % n-by-m +0134 R3Dy = R3 ./ y; +0135 R23 = R2 - R3Dy; +0136 ATP_A = ATP*A + reg*eye(n); +0137 dx = ATP_A \ (R1 + ATP*R23); +0138 Adx = A * dx; +0139 dyDy = G \ (y2h .* (Adx - R23)); +0140 dy = y .* dyDy; +0141 dz = R3Dy - z .* dyDy; +0142 +0143 ax = -1 / min([-Adx ./ bmAx; -0.5]); +0144 ay = -1 / min([ dyDy; -0.5]); +0145 az = -1 / min([ dz ./ z; -0.5]); +0146 tau = max(tau0, 1 - res); +0147 astep = tau * min([1, ax, ay, az]); +0148 +0149 x = x + astep*dx; +0150 y = y + astep*dy; +0151 z = z + astep*dz; +0152 end +0153 +0154 if ~converged +0155 x = x + x0; +0156 end +0157 +0158 E = nearestSPDchol(E2); +0159 end 0160 -0161 function E = nearestSPDchol(M) -0162 % Lower-triangular Cholesky factor of the nearest SPD matrix to M, so E*E' = M. -0163 M = (M + M') / 2; -0164 [E, p] = chol(M, 'lower'); -0165 if p == 0 -0166 return; -0167 end -0168 ev = eig(M); -0169 jitter = max(-min(ev), 0) + 1e-12; -0170 nn = size(M, 1); -0171 for k = 1:30 -0172 [E, p] = chol(M + jitter*eye(nn), 'lower'); -0173 if p == 0 -0174 return; -0175 end -0176 jitter = jitter * 10; -0177 end -0178 E = chol(M + jitter*eye(nn), 'lower'); -0179 end +0161 +0162 function E = nearestSPDchol(M) +0163 % Lower-triangular Cholesky factor of the nearest SPD matrix to M, so E*E' = M. +0164 M = (M + M') / 2; +0165 [E, p] = chol(M, 'lower'); +0166 if p == 0 +0167 return; +0168 end +0169 ev = eig(M); +0170 jitter = max(-min(ev), 0) + 1e-12; +0171 nn = size(M, 1); +0172 for k = 1:30 +0173 [E, p] = chol(M + jitter*eye(nn), 'lower'); +0174 if p == 0 +0175 return; +0176 end +0177 jitter = jitter * 10; +0178 end +0179 E = chol(M + jitter*eye(nn), 'lower'); +0180 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/analysis/sampleWarmupPoints.html b/docs/html/analysis/sampleWarmupPoints.html index 5da0488e..ba6d7b99 100644 --- a/docs/html/analysis/sampleWarmupPoints.html +++ b/docs/html/analysis/sampleWarmupPoints.html @@ -31,18 +31,19 @@

    DESCRIPTION ^SOURCE CODE ^% 0004 % Generates warmup points for sampleACHR by maximising and minimising each 0005 % reaction in turn and storing the full flux distribution at each optimum. -0006 % These vertices span the flux polytope and seed the hit-and-run directions. -0007 % -0008 % Parameters -0009 % ---------- -0010 % model : struct -0011 % a RAVEN model structure. -0012 % -0013 % Name-Value Arguments -0014 % -------------------- -0015 % runParallel : logical -0016 % speed up calculations by parallel processing, as in getAllowedBounds, -0017 % which solves the same per-reaction min/max LPs (default true). -0018 % -0019 % Returns -0020 % ------- -0021 % warmup : double -0022 % nPoints-by-nRxns matrix; each row is a vertex flux distribution. Duplicate -0023 % rows are removed. -0024 % -0025 % See also -0026 % -------- -0027 % sampleACHR, randomSampling, getAllowedBounds -0028 -0029 p=parseRAVENargs(varargin, {'runParallel',true}); -0030 runParallel=p.runParallel; -0031 -0032 nRxns = numel(model.rxns); -0033 nW = parallelWorkersRAVEN(runParallel); -0034 fixedRxn = (model.ub - model.lb) < 1e-9; % fixed reaction — no warmup direction -0035 -0036 %Maximise and minimise each reaction in turn. Each direction is its own -0037 %fixed-size (rxns x rxns) buffer (NaN where a reaction is fixed or -0038 %infeasible) so both parfor loops write to statically-indexed rows, -0039 %mirroring getAllowedBounds' own min/max-per-reaction parfor. -0040 ptsMax = NaN(nRxns, nRxns); -0041 ptsMin = NaN(nRxns, nRxns); -0042 -0043 PB = progressReport(2*nRxns,'Running sampleWarmupPoints'); -0044 parfor (i = 1:nRxns, nW) -0045 if ~fixedRxn(i) -0046 tmp = model; -0047 tmp.c = zeros(nRxns, 1); -0048 tmp.c(i) = 1; % solveLP maximises c'*v -0049 sol = solveLP(tmp); -0050 if ~isempty(sol.x) && sol.stat > 0 -0051 ptsMax(i,:) = sol.x'; -0052 end -0053 end -0054 count(PB) -0055 end -0056 parfor (i = 1:nRxns, nW) -0057 if ~fixedRxn(i) -0058 tmp = model; -0059 tmp.c = zeros(nRxns, 1); -0060 tmp.c(i) = -1; -0061 sol = solveLP(tmp); -0062 if ~isempty(sol.x) && sol.stat > 0 -0063 ptsMin(i,:) = sol.x'; -0064 end -0065 end -0066 count(PB) -0067 end -0068 -0069 pts = [ptsMax; ptsMin]; -0070 warmup = pts(~any(isnan(pts),2), :); -0071 if isempty(warmup) -0072 error('RAVEN:sampling', ... -0073 'sampleWarmupPoints: could not generate any warmup points; check model feasibility.'); -0074 end -0075 warmup = unique(warmup, 'rows'); -0076 if size(warmup, 1) < 2 -0077 error('RAVEN:sampling', ... -0078 'sampleWarmupPoints: flux cone collapses to a single point; cannot sample.'); -0079 end -0080 end +0006 % These vertices span the flux polytope and define the hit-and-run +0007 % directions. +0008 % +0009 % Parameters +0010 % ---------- +0011 % model : struct +0012 % a model structure. +0013 % +0014 % Name-Value Arguments +0015 % -------------------- +0016 % runParallel : logical +0017 % run the calculations in parallel, as in getAllowedBounds, which solves +0018 % the same per-reaction minimisation and maximisation LPs (default true). +0019 % +0020 % Returns +0021 % ------- +0022 % warmup : double +0023 % nPoints-by-nRxns matrix; each row is a vertex flux distribution. Duplicate +0024 % rows are removed. +0025 % +0026 % See also +0027 % -------- +0028 % sampleACHR, randomSampling, getAllowedBounds +0029 +0030 p=parseRAVENargs(varargin, {'runParallel',true}); +0031 runParallel=p.runParallel; +0032 +0033 nRxns = numel(model.rxns); +0034 nW = parallelWorkersRAVEN(runParallel); +0035 fixedRxn = (model.ub - model.lb) < 1e-9; % fixed reaction — no warmup direction +0036 +0037 %Maximise and minimise each reaction in turn. Each direction is its own +0038 %fixed-size (rxns x rxns) buffer (NaN where a reaction is fixed or +0039 %infeasible) so both parfor loops write to statically-indexed rows, +0040 %mirroring getAllowedBounds' own min/max-per-reaction parfor. +0041 ptsMax = NaN(nRxns, nRxns); +0042 ptsMin = NaN(nRxns, nRxns); +0043 +0044 PB = progressReport(2*nRxns,'Running sampleWarmupPoints'); +0045 parfor (i = 1:nRxns, nW) +0046 if ~fixedRxn(i) +0047 tmp = model; +0048 tmp.c = zeros(nRxns, 1); +0049 tmp.c(i) = 1; % solveLP maximises c'*v +0050 sol = solveLP(tmp); +0051 if ~isempty(sol.x) && sol.stat > 0 +0052 ptsMax(i,:) = sol.x'; +0053 end +0054 end +0055 count(PB) +0056 end +0057 parfor (i = 1:nRxns, nW) +0058 if ~fixedRxn(i) +0059 tmp = model; +0060 tmp.c = zeros(nRxns, 1); +0061 tmp.c(i) = -1; +0062 sol = solveLP(tmp); +0063 if ~isempty(sol.x) && sol.stat > 0 +0064 ptsMin(i,:) = sol.x'; +0065 end +0066 end +0067 count(PB) +0068 end +0069 +0070 pts = [ptsMax; ptsMin]; +0071 warmup = pts(~any(isnan(pts),2), :); +0072 if isempty(warmup) +0073 error('RAVEN:sampling', ... +0074 'sampleWarmupPoints: could not generate any warmup points; check model feasibility.'); +0075 end +0076 warmup = unique(warmup, 'rows'); +0077 if size(warmup, 1) < 2 +0078 error('RAVEN:sampling', ... +0079 'sampleWarmupPoints: flux cone collapses to a single point; cannot sample.'); +0080 end +0081 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/analysis/traceFluxPath.html b/docs/html/analysis/traceFluxPath.html index 8984bc4c..cc57b130 100644 --- a/docs/html/analysis/traceFluxPath.html +++ b/docs/html/analysis/traceFluxPath.html @@ -33,16 +33,16 @@

    DESCRIPTION ^DESCRIPTION ^DESCRIPTION ^DESCRIPTION ^CROSS-REFERENCE INFORMATION ^

    @@ -124,16 +126,16 @@

    SOURCE CODE ^% network, quantifying what fraction of the source reaction's flux reaches 0006 % the target. At each metabolite junction the flux is split proportionally 0007 % among all consuming reactions; the path with the highest cumulative -0008 % fraction is returned and printed. +0008 % fraction is returned, and printed if verbose is true. 0009 % -0010 % By default the search excludes currency metabolites (ATP, NAD, H2O, etc.) -0011 % so the returned path represents material (carbon-skeleton) flow rather -0012 % than cofactor shortcuts. Set traceMaterial=false to disable this. +0010 % By default the search excludes currency metabolites (for example ATP, +0011 % NAD, H2O), which makes the returned path follow material flow. The +0012 % setting traceMaterial=false disables this. 0013 % 0014 % Parameters 0015 % ---------- 0016 % model : struct -0017 % RAVEN model structure. +0017 % a model structure. 0018 % fluxes : double 0019 % flux vector (length = numel(model.rxns)). 0020 % fromRxn : char @@ -144,372 +146,374 @@

    SOURCE CODE ^% Name-Value Arguments 0026 % -------------------- 0027 % cutoff : double -0028 % minimum |flux| for a reaction to be considered flux-carrying -0029 % (default 1e-8). +0028 % minimum net flux through a metabolite (|S_ij * v_j|) for a consuming +0029 % reaction to be considered (default 1e-8). 0030 % maxHops : double -0031 % maximum number of reactions in the path; longer paths are pruned -0032 % (default 30). +0031 % paths of more than maxHops reactions are not extended further, so a +0032 % returned path has at most maxHops + 1 reactions (default 30). 0033 % verbose : logical 0034 % print the path to the command window (default true). 0035 % traceMaterial : logical -0036 % exclude common currency/cofactor metabolites (ATP, ADP, NAD, NADH, -0037 % H2O, CoA, Pi, CO2, …) so the path follows material (carbon-skeleton) -0038 % flux rather than energy-carrier shortcuts (default true). +0036 % exclude common currency and cofactor metabolites (ATP, ADP, NAD, NADH, +0037 % H2O, CoA, Pi, CO2 and others); the path then follows material flux +0038 % (default true). 0039 % carbonOnly : logical 0040 % additionally require each intermediate metabolite to contain at least 0041 % one carbon atom, inferred from model.metFormulas. Drops H+, H2O, Pi, -0042 % O2, NH3, CO2 that are not already caught by the currency list. -0043 % Silently ignored when model.metFormulas is absent (default false). -0044 % excludeMets : cell of char -0045 % extra metabolite names or IDs (after stripping compartment suffix) to -0046 % exclude, in addition to the built-in currency list (default {}). -0047 % -0048 % Returns -0049 % ------- -0050 % pathRxns : cell -0051 % cell array of reaction IDs on the best path, including fromRxn and -0052 % toRxn. Empty if no path is found. -0053 % pathMets : cell -0054 % metabolite IDs at each junction (numel = numel(pathRxns)-1). -0055 % cumFrac : double -0056 % fraction of fromRxn's flux that routes through this exact path -0057 % (0 = no path found, 1 = all flux reaches toRxn directly). -0058 % -0059 % Examples -0060 % -------- -0061 % [p, m, f] = traceFluxPath(model, sol.x, 'PFK', 'ATPS4rpp') -0062 % % disable material filter to see all connections including ATP/ADP: -0063 % [p, m, f] = traceFluxPath(model, sol.x, 'PFK', 'ATPS4rpp', 'traceMaterial', false) -0064 % % add model-specific cofactors to the exclusion list: -0065 % [p, m, f] = traceFluxPath(model, sol.x, 'PFK', 'CS', 'excludeMets', {'acetyl-CoA'}) -0066 % -0067 % Notes -0068 % ----- -0069 % The algorithm follows metabolites in the forward direction only: it -0070 % traces the metabolites that fromRxn PRODUCES and finds which consuming -0071 % reactions eventually reach toRxn. If fromRxn and toRxn are not connected -0072 % in the forward flux direction (e.g. they are in parallel branches), an -0073 % empty path is returned. A best-first (greedy) search ensures the -0074 % highest-fraction path is found efficiently; a visited set prevents -0075 % revisiting reactions. -0076 -0077 p = parseRAVENargs(varargin, {'cutoff',1e-8; 'maxHops',30; 'verbose',true; ... -0078 'traceMaterial',true; 'carbonOnly',false; 'excludeMets',{}}); -0079 cutoff = p.cutoff; -0080 maxHops = p.maxHops; -0081 verbose = p.verbose; -0082 traceMaterial = p.traceMaterial; -0083 carbonOnly = p.carbonOnly; -0084 excludeMets = p.excludeMets; -0085 if ischar(excludeMets), excludeMets = {excludeMets}; end -0086 -0087 % ---- Build exclusion set ---- -0088 if traceMaterial -0089 currencyList = [defaultCurrencyMets_(), excludeMets(:)']; -0090 else -0091 currencyList = excludeMets(:)'; -0092 end -0093 -0094 % Pre-compute carbon counts per metabolite (for carbonOnly filter) -0095 hasCarbonFilter = false; -0096 carbonCounts = []; -0097 if carbonOnly -0098 if isfield(model,'metFormulas') && ~isempty(model.metFormulas) -0099 carbonCounts = cellfun(@countCarbon_, model.metFormulas); -0100 hasCarbonFilter = true; -0101 else -0102 warning('traceFluxPath:noFormulas', ... -0103 'carbonOnly requires model.metFormulas; the option is ignored.'); -0104 end -0105 end -0106 -0107 % ---- Resolve reactions ---- -0108 pathRxns = {}; -0109 pathMets = {}; -0110 cumFrac = 0; -0111 -0112 fromIdx = resolveRxn_(model, fromRxn); -0113 toIdx = resolveRxn_(model, toRxn); -0114 -0115 if fromIdx == toIdx -0116 pathRxns = model.rxns(fromIdx); -0117 pathMets = {}; -0118 cumFrac = 1; -0119 if verbose, printPath_(model, fluxes, pathRxns, pathMets, cumFrac, ... -0120 fromRxn, toRxn, traceMaterial || ~isempty(excludeMets) || carbonOnly); end -0121 return -0122 end -0123 -0124 nRxns = numel(model.rxns); +0042 % O2, NH3, CO2 that are not already caught by the currency list. Ignored +0043 % with a warning when model.metFormulas is absent or empty +0044 % (default false). +0045 % excludeMets : cell of char +0046 % extra metabolite names or IDs (after stripping compartment suffix) to +0047 % exclude, in addition to the built-in currency list (which applies when +0048 % traceMaterial is true) (default {}). +0049 % +0050 % Returns +0051 % ------- +0052 % pathRxns : cell +0053 % cell array of reaction IDs on the best path, including fromRxn and +0054 % toRxn. Empty if no path is found. +0055 % pathMets : cell +0056 % metabolite IDs at each junction (numel = numel(pathRxns)-1). +0057 % cumFrac : double +0058 % fraction of fromRxn's flux that passes through this exact path +0059 % (0 = no path found, 1 = all flux reaches toRxn directly). +0060 % +0061 % Examples +0062 % -------- +0063 % [p, m, f] = traceFluxPath(model, sol.x, 'PFK', 'ATPS4rpp') +0064 % % disable material filter to see all connections including ATP and ADP: +0065 % [p, m, f] = traceFluxPath(model, sol.x, 'PFK', 'ATPS4rpp', 'traceMaterial', false) +0066 % % add model-specific cofactors to the exclusion list: +0067 % [p, m, f] = traceFluxPath(model, sol.x, 'PFK', 'CS', 'excludeMets', {'acetyl-CoA'}) +0068 % +0069 % Notes +0070 % ----- +0071 % The algorithm follows metabolites in the forward direction only: it +0072 % traces the metabolites that fromRxn produces and finds which consuming +0073 % reactions eventually reach toRxn. If fromRxn and toRxn are not connected +0074 % in the forward flux direction (for example they are in parallel branches), an +0075 % empty path is returned. A best-first search, which expands the entry with +0076 % the highest cumulative fraction first, finds the highest-fraction path; +0077 % a path does not revisit a reaction. +0078 +0079 p = parseRAVENargs(varargin, {'cutoff',1e-8; 'maxHops',30; 'verbose',true; ... +0080 'traceMaterial',true; 'carbonOnly',false; 'excludeMets',{}}); +0081 cutoff = p.cutoff; +0082 maxHops = p.maxHops; +0083 verbose = p.verbose; +0084 traceMaterial = p.traceMaterial; +0085 carbonOnly = p.carbonOnly; +0086 excludeMets = p.excludeMets; +0087 if ischar(excludeMets), excludeMets = {excludeMets}; end +0088 +0089 % ---- Build exclusion set ---- +0090 if traceMaterial +0091 currencyList = [defaultCurrencyMets_(), excludeMets(:)']; +0092 else +0093 currencyList = excludeMets(:)'; +0094 end +0095 +0096 % Pre-compute carbon counts per metabolite (for carbonOnly filter) +0097 hasCarbonFilter = false; +0098 carbonCounts = []; +0099 if carbonOnly +0100 if isfield(model,'metFormulas') && ~isempty(model.metFormulas) +0101 carbonCounts = cellfun(@countCarbon_, model.metFormulas); +0102 hasCarbonFilter = true; +0103 else +0104 warning('traceFluxPath:noFormulas', ... +0105 'carbonOnly requires model.metFormulas; the option is ignored.'); +0106 end +0107 end +0108 +0109 % ---- Resolve reactions ---- +0110 pathRxns = {}; +0111 pathMets = {}; +0112 cumFrac = 0; +0113 +0114 fromIdx = resolveRxn_(model, fromRxn); +0115 toIdx = resolveRxn_(model, toRxn); +0116 +0117 if fromIdx == toIdx +0118 pathRxns = model.rxns(fromIdx); +0119 pathMets = {}; +0120 cumFrac = 1; +0121 if verbose, printPath_(model, fluxes, pathRxns, pathMets, cumFrac, ... +0122 fromRxn, toRxn, traceMaterial || ~isempty(excludeMets) || carbonOnly); end +0123 return +0124 end 0125 -0126 % ---- Best-first search (highest cumulative fraction first) ---- -0127 % Queue entries are structs: .rxn (idx), .frac (cumFrac), .rpath, .mpath -0128 bestAtNode = zeros(nRxns,1); -0129 bestAtNode(fromIdx) = 1.0; -0130 -0131 queue = makeEntry_(fromIdx, 1.0, fromIdx, zeros(0,1)); +0126 nRxns = numel(model.rxns); +0127 +0128 % ---- Best-first search (highest cumulative fraction first) ---- +0129 % Queue entries are structs: .rxn (idx), .frac (cumFrac), .rpath, .mpath +0130 bestAtNode = zeros(nRxns,1); +0131 bestAtNode(fromIdx) = 1.0; 0132 -0133 bestFrac = 0; -0134 bestRPath = []; -0135 bestMPath = []; -0136 -0137 while ~isempty(queue) -0138 % Pop highest-fraction entry -0139 entry = queue(1); -0140 queue(1) = []; -0141 -0142 cur = entry.rxn; -0143 frac = entry.frac; -0144 rpath = entry.rpath; -0145 mpath = entry.mpath; -0146 -0147 if cur == toIdx -0148 if frac > bestFrac -0149 bestFrac = frac; -0150 bestRPath = rpath; -0151 bestMPath = mpath; -0152 end -0153 continue -0154 end -0155 -0156 if numel(rpath) > maxHops, continue; end +0133 queue = makeEntry_(fromIdx, 1.0, fromIdx, zeros(0,1)); +0134 +0135 bestFrac = 0; +0136 bestRPath = []; +0137 bestMPath = []; +0138 +0139 while ~isempty(queue) +0140 % Pop highest-fraction entry +0141 entry = queue(1); +0142 queue(1) = []; +0143 +0144 cur = entry.rxn; +0145 frac = entry.frac; +0146 rpath = entry.rpath; +0147 mpath = entry.mpath; +0148 +0149 if cur == toIdx +0150 if frac > bestFrac +0151 bestFrac = frac; +0152 bestRPath = rpath; +0153 bestMPath = mpath; +0154 end +0155 continue +0156 end 0157 -0158 % Find metabolites net-produced by cur. Iterate only the structurally -0159 % nonzero entries of the (sparse) column instead of every metabolite -0160 % in the model, most of which a reaction never touches. -0161 col = model.S(:, cur); -0162 fcur = fluxes(cur); -0163 for m = find(col)' -0164 if col(m) * fcur <= 0, continue; end % skip consumed or zero-contribution mets -0165 -0166 % ---- Material-flux filters ---- -0167 if ~isempty(currencyList) && isCurrencyMet_(model, m, currencyList) -0168 continue -0169 end -0170 if hasCarbonFilter && carbonCounts(m) == 0 -0171 continue -0172 end -0173 -0174 % Reactions that net-consume this metabolite -0175 row = full(model.S(m, :)); -0176 fVec = fluxes(:)'; -0177 consumers = find(row .* fVec < -cutoff); -0178 consumers = consumers(~ismember(consumers, rpath)); % no revisiting -0179 if isempty(consumers), continue; end -0180 -0181 totalCons = sum(abs(row(consumers) .* fVec(consumers))); +0158 if numel(rpath) > maxHops, continue; end +0159 +0160 % Find metabolites net-produced by cur. Iterate only the structurally +0161 % nonzero entries of the (sparse) column instead of every metabolite +0162 % in the model, most of which a reaction never touches. +0163 col = model.S(:, cur); +0164 fcur = fluxes(cur); +0165 for m = find(col)' +0166 if col(m) * fcur <= 0, continue; end % skip consumed or zero-contribution mets +0167 +0168 % ---- Material-flux filters ---- +0169 if ~isempty(currencyList) && isCurrencyMet_(model, m, currencyList) +0170 continue +0171 end +0172 if hasCarbonFilter && carbonCounts(m) == 0 +0173 continue +0174 end +0175 +0176 % Reactions that net-consume this metabolite +0177 row = full(model.S(m, :)); +0178 fVec = fluxes(:)'; +0179 consumers = find(row .* fVec < -cutoff); +0180 consumers = consumers(~ismember(consumers, rpath)); % no revisiting +0181 if isempty(consumers), continue; end 0182 -0183 for ci = 1:numel(consumers) -0184 nr = consumers(ci); -0185 frac_r = abs(row(nr) * fluxes(nr)) / totalCons; -0186 newFrac = frac * frac_r; -0187 -0188 if newFrac > bestAtNode(nr) || nr == toIdx -0189 bestAtNode(nr) = max(bestAtNode(nr), newFrac); -0190 newEntry = makeEntry_(nr, newFrac, [rpath, nr], [mpath; m]); -0191 -0192 % Insert in decreasing-fraction order -0193 if isempty(queue) -0194 queue = newEntry; -0195 else -0196 fracs = [queue.frac]; -0197 pos = find(fracs < newFrac, 1); -0198 if isempty(pos) -0199 queue(end+1) = newEntry; %#ok<AGROW> -0200 else -0201 queue = [queue(1:pos-1), newEntry, queue(pos:end)]; -0202 end -0203 end -0204 end -0205 end -0206 end -0207 end -0208 -0209 % ---- Package and display ---- -0210 cumFrac = bestFrac; -0211 if ~isempty(bestRPath) -0212 pathRxns = model.rxns(bestRPath); -0213 pathMets = model.mets(bestMPath); -0214 end -0215 -0216 if verbose -0217 materialMode = traceMaterial || ~isempty(excludeMets) || carbonOnly; -0218 printPath_(model, fluxes, pathRxns, pathMets, cumFrac, fromRxn, toRxn, materialMode); -0219 end -0220 end -0221 -0222 %-------------------------------------------------------------------------- -0223 function e = makeEntry_(rxn, frac, rpath, mpath) -0224 e.rxn = rxn; -0225 e.frac = frac; -0226 e.rpath = rpath; -0227 e.mpath = mpath; -0228 end -0229 -0230 function printPath_(model, fluxes, pathRxns, pathMets, cumFrac, fromRxn, toRxn, materialMode) -0231 W = 66; -0232 bar = repmat('=', 1, W); -0233 fprintf('\n%s\n', bar); -0234 fprintf(' Flux path: %s \x2192 %s\n', fromRxn, toRxn); -0235 if materialMode -0236 fprintf(' (material flux — currency metabolites excluded)\n'); -0237 end -0238 fprintf('%s\n', bar); -0239 -0240 if isempty(pathRxns) -0241 fprintf(' No forward flux path found between these reactions.\n'); -0242 if materialMode -0243 fprintf(' Tip: try traceMaterial=false to include cofactor connections,\n'); -0244 fprintf(' or excludeMets={} to see what the filter removed.\n'); -0245 else -0246 fprintf(' (They may be in parallel branches or connected only in reverse.)\n'); -0247 end -0248 fprintf('%s\n\n', bar); -0249 return -0250 end -0251 -0252 % One-line path with junction fractions -0253 parts = pathRxns{1}; -0254 for i = 2:numel(pathRxns) -0255 mi = find(strcmp(model.mets, pathMets{i-1})); -0256 row = full(model.S(mi, :)); -0257 consumers = find(row .* fluxes' < -1e-12); -0258 if isempty(consumers) -0259 pct = NaN; -0260 else -0261 totalCons = sum(abs(row(consumers) .* fluxes(consumers)')); -0262 ni = find(strcmp(model.rxns, pathRxns{i})); -0263 pct = abs(row(ni) * fluxes(ni)) / totalCons * 100; -0264 end -0265 mLbl = getMetLabel_(model, mi); -0266 if isnan(pct) -0267 parts = [parts sprintf(' \x2192[%s]\x2192 %s', mLbl, pathRxns{i})]; %#ok<AGROW> -0268 else -0269 parts = [parts sprintf(' \x2192[%s: %.0f%%]\x2192 %s', mLbl, pct, pathRxns{i})]; %#ok<AGROW> -0270 end -0271 end -0272 fprintf('\n %s\n\n', parts); -0273 -0274 fprintf(' Cumulative fraction: %.2f%%\n', cumFrac*100); -0275 fprintf(' Path length: %d reactions\n', numel(pathRxns)); -0276 -0277 fprintf('\n Reactions:\n'); -0278 eqns = constructEquations(model, pathRxns); -0279 for i = 1:numel(pathRxns) -0280 ri = find(strcmp(model.rxns, pathRxns{i})); -0281 nm = ''; -0282 if isfield(model,'rxnNames') && ri <= numel(model.rxnNames) -0283 nm = char(model.rxnNames{ri}); -0284 end -0285 if numel(nm) > 25, nm = [nm(1:22) '...']; end -0286 fprintf(' %2d. %-14s flux: %+10.4g %-25s %s\n', i, pathRxns{i}, fluxes(ri), nm, eqns{i}); -0287 end -0288 fprintf('\n%s\n\n', bar); -0289 end -0290 -0291 function idx = resolveRxn_(model, rxn) -0292 if isnumeric(rxn) -0293 idx = rxn; -0294 else -0295 idx = find(strcmp(model.rxns, char(rxn))); -0296 if isempty(idx) -0297 error('Reaction ''%s'' not found in model.', rxn); -0298 end -0299 idx = idx(1); -0300 end -0301 end -0302 -0303 function s = getMetLabel_(model, metIdx) -0304 if isfield(model,'metNames') && ~isempty(model.metNames{metIdx}) -0305 s = model.metNames{metIdx}; -0306 else -0307 s = model.mets{metIdx}; -0308 end -0309 end -0310 -0311 function tf = isCurrencyMet_(model, m, currencyList) -0312 % Returns true if metabolite m matches any entry in currencyList. -0313 % Matches against metNames (case-insensitive exact) and against the -0314 % metabolite ID after stripping compartment suffixes like [c], [m], _c. -0315 targets = {}; -0316 if isfield(model,'metNames') && m <= numel(model.metNames) && ~isempty(model.metNames{m}) -0317 targets{end+1} = lower(strtrim(model.metNames{m})); -0318 end -0319 % Strip compartment from ID: [c], (c), _c, _C at end -0320 rawId = model.mets{m}; -0321 stripped = regexprep(rawId, '[\[\(][^\]\)]*[\]\)]$|_[a-zA-Z]\d*$', ''); -0322 targets{end+1} = lower(stripped); -0323 -0324 for i = 1:numel(currencyList) -0325 p = lower(strtrim(currencyList{i})); -0326 for j = 1:numel(targets) -0327 if strcmp(targets{j}, p) -0328 tf = true; -0329 return -0330 end -0331 end -0332 end -0333 tf = false; -0334 end -0335 -0336 function nC = countCarbon_(formula) -0337 % Count carbon atoms in a molecular formula string like 'C6H12O6'. -0338 if isempty(formula) -0339 nC = 0; return -0340 end -0341 tok = regexp(char(formula), 'C(\d*)', 'tokens', 'once'); -0342 if isempty(tok) -0343 nC = 0; -0344 elseif isempty(tok{1}) -0345 nC = 1; -0346 else -0347 nC = str2double(tok{1}); -0348 end -0349 end -0350 -0351 function lst = defaultCurrencyMets_() -0352 % Common currency / cofactor metabolites matched case-insensitively. -0353 % Bare comment lines inside {} act as row separators in older MATLAB, so -0354 % all comments are placed after ... on data lines instead. -0355 lst = { ... -0356 'ATP','ADP','AMP','dATP','dADP','dAMP', ... % adenosine phosphates -0357 'atp','adp','amp','datp','dadp','damp', ... -0358 'GTP','GDP','GMP','dGTP','dGDP','dGMP', ... % other NTPs/NDPs/NMPs -0359 'CTP','CDP','CMP','dCTP','dCDP','dCMP', ... -0360 'UTP','UDP','UMP','dUTP','dUDP','dUMP', ... -0361 'TTP','TDP','TMP','dTTP','dTDP','dTMP', ... -0362 'gtp','gdp','gmp','dgtp','dgdp','dgmp', ... -0363 'ctp','cdp','cmp','dctp','dcdp','dcmp', ... -0364 'utp','udp','ump','dutp','dudp','dump', ... -0365 'ttp','tdp','tmp','dttp','dtdp','dtmp', ... -0366 'NAD','NADH','NADP','NADPH','NAD+','NADP+', ... % nicotinamide cofactors -0367 'nad','nadh','nadp','nadph', ... -0368 'FAD','FADH2','FMN','FMNH2', ... % flavin cofactors -0369 'fad','fadh2','fmn','fmnh2', ... -0370 'CoA','coenzyme A','coenzyme a','Coenzyme A', ... % coenzyme A -0371 'coa', ... -0372 'H2O','water','H+','proton','OH-','hydroxide', ... % water / proton -0373 'h2o','h','oh1', ... -0374 'phosphate','orthophosphate','pyrophosphate','diphosphate', ... % inorganic phosphate -0375 'pi','ppi','pp', ... -0376 'CO2','carbon dioxide','bicarbonate','HCO3-', ... % carbon dioxide -0377 'co2','hco3', ... -0378 'O2','oxygen','NH3','ammonia','NH4+','ammonium','nitrogen', ... % oxygen / nitrogen -0379 'o2','nh3','nh4', ... -0380 'sulfate','sulfite','sulfide','thiosulfate', ... % sulfur species -0381 'thioredoxin','thioredoxin-SH','thioredoxin-S2', ... % redox carriers -0382 'trdrd','trdox', ... -0383 'ferredoxin','ferredoxin reduced','ferredoxin oxidized', ... -0384 'fdred','fdox', ... -0385 'ubiquinol','ubiquinone','menaquinol','menaquinone', ... -0386 'q8h2','q8','mqn8','mql8', ... -0387 'lipoamide','dihydrolipoamide', ... -0388 'S-adenosyl-L-methionine','S-adenosylhomocysteine', ... % one-carbon donors -0389 'sam','sah','amet','ahcys', ... -0390 '5-methyltetrahydrofolate','tetrahydrofolate','dihydrofolate', ... -0391 'thf','dhf','mlthf','methf' ... -0392 }; -0393 end +0183 totalCons = sum(abs(row(consumers) .* fVec(consumers))); +0184 +0185 for ci = 1:numel(consumers) +0186 nr = consumers(ci); +0187 frac_r = abs(row(nr) * fluxes(nr)) / totalCons; +0188 newFrac = frac * frac_r; +0189 +0190 if newFrac > bestAtNode(nr) || nr == toIdx +0191 bestAtNode(nr) = max(bestAtNode(nr), newFrac); +0192 newEntry = makeEntry_(nr, newFrac, [rpath, nr], [mpath; m]); +0193 +0194 % Insert in decreasing-fraction order +0195 if isempty(queue) +0196 queue = newEntry; +0197 else +0198 fracs = [queue.frac]; +0199 pos = find(fracs < newFrac, 1); +0200 if isempty(pos) +0201 queue(end+1) = newEntry; %#ok<AGROW> +0202 else +0203 queue = [queue(1:pos-1), newEntry, queue(pos:end)]; +0204 end +0205 end +0206 end +0207 end +0208 end +0209 end +0210 +0211 % ---- Package and display ---- +0212 cumFrac = bestFrac; +0213 if ~isempty(bestRPath) +0214 pathRxns = model.rxns(bestRPath); +0215 pathMets = model.mets(bestMPath); +0216 end +0217 +0218 if verbose +0219 materialMode = traceMaterial || ~isempty(excludeMets) || carbonOnly; +0220 printPath_(model, fluxes, pathRxns, pathMets, cumFrac, fromRxn, toRxn, materialMode); +0221 end +0222 end +0223 +0224 %-------------------------------------------------------------------------- +0225 function e = makeEntry_(rxn, frac, rpath, mpath) +0226 e.rxn = rxn; +0227 e.frac = frac; +0228 e.rpath = rpath; +0229 e.mpath = mpath; +0230 end +0231 +0232 function printPath_(model, fluxes, pathRxns, pathMets, cumFrac, fromRxn, toRxn, materialMode) +0233 W = 66; +0234 bar = repmat('=', 1, W); +0235 fprintf('\n%s\n', bar); +0236 fprintf(' Flux path: %s \x2192 %s\n', fromRxn, toRxn); +0237 if materialMode +0238 fprintf(' (material flux — currency metabolites excluded)\n'); +0239 end +0240 fprintf('%s\n', bar); +0241 +0242 if isempty(pathRxns) +0243 fprintf(' No forward flux path found between these reactions.\n'); +0244 if materialMode +0245 fprintf(' Tip: try traceMaterial=false to include cofactor connections,\n'); +0246 fprintf(' or excludeMets={} to see what the filter removed.\n'); +0247 else +0248 fprintf(' (They may be in parallel branches or connected only in reverse.)\n'); +0249 end +0250 fprintf('%s\n\n', bar); +0251 return +0252 end +0253 +0254 % One-line path with junction fractions +0255 parts = pathRxns{1}; +0256 for i = 2:numel(pathRxns) +0257 mi = find(strcmp(model.mets, pathMets{i-1})); +0258 row = full(model.S(mi, :)); +0259 consumers = find(row .* fluxes' < -1e-12); +0260 if isempty(consumers) +0261 pct = NaN; +0262 else +0263 totalCons = sum(abs(row(consumers) .* fluxes(consumers)')); +0264 ni = find(strcmp(model.rxns, pathRxns{i})); +0265 pct = abs(row(ni) * fluxes(ni)) / totalCons * 100; +0266 end +0267 mLbl = getMetLabel_(model, mi); +0268 if isnan(pct) +0269 parts = [parts sprintf(' \x2192[%s]\x2192 %s', mLbl, pathRxns{i})]; %#ok<AGROW> +0270 else +0271 parts = [parts sprintf(' \x2192[%s: %.0f%%]\x2192 %s', mLbl, pct, pathRxns{i})]; %#ok<AGROW> +0272 end +0273 end +0274 fprintf('\n %s\n\n', parts); +0275 +0276 fprintf(' Cumulative fraction: %.2f%%\n', cumFrac*100); +0277 fprintf(' Path length: %d reactions\n', numel(pathRxns)); +0278 +0279 fprintf('\n Reactions:\n'); +0280 eqns = constructEquations(model, pathRxns); +0281 for i = 1:numel(pathRxns) +0282 ri = find(strcmp(model.rxns, pathRxns{i})); +0283 nm = ''; +0284 if isfield(model,'rxnNames') && ri <= numel(model.rxnNames) +0285 nm = char(model.rxnNames{ri}); +0286 end +0287 if numel(nm) > 25, nm = [nm(1:22) '...']; end +0288 fprintf(' %2d. %-14s flux: %+10.4g %-25s %s\n', i, pathRxns{i}, fluxes(ri), nm, eqns{i}); +0289 end +0290 fprintf('\n%s\n\n', bar); +0291 end +0292 +0293 function idx = resolveRxn_(model, rxn) +0294 if isnumeric(rxn) +0295 idx = rxn; +0296 else +0297 idx = find(strcmp(model.rxns, char(rxn))); +0298 if isempty(idx) +0299 error('Reaction ''%s'' not found in model.', rxn); +0300 end +0301 idx = idx(1); +0302 end +0303 end +0304 +0305 function s = getMetLabel_(model, metIdx) +0306 if isfield(model,'metNames') && ~isempty(model.metNames{metIdx}) +0307 s = model.metNames{metIdx}; +0308 else +0309 s = model.mets{metIdx}; +0310 end +0311 end +0312 +0313 function tf = isCurrencyMet_(model, m, currencyList) +0314 % Returns true if metabolite m matches any entry in currencyList. +0315 % Matches against metNames (case-insensitive exact) and against the +0316 % metabolite ID after stripping compartment suffixes like [c], [m], _c. +0317 targets = {}; +0318 if isfield(model,'metNames') && m <= numel(model.metNames) && ~isempty(model.metNames{m}) +0319 targets{end+1} = lower(strtrim(model.metNames{m})); +0320 end +0321 % Strip compartment from ID: [c], (c), _c, _C at end +0322 rawId = model.mets{m}; +0323 stripped = regexprep(rawId, '[\[\(][^\]\)]*[\]\)]$|_[a-zA-Z]\d*$', ''); +0324 targets{end+1} = lower(stripped); +0325 +0326 for i = 1:numel(currencyList) +0327 p = lower(strtrim(currencyList{i})); +0328 for j = 1:numel(targets) +0329 if strcmp(targets{j}, p) +0330 tf = true; +0331 return +0332 end +0333 end +0334 end +0335 tf = false; +0336 end +0337 +0338 function nC = countCarbon_(formula) +0339 % Count carbon atoms in a molecular formula string like 'C6H12O6'. +0340 if isempty(formula) +0341 nC = 0; return +0342 end +0343 tok = regexp(char(formula), 'C(\d*)', 'tokens', 'once'); +0344 if isempty(tok) +0345 nC = 0; +0346 elseif isempty(tok{1}) +0347 nC = 1; +0348 else +0349 nC = str2double(tok{1}); +0350 end +0351 end +0352 +0353 function lst = defaultCurrencyMets_() +0354 % Common currency / cofactor metabolites matched case-insensitively. +0355 % Bare comment lines inside {} act as row separators in older MATLAB, so +0356 % all comments are placed after ... on data lines instead. +0357 lst = { ... +0358 'ATP','ADP','AMP','dATP','dADP','dAMP', ... % adenosine phosphates +0359 'atp','adp','amp','datp','dadp','damp', ... +0360 'GTP','GDP','GMP','dGTP','dGDP','dGMP', ... % other NTPs/NDPs/NMPs +0361 'CTP','CDP','CMP','dCTP','dCDP','dCMP', ... +0362 'UTP','UDP','UMP','dUTP','dUDP','dUMP', ... +0363 'TTP','TDP','TMP','dTTP','dTDP','dTMP', ... +0364 'gtp','gdp','gmp','dgtp','dgdp','dgmp', ... +0365 'ctp','cdp','cmp','dctp','dcdp','dcmp', ... +0366 'utp','udp','ump','dutp','dudp','dump', ... +0367 'ttp','tdp','tmp','dttp','dtdp','dtmp', ... +0368 'NAD','NADH','NADP','NADPH','NAD+','NADP+', ... % nicotinamide cofactors +0369 'nad','nadh','nadp','nadph', ... +0370 'FAD','FADH2','FMN','FMNH2', ... % flavin cofactors +0371 'fad','fadh2','fmn','fmnh2', ... +0372 'CoA','coenzyme A','coenzyme a','Coenzyme A', ... % coenzyme A +0373 'coa', ... +0374 'H2O','water','H+','proton','OH-','hydroxide', ... % water / proton +0375 'h2o','h','oh1', ... +0376 'phosphate','orthophosphate','pyrophosphate','diphosphate', ... % inorganic phosphate +0377 'pi','ppi','pp', ... +0378 'CO2','carbon dioxide','bicarbonate','HCO3-', ... % carbon dioxide +0379 'co2','hco3', ... +0380 'O2','oxygen','NH3','ammonia','NH4+','ammonium','nitrogen', ... % oxygen / nitrogen +0381 'o2','nh3','nh4', ... +0382 'sulfate','sulfite','sulfide','thiosulfate', ... % sulfur species +0383 'thioredoxin','thioredoxin-SH','thioredoxin-S2', ... % redox carriers +0384 'trdrd','trdox', ... +0385 'ferredoxin','ferredoxin reduced','ferredoxin oxidized', ... +0386 'fdred','fdox', ... +0387 'ubiquinol','ubiquinone','menaquinol','menaquinone', ... +0388 'q8h2','q8','mqn8','mql8', ... +0389 'lipoamide','dihydrolipoamide', ... +0390 'S-adenosyl-L-methionine','S-adenosylhomocysteine', ... % one-carbon donors +0391 'sam','sah','amet','ahcys', ... +0392 '5-methyltetrahydrofolate','tetrahydrofolate','dihydrofolate', ... +0393 'thf','dhf','mlthf','methf' ... +0394 }; +0395 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/analysis/walkFluxes.html b/docs/html/analysis/walkFluxes.html index 7ee884e0..7d0214e6 100644 --- a/docs/html/analysis/walkFluxes.html +++ b/docs/html/analysis/walkFluxes.html @@ -29,26 +29,30 @@

    SYNOPSIS ^DESCRIPTION ^

     walkFluxes  Interactively navigate a flux distribution reaction by reaction.
     
    - Starting from a chosen reaction, displays all flux-carrying reactions
    + Starting from a chosen reaction, displays the flux-carrying reactions
      connected through shared metabolites, grouped by metabolite and labelled
    - with each neighbour's role (produces / consumes). Select a neighbour by
    - number to step to it; build a history and rewind with 'b'.
    + with each neighbour's role (produces or consumes). Entering the number of
    + a neighbour steps to it. The visited reactions are kept in a history, and
    + 'b' steps back through it.
     
      Parameters
      ----------
      model : struct
    -     RAVEN model structure.
    +     a model structure.
      fluxes : double
          flux vector (length = numel(model.rxns)).
      startRxn : char or double
    -     starting reaction ID (string) or column index.
    +     starting reaction ID (string) or reaction index.
     
      Name-Value Arguments
      --------------------
      cutoff : double
    -     minimum |flux| to display a reaction as a neighbour (default 1e-8).
    +     minimum net flux through a metabolite (|S_ij * v_j|) for the metabolite
    +     to be listed for the current reaction and for a reaction to be
    +     displayed as a neighbour (default 1e-8).
      maxPerMet : double
    -     maximum neighbour reactions displayed per metabolite (default 8).
    +     maximum neighbour reactions displayed per metabolite, selected by
    +     decreasing absolute flux (default 8).
     
      Examples
      --------
    @@ -59,14 +63,12 @@ 

    DESCRIPTION ^CROSS-REFERENCE INFORMATION ^

    @@ -86,201 +88,203 @@

    SOURCE CODE ^
    0001 function walkFluxes(model, fluxes, startRxn, varargin)
     0002 % walkFluxes  Interactively navigate a flux distribution reaction by reaction.
     0003 %
    -0004 % Starting from a chosen reaction, displays all flux-carrying reactions
    +0004 % Starting from a chosen reaction, displays the flux-carrying reactions
     0005 % connected through shared metabolites, grouped by metabolite and labelled
    -0006 % with each neighbour's role (produces / consumes). Select a neighbour by
    -0007 % number to step to it; build a history and rewind with 'b'.
    -0008 %
    -0009 % Parameters
    -0010 % ----------
    -0011 % model : struct
    -0012 %     RAVEN model structure.
    -0013 % fluxes : double
    -0014 %     flux vector (length = numel(model.rxns)).
    -0015 % startRxn : char or double
    -0016 %     starting reaction ID (string) or column index.
    -0017 %
    -0018 % Name-Value Arguments
    -0019 % --------------------
    -0020 % cutoff : double
    -0021 %     minimum |flux| to display a reaction as a neighbour (default 1e-8).
    -0022 % maxPerMet : double
    -0023 %     maximum neighbour reactions displayed per metabolite (default 8).
    -0024 %
    -0025 % Examples
    -0026 % --------
    -0027 %     sol = solveLP(model);
    -0028 %     walkFluxes(model, sol.x, 'BIOMASS_Ec_iJO1366_core_53p95M')
    -0029 %     walkFluxes(model, sol.x, 'PFK', 'cutoff', 1e-6)
    -0030 %
    -0031 % Notes
    -0032 % -----
    -0033 % Navigation commands:
    -0034 %   <number>  — step to that neighbour reaction
    -0035 %   b         — go back to the previous reaction
    -0036 %   q         — quit
    -0037 %
    -0038 % This function is interactive and reads from the terminal at every step;
    -0039 % it errors immediately under -batch, in deployed code, or otherwise without
    -0040 % a real terminal, rather than blocking forever waiting for input that will
    -0041 % never arrive.
    -0042 
    -0043 isBatch = false;
    -0044 try
    -0045     isBatch = batchStartupOptionUsed;
    -0046 catch
    -0047 end
    -0048 if isBatch || isdeployed
    -0049     error('walkFluxes:nonInteractive', ...
    -0050         ['walkFluxes is interactive and reads from the terminal at every step, ' ...
    -0051          'so it cannot run under -batch or in deployed code. Run it in an ' ...
    -0052          'interactive MATLAB session instead.']);
    -0053 end
    -0054 
    -0055 p = parseRAVENargs(varargin, {'cutoff',1e-8; 'maxPerMet',8});
    -0056 cutoff    = p.cutoff;
    -0057 maxPerMet = p.maxPerMet;
    -0058 
    -0059 rxnIdx  = resolveRxn_(model, startRxn);
    -0060 history = zeros(0,1);
    -0061 W       = 68;
    -0062 
    -0063 while true
    -0064     % ---- Header ----
    -0065     fprintf('\n%s\n', repmat('=', 1, W));
    -0066     f   = fluxes(rxnIdx);
    -0067     rID = model.rxns{rxnIdx};
    -0068     rName = getRxnName_(model, rxnIdx);
    -0069     if isempty(rName)
    -0070         fprintf('  [%s]  flux: %+.6g\n', rID, f);
    -0071     else
    -0072         fprintf('  [%s]  %s\n  flux: %+.6g\n', rID, rName, f);
    -0073     end
    -0074     eqn = constructEquations(model, model.rxns(rxnIdx));
    -0075     fprintf('  %s\n', eqn{1});
    -0076     if isfield(model,'grRules') && rxnIdx <= numel(model.grRules) ...
    -0077             && ~isempty(model.grRules{rxnIdx})
    -0078         fprintf('  genes: %s\n', model.grRules{rxnIdx});
    -0079     end
    -0080     fprintf('%s\n', repmat('=', 1, W));
    -0081 
    -0082     % ---- Collect neighbours grouped by metabolite ----
    -0083     col         = full(model.S(:, rxnIdx));
    -0084     nonzeroMets = find(abs(col) > 0);
    -0085 
    -0086     % One number per unique reaction across all metabolite groups
    -0087     seenRxns   = containers.Map('KeyType','double','ValueType','double');
    -0088     allNeighbors = [];
    -0089 
    -0090     for mi = 1:numel(nonzeroMets)
    -0091         m    = nonzeroMets(mi);
    -0092         netF = col(m) * f;   % >0: current rxn produces m; <0: consumes m
    -0093 
    -0094         if abs(netF) < cutoff, continue; end
    +0006 % with each neighbour's role (produces or consumes). Entering the number of
    +0007 % a neighbour steps to it. The visited reactions are kept in a history, and
    +0008 % 'b' steps back through it.
    +0009 %
    +0010 % Parameters
    +0011 % ----------
    +0012 % model : struct
    +0013 %     a model structure.
    +0014 % fluxes : double
    +0015 %     flux vector (length = numel(model.rxns)).
    +0016 % startRxn : char or double
    +0017 %     starting reaction ID (string) or reaction index.
    +0018 %
    +0019 % Name-Value Arguments
    +0020 % --------------------
    +0021 % cutoff : double
    +0022 %     minimum net flux through a metabolite (|S_ij * v_j|) for the metabolite
    +0023 %     to be listed for the current reaction and for a reaction to be
    +0024 %     displayed as a neighbour (default 1e-8).
    +0025 % maxPerMet : double
    +0026 %     maximum neighbour reactions displayed per metabolite, selected by
    +0027 %     decreasing absolute flux (default 8).
    +0028 %
    +0029 % Examples
    +0030 % --------
    +0031 %     sol = solveLP(model);
    +0032 %     walkFluxes(model, sol.x, 'BIOMASS_Ec_iJO1366_core_53p95M')
    +0033 %     walkFluxes(model, sol.x, 'PFK', 'cutoff', 1e-6)
    +0034 %
    +0035 % Notes
    +0036 % -----
    +0037 % Navigation commands:
    +0038 %   <number>  : step to that neighbour reaction
    +0039 %   b         : go back to the previous reaction
    +0040 %   q         : quit
    +0041 %
    +0042 % This function is interactive and reads from the terminal at every step.
    +0043 % It raises an error under -batch and in deployed code.
    +0044 
    +0045 isBatch = false;
    +0046 try
    +0047     isBatch = batchStartupOptionUsed;
    +0048 catch
    +0049 end
    +0050 if isBatch || isdeployed
    +0051     error('walkFluxes:nonInteractive', ...
    +0052         ['walkFluxes is interactive and reads from the terminal at every step, ' ...
    +0053          'so it cannot run under -batch or in deployed code. Run it in an ' ...
    +0054          'interactive MATLAB session instead.']);
    +0055 end
    +0056 
    +0057 p = parseRAVENargs(varargin, {'cutoff',1e-8; 'maxPerMet',8});
    +0058 cutoff    = p.cutoff;
    +0059 maxPerMet = p.maxPerMet;
    +0060 
    +0061 rxnIdx  = resolveRxn_(model, startRxn);
    +0062 history = zeros(0,1);
    +0063 W       = 68;
    +0064 
    +0065 while true
    +0066     % ---- Header ----
    +0067     fprintf('\n%s\n', repmat('=', 1, W));
    +0068     f   = fluxes(rxnIdx);
    +0069     rID = model.rxns{rxnIdx};
    +0070     rName = getRxnName_(model, rxnIdx);
    +0071     if isempty(rName)
    +0072         fprintf('  [%s]  flux: %+.6g\n', rID, f);
    +0073     else
    +0074         fprintf('  [%s]  %s\n  flux: %+.6g\n', rID, rName, f);
    +0075     end
    +0076     eqn = constructEquations(model, model.rxns(rxnIdx));
    +0077     fprintf('  %s\n', eqn{1});
    +0078     if isfield(model,'grRules') && rxnIdx <= numel(model.grRules) ...
    +0079             && ~isempty(model.grRules{rxnIdx})
    +0080         fprintf('  genes: %s\n', model.grRules{rxnIdx});
    +0081     end
    +0082     fprintf('%s\n', repmat('=', 1, W));
    +0083 
    +0084     % ---- Collect neighbours grouped by metabolite ----
    +0085     col         = full(model.S(:, rxnIdx));
    +0086     nonzeroMets = find(abs(col) > 0);
    +0087 
    +0088     % One number per unique reaction across all metabolite groups
    +0089     seenRxns   = containers.Map('KeyType','double','ValueType','double');
    +0090     allNeighbors = [];
    +0091 
    +0092     for mi = 1:numel(nonzeroMets)
    +0093         m    = nonzeroMets(mi);
    +0094         netF = col(m) * f;   % >0: current rxn produces m; <0: consumes m
     0095 
    -0096         if netF < 0
    -0097             role = 'consumed';
    -0098         else
    -0099             role = 'produced';
    -0100         end
    -0101 
    -0102         % Other flux-carrying reactions involving this metabolite
    -0103         row   = full(model.S(m, :));
    -0104         carry = find(abs(row .* fluxes') > cutoff);
    -0105         carry = carry(carry ~= rxnIdx);
    -0106         if isempty(carry), continue; end
    -0107 
    -0108         [~, si] = sort(abs(fluxes(carry)), 'descend');
    -0109         carry   = carry(si(1:min(end, maxPerMet)));
    -0110 
    -0111         mLbl = getMetLabel_(model, m);
    -0112         fprintf('\n  %s  [%s, %.4g]\n', mLbl, role, abs(netF));
    -0113 
    -0114         for ci = 1:numel(carry)
    -0115             nr         = carry(ci);
    -0116             neighNetF  = row(nr) * fluxes(nr);  % >0: nr produces m; <0: consumes m
    -0117             neighRole  = 'produces';
    -0118             if neighNetF < 0, neighRole = 'consumes'; end
    -0119 
    -0120             nrName = getRxnName_(model, nr);
    -0121             if numel(nrName) > 28, nrName = [nrName(1:25) '...']; end
    -0122 
    -0123             if isKey(seenRxns, nr)
    -0124                 n = seenRxns(nr);   % re-use existing number; don't re-add
    -0125             else
    -0126                 n = numel(allNeighbors) + 1;
    -0127                 seenRxns(nr)     = n;
    -0128                 allNeighbors(end+1) = nr; %#ok<AGROW>
    -0129             end
    -0130 
    -0131             fprintf('  %3d. %-14s  %+9.4g  %-8s  %s\n', ...
    -0132                 n, model.rxns{nr}, fluxes(nr), neighRole, nrName);
    -0133         end
    -0134     end
    -0135 
    -0136     fprintf('\n');
    -0137     nN = numel(allNeighbors);
    -0138 
    -0139     if nN == 0
    -0140         fprintf('  (no flux-carrying neighbours at this cutoff)\n');
    -0141         fprintf('  b: go back   q: quit\n');
    -0142         resp = strtrim(input('  > ', 's'));
    -0143     else
    -0144         fprintf('  1-%d: step to reaction   b: go back   q: quit\n', nN);
    -0145         resp = strtrim(input('  > ', 's'));
    -0146     end
    -0147 
    -0148     if strcmpi(resp, 'q')
    -0149         fprintf('Navigator closed.\n');
    -0150         return
    -0151 
    -0152     elseif strcmpi(resp, 'b')
    -0153         if isempty(history)
    -0154             fprintf('  (no history)\n');
    -0155         else
    -0156             rxnIdx     = history(end);
    -0157             history(end) = [];
    -0158         end
    -0159 
    -0160     else
    -0161         n = str2double(resp);
    -0162         if ~isnan(n) && n >= 1 && n <= nN && n == round(n)
    -0163             history(end+1) = rxnIdx; %#ok<AGROW>
    -0164             rxnIdx         = allNeighbors(n);
    -0165         else
    -0166             fprintf('  Enter a number 1-%d, ''b'', or ''q''.\n', max(nN,1));
    -0167         end
    -0168     end
    -0169 end
    -0170 end
    -0171 
    -0172 %--------------------------------------------------------------------------
    -0173 function idx = resolveRxn_(model, rxn)
    -0174     if isnumeric(rxn)
    -0175         idx = rxn;
    -0176     else
    -0177         idx = find(strcmp(model.rxns, char(rxn)));
    -0178         if isempty(idx)
    -0179             error('Reaction ''%s'' not found in model.', rxn);
    -0180         end
    -0181         idx = idx(1);
    -0182     end
    -0183 end
    -0184 
    -0185 function s = getRxnName_(model, rxnIdx)
    -0186     s = '';
    -0187     if isfield(model,'rxnNames') && rxnIdx <= numel(model.rxnNames)
    -0188         s = char(model.rxnNames{rxnIdx});
    -0189     end
    -0190 end
    -0191 
    -0192 function s = getMetLabel_(model, metIdx)
    -0193     if isfield(model,'metNames') && ~isempty(model.metNames{metIdx})
    -0194         s = model.metNames{metIdx};
    -0195     else
    -0196         s = model.mets{metIdx};
    -0197     end
    -0198 end

    +0096 if abs(netF) < cutoff, continue; end +0097 +0098 if netF < 0 +0099 role = 'consumed'; +0100 else +0101 role = 'produced'; +0102 end +0103 +0104 % Other flux-carrying reactions involving this metabolite +0105 row = full(model.S(m, :)); +0106 carry = find(abs(row .* fluxes') > cutoff); +0107 carry = carry(carry ~= rxnIdx); +0108 if isempty(carry), continue; end +0109 +0110 [~, si] = sort(abs(fluxes(carry)), 'descend'); +0111 carry = carry(si(1:min(end, maxPerMet))); +0112 +0113 mLbl = getMetLabel_(model, m); +0114 fprintf('\n %s [%s, %.4g]\n', mLbl, role, abs(netF)); +0115 +0116 for ci = 1:numel(carry) +0117 nr = carry(ci); +0118 neighNetF = row(nr) * fluxes(nr); % >0: nr produces m; <0: consumes m +0119 neighRole = 'produces'; +0120 if neighNetF < 0, neighRole = 'consumes'; end +0121 +0122 nrName = getRxnName_(model, nr); +0123 if numel(nrName) > 28, nrName = [nrName(1:25) '...']; end +0124 +0125 if isKey(seenRxns, nr) +0126 n = seenRxns(nr); % re-use existing number; don't re-add +0127 else +0128 n = numel(allNeighbors) + 1; +0129 seenRxns(nr) = n; +0130 allNeighbors(end+1) = nr; %#ok<AGROW> +0131 end +0132 +0133 fprintf(' %3d. %-14s %+9.4g %-8s %s\n', ... +0134 n, model.rxns{nr}, fluxes(nr), neighRole, nrName); +0135 end +0136 end +0137 +0138 fprintf('\n'); +0139 nN = numel(allNeighbors); +0140 +0141 if nN == 0 +0142 fprintf(' (no flux-carrying neighbours at this cutoff)\n'); +0143 fprintf(' b: go back q: quit\n'); +0144 resp = strtrim(input(' > ', 's')); +0145 else +0146 fprintf(' 1-%d: step to reaction b: go back q: quit\n', nN); +0147 resp = strtrim(input(' > ', 's')); +0148 end +0149 +0150 if strcmpi(resp, 'q') +0151 fprintf('Navigator closed.\n'); +0152 return +0153 +0154 elseif strcmpi(resp, 'b') +0155 if isempty(history) +0156 fprintf(' (no history)\n'); +0157 else +0158 rxnIdx = history(end); +0159 history(end) = []; +0160 end +0161 +0162 else +0163 n = str2double(resp); +0164 if ~isnan(n) && n >= 1 && n <= nN && n == round(n) +0165 history(end+1) = rxnIdx; %#ok<AGROW> +0166 rxnIdx = allNeighbors(n); +0167 else +0168 fprintf(' Enter a number 1-%d, ''b'', or ''q''.\n', max(nN,1)); +0169 end +0170 end +0171 end +0172 end +0173 +0174 %-------------------------------------------------------------------------- +0175 function idx = resolveRxn_(model, rxn) +0176 if isnumeric(rxn) +0177 idx = rxn; +0178 else +0179 idx = find(strcmp(model.rxns, char(rxn))); +0180 if isempty(idx) +0181 error('Reaction ''%s'' not found in model.', rxn); +0182 end +0183 idx = idx(1); +0184 end +0185 end +0186 +0187 function s = getRxnName_(model, rxnIdx) +0188 s = ''; +0189 if isfield(model,'rxnNames') && rxnIdx <= numel(model.rxnNames) +0190 s = char(model.rxnNames{rxnIdx}); +0191 end +0192 end +0193 +0194 function s = getMetLabel_(model, metIdx) +0195 if isfield(model,'metNames') && ~isempty(model.metNames{metIdx}) +0196 s = model.metNames{metIdx}; +0197 else +0198 s = model.mets{metIdx}; +0199 end +0200 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/annotation/assignSBOterms.html b/docs/html/annotation/assignSBOterms.html index f15081a2..0f81feac 100644 --- a/docs/html/annotation/assignSBOterms.html +++ b/docs/html/annotation/assignSBOterms.html @@ -29,26 +29,26 @@

    SYNOPSIS ^DESCRIPTION ^

     assignSBOterms  Assign SBO terms to metabolites and reactions.
     
    - Assign SBO terms to metabolites and reactions following a generic rule
    - set. The MATLAB counterpart of yeast-GEM's legacy addSBOterms;
    - organism-agnostic, parameterised entirely by opts.
    + Assigns SBO terms to metabolites and reactions following a generic rule
    + set that opts parameterises. The function is the MATLAB counterpart of
    + yeast-GEM's addSBOterms.
     
    - SBO is written via editMiriam(..., 'fill') so pre-existing SBO
    - annotations are preserved. The terms are stored in model.metMiriams and
    + SBO is written via editMiriam(..., 'fill'), which preserves pre-existing
    + SBO annotations. The terms are stored in model.metMiriams and
      model.rxnMiriams under the namespace 'sbo' (there is no separate SBO
    - field); read them with extractMiriam(model.rxnMiriams, 'sbo').
    + field); they can be read with extractMiriam(model.rxnMiriams, 'sbo').
      exportModel writes them as the SBML sboTerm attribute.
     
      Parameters
      ----------
      model : struct
    -     RAVEN model struct.
    +     a model structure.
     
      Name-Value Arguments
      --------------------
      opts : struct
    -     Struct with any of the following fields. Missing fields take the
    -     defaults shown:
    +     structure with any of the following fields. Missing or empty fields
    +     take the defaults shown:
     
          - biomassMetNames : {'biomass','DNA','RNA','protein','carbohydrate',
            'lipid','cofactor','ion'}
    @@ -56,15 +56,19 @@ 

    DESCRIPTION ^DESCRIPTION ^SOURCE CODE ^
    0001 function model = assignSBOterms(model, varargin)
     0002 % assignSBOterms  Assign SBO terms to metabolites and reactions.
     0003 %
    -0004 % Assign SBO terms to metabolites and reactions following a generic rule
    -0005 % set. The MATLAB counterpart of yeast-GEM's legacy addSBOterms;
    -0006 % organism-agnostic, parameterised entirely by opts.
    +0004 % Assigns SBO terms to metabolites and reactions following a generic rule
    +0005 % set that opts parameterises. The function is the MATLAB counterpart of
    +0006 % yeast-GEM's addSBOterms.
     0007 %
    -0008 % SBO is written via editMiriam(..., 'fill') so pre-existing SBO
    -0009 % annotations are preserved. The terms are stored in model.metMiriams and
    +0008 % SBO is written via editMiriam(..., 'fill'), which preserves pre-existing
    +0009 % SBO annotations. The terms are stored in model.metMiriams and
     0010 % model.rxnMiriams under the namespace 'sbo' (there is no separate SBO
    -0011 % field); read them with extractMiriam(model.rxnMiriams, 'sbo').
    +0011 % field); they can be read with extractMiriam(model.rxnMiriams, 'sbo').
     0012 % exportModel writes them as the SBML sboTerm attribute.
     0013 %
     0014 % Parameters
     0015 % ----------
     0016 % model : struct
    -0017 %     RAVEN model struct.
    +0017 %     a model structure.
     0018 %
     0019 % Name-Value Arguments
     0020 % --------------------
     0021 % opts : struct
    -0022 %     Struct with any of the following fields. Missing fields take the
    -0023 %     defaults shown:
    +0022 %     structure with any of the following fields. Missing or empty fields
    +0023 %     take the defaults shown:
     0024 %
     0025 %     - biomassMetNames : {'biomass','DNA','RNA','protein','carbohydrate',
     0026 %       'lipid','cofactor','ion'}
    @@ -141,147 +144,150 @@ 

    SOURCE CODE ^% - biomassRxnName : 'biomass pseudoreaction' 0029 % - ngamRxnName : 'non-growth associated maintenance reaction' 0030 % - pseudoreactionSubstrings : {'pseudoreaction','SLIME rxn'} -0031 % - onlyLastReactionForPseudo : false. yeast-GEM bug-compat flag that -0032 % replicates the legacy `for i=numel(...)` typo (pseudoreaction SBOs -0033 % applied only to the last reaction). Off by default; turn ON for -0034 % byte-equivalent yeast-GEM output. -0035 % -0036 % Returns -0037 % ------- -0038 % model : struct -0039 % Modified model. -0040 % -0041 % Examples -0042 % -------- -0043 % model = assignSBOterms(model); -0044 % model = assignSBOterms(model, struct('onlyLastReactionForPseudo', true)); -0045 % -0046 % Notes -0047 % ----- -0048 % Metabolites: +0031 % - onlyLastReactionForPseudo : false. If true, the biomass, NGAM and +0032 % pseudoreaction terms are applied only to the last reaction of the +0033 % model. This reproduces yeast-GEM's addSBOterms.m, which applies the +0034 % pseudoreaction rule to the last reaction only, because of a typo in +0035 % the loop header (for i=numel(...)). +0036 % - transportRxnIdxs : indexes of the transport reactions (default: +0037 % the reactions identified by getTransportRxns). +0038 % +0039 % Returns +0040 % ------- +0041 % model : struct +0042 % model structure with the SBO terms added to model.metMiriams and +0043 % model.rxnMiriams. +0044 % +0045 % Examples +0046 % -------- +0047 % model = assignSBOterms(model); +0048 % model = assignSBOterms(model, struct('onlyLastReactionForPseudo', true)); 0049 % -0050 % SBO:0000649 (Biomass) when met.name is in opts.biomassMetNames, or -0051 % ends with any of opts.biomassMetSuffixes. Otherwise SBO:0000247 -0052 % (Simple chemical). +0050 % Notes +0051 % ----- +0052 % Metabolites: 0053 % -0054 % Reactions (default → override → pseudoreaction override): -0055 % -0056 % SBO:0000176 (Metabolic reaction) default. -0057 % Single-reactant reactions become: -0058 % SBO:0000627 (exchange) if the lone metabolite is extracellular -0059 % (compartment 'e' or compartment name containing -0060 % 'extracellular'), -0061 % SBO:0000632 (sink) if coef < 0, -0062 % SBO:0000628 (demand) otherwise. -0063 % Transport reactions (detected by opts.transportDetector or the -0064 % default heuristic: same metName in ≥ 2 compartments in a single -0065 % reaction) → SBO:0000655. -0066 % Reactions whose name matches opts.biomassRxnName → SBO:0000629. -0067 % Reactions whose name matches opts.ngamRxnName → SBO:0000630. -0068 % Reactions whose name contains any of opts.pseudoreactionSubstrings -0069 % → SBO:0000395. -0070 -0071 p=parseRAVENargs(varargin, {'opts',[]}); -0072 opts=p.opts; -0073 if isempty(opts) -0074 opts = struct(); -0075 end -0076 opts = applyDefaults(opts); -0077 -0078 % Metabolite SBO ------------------------------------------------------ -0079 metsSBO = cell(size(model.mets)); -0080 for i = 1:length(model.mets) -0081 metName = model.metNames{i}; -0082 if any(strcmp(opts.biomassMetNames, metName)) || endsWithAny(metName, opts.biomassMetSuffixes) -0083 metsSBO{i} = 'SBO:0000649'; -0084 else -0085 metsSBO{i} = 'SBO:0000247'; -0086 end -0087 end -0088 -0089 % Reaction SBO -------------------------------------------------------- -0090 rxnSBO = cell(size(model.rxns)); -0091 rxnSBO(:) = {'SBO:0000176'}; -0092 -0093 % Single-reactant reactions -0094 reactantNumber = sum(model.S ~= 0, 1); -0095 singleRxns = find(reactantNumber == 1); -0096 for k = 1:numel(singleRxns) -0097 idx = singleRxns(k); -0098 metRow = find(model.S(:, idx)); -0099 compName = model.compNames{model.metComps(metRow)}; -0100 compShort = model.comps{model.metComps(metRow)}; -0101 if strcmp(compShort, 'e') || strcmp(compName, 'extracellular') -0102 rxnSBO{idx} = 'SBO:0000627'; -0103 elseif sum(model.S(:, idx)) < 0 -0104 rxnSBO{idx} = 'SBO:0000632'; -0105 else -0106 rxnSBO{idx} = 'SBO:0000628'; -0107 end -0108 end -0109 -0110 % Transport reactions -0111 if isfield(opts, 'transportRxnIdxs') && ~isempty(opts.transportRxnIdxs) -0112 transportIdxs = opts.transportRxnIdxs; -0113 else -0114 transportIdxs = getTransportRxns(model); -0115 end -0116 rxnSBO(transportIdxs) = {'SBO:0000655'}; -0117 -0118 % Pseudoreaction overrides -0119 if opts.onlyLastReactionForPseudo -0120 pseudoTargets = numel(model.rxns); -0121 else -0122 pseudoTargets = 1:numel(model.rxns); -0123 end -0124 for ii = pseudoTargets -0125 name = model.rxnNames{ii}; -0126 if strcmp(name, opts.biomassRxnName) -0127 rxnSBO{ii} = 'SBO:0000629'; -0128 elseif strcmp(name, opts.ngamRxnName) -0129 rxnSBO{ii} = 'SBO:0000630'; -0130 else -0131 for k = 1:numel(opts.pseudoreactionSubstrings) -0132 if contains(name, opts.pseudoreactionSubstrings{k}) -0133 rxnSBO{ii} = 'SBO:0000395'; -0134 break; -0135 end -0136 end -0137 end -0138 end -0139 -0140 model = editMiriam(model, 'met', 'all', 'sbo', metsSBO, 'fill'); -0141 model = editMiriam(model, 'rxn', 'all', 'sbo', rxnSBO, 'fill'); -0142 end -0143 -0144 -0145 function opts = applyDefaults(opts) -0146 defaults = struct( ... -0147 'biomassMetNames', {{'biomass','DNA','RNA','protein','carbohydrate','lipid','cofactor','ion'}}, ... -0148 'biomassMetSuffixes', {{' backbone',' chain'}}, ... -0149 'biomassRxnName', 'biomass pseudoreaction', ... -0150 'ngamRxnName', 'non-growth associated maintenance reaction', ... -0151 'pseudoreactionSubstrings', {{'pseudoreaction','SLIME rxn'}}, ... -0152 'onlyLastReactionForPseudo', false); -0153 fields = fieldnames(defaults); -0154 for k = 1:numel(fields) -0155 f = fields{k}; -0156 if ~isfield(opts, f) || isempty(opts.(f)) -0157 opts.(f) = defaults.(f); -0158 end -0159 end -0160 end -0161 -0162 -0163 function tf = endsWithAny(s, suffixes) -0164 tf = false; -0165 for i = 1:numel(suffixes) -0166 if endsWith(s, suffixes{i}) -0167 tf = true; -0168 return; -0169 end -0170 end -0171 end

    +0054 % SBO:0000649 (Biomass) when the metabolite name is in +0055 % opts.biomassMetNames, or ends with any of opts.biomassMetSuffixes. +0056 % Otherwise SBO:0000247 (Simple chemical). +0057 % +0058 % Reactions (default → override → pseudoreaction override): +0059 % +0060 % SBO:0000176 (Metabolic reaction) default. +0061 % Single-reactant reactions become: +0062 % SBO:0000627 (exchange) if the lone metabolite is extracellular +0063 % (compartment 'e' or compartment name 'extracellular'), +0064 % SBO:0000632 (sink) if coef < 0, +0065 % SBO:0000628 (demand) otherwise. +0066 % Transport reactions (opts.transportRxnIdxs or, by default, the +0067 % reactions identified by getTransportRxns: the same metabolite +0068 % name more than once in a single reaction) → SBO:0000655. +0069 % Reactions whose name matches opts.biomassRxnName → SBO:0000629. +0070 % Reactions whose name matches opts.ngamRxnName → SBO:0000630. +0071 % Reactions whose name contains any of opts.pseudoreactionSubstrings +0072 % → SBO:0000395. +0073 +0074 p=parseRAVENargs(varargin, {'opts',[]}); +0075 opts=p.opts; +0076 if isempty(opts) +0077 opts = struct(); +0078 end +0079 opts = applyDefaults(opts); +0080 +0081 % Metabolite SBO ------------------------------------------------------ +0082 metsSBO = cell(size(model.mets)); +0083 for i = 1:length(model.mets) +0084 metName = model.metNames{i}; +0085 if any(strcmp(opts.biomassMetNames, metName)) || endsWithAny(metName, opts.biomassMetSuffixes) +0086 metsSBO{i} = 'SBO:0000649'; +0087 else +0088 metsSBO{i} = 'SBO:0000247'; +0089 end +0090 end +0091 +0092 % Reaction SBO -------------------------------------------------------- +0093 rxnSBO = cell(size(model.rxns)); +0094 rxnSBO(:) = {'SBO:0000176'}; +0095 +0096 % Single-reactant reactions +0097 reactantNumber = sum(model.S ~= 0, 1); +0098 singleRxns = find(reactantNumber == 1); +0099 for k = 1:numel(singleRxns) +0100 idx = singleRxns(k); +0101 metRow = find(model.S(:, idx)); +0102 compName = model.compNames{model.metComps(metRow)}; +0103 compShort = model.comps{model.metComps(metRow)}; +0104 if strcmp(compShort, 'e') || strcmp(compName, 'extracellular') +0105 rxnSBO{idx} = 'SBO:0000627'; +0106 elseif sum(model.S(:, idx)) < 0 +0107 rxnSBO{idx} = 'SBO:0000632'; +0108 else +0109 rxnSBO{idx} = 'SBO:0000628'; +0110 end +0111 end +0112 +0113 % Transport reactions +0114 if isfield(opts, 'transportRxnIdxs') && ~isempty(opts.transportRxnIdxs) +0115 transportIdxs = opts.transportRxnIdxs; +0116 else +0117 transportIdxs = getTransportRxns(model); +0118 end +0119 rxnSBO(transportIdxs) = {'SBO:0000655'}; +0120 +0121 % Pseudoreaction overrides +0122 if opts.onlyLastReactionForPseudo +0123 pseudoTargets = numel(model.rxns); +0124 else +0125 pseudoTargets = 1:numel(model.rxns); +0126 end +0127 for ii = pseudoTargets +0128 name = model.rxnNames{ii}; +0129 if strcmp(name, opts.biomassRxnName) +0130 rxnSBO{ii} = 'SBO:0000629'; +0131 elseif strcmp(name, opts.ngamRxnName) +0132 rxnSBO{ii} = 'SBO:0000630'; +0133 else +0134 for k = 1:numel(opts.pseudoreactionSubstrings) +0135 if contains(name, opts.pseudoreactionSubstrings{k}) +0136 rxnSBO{ii} = 'SBO:0000395'; +0137 break; +0138 end +0139 end +0140 end +0141 end +0142 +0143 model = editMiriam(model, 'met', 'all', 'sbo', metsSBO, 'fill'); +0144 model = editMiriam(model, 'rxn', 'all', 'sbo', rxnSBO, 'fill'); +0145 end +0146 +0147 +0148 function opts = applyDefaults(opts) +0149 defaults = struct( ... +0150 'biomassMetNames', {{'biomass','DNA','RNA','protein','carbohydrate','lipid','cofactor','ion'}}, ... +0151 'biomassMetSuffixes', {{' backbone',' chain'}}, ... +0152 'biomassRxnName', 'biomass pseudoreaction', ... +0153 'ngamRxnName', 'non-growth associated maintenance reaction', ... +0154 'pseudoreactionSubstrings', {{'pseudoreaction','SLIME rxn'}}, ... +0155 'onlyLastReactionForPseudo', false); +0156 fields = fieldnames(defaults); +0157 for k = 1:numel(fields) +0158 f = fields{k}; +0159 if ~isfield(opts, f) || isempty(opts.(f)) +0160 opts.(f) = defaults.(f); +0161 end +0162 end +0163 end +0164 +0165 +0166 function tf = endsWithAny(s, suffixes) +0167 tf = false; +0168 for i = 1:numel(suffixes) +0169 if endsWith(s, suffixes{i}) +0170 tf = true; +0171 return; +0172 end +0173 end +0174 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/annotation/editMiriam.html b/docs/html/annotation/editMiriam.html index 5a11c397..67474ea5 100644 --- a/docs/html/annotation/editMiriam.html +++ b/docs/html/annotation/editMiriam.html @@ -32,34 +32,32 @@

    DESCRIPTION ^DESCRIPTION ^CROSS-REFERENCE INFORMATION ^

    This function calls:
      -
    • extractMiriam extractMiriam Unpack MIRIAM annotations into a human-readable table.
    +
  • extractMiriam extractMiriam Unpack MIRIAM annotations into a table.
  • This function is called by:
    • assignSBOterms assignSBOterms Assign SBO terms to metabolites and reactions.
    @@ -93,135 +91,133 @@

    SOURCE CODE ^% Parameters 0005 % ---------- 0006 % model : struct -0007 % model structure. +0007 % a model structure. 0008 % type : char -0009 % 'met', 'rxn', 'gene' or 'comp' dependent on which objects the -0010 % annotations should be assigned to. +0009 % 'met', 'rxn', 'gene' or 'comp', the type of object that the +0010 % annotations are assigned to. 0011 % object : cell or logical or double or char 0012 % either a cell array of IDs, a logical vector with the same number of -0013 % elements as the type (see above) in the model, a vector of indexes, +0013 % elements as objects of that type in the model, a vector of indexes, 0014 % or 'all'. 0015 % miriamName : char 0016 % string specifying the namespace of the identifier, for instance -0017 % 'bigg.metabolite'. Should be a valid prefix from identifiers.org -0018 % (e.g. https://registry.identifiers.org/registry/bigg.metabolite). +0017 % 'bigg.metabolite'. A valid prefix from identifiers.org is expected +0018 % (for example https://registry.identifiers.org/registry/bigg.metabolite). 0019 % miriams : char or cell -0020 % string or cell array of strings with annotation identifiers, e.g. +0020 % string or cell array of strings with annotation identifiers, for example 0021 % '12dgr161'. 0022 % keep : char 0023 % one of the following strings, specifying what should be done if an 0024 % object already has existing MIRIAM annotations with the same 0025 % miriamName: 0026 % -0027 % - 'replace' : discard all existing annotations, all will be -0028 % overwritten, even if the new annotation is an empty field. Should -0029 % only be used if you do not want to keep any of the old annotation -0030 % with the same miriamName. -0031 % - 'fill' : only add annotations to those objects that did not yet -0032 % have an annotation with that miriamName. Otherwise, the existing -0033 % annotation is kept, even if it is different from the suggested new -0034 % annotation. -0035 % - 'add' : keep all existing annotations, and add any new -0036 % annotations, after removing duplicates. -0037 % - 'remove' : remove all annotations with the given miriamName from -0038 % the specified objects. The miriams argument is ignored. -0039 % -0040 % Returns -0041 % ------- -0042 % model : struct -0043 % model structure with updated MIRIAM annotation field. -0044 % -0045 % Examples -0046 % -------- -0047 % model = editMiriam(model, type, object, miriamName, miriams, keep); -0048 miriamName=char(miriamName); -0049 miriams=convertCharArray(miriams); -0050 -0051 %Check 'keep' input -0052 keep=char(keep); -0053 if ~any(strcmp(keep,{'replace','fill','add','remove'})) -0054 error('Invalid ''keep'', should be ''replace'',''fill'',''add'',''remove''.') -0055 end -0056 %Check 'type' input -0057 type=char(type); -0058 if ~any(strcmp(type,{'met','gene','rxn','comp'})) -0059 error('Invalid ''type'', should be ''met'', ''gene'', ''rxn'' or ''comp''.') -0060 end -0061 %Check 'object' input -0062 if islogical(object) -0063 idxInModel=find(object); -0064 elseif isnumeric(object) -0065 idxInModel=object; -0066 else -0067 object=convertCharArray(object); -0068 if numel(object)==1 && strcmp(object{1},'all') -0069 idxInModel=1:numel(model.([type,'s'])); -0070 else -0071 idxInModel=getIndexes(model,object,[type,'s']); -0072 if ~all(idxInModel) -0073 error('RAVEN:badInput', '%s', ravenList('The following objects cannot be found in the model: ', object(~idxInModel))) -0074 end -0075 end -0076 end -0077 %Check 'miriams' input (skip for 'remove' — miriams argument is not used) -0078 if ~strcmp(keep,'remove') -0079 if numel(miriams)==1 && numel(idxInModel)~=1 -0080 miriams=repmat(miriams,numel(idxInModel),1); -0081 elseif numel(miriams)~=numel(idxInModel) -0082 error('The number of annotations does not match the number of objects.') -0083 end -0084 end +0027 % - 'replace' : discard all existing annotations with the same +0028 % miriamName; they are overwritten, even if the new annotation is an +0029 % empty field. +0030 % - 'fill' : add annotations only to those objects that have no +0031 % annotation with that miriamName. Otherwise, the existing annotation +0032 % is kept, even if it differs from the new annotation. +0033 % - 'add' : keep all existing annotations, and add any new +0034 % annotations, after removing duplicates. +0035 % - 'remove' : remove all annotations with the given miriamName from +0036 % the specified objects. The miriams argument is ignored. +0037 % +0038 % Returns +0039 % ------- +0040 % model : struct +0041 % model structure with updated MIRIAM annotation field. +0042 % +0043 % Examples +0044 % -------- +0045 % model = editMiriam(model, type, object, miriamName, miriams, keep); +0046 miriamName=char(miriamName); +0047 miriams=convertCharArray(miriams); +0048 +0049 %Check 'keep' input +0050 keep=char(keep); +0051 if ~any(strcmp(keep,{'replace','fill','add','remove'})) +0052 error('Invalid ''keep'', should be ''replace'',''fill'',''add'',''remove''.') +0053 end +0054 %Check 'type' input +0055 type=char(type); +0056 if ~any(strcmp(type,{'met','gene','rxn','comp'})) +0057 error('Invalid ''type'', should be ''met'', ''gene'', ''rxn'' or ''comp''.') +0058 end +0059 %Check 'object' input +0060 if islogical(object) +0061 idxInModel=find(object); +0062 elseif isnumeric(object) +0063 idxInModel=object; +0064 else +0065 object=convertCharArray(object); +0066 if numel(object)==1 && strcmp(object{1},'all') +0067 idxInModel=1:numel(model.([type,'s'])); +0068 else +0069 idxInModel=getIndexes(model,object,[type,'s']); +0070 if ~all(idxInModel) +0071 error('RAVEN:badInput', '%s', ravenList('The following objects cannot be found in the model: ', object(~idxInModel))) +0072 end +0073 end +0074 end +0075 %Check 'miriams' input (skip for 'remove' — miriams argument is not used) +0076 if ~strcmp(keep,'remove') +0077 if numel(miriams)==1 && numel(idxInModel)~=1 +0078 miriams=repmat(miriams,numel(idxInModel),1); +0079 elseif numel(miriams)~=numel(idxInModel) +0080 error('The number of annotations does not match the number of objects.') +0081 end +0082 end +0083 +0084 miriamFieldName=[type,'Miriams']; 0085 -0086 miriamFieldName=[type,'Miriams']; -0087 -0088 if isfield(model,miriamFieldName) -0089 [extractedMiriams,extractedMiriamNames]=extractMiriam(model.(miriamFieldName)(idxInModel),'all'); -0090 else -0091 extractedMiriams=cell(numel(idxInModel),1); -0092 extractedMiriamNames={miriamName}; -0093 end -0094 -0095 [~, midx]=ismember(miriamName,extractedMiriamNames); -0096 if strcmp(keep,'remove') -0097 if midx>0 -0098 extractedMiriams(:,midx)={''}; -0099 end -0100 % Nothing to do when midx==0: miriamName not present -0101 elseif midx==0 -0102 midx=numel(extractedMiriamNames)+1; -0103 extractedMiriamNames(end+1)={miriamName}; -0104 extractedMiriams(:,end+1)=miriams; -0105 elseif strcmp(keep,'replace') -0106 extractedMiriams(:,midx)=miriams; -0107 else -0108 noMiriam=cellfun(@isempty,extractedMiriams(:,midx)); -0109 extractedMiriams(noMiriam,midx)=miriams(noMiriam); -0110 if strcmp(keep,'add') % Skipped when only filling empty entries -0111 existingMiriams=[split(extractedMiriams(~noMiriam,midx),'; '), miriams(~noMiriam)]; -0112 uniqueMiriams=cell(size(existingMiriams,1),1); -0113 for i=1:size(existingMiriams,1) -0114 uniqueMiriams{i,1}=strjoin(unique(existingMiriams(i,:)), {'; '}); -0115 end -0116 extractedMiriams(~noMiriam,midx)=uniqueMiriams; -0117 end -0118 end -0119 -0120 % Make Miriam field again -0121 for i=1:numel(idxInModel) -0122 miriam.name=cell(1,1); -0123 miriam.value=cell(1,1); -0124 for j=1:numel(extractedMiriamNames) -0125 if ~isempty(extractedMiriams{i,j}) -0126 values=strsplit(extractedMiriams{i,j},'; '); -0127 miriam.name(end+1:end+numel(values),1)=extractedMiriamNames(j); -0128 miriam.value(end+1:end+numel(values),1)=values; -0129 end -0130 end -0131 miriam.name(1)=[]; -0132 miriam.value(1)=[]; -0133 model.(miriamFieldName){idxInModel(i),1}=miriam; -0134 end -0135 end +0086 if isfield(model,miriamFieldName) +0087 [extractedMiriams,extractedMiriamNames]=extractMiriam(model.(miriamFieldName)(idxInModel),'all'); +0088 else +0089 extractedMiriams=cell(numel(idxInModel),1); +0090 extractedMiriamNames={miriamName}; +0091 end +0092 +0093 [~, midx]=ismember(miriamName,extractedMiriamNames); +0094 if strcmp(keep,'remove') +0095 if midx>0 +0096 extractedMiriams(:,midx)={''}; +0097 end +0098 % Nothing to do when midx==0: miriamName not present +0099 elseif midx==0 +0100 midx=numel(extractedMiriamNames)+1; +0101 extractedMiriamNames(end+1)={miriamName}; +0102 extractedMiriams(:,end+1)=miriams; +0103 elseif strcmp(keep,'replace') +0104 extractedMiriams(:,midx)=miriams; +0105 else +0106 noMiriam=cellfun(@isempty,extractedMiriams(:,midx)); +0107 extractedMiriams(noMiriam,midx)=miriams(noMiriam); +0108 if strcmp(keep,'add') % Skipped when only filling empty entries +0109 existingMiriams=[split(extractedMiriams(~noMiriam,midx),'; '), miriams(~noMiriam)]; +0110 uniqueMiriams=cell(size(existingMiriams,1),1); +0111 for i=1:size(existingMiriams,1) +0112 uniqueMiriams{i,1}=strjoin(unique(existingMiriams(i,:)), {'; '}); +0113 end +0114 extractedMiriams(~noMiriam,midx)=uniqueMiriams; +0115 end +0116 end +0117 +0118 % Make Miriam field again +0119 for i=1:numel(idxInModel) +0120 miriam.name=cell(1,1); +0121 miriam.value=cell(1,1); +0122 for j=1:numel(extractedMiriamNames) +0123 if ~isempty(extractedMiriams{i,j}) +0124 values=strsplit(extractedMiriams{i,j},'; '); +0125 miriam.name(end+1:end+numel(values),1)=extractedMiriamNames(j); +0126 miriam.value(end+1:end+numel(values),1)=values; +0127 end +0128 end +0129 miriam.name(1)=[]; +0130 miriam.value(1)=[]; +0131 model.(miriamFieldName){idxInModel(i),1}=miriam; +0132 end +0133 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/annotation/extractMiriam.html b/docs/html/annotation/extractMiriam.html index 12586d45..2dc7382c 100644 --- a/docs/html/annotation/extractMiriam.html +++ b/docs/html/annotation/extractMiriam.html @@ -4,7 +4,7 @@ Description of extractMiriam - + @@ -21,40 +21,36 @@

    extractMiriam

    PURPOSE ^

    -
    extractMiriam Unpack MIRIAM annotations into a human-readable table.
    +
    extractMiriam Unpack MIRIAM annotations into a table.

    SYNOPSIS ^

    function [miriams,extractedMiriamNames]=extractMiriam(modelMiriams,varargin)

    DESCRIPTION ^

    -
     extractMiriam  Unpack MIRIAM annotations into a human-readable table.
    +
     extractMiriam  Unpack MIRIAM annotations into a table.
     
    - Unpacks metMiriams, rxnMiriams, geneMiriams or compMiriams into a
    - human-readable cell array, formatted as in Excel, with the columns split
    - so each miriam name gets its own column.
    + Unpacks metMiriams, rxnMiriams, geneMiriams or compMiriams into a cell
    + array with one column per miriam name.
     
      Parameters
      ----------
      modelMiriams : cell
    -     a miriam structure (e.g. model.metMiriams) for one or multiple
    -     metabolites.
    +     a miriam structure (for example model.metMiriams) for one or more
    +     metabolites, reactions, genes or compartments.
     
      Name-Value Arguments
      --------------------
      miriamNames : cell or char
    -     cell array with miriam names to be extracted (default 'all', meaning
    -     that annotation for all miriam names found in modelMiriams will be
    -     extracted).
    +     cell array with miriam names to be extracted (default 'all', which
    +     extracts the annotation for all miriam names found in modelMiriams).
     
      Returns
      -------
      miriams : cell
    -     a cell array with extracted miriams. If several miriam names are
    -     requested, the corresponding information is saved in different
    -     columns. If there are several ids available for the same entity
    -     (metabolite, gene, reaction or compartment), they are concatenated
    -     into one column. The total number of columns represents the number
    -     of unique miriam names per entity.
    +     a cell array with extracted miriams, with one row per entity and one
    +     column per extracted miriam name. If several ids are available for
    +     the same entity (metabolite, gene, reaction or compartment) and miriam
    +     name, they are concatenated into one cell, separated by '; '.
      extractedMiriamNames : cell
          cell array with extracted miriam names.
     
    @@ -78,97 +74,93 @@ 

    SUBFUNCTIONS ^SOURCE CODE ^

    0001 function [miriams,extractedMiriamNames]=extractMiriam(modelMiriams,varargin)
    -0002 % extractMiriam  Unpack MIRIAM annotations into a human-readable table.
    +0002 % extractMiriam  Unpack MIRIAM annotations into a table.
     0003 %
    -0004 % Unpacks metMiriams, rxnMiriams, geneMiriams or compMiriams into a
    -0005 % human-readable cell array, formatted as in Excel, with the columns split
    -0006 % so each miriam name gets its own column.
    -0007 %
    -0008 % Parameters
    -0009 % ----------
    -0010 % modelMiriams : cell
    -0011 %     a miriam structure (e.g. model.metMiriams) for one or multiple
    -0012 %     metabolites.
    -0013 %
    -0014 % Name-Value Arguments
    -0015 % --------------------
    -0016 % miriamNames : cell or char
    -0017 %     cell array with miriam names to be extracted (default 'all', meaning
    -0018 %     that annotation for all miriam names found in modelMiriams will be
    -0019 %     extracted).
    -0020 %
    -0021 % Returns
    -0022 % -------
    -0023 % miriams : cell
    -0024 %     a cell array with extracted miriams. If several miriam names are
    -0025 %     requested, the corresponding information is saved in different
    -0026 %     columns. If there are several ids available for the same entity
    -0027 %     (metabolite, gene, reaction or compartment), they are concatenated
    -0028 %     into one column. The total number of columns represents the number
    -0029 %     of unique miriam names per entity.
    -0030 % extractedMiriamNames : cell
    -0031 %     cell array with extracted miriam names.
    -0032 %
    -0033 % Examples
    -0034 % --------
    -0035 %     [miriams, extractedMiriamNames] = extractMiriam(modelMiriams, miriamNames);
    -0036 
    -0037 p=parseRAVENargs(varargin, {'miriamNames',[]});
    -0038 miriamNames=p.miriamNames;
    -0039 if isempty(miriamNames) || (ischar(miriamNames) && strcmp(miriamNames,'all'))
    -0040     extractAllTypes=true;
    -0041 else
    -0042     extractAllTypes=false;
    -0043     miriamNames=convertCharArray(miriamNames);
    -0044 end
    -0045 
    -0046 %The annotation for all miriam names should be extracted
    -0047 if extractAllTypes
    -0048     miriamNames={''};
    -0049     for i=1:numel(modelMiriams)
    -0050         if ~isempty(modelMiriams{i,1})
    -0051             for j=1:numel(modelMiriams{i,1}.name)
    -0052                 miriamNames{numel(miriamNames)+1,1}=modelMiriams{i,1}.name{j};
    -0053             end
    -0054         end
    -0055     end
    -0056     miriamNames=sort(unique(miriamNames));
    -0057 end
    -0058 
    -0059 %Aggregate the final cell array table with extracted miriam information
    -0060 miriams=cell(numel(modelMiriams),0);
    -0061 for i=1:numel(miriamNames)
    -0062     miriams=[miriams, extractMiriamType(modelMiriams,miriamNames{i})];
    -0063 end
    -0064 extractedMiriamNames=miriamNames;
    -0065 
    -0066 end
    -0067 
    -0068 function miriams=extractMiriamType(modelMiriams,miriamName)
    -0069 %Create an empty cell array for ids vector
    -0070 tempMiriams = cell([size(modelMiriams,1) 1]);
    -0071 %Obtain the list of relevant miriam ids. Several entries may have
    -0072 %several miriam ids, such ids are kept in additional columns
    -0073 for i=1:numel(modelMiriams)
    -0074     if (~isempty(modelMiriams{i,1})) && any(strcmp(modelMiriams{i,1}.name,miriamName))
    -0075         for j=1:numel(modelMiriams{i,1}.name)
    -0076             if strcmp(modelMiriams{i,1}.name(j),miriamName)
    -0077                 tempMiriams(i,j) = modelMiriams{i,1}.value(j);
    -0078             end
    -0079         end
    -0080     else
    -0081         modelMiriams{i,1} = '';
    -0082     end
    -0083 end
    -0084 
    -0085 %Concatenate multiple ids per miriam name in one column with semicolon as
    -0086 %separator
    -0087 miriams = cell([size(tempMiriams,1) 1]);
    -0088 notEmpty=~cellfun(@isempty,tempMiriams);
    -0089 for i=1:size(miriams,1)
    -0090     miriams{i}=strjoin(tempMiriams(i,notEmpty(i,:)),{'; '});
    -0091 end
    -0092 end
    +0004 % Unpacks metMiriams, rxnMiriams, geneMiriams or compMiriams into a cell +0005 % array with one column per miriam name. +0006 % +0007 % Parameters +0008 % ---------- +0009 % modelMiriams : cell +0010 % a miriam structure (for example model.metMiriams) for one or more +0011 % metabolites, reactions, genes or compartments. +0012 % +0013 % Name-Value Arguments +0014 % -------------------- +0015 % miriamNames : cell or char +0016 % cell array with miriam names to be extracted (default 'all', which +0017 % extracts the annotation for all miriam names found in modelMiriams). +0018 % +0019 % Returns +0020 % ------- +0021 % miriams : cell +0022 % a cell array with extracted miriams, with one row per entity and one +0023 % column per extracted miriam name. If several ids are available for +0024 % the same entity (metabolite, gene, reaction or compartment) and miriam +0025 % name, they are concatenated into one cell, separated by '; '. +0026 % extractedMiriamNames : cell +0027 % cell array with extracted miriam names. +0028 % +0029 % Examples +0030 % -------- +0031 % [miriams, extractedMiriamNames] = extractMiriam(modelMiriams, miriamNames); +0032 +0033 p=parseRAVENargs(varargin, {'miriamNames',[]}); +0034 miriamNames=p.miriamNames; +0035 if isempty(miriamNames) || (ischar(miriamNames) && strcmp(miriamNames,'all')) +0036 extractAllTypes=true; +0037 else +0038 extractAllTypes=false; +0039 miriamNames=convertCharArray(miriamNames); +0040 end +0041 +0042 %The annotation for all miriam names should be extracted +0043 if extractAllTypes +0044 miriamNames={''}; +0045 for i=1:numel(modelMiriams) +0046 if ~isempty(modelMiriams{i,1}) +0047 for j=1:numel(modelMiriams{i,1}.name) +0048 miriamNames{numel(miriamNames)+1,1}=modelMiriams{i,1}.name{j}; +0049 end +0050 end +0051 end +0052 miriamNames=sort(unique(miriamNames)); +0053 end +0054 +0055 %Aggregate the final cell array table with extracted miriam information +0056 miriams=cell(numel(modelMiriams),0); +0057 for i=1:numel(miriamNames) +0058 miriams=[miriams, extractMiriamType(modelMiriams,miriamNames{i})]; +0059 end +0060 extractedMiriamNames=miriamNames; +0061 +0062 end +0063 +0064 function miriams=extractMiriamType(modelMiriams,miriamName) +0065 %Create an empty cell array for ids vector +0066 tempMiriams = cell([size(modelMiriams,1) 1]); +0067 %Obtain the list of relevant miriam ids. Several entries may have +0068 %several miriam ids, such ids are kept in additional columns +0069 for i=1:numel(modelMiriams) +0070 if (~isempty(modelMiriams{i,1})) && any(strcmp(modelMiriams{i,1}.name,miriamName)) +0071 for j=1:numel(modelMiriams{i,1}.name) +0072 if strcmp(modelMiriams{i,1}.name(j),miriamName) +0073 tempMiriams(i,j) = modelMiriams{i,1}.value(j); +0074 end +0075 end +0076 else +0077 modelMiriams{i,1} = ''; +0078 end +0079 end +0080 +0081 %Concatenate multiple ids per miriam name in one column with semicolon as +0082 %separator +0083 miriams = cell([size(tempMiriams,1) 1]); +0084 notEmpty=~cellfun(@isempty,tempMiriams); +0085 for i=1:size(miriams,1) +0086 miriams{i}=strjoin(tempMiriams(i,notEmpty(i,:)),{'; '}); +0087 end +0088 end

    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/annotation/index.html b/docs/html/annotation/index.html index 09466180..981bdd1a 100644 --- a/docs/html/annotation/index.html +++ b/docs/html/annotation/index.html @@ -19,7 +19,7 @@

    Index for annotation

    Matlab files in this directory:

    -
     assignSBOtermsassignSBOterms Assign SBO terms to metabolites and reactions.
     editMiriameditMiriam Change MIRIAM annotation fields, one type at a time.
     extractMiriamextractMiriam Unpack MIRIAM annotations into a human-readable table.
     loadDeltaGCSVloadDeltaGCSV Load metDeltaG and rxnDeltaG from CSV files.
     saveDeltaGCSVsaveDeltaGCSV Save metDeltaG and rxnDeltaG to CSV files.
    + assignSBOtermsassignSBOterms Assign SBO terms to metabolites and reactions.  editMiriameditMiriam Change MIRIAM annotation fields, one type at a time.  extractMiriamextractMiriam Unpack MIRIAM annotations into a table.  loadDeltaGCSVloadDeltaGCSV Load metDeltaG and rxnDeltaG from CSV files.  saveDeltaGCSVsaveDeltaGCSV Save metDeltaG and rxnDeltaG to CSV files. diff --git a/docs/html/annotation/loadDeltaGCSV.html b/docs/html/annotation/loadDeltaGCSV.html index bc2c535e..cef7a0f9 100644 --- a/docs/html/annotation/loadDeltaGCSV.html +++ b/docs/html/annotation/loadDeltaGCSV.html @@ -30,29 +30,30 @@

    DESCRIPTION ^
     loadDeltaGCSV  Load metDeltaG and rxnDeltaG from CSV files.
     
      Each CSV is a two-column table: identifier, deltaG. On load, rows whose
    - identifier is not in the model are silently skipped, and every matched
    - value is stored exactly as it appears in the CSV -- including yeast-GEM's
    - own "no measurement" placeholder (10000000.0). Callers that need to treat
    - a particular value as absent should filter it themselves after loading;
    - this function does not interpret CSV values. Pass '' for either metCsv
    - or rxnCsv to skip that side.
    + identifier is not in the model are skipped without a message, and every
    + matched value is stored as it appears in the CSV, including yeast-GEM's
    + "no measurement" placeholder (10000000.0). The function does not
    + interpret CSV values, so a value that is to be treated as absent has to
    + be filtered after loading. An empty string ('') for metCsv or rxnCsv
    + skips that side.
     
      Parameters
      ----------
      model : struct
    -     RAVEN model struct.
    +     a model structure.
     
      Name-Value Arguments
      --------------------
      metCsv : char
    -     Path to the metabolite deltaG CSV, or '' to skip.
    +     path to the metabolite deltaG CSV, or '' to skip (default '').
      rxnCsv : char
    -     Path to the reaction deltaG CSV, or '' to skip.
    +     path to the reaction deltaG CSV, or '' to skip (default '').
     
      Returns
      -------
      model : struct
    -     Updated model struct.
    +     model structure. The metDeltaG field is set only if metCsv is given and
    +     the rxnDeltaG field only if rxnCsv is given.
     
      Examples
      --------
    @@ -78,60 +79,61 @@ 

    SOURCE CODE ^% loadDeltaGCSV Load metDeltaG and rxnDeltaG from CSV files. 0003 % 0004 % Each CSV is a two-column table: identifier, deltaG. On load, rows whose -0005 % identifier is not in the model are silently skipped, and every matched -0006 % value is stored exactly as it appears in the CSV -- including yeast-GEM's -0007 % own "no measurement" placeholder (10000000.0). Callers that need to treat -0008 % a particular value as absent should filter it themselves after loading; -0009 % this function does not interpret CSV values. Pass '' for either metCsv -0010 % or rxnCsv to skip that side. +0005 % identifier is not in the model are skipped without a message, and every +0006 % matched value is stored as it appears in the CSV, including yeast-GEM's +0007 % "no measurement" placeholder (10000000.0). The function does not +0008 % interpret CSV values, so a value that is to be treated as absent has to +0009 % be filtered after loading. An empty string ('') for metCsv or rxnCsv +0010 % skips that side. 0011 % 0012 % Parameters 0013 % ---------- 0014 % model : struct -0015 % RAVEN model struct. +0015 % a model structure. 0016 % 0017 % Name-Value Arguments 0018 % -------------------- 0019 % metCsv : char -0020 % Path to the metabolite deltaG CSV, or '' to skip. +0020 % path to the metabolite deltaG CSV, or '' to skip (default ''). 0021 % rxnCsv : char -0022 % Path to the reaction deltaG CSV, or '' to skip. +0022 % path to the reaction deltaG CSV, or '' to skip (default ''). 0023 % 0024 % Returns 0025 % ------- 0026 % model : struct -0027 % Updated model struct. -0028 % -0029 % Examples -0030 % -------- -0031 % model = loadDeltaGCSV(model, 'data/met_dG.csv', 'data/rxn_dG.csv'); -0032 % model = loadDeltaGCSV(model, 'metCsv', 'data/met_dG.csv'); -0033 -0034 p=parseRAVENargs(varargin, {'metCsv',''; 'rxnCsv',''}); -0035 metCsv=p.metCsv; -0036 rxnCsv=p.rxnCsv; -0037 -0038 model=loadField(model, 'metDeltaG', model.mets, metCsv); -0039 model=loadField(model, 'rxnDeltaG', model.rxns, rxnCsv); -0040 end -0041 -0042 function model=loadField(model, fieldname, ids, csvPath) -0043 if isempty(csvPath); return; end -0044 if isfield(model, fieldname) -0045 disp(['Existing ' fieldname ' field will be overwritten.']) -0046 else -0047 model.(fieldname)=nan(numel(ids),1); -0048 end -0049 G=readtable(csvPath); -0050 values=G.(G.Properties.VariableNames{2}); -0051 [a, b]=ismember(ids, G.(G.Properties.VariableNames{1})); -0052 model.(fieldname)(a)=values(b(a)); -0053 if any(~a) -0054 fprintf(['Not all identifiers are matched to %s; the latter\n' ... -0055 'file might have to be supplemented with deltaG values for new entries.\n'], ... -0056 csvPath); -0057 end -0058 end

    +0027 % model structure. The metDeltaG field is set only if metCsv is given and +0028 % the rxnDeltaG field only if rxnCsv is given. +0029 % +0030 % Examples +0031 % -------- +0032 % model = loadDeltaGCSV(model, 'data/met_dG.csv', 'data/rxn_dG.csv'); +0033 % model = loadDeltaGCSV(model, 'metCsv', 'data/met_dG.csv'); +0034 +0035 p=parseRAVENargs(varargin, {'metCsv',''; 'rxnCsv',''}); +0036 metCsv=p.metCsv; +0037 rxnCsv=p.rxnCsv; +0038 +0039 model=loadField(model, 'metDeltaG', model.mets, metCsv); +0040 model=loadField(model, 'rxnDeltaG', model.rxns, rxnCsv); +0041 end +0042 +0043 function model=loadField(model, fieldname, ids, csvPath) +0044 if isempty(csvPath); return; end +0045 if isfield(model, fieldname) +0046 disp(['Existing ' fieldname ' field will be overwritten.']) +0047 else +0048 model.(fieldname)=nan(numel(ids),1); +0049 end +0050 G=readtable(csvPath); +0051 values=G.(G.Properties.VariableNames{2}); +0052 [a, b]=ismember(ids, G.(G.Properties.VariableNames{1})); +0053 model.(fieldname)(a)=values(b(a)); +0054 if any(~a) +0055 fprintf(['Not all identifiers are matched to %s; the latter\n' ... +0056 'file might have to be supplemented with deltaG values for new entries.\n'], ... +0057 csvPath); +0058 end +0059 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/annotation/saveDeltaGCSV.html b/docs/html/annotation/saveDeltaGCSV.html index 7573b70c..c376e7e1 100644 --- a/docs/html/annotation/saveDeltaGCSV.html +++ b/docs/html/annotation/saveDeltaGCSV.html @@ -29,11 +29,11 @@

    SYNOPSIS ^DESCRIPTION ^

     saveDeltaGCSV  Save metDeltaG and rxnDeltaG to CSV files.
     
    - Writes a two-column table (identifier, deltaG) per side, one row per
    - entity, in model order. An entity with no value stored is written as
    - NaN, unchanged from whatever the model field holds -- this function does
    - not interpret or substitute values. Pass '' for either metCsv or rxnCsv
    - to skip that side.
    + Writes a two-column table (identifier, deltaG) for metabolites and one for
    + reactions, one row per entity, in model order. Values are written as the
    + model field holds them, so an entity with no value stored is written as
    + NaN. A file is skipped when its path is '' or when the model lacks the
    + corresponding field.
     
      Parameters
      ----------
    @@ -72,11 +72,11 @@ 

    SOURCE CODE ^
    0001 function saveDeltaGCSV(model, varargin)
     0002 % saveDeltaGCSV  Save metDeltaG and rxnDeltaG to CSV files.
     0003 %
    -0004 % Writes a two-column table (identifier, deltaG) per side, one row per
    -0005 % entity, in model order. An entity with no value stored is written as
    -0006 % NaN, unchanged from whatever the model field holds -- this function does
    -0007 % not interpret or substitute values. Pass '' for either metCsv or rxnCsv
    -0008 % to skip that side.
    +0004 % Writes a two-column table (identifier, deltaG) for metabolites and one for
    +0005 % reactions, one row per entity, in model order. Values are written as the
    +0006 % model field holds them, so an entity with no value stored is written as
    +0007 % NaN. A file is skipped when its path is '' or when the model lacks the
    +0008 % corresponding field.
     0009 %
     0010 % Parameters
     0011 % ----------
    diff --git a/docs/html/biomass/fitParameters.html b/docs/html/biomass/fitParameters.html
    index 62776151..11bb4aa6 100644
    --- a/docs/html/biomass/fitParameters.html
    +++ b/docs/html/biomass/fitParameters.html
    @@ -33,10 +33,11 @@ 

    DESCRIPTION ^DESCRIPTION ^SOURCE CODE ^% between the fluxes the model predicts and the measured ones, using 0006 % fminsearch (Nelder-Mead). Each candidate parameter set is written into the 0007 % S matrix at the positions given by parameterPositions, and the model is -0008 % solved once per data point. -0009 % -0010 % Use this to *derive* a value from measurements; use setGAM to *apply* a -0011 % known growth-associated maintenance value to a biomass pseudoreaction. -0012 % -0013 % Parameters -0014 % ---------- -0015 % model : struct -0016 % a model structure. -0017 % xRxns : cell -0018 % cell array with the IDs of the reactions that will be fixed for each -0019 % data point. -0020 % xValues : double -0021 % matrix with the corresponding values for each xRxns (columns are -0022 % reactions). -0023 % rxnsToFit : cell -0024 % cell array with the IDs of reactions that will be fitted to. -0025 % valuesToFit : double -0026 % matrix with the corresponding values for each rxnsToFit (columns are -0027 % reactions). -0028 % parameterPositions : struct -0029 % structure that determines where the parameters are in the -0030 % stoichiometric matrix, with fields: -0031 % -0032 % - position : cell array of vectors where each element contains the -0033 % positions in the S-matrix for that parameter -0034 % - isNegative : cell array of vectors where the elements are true if -0035 % that position should be the negative of the fitted value (to -0036 % differentiate between production/consumption) -0037 % -0038 % Name-Value Arguments -0039 % -------------------- -0040 % fitToRatio : logical -0041 % if the ratio of simulated to measured values should be fitted -0042 % instead of the absolute value. Used to prevent large fluxes from -0043 % having too large an impact (default true). -0044 % initialGuess : double -0045 % initial guess of the parameters (default ones). -0046 % plotFitting : logical -0047 % true if the resulting fitting should be plotted (default false). -0048 % -0049 % Returns -0050 % ------- -0051 % parameters : double -0052 % fitted parameters in the same order as in parameterPositions. -0053 % fitnessScore : double -0054 % the corresponding residual sum of squares. -0055 % exitFlag : double -0056 % exit status returned by fminsearch. -0057 % newModel : struct -0058 % updated model structure with the fitted parameters. -0059 % -0060 % Examples -0061 % -------- -0062 % [parameters, fitnessScore, exitFlag, newModel]=fitParameters(model,... -0063 % xRxns,xValues,rxnsToFit,valuesToFit,parameterPositions,fitToRatio,... -0064 % initialGuess,plotFitting); -0065 -0066 p=parseRAVENargs(varargin, {'fitToRatio',true; 'initialGuess',[]; 'plotFitting',false}); -0067 fitToRatio=p.fitToRatio; -0068 initialGuess=p.initialGuess; -0069 if isempty(initialGuess) -0070 initialGuess=ones(numel(parameterPositions.position),1); -0071 end -0072 plotFitting=p.plotFitting; -0073 -0074 xRxns=convertCharArray(xRxns); -0075 rxnsToFit=convertCharArray(rxnsToFit); -0076 -0077 %Find the indexes of reactions that will be fitted -0078 [I, rxnsToFitIndexes]=ismember(rxnsToFit,model.rxns); -0079 -0080 if ~all(I) -0081 EM='Could not find all reactions in rxnsToFit'; -0082 error('RAVEN:badInput', '%s', EM); -0083 end -0084 -0085 %Find the indexes of reactions that will be used for constraints. -0086 [I, xRxnsIndexes]=ismember(xRxns,model.rxns); -0087 -0088 if ~all(I) -0089 EM='Could not find all reactions in xRxns'; -0090 error('RAVEN:badInput', '%s', EM); -0091 end -0092 -0093 [parameters, fitnessScore, exitFlag]=fminsearch(@(parameters) getRSS(parameters,model,xRxnsIndexes,xValues,rxnsToFitIndexes,valuesToFit,parameterPositions,fitToRatio),initialGuess); -0094 -0095 parameters=abs(parameters); +0008 % solved once per data point with the xRxns bounds fixed to the values in +0009 % xValues. The fitted parameters are returned as absolute values. +0010 % +0011 % The function derives a value from measurements. To apply a known +0012 % growth-associated maintenance value to a biomass pseudoreaction, use setGAM. +0013 % +0014 % Parameters +0015 % ---------- +0016 % model : struct +0017 % a model structure. +0018 % xRxns : cell +0019 % cell array with the IDs of the reactions that will be fixed for each +0020 % data point. +0021 % xValues : double +0022 % matrix with the corresponding values for each xRxns (columns are +0023 % reactions). +0024 % rxnsToFit : cell +0025 % cell array with the IDs of reactions that will be fitted to. +0026 % valuesToFit : double +0027 % matrix with the corresponding values for each rxnsToFit (columns are +0028 % reactions). +0029 % parameterPositions : struct +0030 % structure that determines where the parameters are in the +0031 % stoichiometric matrix, with fields: +0032 % +0033 % - position : cell array of vectors where each element contains the +0034 % positions in the S-matrix for that parameter +0035 % - isNegative : cell array of vectors where the elements are true if +0036 % that position is set to the negative of the fitted value (to +0037 % distinguish production from consumption) +0038 % +0039 % Name-Value Arguments +0040 % -------------------- +0041 % fitToRatio : logical +0042 % true to fit the ratio of simulated to measured values, false to fit +0043 % the absolute difference. The ratio limits the influence of large +0044 % fluxes on the residual sum of squares (default true). +0045 % initialGuess : double +0046 % initial guess of the parameters (default ones). +0047 % plotFitting : logical +0048 % true to plot the measured values and the fitted fluxes against +0049 % xValues (default false). +0050 % +0051 % Returns +0052 % ------- +0053 % parameters : double +0054 % fitted parameters (absolute values) in the same order as in +0055 % parameterPositions. +0056 % fitnessScore : double +0057 % the corresponding residual sum of squares. +0058 % exitFlag : double +0059 % exit status returned by fminsearch. +0060 % newModel : struct +0061 % updated model structure with the fitted parameters. It is assigned +0062 % only when plotFitting is true. +0063 % +0064 % Examples +0065 % -------- +0066 % [parameters, fitnessScore, exitFlag, newModel]=fitParameters(model,... +0067 % xRxns,xValues,rxnsToFit,valuesToFit,parameterPositions,fitToRatio,... +0068 % initialGuess,plotFitting); +0069 +0070 p=parseRAVENargs(varargin, {'fitToRatio',true; 'initialGuess',[]; 'plotFitting',false}); +0071 fitToRatio=p.fitToRatio; +0072 initialGuess=p.initialGuess; +0073 if isempty(initialGuess) +0074 initialGuess=ones(numel(parameterPositions.position),1); +0075 end +0076 plotFitting=p.plotFitting; +0077 +0078 xRxns=convertCharArray(xRxns); +0079 rxnsToFit=convertCharArray(rxnsToFit); +0080 +0081 %Find the indexes of reactions that will be fitted +0082 [I, rxnsToFitIndexes]=ismember(rxnsToFit,model.rxns); +0083 +0084 if ~all(I) +0085 EM='Could not find all reactions in rxnsToFit'; +0086 error('RAVEN:badInput', '%s', EM); +0087 end +0088 +0089 %Find the indexes of reactions that will be used for constraints. +0090 [I, xRxnsIndexes]=ismember(xRxns,model.rxns); +0091 +0092 if ~all(I) +0093 EM='Could not find all reactions in xRxns'; +0094 error('RAVEN:badInput', '%s', EM); +0095 end 0096 -0097 if plotFitting==true -0098 %Set the resulting parameters -0099 [~, resultingFluxes, newModel]=getRSS(parameters,model,xRxnsIndexes,xValues,rxnsToFitIndexes,valuesToFit,parameterPositions,true); -0100 plot(xValues,valuesToFit,'o',xValues,resultingFluxes,'-*'); -0101 end -0102 end -0103 -0104 function [rss, resultingFluxes, newModel]=getRSS(parameters,model,xRxnsIndexes,xValues,rxnsToFitIndexes,valuesToFit,parameterPositions,fitToRatio) -0105 parameters=abs(parameters); -0106 -0107 %Set the parameters at the positions specified in parameterPositions -0108 for i=1:numel(parameterPositions.position) -0109 %Set positive -0110 model.S(parameterPositions.position{i}(parameterPositions.isNegative{i}==false))=parameters(i); -0111 -0112 %Set negative -0113 model.S(parameterPositions.position{i}(parameterPositions.isNegative{i}==true))=parameters(i)*-1; -0114 end -0115 -0116 %Also return an updated model -0117 newModel=model; -0118 -0119 %Loop through each data point, set xRxns to xValues and calculate the sum -0120 %of squares for the rxnsToFit -0121 rss=0; -0122 resultingFluxes=[]; -0123 for i=1:size(xValues,1) -0124 model.lb(xRxnsIndexes)=xValues(i,:); -0125 model.ub(xRxnsIndexes)=xValues(i,:); -0126 -0127 sol=solveLP(model); -0128 -0129 %Calculate the rss -0130 if fitToRatio==false -0131 rs=sol.x(rxnsToFitIndexes)'-valuesToFit(i,:); -0132 else -0133 rs=sol.x(rxnsToFitIndexes)'./valuesToFit(i,:)-ones(1,size(valuesToFit,2)); -0134 end -0135 rss=rss+rs*rs'; -0136 resultingFluxes=[resultingFluxes sol.x(rxnsToFitIndexes)]; -0137 end -0138 end

    +0097 [parameters, fitnessScore, exitFlag]=fminsearch(@(parameters) getRSS(parameters,model,xRxnsIndexes,xValues,rxnsToFitIndexes,valuesToFit,parameterPositions,fitToRatio),initialGuess); +0098 +0099 parameters=abs(parameters); +0100 +0101 if plotFitting==true +0102 %Set the resulting parameters +0103 [~, resultingFluxes, newModel]=getRSS(parameters,model,xRxnsIndexes,xValues,rxnsToFitIndexes,valuesToFit,parameterPositions,true); +0104 plot(xValues,valuesToFit,'o',xValues,resultingFluxes,'-*'); +0105 end +0106 end +0107 +0108 function [rss, resultingFluxes, newModel]=getRSS(parameters,model,xRxnsIndexes,xValues,rxnsToFitIndexes,valuesToFit,parameterPositions,fitToRatio) +0109 parameters=abs(parameters); +0110 +0111 %Set the parameters at the positions specified in parameterPositions +0112 for i=1:numel(parameterPositions.position) +0113 %Set positive +0114 model.S(parameterPositions.position{i}(parameterPositions.isNegative{i}==false))=parameters(i); +0115 +0116 %Set negative +0117 model.S(parameterPositions.position{i}(parameterPositions.isNegative{i}==true))=parameters(i)*-1; +0118 end +0119 +0120 %Also return an updated model +0121 newModel=model; +0122 +0123 %Loop through each data point, set xRxns to xValues and calculate the sum +0124 %of squares for the rxnsToFit +0125 rss=0; +0126 resultingFluxes=[]; +0127 for i=1:size(xValues,1) +0128 model.lb(xRxnsIndexes)=xValues(i,:); +0129 model.ub(xRxnsIndexes)=xValues(i,:); +0130 +0131 sol=solveLP(model); +0132 +0133 %Calculate the rss +0134 if fitToRatio==false +0135 rs=sol.x(rxnsToFitIndexes)'-valuesToFit(i,:); +0136 else +0137 rs=sol.x(rxnsToFitIndexes)'./valuesToFit(i,:)-ones(1,size(valuesToFit,2)); +0138 end +0139 rss=rss+rs*rs'; +0140 resultingFluxes=[resultingFluxes sol.x(rxnsToFitIndexes)]; +0141 end +0142 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/biomass/getBiomassFractions.html b/docs/html/biomass/getBiomassFractions.html index 41926941..6f78d373 100644 --- a/docs/html/biomass/getBiomassFractions.html +++ b/docs/html/biomass/getBiomassFractions.html @@ -32,9 +32,16 @@

    DESCRIPTION ^DESCRIPTION ^DESCRIPTION ^CROSS-REFERENCE INFORMATION ^

    @@ -96,129 +104,137 @@

    SOURCE CODE ^% Compute the mass fraction (g/gDW) per biomass component plus the total. 0005 % The MATLAB counterpart of yeast-GEM's legacy sumBioMass. 0006 % -0007 % The biomassConfig struct describes the per-organism biomass layout — see -0008 % Parameters below. Components whose pseudoreaction is missing from the -0009 % model contribute 0. -0010 % -0011 % Parameters -0012 % ---------- -0013 % model : struct -0014 % RAVEN model struct. -0015 % biomassConfig : struct -0016 % Struct describing the biomass layout, with fields: +0007 % The biomassConfig struct describes the per-organism biomass layout (see +0008 % Parameters). A component contributes 0 when its pseudoreaction is missing +0009 % from the model or has no substrates. For a component whose pseudoreaction +0010 % exists and has substrates, and whose mass_strategy is not 'grams', an error +0011 % is raised when the molecular weight of a substrate is 0 (an empty +0012 % metFormula field, or a formula that consists only of R), when its formula +0013 % contains an element outside the built-in element table, or when +0014 % mass_strategy is unknown. A component with an unknown mass_strategy whose +0015 % pseudoreaction is missing or has no substrates contributes 0 without an +0016 % error. 0017 % -0018 % - biomass_rxn : rxn id of the top-level biomass pseudoreaction. -0019 % - proton_met : met id of cytosolic H+ (used only by -0020 % rescalePseudoreaction; may be unused here). -0021 % - components : cell array of component structs, each with fields: -0022 % -0023 % - name : component name (e.g. 'protein'). -0024 % - pseudoreaction_name : model.rxnNames entry to identify the -0025 % pseudoreaction. -0026 % - mass_strategy : 'mw' | 'mw_minus_2h' | 'mw_minus_water' | -0027 % 'grams' — see Notes below. -0028 % -0029 % Returns -0030 % ------- -0031 % fractions : struct -0032 % Struct keyed by component name plus 'total': fractions.protein, -0033 % fractions.RNA, ... etc. All values are in g/gDW. -0034 % -0035 % Examples -0036 % -------- -0037 % fractions = getBiomassFractions(model, biomassConfig); -0038 % -0039 % Notes -0040 % ----- -0041 % mass_strategy values: +0018 % Parameters +0019 % ---------- +0020 % model : struct +0021 % RAVEN model struct. +0022 % biomassConfig : struct +0023 % Struct describing the biomass layout, with fields: +0024 % +0025 % - biomass_rxn : rxn id of the top-level biomass pseudoreaction. +0026 % - proton_met : met id of cytosolic H+ (used by +0027 % scaleBiomassPseudoreaction, not by getBiomassFractions). +0028 % - components : cell array of component structs, each with fields: +0029 % +0030 % - name : component name (for example 'protein'). +0031 % - pseudoreaction_name : model.rxnNames entry to identify the +0032 % pseudoreaction. +0033 % - mass_strategy : 'mw', 'mw_minus_2h', 'mw_minus_water' or +0034 % 'grams' (see Notes). +0035 % +0036 % Returns +0037 % ------- +0038 % fractions : struct +0039 % Struct with one field per component name and a field 'total' +0040 % (for example fractions.protein and fractions.RNA). All values are in +0041 % g/gDW. 0042 % -0043 % 'mw' MW from chemical formula -0044 % 'mw_minus_2h' MW − 2.016 g/mol (two protons released per charged -0045 % tRNA — protein-pseudoreaction substrates) -0046 % 'mw_minus_water' MW − 18.015 g/mol (water released per -0047 % polymerisation step — RNA / DNA) -0048 % 'grams' stoichiometry already in g/gDW (lipid backbone) -0049 -0050 fractions = struct(); -0051 total = 0; -0052 for i = 1:numel(biomassConfig.components) -0053 comp = biomassConfig.components{i}; -0054 f = computeComponentFraction(model, comp); -0055 fractions.(comp.name) = f; -0056 total = total + f; -0057 end -0058 fractions.total = total; -0059 end -0060 -0061 function f = computeComponentFraction(model, comp) -0062 rxnPos = strcmp(model.rxnNames, comp.pseudoreaction_name); -0063 if ~any(rxnPos) -0064 f = 0; -0065 return; -0066 end -0067 S_col = model.S(:, rxnPos); -0068 isSub = find(S_col < 0); -0069 if isempty(isSub) -0070 f = 0; -0071 return; -0072 end -0073 if strcmp(comp.mass_strategy, 'grams') -0074 f = full(-sum(S_col(isSub))); -0075 return; -0076 end -0077 offset = mwOffset(comp.mass_strategy); -0078 formulas = model.metFormulas(isSub); -0079 MWs = zeros(numel(formulas), 1); -0080 for i = 1:numel(formulas) -0081 MWs(i) = computeFormulaMW(formulas{i}); -0082 end -0083 zeroMW = MWs == 0; -0084 if any(zeroMW) -0085 error('getBiomassFractions:emptyFormula', ... -0086 'Biomass metabolite %s has an empty metFormula field.', ... -0087 model.mets{isSub(find(zeroMW, 1))}); -0088 end -0089 MWs = MWs + offset; -0090 f = full(-sum(S_col(isSub) .* MWs) / 1000); -0091 end -0092 -0093 function offset = mwOffset(strategy) -0094 switch strategy -0095 case 'mw' -0096 offset = 0; -0097 case 'mw_minus_2h' -0098 offset = -2.016; -0099 case 'mw_minus_water' -0100 offset = -18.015; -0101 otherwise -0102 error('getBiomassFractions:unknownStrategy', ... -0103 'Unknown mass_strategy: %s', strategy); -0104 end -0105 end -0106 -0107 function mw = computeFormulaMW(formula) -0108 % Molecular weight in g/mol from a Hill-style chemical formula. Same -0109 % element table as the legacy yeast-GEM sumBioMass. -0110 tokens = regexp(formula, '([A-Z][a-z]*)(\d*)', 'tokens'); -0111 if isempty(tokens) -0112 mw = 0; -0113 return; -0114 end -0115 tokensMatrix = vertcat(tokens{:}); -0116 tokensMatrix(cellfun(@isempty, tokensMatrix(:,2)), 2) = {'1'}; -0117 elements = tokensMatrix(:, 1); -0118 counts = str2double(tokensMatrix(:, 2)); -0119 elem = {'C', 12.01; 'H', 1.008; 'N', 14.007; 'O', 15.999; ... -0120 'P', 30.974; 'S', 32.06; 'R', 0; ... -0121 'Fe', 55.845; 'K', 39.098; 'Na', 22.99; 'Cl', 35.45; ... -0122 'Mn', 54.938; 'Zn', 65.38; 'Ca', 40.078; 'Mg', 24.305; 'Cu', 63.546}; -0123 [~, elemMatch] = ismember(elements, elem(:,1)); -0124 if any(elemMatch == 0) -0125 error('getBiomassFractions:unknownElement', ... -0126 'Unknown element in formula %s', formula); -0127 end -0128 mw = sum(counts .* transpose([elem{elemMatch, 2}]), 'all'); -0129 end +0043 % Examples +0044 % -------- +0045 % fractions = getBiomassFractions(model, biomassConfig); +0046 % +0047 % Notes +0048 % ----- +0049 % mass_strategy values: +0050 % +0051 % 'mw' MW from chemical formula +0052 % 'mw_minus_2h' MW - 2.016 g/mol (two protons released per charged +0053 % tRNA; protein pseudoreaction substrates) +0054 % 'mw_minus_water' MW - 18.015 g/mol (water released per +0055 % polymerisation step; RNA and DNA) +0056 % 'grams' stoichiometry is already in g/gDW (lipid backbone) +0057 +0058 fractions = struct(); +0059 total = 0; +0060 for i = 1:numel(biomassConfig.components) +0061 comp = biomassConfig.components{i}; +0062 f = computeComponentFraction(model, comp); +0063 fractions.(comp.name) = f; +0064 total = total + f; +0065 end +0066 fractions.total = total; +0067 end +0068 +0069 function f = computeComponentFraction(model, comp) +0070 rxnPos = strcmp(model.rxnNames, comp.pseudoreaction_name); +0071 if ~any(rxnPos) +0072 f = 0; +0073 return; +0074 end +0075 S_col = model.S(:, rxnPos); +0076 isSub = find(S_col < 0); +0077 if isempty(isSub) +0078 f = 0; +0079 return; +0080 end +0081 if strcmp(comp.mass_strategy, 'grams') +0082 f = full(-sum(S_col(isSub))); +0083 return; +0084 end +0085 offset = mwOffset(comp.mass_strategy); +0086 formulas = model.metFormulas(isSub); +0087 MWs = zeros(numel(formulas), 1); +0088 for i = 1:numel(formulas) +0089 MWs(i) = computeFormulaMW(formulas{i}); +0090 end +0091 zeroMW = MWs == 0; +0092 if any(zeroMW) +0093 error('getBiomassFractions:emptyFormula', ... +0094 'Biomass metabolite %s has an empty metFormula field.', ... +0095 model.mets{isSub(find(zeroMW, 1))}); +0096 end +0097 MWs = MWs + offset; +0098 f = full(-sum(S_col(isSub) .* MWs) / 1000); +0099 end +0100 +0101 function offset = mwOffset(strategy) +0102 switch strategy +0103 case 'mw' +0104 offset = 0; +0105 case 'mw_minus_2h' +0106 offset = -2.016; +0107 case 'mw_minus_water' +0108 offset = -18.015; +0109 otherwise +0110 error('getBiomassFractions:unknownStrategy', ... +0111 'Unknown mass_strategy: %s', strategy); +0112 end +0113 end +0114 +0115 function mw = computeFormulaMW(formula) +0116 % Molecular weight in g/mol from a Hill-style chemical formula. Same +0117 % element table as the legacy yeast-GEM sumBioMass. +0118 tokens = regexp(formula, '([A-Z][a-z]*)(\d*)', 'tokens'); +0119 if isempty(tokens) +0120 mw = 0; +0121 return; +0122 end +0123 tokensMatrix = vertcat(tokens{:}); +0124 tokensMatrix(cellfun(@isempty, tokensMatrix(:,2)), 2) = {'1'}; +0125 elements = tokensMatrix(:, 1); +0126 counts = str2double(tokensMatrix(:, 2)); +0127 elem = {'C', 12.01; 'H', 1.008; 'N', 14.007; 'O', 15.999; ... +0128 'P', 30.974; 'S', 32.06; 'R', 0; ... +0129 'Fe', 55.845; 'K', 39.098; 'Na', 22.99; 'Cl', 35.45; ... +0130 'Mn', 54.938; 'Zn', 65.38; 'Ca', 40.078; 'Mg', 24.305; 'Cu', 63.546}; +0131 [~, elemMatch] = ismember(elements, elem(:,1)); +0132 if any(elemMatch == 0) +0133 error('getBiomassFractions:unknownElement', ... +0134 'Unknown element in formula %s', formula); +0135 end +0136 mw = sum(counts .* transpose([elem{elemMatch, 2}]), 'all'); +0137 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/biomass/scaleBiomassFraction.html b/docs/html/biomass/scaleBiomassFraction.html index 9b00536c..c7a450de 100644 --- a/docs/html/biomass/scaleBiomassFraction.html +++ b/docs/html/biomass/scaleBiomassFraction.html @@ -39,9 +39,10 @@

    DESCRIPTION ^DESCRIPTION ^DESCRIPTION ^SOURCE CODE ^% model : struct 0012 % RAVEN model struct. 0013 % biomassConfig : struct -0014 % Struct (see getBiomassFractions). +0014 % Biomass layout struct, as described in getBiomassFractions. 0015 % componentName : char -0016 % Component to rescale. -0017 % newValue : double -0018 % Target fraction in g/gDW. -0019 % -0020 % Name-Value Arguments -0021 % -------------------- -0022 % balanceOut : char -0023 % Second component name to adjust so the biomass total becomes -0024 % targetTotal. It must be large enough to absorb the change: an error -0025 % is raised if its fraction would become zero or negative, or if it is -0026 % 0 to begin with. Empty / omit to skip balancing. -0027 % targetTotal : double -0028 % Biomass total in g/gDW that balanceOut brings the components to -0029 % (default 1). -0030 % -0031 % Returns -0032 % ------- -0033 % model : struct -0034 % Modified model. -0035 % -0036 % Examples -0037 % -------- -0038 % model = scaleBiomassFraction(model, biomassConfig, 'protein', 0.5, 'carbohydrate'); -0039 % model = scaleBiomassFraction(model, biomassConfig, 'protein', 0.5, ... -0040 % 'balanceOut', 'carbohydrate', 'targetTotal', 0.95); -0041 % -0042 % See also -0043 % -------- -0044 % getBiomassFractions -0045 -0046 p=parseRAVENargs(varargin, {'balanceOut',''; 'targetTotal',1}); -0047 balanceOut=p.balanceOut; -0048 targetTotal=p.targetTotal; -0049 -0050 fractions = getBiomassFractions(model, biomassConfig); -0051 if ~isfield(fractions, componentName) -0052 error('scaleBiomassFraction:unknownComponent', ... -0053 'biomassConfig has no component named %s', componentName); -0054 end -0055 current = fractions.(componentName); -0056 if current == 0 -0057 error('scaleBiomassFraction:zeroCurrent', ... -0058 ['Cannot scale %s to %g: current fraction is 0 ' ... -0059 '(pseudoreaction missing or empty).'], componentName, newValue); -0060 end -0061 factor = newValue / current; -0062 model = scaleBiomassPseudoreaction(model, biomassConfig, componentName, factor); -0063 -0064 if ~isempty(balanceOut) -0065 fractions = getBiomassFractions(model, biomassConfig); -0066 total = fractions.total; -0067 if ~isfield(fractions, balanceOut) -0068 error('scaleBiomassFraction:unknownComponent', ... -0069 'biomassConfig has no component named %s', balanceOut); -0070 end -0071 balanceCurrent = fractions.(balanceOut); -0072 if balanceCurrent == 0 -0073 error('scaleBiomassFraction:zeroCurrent', ... -0074 ['Cannot balance with %s: current fraction is 0 ' ... -0075 '(pseudoreaction missing or empty).'], balanceOut); -0076 end -0077 balanceNew = balanceCurrent + (targetTotal - total); -0078 if balanceNew <= 0 -0079 error('scaleBiomassFraction:negativeBalance', ... -0080 ['Cannot balance with %s: its fraction would become %g g/gDW. ' ... -0081 'It is %g g/gDW, but the other components total %g g/gDW.'], ... -0082 balanceOut, balanceNew, balanceCurrent, total - balanceCurrent); -0083 end -0084 balanceFactor = balanceNew / balanceCurrent; -0085 model = scaleBiomassPseudoreaction(model, biomassConfig, balanceOut, balanceFactor); -0086 end -0087 end +0016 % Name of the component to rescale. An error is raised if its current +0017 % fraction is 0. +0018 % newValue : double +0019 % Target fraction in g/gDW. +0020 % +0021 % Name-Value Arguments +0022 % -------------------- +0023 % balanceOut : char +0024 % Second component name to adjust so the biomass total becomes +0025 % targetTotal. An error is raised if its fraction would become zero or +0026 % negative, or if it is 0 to begin with. No balancing is done when +0027 % empty or omitted (default ''). +0028 % targetTotal : double +0029 % Biomass total in g/gDW that balanceOut brings the components to +0030 % (default 1). +0031 % +0032 % Returns +0033 % ------- +0034 % model : struct +0035 % Model with the rescaled pseudoreactions. +0036 % +0037 % Examples +0038 % -------- +0039 % model = scaleBiomassFraction(model, biomassConfig, 'protein', 0.5, 'carbohydrate'); +0040 % model = scaleBiomassFraction(model, biomassConfig, 'protein', 0.5, ... +0041 % 'balanceOut', 'carbohydrate', 'targetTotal', 0.95); +0042 % +0043 % See also +0044 % -------- +0045 % getBiomassFractions +0046 +0047 p=parseRAVENargs(varargin, {'balanceOut',''; 'targetTotal',1}); +0048 balanceOut=p.balanceOut; +0049 targetTotal=p.targetTotal; +0050 +0051 fractions = getBiomassFractions(model, biomassConfig); +0052 if ~isfield(fractions, componentName) +0053 error('scaleBiomassFraction:unknownComponent', ... +0054 'biomassConfig has no component named %s', componentName); +0055 end +0056 current = fractions.(componentName); +0057 if current == 0 +0058 error('scaleBiomassFraction:zeroCurrent', ... +0059 ['Cannot scale %s to %g: current fraction is 0 ' ... +0060 '(pseudoreaction missing or empty).'], componentName, newValue); +0061 end +0062 factor = newValue / current; +0063 model = scaleBiomassPseudoreaction(model, biomassConfig, componentName, factor); +0064 +0065 if ~isempty(balanceOut) +0066 fractions = getBiomassFractions(model, biomassConfig); +0067 total = fractions.total; +0068 if ~isfield(fractions, balanceOut) +0069 error('scaleBiomassFraction:unknownComponent', ... +0070 'biomassConfig has no component named %s', balanceOut); +0071 end +0072 balanceCurrent = fractions.(balanceOut); +0073 if balanceCurrent == 0 +0074 error('scaleBiomassFraction:zeroCurrent', ... +0075 ['Cannot balance with %s: current fraction is 0 ' ... +0076 '(pseudoreaction missing or empty).'], balanceOut); +0077 end +0078 balanceNew = balanceCurrent + (targetTotal - total); +0079 if balanceNew <= 0 +0080 error('scaleBiomassFraction:negativeBalance', ... +0081 ['Cannot balance with %s: its fraction would become %g g/gDW. ' ... +0082 'It is %g g/gDW, but the other components total %g g/gDW.'], ... +0083 balanceOut, balanceNew, balanceCurrent, total - balanceCurrent); +0084 end +0085 balanceFactor = balanceNew / balanceCurrent; +0086 model = scaleBiomassPseudoreaction(model, biomassConfig, balanceOut, balanceFactor); +0087 end +0088 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/biomass/scaleBiomassPseudoreaction.html b/docs/html/biomass/scaleBiomassPseudoreaction.html index 2bef595c..66be1718 100644 --- a/docs/html/biomass/scaleBiomassPseudoreaction.html +++ b/docs/html/biomass/scaleBiomassPseudoreaction.html @@ -34,10 +34,12 @@

    DESCRIPTION ^DESCRIPTION ^SOURCE CODE ^% The MATLAB counterpart of yeast-GEM's legacy rescalePseudoReaction. 0007 % 0008 % "Substrate" means every metabolite in the pseudoreaction whose -0009 % metabolite name does NOT match the component name (the component's -0010 % product is left untouched). After rescaling, the coefficient of -0011 % biomassConfig.proton_met is recomputed so the pseudoreaction's total -0012 % ionic charge sums to zero. -0013 % -0014 % Parameters -0015 % ---------- -0016 % model : struct -0017 % RAVEN model struct. -0018 % biomassConfig : struct -0019 % Struct (see getBiomassFractions). -0020 % componentName : char -0021 % Name of the component to rescale (must match -0022 % biomassConfig.components{i}.name for some i, AND be the -0023 % model.metNames of the produced metabolite in the matching -0024 % pseudoreaction). -0025 % factor : double -0026 % Multiplicative factor. -0027 % -0028 % Returns -0029 % ------- -0030 % model : struct -0031 % Modified model. -0032 % -0033 % Examples -0034 % -------- -0035 % model = scaleBiomassPseudoreaction(model, biomassConfig, 'protein', 0.9); -0036 % -0037 % See also -0038 % -------- -0039 % getBiomassFractions -0040 -0041 comp = findComponent(biomassConfig, componentName); -0042 rxnPos = find(strcmp(model.rxnNames, comp.pseudoreaction_name)); -0043 if isempty(rxnPos) -0044 error('scaleBiomassPseudoreaction:missingPseudoreaction', ... -0045 'No reaction named %s in model.', comp.pseudoreaction_name); -0046 end -0047 -0048 Hc = find(strcmp(model.mets, biomassConfig.proton_met)); -0049 if isempty(Hc) -0050 error('scaleBiomassPseudoreaction:missingProton', ... -0051 'Proton metabolite %s not found in model.', biomassConfig.proton_met); -0052 end -0053 -0054 for i = 1:length(model.mets) -0055 S_ir = model.S(i, rxnPos); -0056 isProd = strcmp(model.metNames{i}, componentName); -0057 if S_ir ~= 0 && ~isProd -0058 model.S(i, rxnPos) = factor * S_ir; -0059 end -0060 end -0061 -0062 % Rebalance H+ to keep charge neutrality. -0063 model.S(Hc, rxnPos) = 0; -0064 model.S(Hc, rxnPos) = -chargeResidual(model, rxnPos); -0065 end -0066 -0067 function residual = chargeResidual(model, rxnPos) -0068 % Sum of charges over the metabolites that take part in a reaction. Only the -0069 % participants may be summed: S is sparse and 0*NaN is NaN, so any unset -0070 % charge in the model would otherwise poison the result. Summing with -0071 % 'omitnan' instead would treat an unset charge as neutral and silently -0072 % rebalance the reaction to the wrong coefficient. -0073 metIdx = find(model.S(:, rxnPos)); -0074 if ~isfield(model, 'metCharges') -0075 error('scaleBiomassPseudoreaction:noCharges', ... -0076 ['Cannot rebalance charge: the model has no metCharges field. ' ... -0077 'Provide metabolite charges, or rescale without charge balancing.']); -0078 end -0079 unknown = metIdx(isnan(model.metCharges(metIdx))); -0080 if ~isempty(unknown) -0081 error('scaleBiomassPseudoreaction:unknownCharge', ... -0082 ['Cannot rebalance charge of %s: the following metabolites have no ' ... -0083 'charge, so the charge balance is unknown rather than zero:\n\t%s'], ... -0084 model.rxns{rxnPos}, strjoin(model.mets(unknown), ', ')); -0085 end -0086 residual = sum(full(model.S(metIdx, rxnPos)) .* model.metCharges(metIdx)); -0087 end -0088 -0089 function comp = findComponent(cfg, name) -0090 for i = 1:numel(cfg.components) -0091 if strcmp(cfg.components{i}.name, name) -0092 comp = cfg.components{i}; -0093 return; -0094 end -0095 end -0096 error('scaleBiomassPseudoreaction:unknownComponent', ... -0097 'biomassConfig has no component named %s', name); -0098 end +0009 % metabolite name differs from the component name; the coefficient of the +0010 % component's product is unchanged. After rescaling, the coefficient of +0011 % biomassConfig.proton_met is recomputed so that the total charge of the +0012 % pseudoreaction is zero. An error is raised if the model has no +0013 % metCharges field or if a metabolite other than proton_met in the +0014 % pseudoreaction has an undefined (NaN) charge. +0015 % +0016 % Parameters +0017 % ---------- +0018 % model : struct +0019 % RAVEN model struct. +0020 % biomassConfig : struct +0021 % Struct (see getBiomassFractions). +0022 % componentName : char +0023 % Name of the component to rescale. It must match +0024 % biomassConfig.components{i}.name for some i and the model.metNames +0025 % entry of the produced metabolite in the matching pseudoreaction. +0026 % factor : double +0027 % Factor by which the substrate coefficients are multiplied. +0028 % +0029 % Returns +0030 % ------- +0031 % model : struct +0032 % Modified model. +0033 % +0034 % Examples +0035 % -------- +0036 % model = scaleBiomassPseudoreaction(model, biomassConfig, 'protein', 0.9); +0037 % +0038 % See also +0039 % -------- +0040 % getBiomassFractions +0041 +0042 comp = findComponent(biomassConfig, componentName); +0043 rxnPos = find(strcmp(model.rxnNames, comp.pseudoreaction_name)); +0044 if isempty(rxnPos) +0045 error('scaleBiomassPseudoreaction:missingPseudoreaction', ... +0046 'No reaction named %s in model.', comp.pseudoreaction_name); +0047 end +0048 +0049 Hc = find(strcmp(model.mets, biomassConfig.proton_met)); +0050 if isempty(Hc) +0051 error('scaleBiomassPseudoreaction:missingProton', ... +0052 'Proton metabolite %s not found in model.', biomassConfig.proton_met); +0053 end +0054 +0055 for i = 1:length(model.mets) +0056 S_ir = model.S(i, rxnPos); +0057 isProd = strcmp(model.metNames{i}, componentName); +0058 if S_ir ~= 0 && ~isProd +0059 model.S(i, rxnPos) = factor * S_ir; +0060 end +0061 end +0062 +0063 % Rebalance H+ to keep charge neutrality. +0064 model.S(Hc, rxnPos) = 0; +0065 model.S(Hc, rxnPos) = -chargeResidual(model, rxnPos); +0066 end +0067 +0068 function residual = chargeResidual(model, rxnPos) +0069 % Sum of charges over the metabolites that take part in a reaction. Only the +0070 % participants may be summed: S is sparse and 0*NaN is NaN, so any unset +0071 % charge in the model would otherwise poison the result. Summing with +0072 % 'omitnan' instead would treat an unset charge as neutral and silently +0073 % rebalance the reaction to the wrong coefficient. +0074 metIdx = find(model.S(:, rxnPos)); +0075 if ~isfield(model, 'metCharges') +0076 error('scaleBiomassPseudoreaction:noCharges', ... +0077 ['Cannot rebalance charge: the model has no metCharges field. ' ... +0078 'Provide metabolite charges, or rescale without charge balancing.']); +0079 end +0080 unknown = metIdx(isnan(model.metCharges(metIdx))); +0081 if ~isempty(unknown) +0082 error('scaleBiomassPseudoreaction:unknownCharge', ... +0083 ['Cannot rebalance charge of %s: the following metabolites have no ' ... +0084 'charge, so the charge balance is unknown rather than zero:\n\t%s'], ... +0085 model.rxns{rxnPos}, strjoin(model.mets(unknown), ', ')); +0086 end +0087 residual = sum(full(model.S(metIdx, rxnPos)) .* model.metCharges(metIdx)); +0088 end +0089 +0090 function comp = findComponent(cfg, name) +0091 for i = 1:numel(cfg.components) +0092 if strcmp(cfg.components{i}.name, name) +0093 comp = cfg.components{i}; +0094 return; +0095 end +0096 end +0097 error('scaleBiomassPseudoreaction:unknownComponent', ... +0098 'biomassConfig has no component named %s', name); +0099 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/biomass/setGAM.html b/docs/html/biomass/setGAM.html index 3f03a37d..b9530942 100644 --- a/docs/html/biomass/setGAM.html +++ b/docs/html/biomass/setGAM.html @@ -36,11 +36,11 @@

    DESCRIPTION ^DESCRIPTION ^DESCRIPTION ^DESCRIPTION ^fitParameters : estimates such a value from measured fluxes instead of - applying a known one. The two are complementary - fit first, then set. + fitParameters : derives such a value from measured fluxes; setGAM then + applies it.

    CROSS-REFERENCE INFORMATION ^

    @@ -99,11 +100,11 @@

    SOURCE CODE ^% 0009 % For every metabolite in the biomass pseudoreaction whose model.metNames 0010 % entry is in cofactorMetNames, the stoichiometric coefficient is set to -0011 % ±value preserving the sign of the current coefficient. An error is raised +0011 % value with the sign of the current coefficient. An error is raised 0012 % before the model is changed if ngamRxn is given and is not in the model, 0013 % and if ngamValue is given without ngamRxn (RAVEN:badInput). -0014 % Yeast-GEM scales ATP, ADP, H2O, H+ and phosphate (with ATP and H2O on the -0015 % substrate side, ADP / H+ / phosphate on the product side). +0014 % For yeast-GEM, the cofactors are ATP, ADP, H2O, H+ and phosphate (ATP and +0015 % H2O are substrates; ADP, H+ and phosphate are products). 0016 % 0017 % Parameters 0018 % ---------- @@ -114,73 +115,74 @@

    SOURCE CODE ^% biomassRxn : char 0024 % Reaction id of the biomass pseudoreaction. 0025 % cofactorMetNames : cell -0026 % Cell array of metabolite NAMES (not IDs) to rescale, e.g. -0027 % {'ATP','ADP','H2O','H+','phosphate'}. -0028 % -0029 % Name-Value Arguments -0030 % -------------------- -0031 % ngamRxn : char -0032 % NGAM reaction id. Required when ngamValue is supplied. -0033 % ngamValue : double -0034 % NGAM flux to fix. Sets the NGAM reaction's bounds to (ngamValue, -0035 % ngamValue). -0036 % -0037 % Returns -0038 % ------- -0039 % model : struct -0040 % Modified model. -0041 % -0042 % Examples -0043 % -------- -0044 % model = setGAM(model, 80, 'r_4041', {'ATP','ADP','H2O','H+','phosphate'}); -0045 % -0046 % See also -0047 % -------- -0048 % fitParameters : estimates such a value from measured fluxes instead of -0049 % applying a known one. The two are complementary - fit first, then set. -0050 -0051 if nargin < 4 -0052 error('setGAM:missingArgs', ... -0053 'biomassRxn and cofactorMetNames are required.'); -0054 end -0055 -0056 p=parseRAVENargs(varargin, {'ngamRxn',[]; 'ngamValue',[]}); -0057 ngamRxn=p.ngamRxn; -0058 ngamValue=p.ngamValue; -0059 %An empty id counts as not given -0060 if ~isempty(ngamRxn) -0061 ngamRxn=cellstr(ngamRxn); -0062 ngamRxn=ngamRxn(~cellfun(@isempty,ngamRxn)); -0063 end -0064 if ~isempty(ngamValue) && isempty(ngamRxn) -0065 error('RAVEN:badInput', ... -0066 'ngamValue is given without ngamRxn. Give the NGAM reaction id as ''ngamRxn''.'); -0067 end -0068 -0069 bioPos = strcmp(model.rxns, biomassRxn); -0070 if ~any(bioPos) -0071 error('setGAM:missingBiomassRxn', ... -0072 'Reaction %s not found in model.', biomassRxn); -0073 end -0074 -0075 setNgam = ~isempty(ngamRxn) && ~isempty(ngamValue); -0076 if ~isempty(ngamRxn) && ~all(ismember(ngamRxn, model.rxns)) -0077 error('setGAM:missingNgamRxn', ... -0078 'NGAM reaction %s not found in model.', strjoin(ngamRxn, ', ')); -0079 end -0080 -0081 for i = 1:length(model.mets) -0082 S_ix = model.S(i, bioPos); -0083 isCofactor = any(strcmp(cofactorMetNames, model.metNames{i})); -0084 if S_ix ~= 0 && isCofactor -0085 model.S(i, bioPos) = sign(S_ix) * value; -0086 end -0087 end -0088 -0089 if setNgam -0090 model = setParam(model, 'eq', ngamRxn, ngamValue); -0091 end -0092 end +0026 % Cell array of metabolite names (matched against model.metNames, not +0027 % metabolite ids) to rescale, for example +0028 % {'ATP','ADP','H2O','H+','phosphate'}. +0029 % +0030 % Name-Value Arguments +0031 % -------------------- +0032 % ngamRxn : char +0033 % NGAM reaction id. Required when ngamValue is supplied. +0034 % ngamValue : double +0035 % NGAM flux to fix. Both bounds of the NGAM reaction are set to +0036 % ngamValue. +0037 % +0038 % Returns +0039 % ------- +0040 % model : struct +0041 % Modified model. +0042 % +0043 % Examples +0044 % -------- +0045 % model = setGAM(model, 80, 'r_4041', {'ATP','ADP','H2O','H+','phosphate'}); +0046 % +0047 % See also +0048 % -------- +0049 % fitParameters : derives such a value from measured fluxes; setGAM then +0050 % applies it. +0051 +0052 if nargin < 4 +0053 error('setGAM:missingArgs', ... +0054 'biomassRxn and cofactorMetNames are required.'); +0055 end +0056 +0057 p=parseRAVENargs(varargin, {'ngamRxn',[]; 'ngamValue',[]}); +0058 ngamRxn=p.ngamRxn; +0059 ngamValue=p.ngamValue; +0060 %An empty id counts as not given +0061 if ~isempty(ngamRxn) +0062 ngamRxn=cellstr(ngamRxn); +0063 ngamRxn=ngamRxn(~cellfun(@isempty,ngamRxn)); +0064 end +0065 if ~isempty(ngamValue) && isempty(ngamRxn) +0066 error('RAVEN:badInput', ... +0067 'ngamValue is given without ngamRxn. Give the NGAM reaction id as ''ngamRxn''.'); +0068 end +0069 +0070 bioPos = strcmp(model.rxns, biomassRxn); +0071 if ~any(bioPos) +0072 error('setGAM:missingBiomassRxn', ... +0073 'Reaction %s not found in model.', biomassRxn); +0074 end +0075 +0076 setNgam = ~isempty(ngamRxn) && ~isempty(ngamValue); +0077 if ~isempty(ngamRxn) && ~all(ismember(ngamRxn, model.rxns)) +0078 error('setGAM:missingNgamRxn', ... +0079 'NGAM reaction %s not found in model.', strjoin(ngamRxn, ', ')); +0080 end +0081 +0082 for i = 1:length(model.mets) +0083 S_ix = model.S(i, bioPos); +0084 isCofactor = any(strcmp(cofactorMetNames, model.metNames{i})); +0085 if S_ix ~= 0 && isCofactor +0086 model.S(i, bioPos) = sign(S_ix) * value; +0087 end +0088 end +0089 +0090 if setNgam +0091 model = setParam(model, 'eq', ngamRxn, ngamValue); +0092 end +0093 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/comparison/compareMultipleModels.html b/docs/html/comparison/compareMultipleModels.html index 5189ea92..405e9e0e 100644 --- a/docs/html/comparison/compareMultipleModels.html +++ b/docs/html/comparison/compareMultipleModels.html @@ -31,10 +31,10 @@

    DESCRIPTION ^DESCRIPTION ^DESCRIPTION ^SOURCE CODE ^% 0004 % Compares two or more models on overlap of reactions, metabolites, genes, 0005 % EC-codes, metabolite names and reaction equations (with and without -0006 % compartmentalization), and on high-dimensional comparisons in the -0007 % reaction-space: subsystem utilization, structural similarity, an optional -0008 % low-dimensional structural projection, and an optional functional -0009 % (task-based) comparison. +0006 % compartmentalisation), and on high-dimensional comparisons of the +0007 % reaction content: subsystem coverage, structural similarity, a +0008 % low-dimensional structural projection (skipped when neither tsne nor +0009 % mdscale is available), and an optional functional (task-based) comparison. 0010 % 0011 % Parameters 0012 % ---------- @@ -125,550 +130,555 @@

    SOURCE CODE ^% Name-Value Arguments 0017 % -------------------- 0018 % printResults : logical -0019 % true if the results should be printed on the screen (default false). +0019 % true to print the results to the command window (default false). 0020 % plotResults : logical -0021 % true if the results should be plotted (default false). +0021 % true to plot the results (default false). 0022 % groupVector : double or cell -0023 % numeric vector or cell array for grouping similar models, i.e. by -0024 % tissue (default all models ungrouped). -0025 % funcCompare : logical -0026 % should a functional comparison be run (default false). -0027 % taskFile : char -0028 % string containing the name of the task file to use for the functional -0029 % comparison (should be an .xls or .xlsx file, required for functional -0030 % comparison). -0031 % -0032 % Returns -0033 % ------- -0034 % compStruct : struct -0035 % structure that contains the comparison results, with fields: -0036 % -0037 % - modelIDs : cell array of model ids -0038 % - rxns, mets, genes, eccodes, metNames, equ, uEqu : the overlap for -0039 % each field, each with sub-fields comparison (binary matrix where each -0040 % row indicates which models are included in the comparison) and -0041 % nElements (vector with the number of elements for each comparison). -0042 % "equ" are the reaction equations after sorting and "uEqu" are the -0043 % equations when not taking compartmentalization into account. These -0044 % are the only fields of the seven that use EC-code, metabolite name or -0045 % reaction equation to compare models that do not share an id -0046 % convention; the rest compare by identifier only. -0047 % - subsystems : substructure containing subsystem information, with -0048 % fields matrix (matrix with comparison of number of rxns per -0049 % subsystem) and ID (vector consisting of names of all subsystems) -0050 % - reactions : substructure containing reaction information, with -0051 % fields matrix (binary matrix composed of reactions (rows) in each -0052 % model (column), used as the input for the model comparisons) and IDs -0053 % (list of the reactions contained in the reaction matrix) -0054 % - structComp : matrix with pairwise comparisons of model structure -0055 % based on (1-Hamming distance) between models -0056 % - structCompMap : matrix with 3D tSNE (or MDS) mapping of model -0057 % structures based on Hamming distances -0058 % - funcComp : substructure containing function comparison results, with -0059 % fields matrix (matrix with PASS / FAIL (1 / 0) values for each task) -0060 % and tasks (vector containing names of all tasks) -0061 % -0062 % Examples -0063 % -------- -0064 % compStruct = compareMultipleModels(models, printResults, ... -0065 % plotResults, groupVector, funcCompare, taskFile); -0066 -0067 % The core comparison (structComp) runs in base MATLAB. The low-dimensional -0068 % projection (structCompMap) and the heatmap clustering additionally use the -0069 % Statistics and Machine Learning Toolbox (tsne/mdscale, linkage); these -0070 % degrade gracefully and are skipped when the toolbox is not available. -0071 % -0072 % See also -0073 % -------- -0074 % diffModels : takes exactly two models and reports the differing *values* -0075 % (stoichiometry, bounds, grRules), where this reports overlap counts, -0076 % similarity and clustering. -0077 -0078 %% Set up input defaults -0079 p=parseRAVENargs(varargin, {'printResults',false; 'plotResults',false; 'groupVector',[]; 'funcCompare',false; 'taskFile',[]}); -0080 printResults=p.printResults; -0081 plotResults=p.plotResults; -0082 groupVector=p.groupVector; -0083 if isempty(groupVector) -0084 groupVector = []; -0085 elseif ~isnumeric(groupVector) -0086 % convert strings to numeric groups -0087 [groupNames,~,groupVector] = unique(groupVector); -0088 else -0089 % generate group names for vector of numbers -0090 groupNames = arrayfun(@num2str,unique(groupVector),'UniformOutput',false); -0091 end -0092 funcCompare=p.funcCompare; -0093 taskFile=p.taskFile; -0094 if ~isempty(taskFile) -0095 taskFile=char(taskFile); -0096 end -0097 if numel(models) <= 1 -0098 EM = 'Cannot compare only one model. Use printModelStats if you want a summary of a model'; -0099 error('RAVEN:badInput', '%s', EM); +0023 % numeric vector or cell array that assigns each model to a group, for +0024 % example by tissue. The groups colour the points of the structural +0025 % projection plot (default [], no grouping). +0026 % funcCompare : logical +0027 % true to run the functional comparison, which requires taskFile +0028 % (default false). +0029 % taskFile : char +0030 % name of the task file for the functional comparison, in a format that +0031 % parseTaskList reads. Required when funcCompare is true. +0032 % +0033 % Returns +0034 % ------- +0035 % compStruct : struct +0036 % structure that contains the comparison results, with fields: +0037 % +0038 % - modelIDs : cell array of model ids +0039 % - rxns, mets, genes, eccodes, metNames, equ, uEqu : the overlap for +0040 % each field, each with sub-fields comparison (binary matrix where each +0041 % row indicates which models are included in the comparison) and +0042 % nElements (vector with the number of elements for each comparison). +0043 % "equ" are the reaction equations after sorting and "uEqu" are the +0044 % equations without compartments. The fields rxns, mets and genes +0045 % compare identifiers; eccodes, metNames, equ and uEqu compare EC +0046 % codes, metabolite names and reaction equations, which allows +0047 % overlap between models that use different identifiers. +0048 % - subsystems : substructure containing subsystem information, with +0049 % fields matrix (number of reactions per subsystem, subsystems in rows +0050 % and models in columns) and ID (cell array with the names of all +0051 % subsystems). Only the first subsystem of each reaction is counted. +0052 % The field is absent when a model lacks the subSystems field. +0053 % - reactions : substructure containing reaction information, with +0054 % fields matrix (binary matrix with reactions in rows and models in +0055 % columns, used as the input for the model comparisons) and IDs +0056 % (list of the reactions contained in the reaction matrix) +0057 % - structComp : matrix with pairwise comparisons of model structure +0058 % based on (1 - Hamming distance) between models +0059 % - structCompMap : matrix with the 3D tSNE (or MDS) mapping of model +0060 % structures based on Hamming distances. It is empty when neither +0061 % tsne nor mdscale is available. +0062 % - funcComp : substructure containing function comparison results, with +0063 % fields matrix (1 for a passed task and 0 for a failed task, tasks in +0064 % rows and models in columns) and tasks (the descriptions of all +0065 % tasks). It is present only when funcCompare is true. +0066 % +0067 % Examples +0068 % -------- +0069 % compStruct = compareMultipleModels(models, printResults, ... +0070 % plotResults, groupVector, funcCompare, taskFile); +0071 +0072 % The core comparison (structComp) runs in base MATLAB. The low-dimensional +0073 % projection (structCompMap) and the heatmap clustering additionally use the +0074 % Statistics and Machine Learning Toolbox (tsne/mdscale, linkage); these +0075 % degrade gracefully and are skipped when the toolbox is not available. +0076 % +0077 % See also +0078 % -------- +0079 % diffModels : reports the differing values (stoichiometry, bounds, grRules) +0080 % between two models. +0081 +0082 %% Set up input defaults +0083 p=parseRAVENargs(varargin, {'printResults',false; 'plotResults',false; 'groupVector',[]; 'funcCompare',false; 'taskFile',[]}); +0084 printResults=p.printResults; +0085 plotResults=p.plotResults; +0086 groupVector=p.groupVector; +0087 if isempty(groupVector) +0088 groupVector = []; +0089 elseif ~isnumeric(groupVector) +0090 % convert strings to numeric groups +0091 [groupNames,~,groupVector] = unique(groupVector); +0092 else +0093 % generate group names for vector of numbers +0094 groupNames = arrayfun(@num2str,unique(groupVector),'UniformOutput',false); +0095 end +0096 funcCompare=p.funcCompare; +0097 taskFile=p.taskFile; +0098 if ~isempty(taskFile) +0099 taskFile=char(taskFile); 0100 end -0101 if isempty(taskFile) && funcCompare -0102 EM = 'Cannot perform the functional comparison without a task file. Specify taskFile or set funcCompare to FALSE.'; +0101 if numel(models) <= 1 +0102 EM = 'Cannot compare only one model. Use printModelStats if you want a summary of a model'; 0103 error('RAVEN:badInput', '%s', EM); 0104 end -0105 -0106 %% Set up model ID structure -0107 compStruct.modelIDs = {}; -0108 fprintf('\n Getting model IDs \n') -0109 for i = 1:numel(models) -0110 if ~ischar(models{i}.id) % to deal with non-character IDs (cells, strings, etc) -0111 compStruct.modelIDs{i,1} = models{i}.id{1}; -0112 else -0113 compStruct.modelIDs{i,1} = models{i}.id; -0114 end -0115 % Equations are computed once here (with and without compartmentalization) -0116 % so the overlap comparison below can treat them like any other field. -0117 models{i}.equ=constructEquations(models{i},models{i}.rxns,true,true,true); -0118 models{i}.uEqu=constructEquations(models{i},models{i}.rxns,false,true,true); -0119 end -0120 fprintf('*** Done \n\n') -0121 -0122 %% Compare overlap of reactions, metabolites, genes, EC-codes, metabolite -0123 % names and equations (with and without compartmentalization) -0124 fprintf('\n Comparing overlap of reactions, genes, metabolites and compartments \n') -0125 overlapFields={'rxns','mets','genes','eccodes','metNames','equ','uEqu'}; -0126 overlapLabels={'reaction IDs','metabolite IDs','gene IDs','ec-numbers', ... -0127 'metabolite names','equations with compartment','equations without compartment'}; -0128 for i=1:numel(overlapFields) -0129 field=overlapFields{i}; -0130 compStruct.(field).comparison=getToCheck(models,field); -0131 compStruct.(field).nElements=checkStuff(getElements(models,field),compStruct.(field).comparison); -0132 if printResults==true -0133 fprintf(['*** Comparison of ' overlapLabels{i} ':\n']); -0134 printList(models,compStruct.(field).comparison,compStruct.(field).nElements); -0135 fprintf('\n\n'); -0136 end -0137 end -0138 fprintf('*** Done \n\n') -0139 -0140 %% Compare models structure & function based on high-dimensional methods -0141 % Compare number of reactions in each subsystem in each model using a heatmap -0142 field = 'subSystems'; -0143 fprintf('\n Comparing subsystem utilization \n') -0144 if any(~cellfun(@(m) isfield(m,field),models)) -0145 fprintf('\nWARNING: At least one model does not contain the field "subSystems". \n') -0146 fprintf(' Skipping subsystem comparison. \n\n') -0147 else -0148 %% Flatten model subSystems field -0149 % Convert from cell array of cells to cell array of strings -0150 % NOTE: this function currently only recognizes one subSystem per reaction; -0151 % additional subSystems will be ignored! -0152 for i = 1:numel(models) -0153 if any(cellfun(@(x) iscell(x), models{i}.subSystems)); -0154 cells = cellfun(@iscell,models{i}.subSystems); -0155 models{i}.subSystems(cells) = cellfun(@(s) s{1}, models{i}.subSystems(cells), 'UniformOutput', false); -0156 end -0157 end -0158 [id,compMat] = compareModelField(models,field); -0159 compStruct.subsystems.ID = id; -0160 compStruct.subsystems.matrix = compMat; -0161 fprintf('*** Done \n\n') -0162 -0163 if printResults -0164 % This could use some cleaning up -0165 fprintf('*** Comparison of reaction subsystem populations:\n\n'); +0105 if isempty(taskFile) && funcCompare +0106 EM = 'Cannot perform the functional comparison without a task file. Specify taskFile or set funcCompare to FALSE.'; +0107 error('RAVEN:badInput', '%s', EM); +0108 end +0109 +0110 %% Set up model ID structure +0111 compStruct.modelIDs = {}; +0112 fprintf('\n Getting model IDs \n') +0113 for i = 1:numel(models) +0114 if ~ischar(models{i}.id) % to deal with non-character IDs (cells, strings, etc) +0115 compStruct.modelIDs{i,1} = models{i}.id{1}; +0116 else +0117 compStruct.modelIDs{i,1} = models{i}.id; +0118 end +0119 % Equations are computed once here (with and without compartmentalization) +0120 % so the overlap comparison below can treat them like any other field. +0121 models{i}.equ=constructEquations(models{i},models{i}.rxns,true,true,true); +0122 models{i}.uEqu=constructEquations(models{i},models{i}.rxns,false,true,true); +0123 end +0124 fprintf('*** Done \n\n') +0125 +0126 %% Compare overlap of reactions, metabolites, genes, EC-codes, metabolite +0127 % names and equations (with and without compartmentalization) +0128 fprintf('\n Comparing overlap of reactions, genes, metabolites and compartments \n') +0129 overlapFields={'rxns','mets','genes','eccodes','metNames','equ','uEqu'}; +0130 overlapLabels={'reaction IDs','metabolite IDs','gene IDs','ec-numbers', ... +0131 'metabolite names','equations with compartment','equations without compartment'}; +0132 for i=1:numel(overlapFields) +0133 field=overlapFields{i}; +0134 compStruct.(field).comparison=getToCheck(models,field); +0135 compStruct.(field).nElements=checkStuff(getElements(models,field),compStruct.(field).comparison); +0136 if printResults==true +0137 fprintf(['*** Comparison of ' overlapLabels{i} ':\n']); +0138 printList(models,compStruct.(field).comparison,compStruct.(field).nElements); +0139 fprintf('\n\n'); +0140 end +0141 end +0142 fprintf('*** Done \n\n') +0143 +0144 %% Compare models structure & function based on high-dimensional methods +0145 % Compare number of reactions in each subsystem in each model using a heatmap +0146 field = 'subSystems'; +0147 fprintf('\n Comparing subsystem utilization \n') +0148 if any(~cellfun(@(m) isfield(m,field),models)) +0149 fprintf('\nWARNING: At least one model does not contain the field "subSystems". \n') +0150 fprintf(' Skipping subsystem comparison. \n\n') +0151 else +0152 %% Flatten model subSystems field +0153 % Convert from cell array of cells to cell array of strings +0154 % NOTE: this function currently only recognizes one subSystem per reaction; +0155 % additional subSystems will be ignored! +0156 for i = 1:numel(models) +0157 if any(cellfun(@(x) iscell(x), models{i}.subSystems)); +0158 cells = cellfun(@iscell,models{i}.subSystems); +0159 models{i}.subSystems(cells) = cellfun(@(s) s{1}, models{i}.subSystems(cells), 'UniformOutput', false); +0160 end +0161 end +0162 [id,compMat] = compareModelField(models,field); +0163 compStruct.subsystems.ID = id; +0164 compStruct.subsystems.matrix = compMat; +0165 fprintf('*** Done \n\n') 0166 -0167 nrow = min([15,numel(compStruct.subsystems.ID)]); -0168 ncol = min([10,numel(compStruct.modelIDs)]); -0169 summaryArray = [{field}, compStruct.modelIDs(1:ncol)']; -0170 summaryArray = [summaryArray; [compStruct.subsystems.ID(1:nrow), ... -0171 arrayfun(@num2str,compStruct.subsystems.matrix(1:nrow,1:ncol),'UniformOutput',false)]]; -0172 -0173 charArray = []; -0174 for i = 1:size(summaryArray,2) -0175 charArray = [charArray, char(strcat(summaryArray(:,i),{' '}))]; -0176 end -0177 disp(charArray); -0178 if numel(compStruct.subsystems.ID) > 15 -0179 fprintf('...\n'); +0167 if printResults +0168 % This could use some cleaning up +0169 fprintf('*** Comparison of reaction subsystem populations:\n\n'); +0170 +0171 nrow = min([15,numel(compStruct.subsystems.ID)]); +0172 ncol = min([10,numel(compStruct.modelIDs)]); +0173 summaryArray = [{field}, compStruct.modelIDs(1:ncol)']; +0174 summaryArray = [summaryArray; [compStruct.subsystems.ID(1:nrow), ... +0175 arrayfun(@num2str,compStruct.subsystems.matrix(1:nrow,1:ncol),'UniformOutput',false)]]; +0176 +0177 charArray = []; +0178 for i = 1:size(summaryArray,2) +0179 charArray = [charArray, char(strcat(summaryArray(:,i),{' '}))]; 0180 end -0181 fprintf('\n\n'); -0182 end -0183 -0184 if plotResults==true -0185 % Plot all subsystems -0186 figure; -0187 plottingData = (compStruct.subsystems.matrix - mean(compStruct.subsystems.matrix,2))./mean(compStruct.subsystems.matrix,2); -0188 color_map = redblue(length(0:.01:2)); -0189 h = genHeatMap(plottingData',compStruct.subsystems.ID,compStruct.modelIDs,'both','euclidean',color_map,[-1,1]); -0190 title('Subsystem Coverage - all subsystems','FontSize',18,'FontWeight','bold') -0191 -0192 % Plot only subsystems with deviation from mean -0193 keepSubs = (sum(plottingData~=0,2) ~= 0); -0194 if sum(keepSubs) > 1 -0195 figure; -0196 h_small = genHeatMap(plottingData(keepSubs,:)',compStruct.subsystems.ID(keepSubs),... -0197 compStruct.modelIDs,'both','euclidean',color_map,[-1,1]); -0198 title('Subsystem Coverage','FontSize',18,'FontWeight','bold') -0199 -0200 % Plot enrichment in subsystems with deviation from mean -0201 figure; -0202 color_map_bw = [1 1 1;0 0 0]; -0203 h_enriched = genHeatMap(plottingData(keepSubs,:)',compStruct.subsystems.ID(keepSubs),... -0204 compStruct.modelIDs,'both','euclidean',color_map_bw,[-10^-4,10^-4]); -0205 title('Subsystem Enrichment','FontSize',18,'FontWeight','bold') -0206 end -0207 end -0208 -0209 end -0210 -0211 % Compare overall reaction structure across all models using a heatmap -0212 field = 'rxns'; -0213 fprintf('\n Comparing model reaction correlations \n') +0181 disp(charArray); +0182 if numel(compStruct.subsystems.ID) > 15 +0183 fprintf('...\n'); +0184 end +0185 fprintf('\n\n'); +0186 end +0187 +0188 if plotResults==true +0189 % Plot all subsystems +0190 figure; +0191 plottingData = (compStruct.subsystems.matrix - mean(compStruct.subsystems.matrix,2))./mean(compStruct.subsystems.matrix,2); +0192 color_map = redblue(length(0:.01:2)); +0193 h = genHeatMap(plottingData',compStruct.subsystems.ID,compStruct.modelIDs,'both','euclidean',color_map,[-1,1]); +0194 title('Subsystem Coverage - all subsystems','FontSize',18,'FontWeight','bold') +0195 +0196 % Plot only subsystems with deviation from mean +0197 keepSubs = (sum(plottingData~=0,2) ~= 0); +0198 if sum(keepSubs) > 1 +0199 figure; +0200 h_small = genHeatMap(plottingData(keepSubs,:)',compStruct.subsystems.ID(keepSubs),... +0201 compStruct.modelIDs,'both','euclidean',color_map,[-1,1]); +0202 title('Subsystem Coverage','FontSize',18,'FontWeight','bold') +0203 +0204 % Plot enrichment in subsystems with deviation from mean +0205 figure; +0206 color_map_bw = [1 1 1;0 0 0]; +0207 h_enriched = genHeatMap(plottingData(keepSubs,:)',compStruct.subsystems.ID(keepSubs),... +0208 compStruct.modelIDs,'both','euclidean',color_map_bw,[-10^-4,10^-4]); +0209 title('Subsystem Enrichment','FontSize',18,'FontWeight','bold') +0210 end +0211 end +0212 +0213 end 0214 -0215 % Create binary matrix of reactions -0216 [id,binary_matrix] = compareModelField(models,field); -0217 compStruct.reactions.IDs = id; -0218 compStruct.reactions.matrix = binary_matrix; -0219 -0220 % calculate hamming similarity (1 - Hamming distance) between models. This -0221 % is done in base MATLAB so the Statistics Toolbox is not required: for -0222 % binary columns the agreement fraction equals 1 minus the Hamming distance. -0223 B = double(binary_matrix); -0224 compStruct.structComp = (B'*B + (1-B)'*(1-B)) / size(B,1); -0225 fprintf('*** Done \n\n') -0226 if plotResults == true -0227 color_map = [ones(100,1) linspace(1,0,100)' linspace(1,0,100)']; -0228 figure; -0229 h = genHeatMap(compStruct.structComp,compStruct.modelIDs,compStruct.modelIDs,'both','euclidean',color_map); -0230 title('Structural Similarity','FontSize',18,'FontWeight','bold') -0231 end -0232 -0233 % Compare overall reaction structure across all models using a 3D tSNE (or -0234 % MDS) projection. Both require the Statistics and Machine Learning Toolbox; -0235 % if neither is available the projection is skipped and structCompMap is left -0236 % empty. -0237 rng(42) % For consistency -0238 fprintf('\n Comparing model reaction structures \n') -0239 proj_coords = []; -0240 axis_labels = {}; -0241 if exist('tsne','file') > 0 -0242 proj_coords = tsne(double(binary_matrix'),'Distance','hamming','NumDimensions',3); % 3D -0243 axis_labels = {'tSNE 1';'tSNE 2';'tSNE 3'}; -0244 elseif exist('mdscale','file') > 0 -0245 proj_coords = mdscale(pdist(double(binary_matrix'),'hamming'),3); -0246 axis_labels = {'MDS 1';'MDS 2';'MDS 3'}; -0247 else -0248 fprintf([' Skipping low-dimensional projection: requires tsne or ' ... -0249 'mdscale from the Statistics and Machine Learning Toolbox\n']); -0250 end -0251 compStruct.structCompMap = proj_coords; -0252 fprintf('*** Done \n\n') -0253 -0254 % plot structure comparison results -0255 if plotResults == true && ~isempty(proj_coords) -0256 figure; hold on; -0257 if ~isempty(groupVector) -0258 color_data = groupVector; -0259 if length(groupNames) <= 7 -0260 % "lines" colormap only has 7 unique colors -0261 color_palette = lines(length(groupNames)); -0262 else -0263 color_palette = parula(length(groupNames)); -0264 end -0265 colormap(color_palette); -0266 else -0267 color_data = 'k'; -0268 end -0269 scatter3(proj_coords(:,1),proj_coords(:,2),proj_coords(:,3),35,color_data,'filled'); -0270 view(135,25); % to make it obvious that it is a 3D plot -0271 xlabel(axis_labels{1}); ylabel(axis_labels{2}); zlabel(axis_labels{3}); -0272 set(gca,'FontSize',14,'LineWidth',1.25); -0273 title('Structural Comparison','FontSize',18,'FontWeight','bold') -0274 -0275 % add legend -0276 if ~isempty(groupVector) -0277 for i = 1:length(groupNames) -0278 h(i) = scatter3([],[],[],35,color_palette(i,:),'filled'); -0279 end -0280 legend(h,groupNames); -0281 end -0282 end -0283 -0284 % Compare model functions by assessing their capacity to perform tasks -0285 if funcCompare == true && ~isempty(taskFile) -0286 fprintf('\n Checking model performance on specified tasks. \n') -0287 taskStructure=parseTaskList(taskFile); -0288 for i = 1:numel(models) -0289 fprintf('\n Checking model # %.0f \n',i) -0290 taskReport{i} = checkTasks(models{i},[],false,false,false,taskStructure); -0291 end -0292 -0293 % Save results -0294 taskMatrix = zeros(length(taskReport{1}.ok),numel(taskReport)); -0295 for i = 1:numel(taskReport) -0296 taskMatrix(:,i) = taskReport{i}.ok; -0297 end -0298 compStruct.funcComp.matrix = taskMatrix; -0299 compStruct.funcComp.tasks = taskReport{1}.description; -0300 fprintf('*** Done \n\n') -0301 -0302 % Plot results -0303 if plotResults == true -0304 figure; -0305 color_map_bw = [1 1 1;0 0 0]; -0306 h_enriched = genHeatMap(taskMatrix,compStruct.modelIDs,... -0307 taskReport{1}.description,'both','euclidean',color_map_bw,[0,1]); -0308 title('Functional Comparison - All Tasks','FontSize',18,'FontWeight','bold') -0309 -0310 figure; -0311 color_map_bw = [1 1 1;0 0 0]; -0312 h_enriched = genHeatMap(taskMatrix(intersect(find(sum(taskMatrix,2)~=numel(models)),find(sum(taskMatrix,2)~=0)),:),... -0313 compStruct.modelIDs,taskReport{1}.description(intersect(find(sum(taskMatrix,2)~=numel(models)),find(sum(taskMatrix,2)~=0))),... -0314 'both','euclidean',color_map_bw,[0,1]); -0315 title('Functional Similarity','FontSize',18,'FontWeight','bold') -0316 end -0317 end -0318 -0319 end -0320 -0321 %% Additional Functions +0215 % Compare overall reaction structure across all models using a heatmap +0216 field = 'rxns'; +0217 fprintf('\n Comparing model reaction correlations \n') +0218 +0219 % Create binary matrix of reactions +0220 [id,binary_matrix] = compareModelField(models,field); +0221 compStruct.reactions.IDs = id; +0222 compStruct.reactions.matrix = binary_matrix; +0223 +0224 % calculate hamming similarity (1 - Hamming distance) between models. This +0225 % is done in base MATLAB so the Statistics Toolbox is not required: for +0226 % binary columns the agreement fraction equals 1 minus the Hamming distance. +0227 B = double(binary_matrix); +0228 compStruct.structComp = (B'*B + (1-B)'*(1-B)) / size(B,1); +0229 fprintf('*** Done \n\n') +0230 if plotResults == true +0231 color_map = [ones(100,1) linspace(1,0,100)' linspace(1,0,100)']; +0232 figure; +0233 h = genHeatMap(compStruct.structComp,compStruct.modelIDs,compStruct.modelIDs,'both','euclidean',color_map); +0234 title('Structural Similarity','FontSize',18,'FontWeight','bold') +0235 end +0236 +0237 % Compare overall reaction structure across all models using a 3D tSNE (or +0238 % MDS) projection. Both require the Statistics and Machine Learning Toolbox; +0239 % if neither is available the projection is skipped and structCompMap is left +0240 % empty. +0241 rng(42) % For consistency +0242 fprintf('\n Comparing model reaction structures \n') +0243 proj_coords = []; +0244 axis_labels = {}; +0245 if exist('tsne','file') > 0 +0246 proj_coords = tsne(double(binary_matrix'),'Distance','hamming','NumDimensions',3); % 3D +0247 axis_labels = {'tSNE 1';'tSNE 2';'tSNE 3'}; +0248 elseif exist('mdscale','file') > 0 +0249 proj_coords = mdscale(pdist(double(binary_matrix'),'hamming'),3); +0250 axis_labels = {'MDS 1';'MDS 2';'MDS 3'}; +0251 else +0252 fprintf([' Skipping low-dimensional projection: requires tsne or ' ... +0253 'mdscale from the Statistics and Machine Learning Toolbox\n']); +0254 end +0255 compStruct.structCompMap = proj_coords; +0256 fprintf('*** Done \n\n') +0257 +0258 % plot structure comparison results +0259 if plotResults == true && ~isempty(proj_coords) +0260 figure; hold on; +0261 if ~isempty(groupVector) +0262 color_data = groupVector; +0263 if length(groupNames) <= 7 +0264 % "lines" colormap only has 7 unique colors +0265 color_palette = lines(length(groupNames)); +0266 else +0267 color_palette = parula(length(groupNames)); +0268 end +0269 colormap(color_palette); +0270 else +0271 color_data = 'k'; +0272 end +0273 scatter3(proj_coords(:,1),proj_coords(:,2),proj_coords(:,3),35,color_data,'filled'); +0274 view(135,25); % to make it obvious that it is a 3D plot +0275 xlabel(axis_labels{1}); ylabel(axis_labels{2}); zlabel(axis_labels{3}); +0276 set(gca,'FontSize',14,'LineWidth',1.25); +0277 title('Structural Comparison','FontSize',18,'FontWeight','bold') +0278 +0279 % add legend +0280 if ~isempty(groupVector) +0281 for i = 1:length(groupNames) +0282 h(i) = scatter3([],[],[],35,color_palette(i,:),'filled'); +0283 end +0284 legend(h,groupNames); +0285 end +0286 end +0287 +0288 % Compare model functions by assessing their capacity to perform tasks +0289 if funcCompare == true && ~isempty(taskFile) +0290 fprintf('\n Checking model performance on specified tasks. \n') +0291 taskStructure=parseTaskList(taskFile); +0292 for i = 1:numel(models) +0293 fprintf('\n Checking model # %.0f \n',i) +0294 taskReport{i} = checkTasks(models{i},[],false,false,false,taskStructure); +0295 end +0296 +0297 % Save results +0298 taskMatrix = zeros(length(taskReport{1}.ok),numel(taskReport)); +0299 for i = 1:numel(taskReport) +0300 taskMatrix(:,i) = taskReport{i}.ok; +0301 end +0302 compStruct.funcComp.matrix = taskMatrix; +0303 compStruct.funcComp.tasks = taskReport{1}.description; +0304 fprintf('*** Done \n\n') +0305 +0306 % Plot results +0307 if plotResults == true +0308 figure; +0309 color_map_bw = [1 1 1;0 0 0]; +0310 h_enriched = genHeatMap(taskMatrix,compStruct.modelIDs,... +0311 taskReport{1}.description,'both','euclidean',color_map_bw,[0,1]); +0312 title('Functional Comparison - All Tasks','FontSize',18,'FontWeight','bold') +0313 +0314 figure; +0315 color_map_bw = [1 1 1;0 0 0]; +0316 h_enriched = genHeatMap(taskMatrix(intersect(find(sum(taskMatrix,2)~=numel(models)),find(sum(taskMatrix,2)~=0)),:),... +0317 compStruct.modelIDs,taskReport{1}.description(intersect(find(sum(taskMatrix,2)~=numel(models)),find(sum(taskMatrix,2)~=0))),... +0318 'both','euclidean',color_map_bw,[0,1]); +0319 title('Functional Similarity','FontSize',18,'FontWeight','bold') +0320 end +0321 end 0322 -0323 function [id,compMat] = compareModelField(models,field) -0324 % Generates a list of unique field entries and a matrix quantifying the -0325 % number of appearances of each field entry in each model +0323 end +0324 +0325 %% Additional Functions 0326 -0327 % get unique list of field entries -0328 hasfield = cellfun(@(m) isfield(m,field),models); -0329 id = cellfun(@(m) m.(field),models(hasfield),'UniformOutput',false); -0330 id = unique(vertcat(id{:})); -0331 -0332 % assemble matrix comparing frequency of each entry in each model -0333 compMat = zeros(numel(id),numel(models)); -0334 for i = 1:numel(models) -0335 [~,entryIndex] = ismember(models{i}.(field),id); % get index of each field entry in the unique id list -0336 compMat(:,i) = histcounts(entryIndex, 0.5:1:(numel(id)+0.5)); % determine the frequency at which each index appears -0337 end -0338 end -0339 -0340 function A=getElements(models,field) -0341 %Gathers the raw field content (e.g. rxns, eccodes, equ) for each model that -0342 %has that field, for use as the universe of elements in checkStuff. -0343 A={}; -0344 for i=1:numel(models) -0345 if isfield(models{i},field) -0346 A=[A;{models{i}.(field)}]; -0347 end -0348 end -0349 end -0350 -0351 function toCheck=getToCheck(models,field) -0352 %Get all the combinations that should be checked for overlap (including the -0353 %single ones) -0354 toCheckA=[]; -0355 I=find(cellfun(@checkField,models)); -0356 nI=numel(I); -0357 for i=nI:-1:1 -0358 combs=nchoosek(1:nI,i); -0359 toAdd=false(size(combs,1),nI); -0360 for j=1:size(combs,1) -0361 toAdd(j,combs(j,:))=true; -0362 end -0363 toCheckA=[toCheckA;toAdd]; -0364 end -0365 -0366 %If not all of the models have the required field -0367 toCheck=false(size(toCheckA,1),numel(models)); -0368 toCheck(:,I)=toCheckA; +0327 function [id,compMat] = compareModelField(models,field) +0328 % Generates a list of unique field entries and a matrix quantifying the +0329 % number of appearances of each field entry in each model +0330 +0331 % get unique list of field entries +0332 hasfield = cellfun(@(m) isfield(m,field),models); +0333 id = cellfun(@(m) m.(field),models(hasfield),'UniformOutput',false); +0334 id = unique(vertcat(id{:})); +0335 +0336 % assemble matrix comparing frequency of each entry in each model +0337 compMat = zeros(numel(id),numel(models)); +0338 for i = 1:numel(models) +0339 [~,entryIndex] = ismember(models{i}.(field),id); % get index of each field entry in the unique id list +0340 compMat(:,i) = histcounts(entryIndex, 0.5:1:(numel(id)+0.5)); % determine the frequency at which each index appears +0341 end +0342 end +0343 +0344 function A=getElements(models,field) +0345 %Gathers the raw field content (e.g. rxns, eccodes, equ) for each model that +0346 %has that field, for use as the universe of elements in checkStuff. +0347 A={}; +0348 for i=1:numel(models) +0349 if isfield(models{i},field) +0350 A=[A;{models{i}.(field)}]; +0351 end +0352 end +0353 end +0354 +0355 function toCheck=getToCheck(models,field) +0356 %Get all the combinations that should be checked for overlap (including the +0357 %single ones) +0358 toCheckA=[]; +0359 I=find(cellfun(@checkField,models)); +0360 nI=numel(I); +0361 for i=nI:-1:1 +0362 combs=nchoosek(1:nI,i); +0363 toAdd=false(size(combs,1),nI); +0364 for j=1:size(combs,1) +0365 toAdd(j,combs(j,:))=true; +0366 end +0367 toCheckA=[toCheckA;toAdd]; +0368 end 0369 -0370 %Ugly thing to get around parameters -0371 function I=checkField(A) -0372 I=isfield(A,field); -0373 end -0374 end -0375 -0376 function printList(models,toCheck,nElements) -0377 %To guess how many spaces that are needed to align -0378 firstLen=[]; -0379 for i=1:size(toCheck,1) -0380 label=[]; -0381 I=find(toCheck(i,:)); -0382 for j=1:numel(I) -0383 label=[label models{I(j)}.id '/']; -0384 end -0385 if i==1 -0386 firstLen=numel(label); -0387 end -0388 nSpaces=firstLen-numel(label); -0389 fprintf([label(1:end-1) ' ' repmat(sprintf(' '),1,nSpaces) num2str(nElements(i)) '\n']); -0390 end -0391 end -0392 -0393 function nElements=checkStuff(A,toCheck) -0394 %Now loop through the toCheck matrix, starting with the combination with -0395 %the most models. Only elements that were not in iteration n are considered -0396 %in iteration n+1. -0397 nElements=zeros(size(toCheck,1),1); -0398 alreadyChecked=[]; -0399 for i=1:size(toCheck,1) -0400 I=find(toCheck(i,:)); -0401 inCommon=setdiff(A{I(1)},alreadyChecked); -0402 for j=2:numel(I) -0403 inCommon=intersect(inCommon,A{I(j)}); -0404 end -0405 alreadyChecked=union(alreadyChecked,inCommon); -0406 nElements(i)=numel(inCommon); -0407 end -0408 end -0409 -0410 function h = genHeatMap(data,colnames,rownames,clust_dim,clust_dist,col_map,col_bounds,grid_color) -0411 %genHeatMap Generate a heatmap for a given matrix of data. -0412 % -0413 % Usage: -0414 % -0415 % genHeatMap(data,colnames,rownames,clust_dim,clust_dist,col_map,col_bounds,grid_color); +0370 %If not all of the models have the required field +0371 toCheck=false(size(toCheckA,1),numel(models)); +0372 toCheck(:,I)=toCheckA; +0373 +0374 %Ugly thing to get around parameters +0375 function I=checkField(A) +0376 I=isfield(A,field); +0377 end +0378 end +0379 +0380 function printList(models,toCheck,nElements) +0381 %To guess how many spaces that are needed to align +0382 firstLen=[]; +0383 for i=1:size(toCheck,1) +0384 label=[]; +0385 I=find(toCheck(i,:)); +0386 for j=1:numel(I) +0387 label=[label models{I(j)}.id '/']; +0388 end +0389 if i==1 +0390 firstLen=numel(label); +0391 end +0392 nSpaces=firstLen-numel(label); +0393 fprintf([label(1:end-1) ' ' repmat(sprintf(' '),1,nSpaces) num2str(nElements(i)) '\n']); +0394 end +0395 end +0396 +0397 function nElements=checkStuff(A,toCheck) +0398 %Now loop through the toCheck matrix, starting with the combination with +0399 %the most models. Only elements that were not in iteration n are considered +0400 %in iteration n+1. +0401 nElements=zeros(size(toCheck,1),1); +0402 alreadyChecked=[]; +0403 for i=1:size(toCheck,1) +0404 I=find(toCheck(i,:)); +0405 inCommon=setdiff(A{I(1)},alreadyChecked); +0406 for j=2:numel(I) +0407 inCommon=intersect(inCommon,A{I(j)}); +0408 end +0409 alreadyChecked=union(alreadyChecked,inCommon); +0410 nElements(i)=numel(inCommon); +0411 end +0412 end +0413 +0414 function h = genHeatMap(data,colnames,rownames,clust_dim,clust_dist,col_map,col_bounds,grid_color) +0415 %genHeatMap Generate a heatmap for a given matrix of data. 0416 % -0417 % Inputs: +0417 % Usage: 0418 % -0419 % data Numerical matrix. +0419 % genHeatMap(data,colnames,rownames,clust_dim,clust_dist,col_map,col_bounds,grid_color); 0420 % -0421 % colnames Cell array of data column names. +0421 % Inputs: 0422 % -0423 % rownames Cell array of data row names. +0423 % data Numerical matrix. 0424 % -0425 % clust_dim "none" - the data will be plotted as provided (DEFAULT) -0426 % "rows" - cluster/rearrange the rows based on distance -0427 % "cols" - cluster/rearrange the columns based on distance -0428 % "both" - cluster/rearrange rows and columns based on distance -0429 % -0430 % clust_dist Distance metric to be used for clustering, ignored if -0431 % clust_dim is "none". Options are the same as those for -0432 % distance in, e.g., PDIST ("euclidean", "hamming", etc.). -0433 % (DEFAULT = "euclidean") -0434 % -0435 % col_map Colormap, provided as string (e.g., "parula", "hot", "etc.") -0436 % or an Nx3 RGB matrix of N colors. -0437 % (DEFAULT = "hot") -0438 % -0439 % col_bounds A 2-element vector with min and max values, to manually set -0440 % the bounds of the colormap. -0441 % (DEFAULT = min/max of data). -0442 % -0443 % grid_color Color of the grid surrounding the heatmap cells. -0444 % (DEFAULT = "none") -0445 % -0446 % -0447 -0448 % handle input arguments -0449 if nargin < 4 || isempty(clust_dim) -0450 clust_dim = 'none'; -0451 elseif ~ismember(clust_dim,{'none','rows','cols','both'}) -0452 error('%s is not a valid CLUST_DIM option. Choose "none", "rows", "cols", or "both".',clust_dim); -0453 end -0454 if nargin < 5 || isempty(clust_dist) -0455 clust_dist = 'euclidean'; -0456 end -0457 if nargin < 6 || isempty(col_map) -0458 col_map = 'hot'; -0459 end -0460 if nargin < 7 || isempty(col_bounds) -0461 col_bounds = [min(data(:)),max(data(:))]; -0462 end -0463 if nargin < 8 -0464 grid_color = 'none'; -0465 end -0466 -0467 % perform hierarchical clustering to sort rows/columns (if specified). -0468 % Clustering needs the Statistics and Machine Learning Toolbox (linkage, -0469 % optimalleaforder, pdist); without it the heatmap is drawn unordered. -0470 linkage_method = 'average'; -0471 if ~strcmp(clust_dim,'none') && ~(exist('linkage','file') && ... -0472 exist('optimalleaforder','file') && exist('pdist','file')) -0473 fprintf([' Skipping heatmap clustering: requires the Statistics and ' ... -0474 'Machine Learning Toolbox\n']); -0475 clust_dim = 'none'; -0476 end -0477 if ismember(clust_dim,{'rows','both'}) -0478 L = linkage(data,linkage_method,clust_dist); -0479 row_ind = optimalleaforder(L,pdist(data,clust_dist)); -0480 else -0481 row_ind = 1:size(data,1); -0482 end -0483 % perform hierarchical clustering to sort columns (if specified) -0484 if ismember(clust_dim,{'cols','both'}) -0485 L = linkage(data',linkage_method,clust_dist); -0486 col_ind = optimalleaforder(L,pdist(data',clust_dist)); -0487 else -0488 col_ind = 1:size(data,2); -0489 end -0490 -0491 % reorder data matrix according to clustering results -0492 sortdata = data(row_ind,col_ind); -0493 sortrows = rownames(row_ind); -0494 sortcols = colnames(col_ind); +0425 % colnames Cell array of data column names. +0426 % +0427 % rownames Cell array of data row names. +0428 % +0429 % clust_dim "none" - the data will be plotted as provided (DEFAULT) +0430 % "rows" - cluster/rearrange the rows based on distance +0431 % "cols" - cluster/rearrange the columns based on distance +0432 % "both" - cluster/rearrange rows and columns based on distance +0433 % +0434 % clust_dist Distance metric to be used for clustering, ignored if +0435 % clust_dim is "none". Options are the same as those for +0436 % distance in pdist (for example "euclidean", "hamming" and +0437 % other metrics). +0438 % (DEFAULT = "euclidean") +0439 % +0440 % col_map Colormap, provided as string (for example "parula" or "hot") +0441 % or an Nx3 RGB matrix of N colors. +0442 % (DEFAULT = "hot") +0443 % +0444 % col_bounds A 2-element vector with min and max values, to manually set +0445 % the bounds of the colormap. +0446 % (DEFAULT = min/max of data). +0447 % +0448 % grid_color Color of the grid surrounding the heatmap cells. +0449 % (DEFAULT = "none") +0450 % +0451 % +0452 +0453 % handle input arguments +0454 if nargin < 4 || isempty(clust_dim) +0455 clust_dim = 'none'; +0456 elseif ~ismember(clust_dim,{'none','rows','cols','both'}) +0457 error('%s is not a valid CLUST_DIM option. Choose "none", "rows", "cols", or "both".',clust_dim); +0458 end +0459 if nargin < 5 || isempty(clust_dist) +0460 clust_dist = 'euclidean'; +0461 end +0462 if nargin < 6 || isempty(col_map) +0463 col_map = 'hot'; +0464 end +0465 if nargin < 7 || isempty(col_bounds) +0466 col_bounds = [min(data(:)),max(data(:))]; +0467 end +0468 if nargin < 8 +0469 grid_color = 'none'; +0470 end +0471 +0472 % perform hierarchical clustering to sort rows/columns (if specified). +0473 % Clustering needs the Statistics and Machine Learning Toolbox (linkage, +0474 % optimalleaforder, pdist); without it the heatmap is drawn unordered. +0475 linkage_method = 'average'; +0476 if ~strcmp(clust_dim,'none') && ~(exist('linkage','file') && ... +0477 exist('optimalleaforder','file') && exist('pdist','file')) +0478 fprintf([' Skipping heatmap clustering: requires the Statistics and ' ... +0479 'Machine Learning Toolbox\n']); +0480 clust_dim = 'none'; +0481 end +0482 if ismember(clust_dim,{'rows','both'}) +0483 L = linkage(data,linkage_method,clust_dist); +0484 row_ind = optimalleaforder(L,pdist(data,clust_dist)); +0485 else +0486 row_ind = 1:size(data,1); +0487 end +0488 % perform hierarchical clustering to sort columns (if specified) +0489 if ismember(clust_dim,{'cols','both'}) +0490 L = linkage(data',linkage_method,clust_dist); +0491 col_ind = optimalleaforder(L,pdist(data',clust_dist)); +0492 else +0493 col_ind = 1:size(data,2); +0494 end 0495 -0496 % check if data is square matrix with identical row and column names -0497 if (length(colnames) == length(rownames)) && all(strcmp(colnames,rownames)) -0498 % flip data so the diagonal is from upper left to lower right -0499 sortdata = fliplr(sortdata); -0500 sortcols = flipud(sortcols); -0501 end -0502 -0503 % pad data matrix with zeros (pcolor cuts off last row and column) -0504 sortdata(end+1,end+1) = 0; -0505 -0506 % generate pcolor plot -0507 a = axes; -0508 set(a,'YAxisLocation','Right','XTick',[],'YTick', (1:size(sortdata,1))+0.5,'YTickLabels',sortrows); -0509 set(a,'TickLength',[0 0],'XLim',[1 size(sortdata,2)],'YLim',[1 size(sortdata,1)]); -0510 hold on -0511 -0512 h = pcolor(sortdata); -0513 set(h,'EdgeColor',grid_color); -0514 set(gca,'XTick', (1:size(sortdata,2))+0.5); -0515 set(gca,'YTick', (1:size(sortdata,1))+0.5); -0516 set(gca,'XTickLabels',sortcols,'YTickLabels',sortrows); -0517 set(gca,'XTickLabelRotation',90); -0518 colormap(col_map); -0519 -0520 if ~isempty(col_bounds) -0521 caxis(col_bounds); -0522 end -0523 -0524 end -0525 -0526 -0527 function c = redblue(m) -0528 %REDBLUE Shades of red and blue color map -0529 % REDBLUE(M), is an M-by-3 matrix that defines a colormap. -0530 % The colors begin with bright blue, range through shades of -0531 % blue to white, and then through shades of red to bright red. -0532 % REDBLUE, by itself, is the same length as the current figure -0533 % colormap. If no figure exists, MATLAB creates one. -0534 % -0535 % For example, to reset the colormap of the current figure: -0536 % -0537 % colormap(redblue) -0538 % -0539 % See also HSV, GRAY, HOT, BONE, COPPER, PINK, FLAG, -0540 % COLORMAP, RGBPLOT. -0541 if nargin < 1, m = size(get(gcf,'colormap'),1); end -0542 -0543 if (mod(m,2) == 0) -0544 % From [0 0 1] to [1 1 1], then [1 1 1] to [1 0 0]; -0545 m1 = m*0.5; -0546 r = (0:m1-1)'/max(m1-1,1); -0547 g = r; -0548 r = [r; ones(m1,1)]; -0549 g = [g; flipud(g)]; -0550 b = flipud(r); -0551 else -0552 % From [0 0 1] to [1 1 1] to [1 0 0]; -0553 m1 = floor(m*0.5); -0554 r = (0:m1-1)'/max(m1,1); -0555 g = r; -0556 r = [r; ones(m1+1,1)]; -0557 g = [g; 1; flipud(g)]; -0558 b = flipud(r); -0559 end -0560 -0561 c = [r g b]; -0562 end +0496 % reorder data matrix according to clustering results +0497 sortdata = data(row_ind,col_ind); +0498 sortrows = rownames(row_ind); +0499 sortcols = colnames(col_ind); +0500 +0501 % check if data is square matrix with identical row and column names +0502 if (length(colnames) == length(rownames)) && all(strcmp(colnames,rownames)) +0503 % flip data so the diagonal is from upper left to lower right +0504 sortdata = fliplr(sortdata); +0505 sortcols = flipud(sortcols); +0506 end +0507 +0508 % pad data matrix with zeros (pcolor cuts off last row and column) +0509 sortdata(end+1,end+1) = 0; +0510 +0511 % generate pcolor plot +0512 a = axes; +0513 set(a,'YAxisLocation','Right','XTick',[],'YTick', (1:size(sortdata,1))+0.5,'YTickLabels',sortrows); +0514 set(a,'TickLength',[0 0],'XLim',[1 size(sortdata,2)],'YLim',[1 size(sortdata,1)]); +0515 hold on +0516 +0517 h = pcolor(sortdata); +0518 set(h,'EdgeColor',grid_color); +0519 set(gca,'XTick', (1:size(sortdata,2))+0.5); +0520 set(gca,'YTick', (1:size(sortdata,1))+0.5); +0521 set(gca,'XTickLabels',sortcols,'YTickLabels',sortrows); +0522 set(gca,'XTickLabelRotation',90); +0523 colormap(col_map); +0524 +0525 if ~isempty(col_bounds) +0526 caxis(col_bounds); +0527 end +0528 +0529 end +0530 +0531 +0532 function c = redblue(m) +0533 %REDBLUE Shades of red and blue color map +0534 % REDBLUE(M), is an M-by-3 matrix that defines a colormap. +0535 % The colors begin with bright blue, range through shades of +0536 % blue to white, and then through shades of red to bright red. +0537 % REDBLUE, by itself, is the same length as the current figure +0538 % colormap. If no figure exists, MATLAB creates one. +0539 % +0540 % For example, to reset the colormap of the current figure: +0541 % +0542 % colormap(redblue) +0543 % +0544 % See also HSV, GRAY, HOT, BONE, COPPER, PINK, FLAG, +0545 % COLORMAP, RGBPLOT. +0546 if nargin < 1, m = size(get(gcf,'colormap'),1); end +0547 +0548 if (mod(m,2) == 0) +0549 % From [0 0 1] to [1 1 1], then [1 1 1] to [1 0 0]; +0550 m1 = m*0.5; +0551 r = (0:m1-1)'/max(m1-1,1); +0552 g = r; +0553 r = [r; ones(m1,1)]; +0554 g = [g; flipud(g)]; +0555 b = flipud(r); +0556 else +0557 % From [0 0 1] to [1 1 1] to [1 0 0]; +0558 m1 = floor(m*0.5); +0559 r = (0:m1-1)'/max(m1,1); +0560 g = r; +0561 r = [r; ones(m1+1,1)]; +0562 g = [g; 1; flipud(g)]; +0563 b = flipud(r); +0564 end +0565 +0566 c = [r g b]; +0567 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/comparison/diffModels.html b/docs/html/comparison/diffModels.html index 74c441fd..12a9d1b1 100644 --- a/docs/html/comparison/diffModels.html +++ b/docs/html/comparison/diffModels.html @@ -29,20 +29,17 @@

    SYNOPSIS ^DESCRIPTION ^

     diffModels  Report the semantic differences between two models.
     
    - Compares two models entry by entry, keyed by identifier, and returns a report listing
    - every difference found. Reactions, metabolites and genes are matched by
    - id; for the ids present in both models the reaction stoichiometry, bounds,
    - objective coefficient, grRule, EC codes, and the metabolite formula,
    - charge and compartment are compared. This is the structured
    - added/removed/changed diff that compareMultipleModels (which reports
    - overlap counts, similarity and clustering across N models) does not
    - provide.
    + Compares two models entry by entry, keyed by identifier, and returns a
    + report listing every difference found. Reactions, metabolites and genes
    + are matched by id and reported when present in only one model. For the
    + ids present in both models, the reaction stoichiometry, bounds,
    + objective coefficient, grRule and EC codes, and the metabolite formula,
    + charge and compartment are compared.
     
    - grRules are compared for logical equality rather than as strings: each
    - rule is expanded to disjunctive normal form and the genes within each
    - isozyme, and the isozymes themselves, are sorted before comparison, so
    - "a and b" equals "b and a" and "(a or b)" equals "(b or a)". This is
    - stronger than a case/whitespace string match.
    + grRules are compared by logical content: each rule is expanded to
    + disjunctive normal form, and the genes within each isozyme and the
    + isozymes themselves are sorted before comparison, so "a and b" equals
    + "b and a" and "(a or b)" equals "(b or a)".
     
      Parameters
      ----------
    @@ -57,10 +54,11 @@ 

    DESCRIPTION ^DESCRIPTION ^compareMultipleModels : N models, set membership across seven identity - notions (ids, EC codes, metabolite names, equations), similarity - matrix, clustering and an optional task-based functional comparison -- - no values.

    + compareMultipleModels : overlap counts, similarity and clustering across + two or more models.

    CROSS-REFERENCE INFORMATION ^

    @@ -101,268 +98,265 @@

    SOURCE CODE ^
    0001 function report = diffModels(modelA, modelB, varargin)
     0002 % diffModels  Report the semantic differences between two models.
     0003 %
    -0004 % Compares two models entry by entry, keyed by identifier, and returns a report listing
    -0005 % every difference found. Reactions, metabolites and genes are matched by
    -0006 % id; for the ids present in both models the reaction stoichiometry, bounds,
    -0007 % objective coefficient, grRule, EC codes, and the metabolite formula,
    -0008 % charge and compartment are compared. This is the structured
    -0009 % added/removed/changed diff that compareMultipleModels (which reports
    -0010 % overlap counts, similarity and clustering across N models) does not
    -0011 % provide.
    -0012 %
    -0013 % grRules are compared for logical equality rather than as strings: each
    -0014 % rule is expanded to disjunctive normal form and the genes within each
    -0015 % isozyme, and the isozymes themselves, are sorted before comparison, so
    -0016 % "a and b" equals "b and a" and "(a or b)" equals "(b or a)". This is
    -0017 % stronger than a case/whitespace string match.
    -0018 %
    -0019 % Parameters
    -0020 % ----------
    -0021 % modelA : struct
    -0022 %     the first model.
    -0023 % modelB : struct
    -0024 %     the second model.
    -0025 %
    -0026 % Name-Value Arguments
    -0027 % --------------------
    -0028 % stoichTol : double
    -0029 %     absolute tolerance for stoichiometric coefficient differences
    -0030 %     (default 1e-9).
    -0031 % maxPerCategory : double
    -0032 %     the maximum number of differences reported per category before the
    -0033 %     rest are truncated with a single summary line (default 50).
    -0034 % printResults : logical
    -0035 %     print the report to the command window (default false).
    -0036 %
    -0037 % Returns
    -0038 % -------
    -0039 % report : struct
    -0040 %     difference report with fields:
    -0041 %
    -0042 %     - equal : true if no differences were found.
    -0043 %     - differences : cell array of human-readable difference strings, each
    -0044 %       naming the category and the entity affected.
    -0045 %     - rxnsOnlyInA / rxnsOnlyInB : reaction ids present in only one model.
    -0046 %     - metsOnlyInA / metsOnlyInB : metabolite ids present in only one model.
    -0047 %     - genesOnlyInA / genesOnlyInB : gene ids present in only one model.
    -0048 %
    -0049 % Usage: report = diffModels(modelA, modelB, ...)
    -0050 %
    -0051 % See also
    -0052 % --------
    -0053 % compareMultipleModels : N models, set membership across seven identity
    -0054 %     notions (ids, EC codes, metabolite names, equations), similarity
    -0055 %     matrix, clustering and an optional task-based functional comparison --
    -0056 %     no values.
    -0057 
    -0058 p = parseRAVENargs(varargin, {'stoichTol', 1e-9; 'maxPerCategory', 50; ...
    -0059     'printResults', false});
    -0060 stoichTol = p.stoichTol;
    -0061 maxPerCategory = p.maxPerCategory;
    -0062 printResults = p.printResults;
    +0004 % Compares two models entry by entry, keyed by identifier, and returns a
    +0005 % report listing every difference found. Reactions, metabolites and genes
    +0006 % are matched by id and reported when present in only one model. For the
    +0007 % ids present in both models, the reaction stoichiometry, bounds,
    +0008 % objective coefficient, grRule and EC codes, and the metabolite formula,
    +0009 % charge and compartment are compared.
    +0010 %
    +0011 % grRules are compared by logical content: each rule is expanded to
    +0012 % disjunctive normal form, and the genes within each isozyme and the
    +0013 % isozymes themselves are sorted before comparison, so "a and b" equals
    +0014 % "b and a" and "(a or b)" equals "(b or a)".
    +0015 %
    +0016 % Parameters
    +0017 % ----------
    +0018 % modelA : struct
    +0019 %     the first model.
    +0020 % modelB : struct
    +0021 %     the second model.
    +0022 %
    +0023 % Name-Value Arguments
    +0024 % --------------------
    +0025 % stoichTol : double
    +0026 %     absolute tolerance for stoichiometric coefficient differences
    +0027 %     (default 1e-9).
    +0028 % maxPerCategory : double
    +0029 %     maximum number of entries in report.differences per category. Further
    +0030 %     differences of that category are replaced by a single summary line
    +0031 %     (default 50).
    +0032 % printResults : logical
    +0033 %     true to print the report to the command window (default false).
    +0034 %
    +0035 % Returns
    +0036 % -------
    +0037 % report : struct
    +0038 %     difference report with fields:
    +0039 %
    +0040 %     - equal : true if no differences were found.
    +0041 %     - differences : cell array of human-readable difference strings, each
    +0042 %       naming the entity affected.
    +0043 %     - rxnsOnlyInA and rxnsOnlyInB : reaction ids present in only one
    +0044 %       model.
    +0045 %     - metsOnlyInA and metsOnlyInB : metabolite ids present in only one
    +0046 %       model.
    +0047 %     - genesOnlyInA and genesOnlyInB : gene ids present in only one model.
    +0048 %       Both are empty when either model lacks the genes field.
    +0049 %
    +0050 % See also
    +0051 % --------
    +0052 % compareMultipleModels : overlap counts, similarity and clustering across
    +0053 %     two or more models.
    +0054 
    +0055 p = parseRAVENargs(varargin, {'stoichTol', 1e-9; 'maxPerCategory', 50; ...
    +0056     'printResults', false});
    +0057 stoichTol = p.stoichTol;
    +0058 maxPerCategory = p.maxPerCategory;
    +0059 printResults = p.printResults;
    +0060 
    +0061 diffs = {};                 % collected difference strings
    +0062 counts = struct();          % per-category counters, for truncation
     0063 
    -0064 diffs = {};                 % collected difference strings
    -0065 counts = struct();          % per-category counters, for truncation
    -0066 
    -0067 report = struct();
    -0068 report.equal = true;
    -0069 report.differences = {};
    -0070 
    -0071 % --- Identifier sets --------------------------------------------------
    -0072 [report.rxnsOnlyInA, report.rxnsOnlyInB, commonRxns] = idSets(modelA.rxns, modelB.rxns);
    -0073 [report.metsOnlyInA, report.metsOnlyInB, commonMets] = idSets(modelA.mets, modelB.mets);
    -0074 if isfield(modelA,'genes') && isfield(modelB,'genes')
    -0075     [report.genesOnlyInA, report.genesOnlyInB] = idSets(modelA.genes, modelB.genes);
    -0076 else
    -0077     report.genesOnlyInA = {}; report.genesOnlyInB = {};
    -0078 end
    -0079 
    -0080 reportSet('reactions', report.rxnsOnlyInA, report.rxnsOnlyInB);
    -0081 reportSet('metabolites', report.metsOnlyInA, report.metsOnlyInB);
    -0082 reportSet('genes', report.genesOnlyInA, report.genesOnlyInB);
    -0083 
    -0084 % --- Reaction content, on the shared ids ------------------------------
    -0085 [~, ia] = ismember(commonRxns, modelA.rxns);
    -0086 [~, ib] = ismember(commonRxns, modelB.rxns);
    -0087 for k = 1:numel(commonRxns)
    -0088     rxn = commonRxns{k};
    -0089     ja = ia(k); jb = ib(k);
    -0090 
    -0091     % Stoichiometry: compare the metabolite id -> coefficient maps.
    -0092     [ma, ca] = rxnStoich(modelA, ja);
    -0093     [mb, cb] = rxnStoich(modelB, jb);
    -0094     if ~isequal(sort(ma), sort(mb))
    -0095         push('stoich', sprintf('%s: stoichiometry involves different metabolites', rxn));
    -0096     else
    -0097         [~, locb] = ismember(ma, mb);
    -0098         for m = 1:numel(ma)
    -0099             if abs(ca(m) - cb(locb(m))) > stoichTol
    -0100                 push('stoich', sprintf('%s: coef[%s] A=%g B=%g', rxn, ma{m}, ca(m), cb(locb(m))));
    -0101             end
    -0102         end
    -0103     end
    -0104 
    -0105     % Bounds.
    -0106     if modelA.lb(ja) ~= modelB.lb(jb) || modelA.ub(ja) ~= modelB.ub(jb)
    -0107         push('bounds', sprintf('%s: bounds A=[%g,%g] B=[%g,%g]', rxn, ...
    -0108             modelA.lb(ja), modelA.ub(ja), modelB.lb(jb), modelB.ub(jb)));
    -0109     end
    -0110 
    -0111     % Objective coefficient.
    -0112     if fieldAt(modelA,'c',ja,0) ~= fieldAt(modelB,'c',jb,0)
    -0113         push('objective', sprintf('%s: objective A=%g B=%g', rxn, ...
    -0114             fieldAt(modelA,'c',ja,0), fieldAt(modelB,'c',jb,0)));
    -0115     end
    -0116 
    -0117     % grRule, compared as logic rather than text.
    -0118     ga = canonicalGpr(cellAt(modelA,'grRules',ja));
    -0119     gb = canonicalGpr(cellAt(modelB,'grRules',jb));
    -0120     if ~strcmp(ga, gb)
    -0121         push('gpr', sprintf('%s: grRule A="%s" B="%s"', rxn, ...
    -0122             cellAt(modelA,'grRules',ja), cellAt(modelB,'grRules',jb)));
    -0123     end
    -0124 
    -0125     % EC codes (RAVEN's first-class annotation field).
    -0126     ea = cellAt(modelA,'eccodes',ja);
    -0127     eb = cellAt(modelB,'eccodes',jb);
    -0128     if ~strcmp(ea, eb)
    -0129         push('eccodes', sprintf('%s: eccodes A="%s" B="%s"', rxn, ea, eb));
    -0130     end
    -0131 end
    -0132 
    -0133 % --- Metabolite content, on the shared ids ----------------------------
    -0134 [~, ia] = ismember(commonMets, modelA.mets);
    -0135 [~, ib] = ismember(commonMets, modelB.mets);
    -0136 for k = 1:numel(commonMets)
    -0137     met = commonMets{k};
    -0138     ja = ia(k); jb = ib(k);
    -0139 
    -0140     fa = cellAt(modelA,'metFormulas',ja);
    -0141     fb = cellAt(modelB,'metFormulas',jb);
    -0142     if ~strcmp(fa, fb)
    -0143         push('formula', sprintf('%s: formula A="%s" B="%s"', met, fa, fb));
    -0144     end
    -0145 
    -0146     if ~isequaln(fieldAt(modelA,'metCharges',ja,NaN), fieldAt(modelB,'metCharges',jb,NaN))
    -0147         push('charge', sprintf('%s: charge A=%g B=%g', met, ...
    -0148             fieldAt(modelA,'metCharges',ja,NaN), fieldAt(modelB,'metCharges',jb,NaN)));
    -0149     end
    -0150 
    -0151     cpA = metComp(modelA, ja);
    -0152     cpB = metComp(modelB, jb);
    -0153     if ~strcmp(cpA, cpB)
    -0154         push('compartment', sprintf('%s: compartment A="%s" B="%s"', met, cpA, cpB));
    -0155     end
    -0156 end
    +0064 report = struct();
    +0065 report.equal = true;
    +0066 report.differences = {};
    +0067 
    +0068 % --- Identifier sets --------------------------------------------------
    +0069 [report.rxnsOnlyInA, report.rxnsOnlyInB, commonRxns] = idSets(modelA.rxns, modelB.rxns);
    +0070 [report.metsOnlyInA, report.metsOnlyInB, commonMets] = idSets(modelA.mets, modelB.mets);
    +0071 if isfield(modelA,'genes') && isfield(modelB,'genes')
    +0072     [report.genesOnlyInA, report.genesOnlyInB] = idSets(modelA.genes, modelB.genes);
    +0073 else
    +0074     report.genesOnlyInA = {}; report.genesOnlyInB = {};
    +0075 end
    +0076 
    +0077 reportSet('reactions', report.rxnsOnlyInA, report.rxnsOnlyInB);
    +0078 reportSet('metabolites', report.metsOnlyInA, report.metsOnlyInB);
    +0079 reportSet('genes', report.genesOnlyInA, report.genesOnlyInB);
    +0080 
    +0081 % --- Reaction content, on the shared ids ------------------------------
    +0082 [~, ia] = ismember(commonRxns, modelA.rxns);
    +0083 [~, ib] = ismember(commonRxns, modelB.rxns);
    +0084 for k = 1:numel(commonRxns)
    +0085     rxn = commonRxns{k};
    +0086     ja = ia(k); jb = ib(k);
    +0087 
    +0088     % Stoichiometry: compare the metabolite id -> coefficient maps.
    +0089     [ma, ca] = rxnStoich(modelA, ja);
    +0090     [mb, cb] = rxnStoich(modelB, jb);
    +0091     if ~isequal(sort(ma), sort(mb))
    +0092         push('stoich', sprintf('%s: stoichiometry involves different metabolites', rxn));
    +0093     else
    +0094         [~, locb] = ismember(ma, mb);
    +0095         for m = 1:numel(ma)
    +0096             if abs(ca(m) - cb(locb(m))) > stoichTol
    +0097                 push('stoich', sprintf('%s: coef[%s] A=%g B=%g', rxn, ma{m}, ca(m), cb(locb(m))));
    +0098             end
    +0099         end
    +0100     end
    +0101 
    +0102     % Bounds.
    +0103     if modelA.lb(ja) ~= modelB.lb(jb) || modelA.ub(ja) ~= modelB.ub(jb)
    +0104         push('bounds', sprintf('%s: bounds A=[%g,%g] B=[%g,%g]', rxn, ...
    +0105             modelA.lb(ja), modelA.ub(ja), modelB.lb(jb), modelB.ub(jb)));
    +0106     end
    +0107 
    +0108     % Objective coefficient.
    +0109     if fieldAt(modelA,'c',ja,0) ~= fieldAt(modelB,'c',jb,0)
    +0110         push('objective', sprintf('%s: objective A=%g B=%g', rxn, ...
    +0111             fieldAt(modelA,'c',ja,0), fieldAt(modelB,'c',jb,0)));
    +0112     end
    +0113 
    +0114     % grRule, compared as logic rather than text.
    +0115     ga = canonicalGpr(cellAt(modelA,'grRules',ja));
    +0116     gb = canonicalGpr(cellAt(modelB,'grRules',jb));
    +0117     if ~strcmp(ga, gb)
    +0118         push('gpr', sprintf('%s: grRule A="%s" B="%s"', rxn, ...
    +0119             cellAt(modelA,'grRules',ja), cellAt(modelB,'grRules',jb)));
    +0120     end
    +0121 
    +0122     % EC codes (RAVEN's first-class annotation field).
    +0123     ea = cellAt(modelA,'eccodes',ja);
    +0124     eb = cellAt(modelB,'eccodes',jb);
    +0125     if ~strcmp(ea, eb)
    +0126         push('eccodes', sprintf('%s: eccodes A="%s" B="%s"', rxn, ea, eb));
    +0127     end
    +0128 end
    +0129 
    +0130 % --- Metabolite content, on the shared ids ----------------------------
    +0131 [~, ia] = ismember(commonMets, modelA.mets);
    +0132 [~, ib] = ismember(commonMets, modelB.mets);
    +0133 for k = 1:numel(commonMets)
    +0134     met = commonMets{k};
    +0135     ja = ia(k); jb = ib(k);
    +0136 
    +0137     fa = cellAt(modelA,'metFormulas',ja);
    +0138     fb = cellAt(modelB,'metFormulas',jb);
    +0139     if ~strcmp(fa, fb)
    +0140         push('formula', sprintf('%s: formula A="%s" B="%s"', met, fa, fb));
    +0141     end
    +0142 
    +0143     if ~isequaln(fieldAt(modelA,'metCharges',ja,NaN), fieldAt(modelB,'metCharges',jb,NaN))
    +0144         push('charge', sprintf('%s: charge A=%g B=%g', met, ...
    +0145             fieldAt(modelA,'metCharges',ja,NaN), fieldAt(modelB,'metCharges',jb,NaN)));
    +0146     end
    +0147 
    +0148     cpA = metComp(modelA, ja);
    +0149     cpB = metComp(modelB, jb);
    +0150     if ~strcmp(cpA, cpB)
    +0151         push('compartment', sprintf('%s: compartment A="%s" B="%s"', met, cpA, cpB));
    +0152     end
    +0153 end
    +0154 
    +0155 report.equal = isempty(diffs);
    +0156 report.differences = diffs(:);
     0157 
    -0158 report.equal = isempty(diffs);
    -0159 report.differences = diffs(:);
    -0160 
    -0161 if printResults
    -0162     if report.equal
    -0163         fprintf('Models are semantically equal.\n');
    -0164     else
    -0165         fprintf('Models differ (%d differences):\n', numel(diffs));
    -0166         fprintf('  - %s\n', diffs{:});
    -0167     end
    -0168 end
    -0169 
    -0170     % --- nested helpers (share diffs / counts / maxPerCategory) --------
    -0171     function push(category, message)
    -0172         if ~isfield(counts, category)
    -0173             counts.(category) = 0;
    -0174         end
    -0175         counts.(category) = counts.(category) + 1;
    -0176         if counts.(category) <= maxPerCategory
    -0177             diffs{end+1,1} = message; %#ok<AGROW>
    -0178         elseif counts.(category) == maxPerCategory + 1
    -0179             diffs{end+1,1} = sprintf('... (%s category truncated at %d entries)', ...
    -0180                 category, maxPerCategory); %#ok<AGROW>
    -0181         end
    -0182     end
    -0183 
    -0184     function reportSet(label, onlyA, onlyB)
    -0185         if ~isempty(onlyA)
    -0186             push([label '_only_in_a'], sprintf('%d %s only in A: %s', ...
    -0187                 numel(onlyA), label, joinTruncated(onlyA)));
    -0188         end
    -0189         if ~isempty(onlyB)
    -0190             push([label '_only_in_b'], sprintf('%d %s only in B: %s', ...
    -0191                 numel(onlyB), label, joinTruncated(onlyB)));
    -0192         end
    -0193     end
    -0194 end
    -0195 
    -0196 % --- local helpers ----------------------------------------------------
    -0197 function [onlyA, onlyB, common] = idSets(a, b)
    -0198 onlyA = setdiff(a, b);
    -0199 onlyB = setdiff(b, a);
    -0200 common = sort(intersect(a, b));
    -0201 onlyA = sort(onlyA(:))'; onlyB = sort(onlyB(:))';
    -0202 onlyA = onlyA(:); onlyB = onlyB(:);
    -0203 end
    -0204 
    -0205 function [mets, coeffs] = rxnStoich(model, j)
    -0206 nz = find(model.S(:, j));
    -0207 mets = model.mets(nz);
    -0208 mets = mets(:)';
    -0209 coeffs = full(model.S(nz, j))';
    -0210 end
    -0211 
    -0212 function v = fieldAt(model, field, j, default)
    -0213 if isfield(model, field) && numel(model.(field)) >= j
    -0214     v = model.(field)(j);
    -0215     if iscell(v), v = v{1}; end
    -0216     if isempty(v), v = default; end
    -0217 else
    -0218     v = default;
    -0219 end
    -0220 end
    -0221 
    -0222 function s = cellAt(model, field, j)
    -0223 % A char scalar from a cell-array field, '' when absent or empty.
    -0224 if isfield(model, field) && numel(model.(field)) >= j
    -0225     s = model.(field){j};
    -0226     if isempty(s), s = ''; end
    -0227 else
    -0228     s = '';
    -0229 end
    -0230 end
    -0231 
    -0232 function cp = metComp(model, j)
    -0233 if isfield(model, 'metComps') && isfield(model, 'comps')
    -0234     cp = model.comps{model.metComps(j)};
    -0235 else
    -0236     cp = '';
    -0237 end
    -0238 end
    -0239 
    -0240 function s = canonicalGpr(rule)
    -0241 % A canonical string for a grRule under which isozymes and their subunits
    -0242 % are order-insensitive. Empty rule -> ''.
    -0243 if isempty(rule)
    -0244     s = '';
    -0245     return;
    -0246 end
    -0247 clauses = grRuleToDNF(rule);
    -0248 parts = cell(1, numel(clauses));
    -0249 for i = 1:numel(clauses)
    -0250     genes = sort(unique(clauses{i}));
    -0251     parts{i} = strjoin(genes(:)', ' and ');
    +0158 if printResults
    +0159     if report.equal
    +0160         fprintf('Models are semantically equal.\n');
    +0161     else
    +0162         fprintf('Models differ (%d differences):\n', numel(diffs));
    +0163         fprintf('  - %s\n', diffs{:});
    +0164     end
    +0165 end
    +0166 
    +0167     % --- nested helpers (share diffs / counts / maxPerCategory) --------
    +0168     function push(category, message)
    +0169         if ~isfield(counts, category)
    +0170             counts.(category) = 0;
    +0171         end
    +0172         counts.(category) = counts.(category) + 1;
    +0173         if counts.(category) <= maxPerCategory
    +0174             diffs{end+1,1} = message; %#ok<AGROW>
    +0175         elseif counts.(category) == maxPerCategory + 1
    +0176             diffs{end+1,1} = sprintf('... (%s category truncated at %d entries)', ...
    +0177                 category, maxPerCategory); %#ok<AGROW>
    +0178         end
    +0179     end
    +0180 
    +0181     function reportSet(label, onlyA, onlyB)
    +0182         if ~isempty(onlyA)
    +0183             push([label '_only_in_a'], sprintf('%d %s only in A: %s', ...
    +0184                 numel(onlyA), label, joinTruncated(onlyA)));
    +0185         end
    +0186         if ~isempty(onlyB)
    +0187             push([label '_only_in_b'], sprintf('%d %s only in B: %s', ...
    +0188                 numel(onlyB), label, joinTruncated(onlyB)));
    +0189         end
    +0190     end
    +0191 end
    +0192 
    +0193 % --- local helpers ----------------------------------------------------
    +0194 function [onlyA, onlyB, common] = idSets(a, b)
    +0195 onlyA = setdiff(a, b);
    +0196 onlyB = setdiff(b, a);
    +0197 common = sort(intersect(a, b));
    +0198 onlyA = sort(onlyA(:))'; onlyB = sort(onlyB(:))';
    +0199 onlyA = onlyA(:); onlyB = onlyB(:);
    +0200 end
    +0201 
    +0202 function [mets, coeffs] = rxnStoich(model, j)
    +0203 nz = find(model.S(:, j));
    +0204 mets = model.mets(nz);
    +0205 mets = mets(:)';
    +0206 coeffs = full(model.S(nz, j))';
    +0207 end
    +0208 
    +0209 function v = fieldAt(model, field, j, default)
    +0210 if isfield(model, field) && numel(model.(field)) >= j
    +0211     v = model.(field)(j);
    +0212     if iscell(v), v = v{1}; end
    +0213     if isempty(v), v = default; end
    +0214 else
    +0215     v = default;
    +0216 end
    +0217 end
    +0218 
    +0219 function s = cellAt(model, field, j)
    +0220 % A char scalar from a cell-array field, '' when absent or empty.
    +0221 if isfield(model, field) && numel(model.(field)) >= j
    +0222     s = model.(field){j};
    +0223     if isempty(s), s = ''; end
    +0224 else
    +0225     s = '';
    +0226 end
    +0227 end
    +0228 
    +0229 function cp = metComp(model, j)
    +0230 if isfield(model, 'metComps') && isfield(model, 'comps')
    +0231     cp = model.comps{model.metComps(j)};
    +0232 else
    +0233     cp = '';
    +0234 end
    +0235 end
    +0236 
    +0237 function s = canonicalGpr(rule)
    +0238 % A canonical string for a grRule under which isozymes and their subunits
    +0239 % are order-insensitive. Empty rule -> ''.
    +0240 if isempty(rule)
    +0241     s = '';
    +0242     return;
    +0243 end
    +0244 clauses = grRuleToDNF(rule);
    +0245 parts = cell(1, numel(clauses));
    +0246 for i = 1:numel(clauses)
    +0247     genes = sort(unique(clauses{i}));
    +0248     parts{i} = strjoin(genes(:)', ' and ');
    +0249 end
    +0250 parts = sort(parts);
    +0251 s = strjoin(parts, ' or ');
     0252 end
    -0253 parts = sort(parts);
    -0254 s = strjoin(parts, ' or ');
    -0255 end
    -0256 
    -0257 function s = joinTruncated(ids)
    -0258 % Join up to the first 10 ids, appending a count of the remainder.
    -0259 ids = ids(:)';
    -0260 if numel(ids) > 10
    -0261     s = [strjoin(ids(1:10), ', ') sprintf(', ... (%d more)', numel(ids) - 10)];
    -0262 else
    -0263     s = strjoin(ids, ', ');
    -0264 end
    -0265 end
    +0253 +0254 function s = joinTruncated(ids) +0255 % Join up to the first 10 ids, appending a count of the remainder. +0256 ids = ids(:)'; +0257 if numel(ids) > 10 +0258 s = [strjoin(ids(1:10), ', ') sprintf(', ... (%d more)', numel(ids) - 10)]; +0259 else +0260 s = strjoin(ids, ', '); +0261 end +0262 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/conditions/applyCondition.html b/docs/html/conditions/applyCondition.html index 0ea72f4d..c9e9cb4c 100644 --- a/docs/html/conditions/applyCondition.html +++ b/docs/html/conditions/applyCondition.html @@ -29,28 +29,39 @@

    SYNOPSIS ^DESCRIPTION ^

     applyCondition  Apply a deterministic condition to a model.
     
    - Apply a deterministic "condition" to a model: a prelude that resets
    - exchange bounds, optional metabolite removals + automatic charge
    - rebalancing of a pseudoreaction, optional biomass-stoichiometry delta,
    - and a per-reaction bounds diff. The schema is intentionally narrow so a
    - condition can be reviewed as data.
    + The steps of a condition are applied in this order: a prelude that resets
    + exchange bounds, metabolite removals from a pseudoreaction with automatic
    + charge rebalancing, a change of biomass stoichiometry, and per-reaction
    + bound changes. A final check compares the number of uptake bounds with an
    + expected count. The schema covers only these steps, so a condition can be
    + reviewed as data.
     
    - The same schema works for any GEM that keeps its condition presets as
    - data rather than as code. Project-specific extensions (e.g. yeast-GEM's amino_acid_ratio step that
    - rewrites a protein pseudoreaction's stoichiometry from a side-car TSV)
    - are handled by the caller before / after this function — kept
    - upstream-narrow on purpose.
    + The schema applies to any GEM whose condition presets are kept as data.
    + Project-specific extensions, for example yeast-GEM's amino_acid_ratio
    + step that rewrites the stoichiometry of a protein pseudoreaction from a
    + separate TSV file, are applied by the caller before or after this
    + function.
     
      Parameters
      ----------
      model : struct
          RAVEN model struct.
      condition : char or struct
    -     Either a path to a YAML condition file or a struct already produced
    -     by parseYAML. The expected schema (all keys optional):
    +     Path to a YAML condition file, or a struct as produced by parseYAML.
    +     The schema is as follows (all keys optional). reset_exchanges is
    +     passed as the reaction type to getExchangeRxns; the lower bound of
    +     the matching reactions is set to 0 and the upper bound to 1000.
    +     charge_balance_met receives the coefficient that makes the charge of
    +     rxn_id zero, which requires the metCharges field; a participant of
    +     rxn_id other than charge_balance_met with a NaN charge raises
    +     applyCondition:unknownCharge. add entries are
    +     added to the existing coefficients. A bounds entry whose rxn is not
    +     in the model is skipped with a warning. expected_uptake_count is
    +     compared with the number of bounds entries with lb of -1000, and a
    +     warning is issued when they differ.
     
              prelude:
    -           reset_exchanges: out      % truthy -> reset all
    +           reset_exchanges: out
     
              cofactor_pseudoreaction:
                rxn_id: r_4598
    @@ -75,7 +86,7 @@ 

    DESCRIPTION ^SOURCE CODE ^
    0001 function model = applyCondition(model, condition)
     0002 % applyCondition  Apply a deterministic condition to a model.
     0003 %
    -0004 % Apply a deterministic "condition" to a model: a prelude that resets
    -0005 % exchange bounds, optional metabolite removals + automatic charge
    -0006 % rebalancing of a pseudoreaction, optional biomass-stoichiometry delta,
    -0007 % and a per-reaction bounds diff. The schema is intentionally narrow so a
    -0008 % condition can be reviewed as data.
    -0009 %
    -0010 % The same schema works for any GEM that keeps its condition presets as
    -0011 % data rather than as code. Project-specific extensions (e.g. yeast-GEM's amino_acid_ratio step that
    -0012 % rewrites a protein pseudoreaction's stoichiometry from a side-car TSV)
    -0013 % are handled by the caller before / after this function — kept
    -0014 % upstream-narrow on purpose.
    -0015 %
    -0016 % Parameters
    -0017 % ----------
    -0018 % model : struct
    -0019 %     RAVEN model struct.
    -0020 % condition : char or struct
    -0021 %     Either a path to a YAML condition file or a struct already produced
    -0022 %     by parseYAML. The expected schema (all keys optional):
    -0023 %
    -0024 %         prelude:
    -0025 %           reset_exchanges: out      % truthy -> reset all
    -0026 %
    -0027 %         cofactor_pseudoreaction:
    -0028 %           rxn_id: r_4598
    -0029 %           remove_mets:
    -0030 %             - { met: s_3714 }
    -0031 %           charge_balance_met: s_0794
    -0032 %
    -0033 %         biomass_stoichiometry_delta:
    -0034 %           rxn_id: r_4041
    -0035 %           add:
    -0036 %             - { met: s_0689, coef:  0.08 }
    -0037 %             - { met: s_0687, coef: -0.08 }
    -0038 %             - { met: s_0794, coef: -0.16 }
    -0039 %
    -0040 %         bounds:
    -0041 %           - { rxn: r_1654, lb: -1000 }
    -0042 %           - { rxn: r_1992, lb: 0 }
    -0043 %           - { rxn: r_1663, lb: 0, ub: 0 }
    -0044 %
    -0045 %         expected_uptake_count: 15
    -0046 %
    -0047 % Returns
    -0048 % -------
    -0049 % model : struct
    -0050 %     Modified model.
    -0051 %
    -0052 % Examples
    -0053 % --------
    -0054 %     model = applyCondition(model, 'data/conditions/anaerobic.yml');
    -0055 %     model = applyCondition(model, parseYAML('data/conditions/anaerobic.yml'));
    -0056 %
    -0057 % See also
    -0058 % --------
    -0059 % parseYAML
    -0060 
    -0061 if ischar(condition) || isstring(condition)
    -0062     cond = parseYAML(char(condition));
    -0063 elseif isstruct(condition)
    -0064     cond = condition;
    -0065 else
    -0066     error('applyCondition:invalidCondition', ...
    -0067         'condition must be a YAML file path or a struct.');
    -0068 end
    -0069 
    -0070 % --- Step 1: prelude ---------------------------------------------------
    -0071 if isfield(cond, 'prelude') && isfield(cond.prelude, 'reset_exchanges')
    -0072     [~, exchangeRxns] = getExchangeRxns(model, cond.prelude.reset_exchanges);
    -0073     model.lb(exchangeRxns) = 0;
    -0074     model.ub(exchangeRxns) = 1000;
    -0075 end
    -0076 
    -0077 % --- Step 2: cofactor pseudoreaction edits ----------------------------
    -0078 if isfield(cond, 'cofactor_pseudoreaction')
    -0079     cp = cond.cofactor_pseudoreaction;
    -0080     cofacIdx = getIndexes(model, cp.rxn_id, 'rxns');
    -0081     if isfield(cp, 'remove_mets')
    -0082         for i = 1:numel(cp.remove_mets)
    -0083             metIdx = getIndexes(model, cp.remove_mets{i}.met, 'mets');
    -0084             model.S(metIdx, cofacIdx) = 0;
    -0085         end
    -0086     end
    -0087     if isfield(cp, 'charge_balance_met')
    -0088         balanceIdx = find(strcmp(model.mets, cp.charge_balance_met));
    -0089         model.S(balanceIdx, cofacIdx) = 0;
    -0090         % Only the participating metabolites may be summed: S is sparse and
    -0091         % 0*NaN is NaN, while 'omitnan' would treat an unset charge as
    -0092         % neutral and silently write the wrong balancing coefficient.
    -0093         metIdx = find(model.S(:, cofacIdx));
    -0094         if ~isfield(model, 'metCharges')
    -0095             error('applyCondition:noCharges', ...
    -0096                 ['Cannot charge balance %s: the model has no metCharges ' ...
    -0097                  'field.'], cp.rxn_id);
    -0098         end
    -0099         unknown = metIdx(isnan(model.metCharges(metIdx)));
    -0100         if ~isempty(unknown)
    -0101             error('applyCondition:unknownCharge', ...
    -0102                 ['Cannot charge balance %s: the following metabolites have ' ...
    -0103                  'no charge, so the charge balance is unknown rather than ' ...
    -0104                  'zero:\n\t%s'], cp.rxn_id, strjoin(model.mets(unknown), ', '));
    -0105         end
    -0106         model.S(balanceIdx, cofacIdx) = ...
    -0107             -sum(full(model.S(metIdx, cofacIdx)) .* model.metCharges(metIdx));
    -0108     end
    -0109 end
    -0110 
    -0111 % --- Step 3: biomass stoichiometry delta ------------------------------
    -0112 if isfield(cond, 'biomass_stoichiometry_delta')
    -0113     delta = cond.biomass_stoichiometry_delta;
    -0114     bioIdx = getIndexes(model, delta.rxn_id, 'rxns');
    -0115     if isfield(delta, 'add')
    -0116         for i = 1:numel(delta.add)
    -0117             entry = delta.add{i};
    -0118             metIdx = getIndexes(model, entry.met, 'mets');
    -0119             model.S(metIdx, bioIdx) = full(model.S(metIdx, bioIdx)) + entry.coef;
    -0120         end
    -0121     end
    -0122 end
    -0123 
    -0124 % --- Step 4: bounds ---------------------------------------------------
    -0125 nUptake = 0;
    -0126 if isfield(cond, 'bounds')
    -0127     for i = 1:numel(cond.bounds)
    -0128         b = cond.bounds{i};
    -0129         rxnIdx = find(strcmp(model.rxns, b.rxn));
    -0130         if isempty(rxnIdx)
    -0131             warning('applyCondition:missingRxn', ...
    -0132                 'Reaction %s not found in model; skipping.', b.rxn);
    -0133             continue;
    -0134         end
    -0135         if isfield(b, 'lb')
    -0136             model.lb(rxnIdx) = b.lb;
    -0137             if b.lb == -1000
    -0138                 nUptake = nUptake + 1;
    -0139             end
    -0140         end
    -0141         if isfield(b, 'ub')
    -0142             model.ub(rxnIdx) = b.ub;
    -0143         end
    -0144     end
    -0145 end
    -0146 
    -0147 % --- Step 5: uptake sanity check --------------------------------------
    -0148 if isfield(cond, 'expected_uptake_count')
    -0149     if nUptake ~= cond.expected_uptake_count
    -0150         warning('applyCondition:uptakeCountMismatch', ...
    -0151             'Expected %d uptake reactions, applied %d. Some may be missing from the model.', ...
    -0152             cond.expected_uptake_count, nUptake);
    -0153     end
    -0154 end
    -0155 
    -0156 end

    +0004 % The steps of a condition are applied in this order: a prelude that resets +0005 % exchange bounds, metabolite removals from a pseudoreaction with automatic +0006 % charge rebalancing, a change of biomass stoichiometry, and per-reaction +0007 % bound changes. A final check compares the number of uptake bounds with an +0008 % expected count. The schema covers only these steps, so a condition can be +0009 % reviewed as data. +0010 % +0011 % The schema applies to any GEM whose condition presets are kept as data. +0012 % Project-specific extensions, for example yeast-GEM's amino_acid_ratio +0013 % step that rewrites the stoichiometry of a protein pseudoreaction from a +0014 % separate TSV file, are applied by the caller before or after this +0015 % function. +0016 % +0017 % Parameters +0018 % ---------- +0019 % model : struct +0020 % RAVEN model struct. +0021 % condition : char or struct +0022 % Path to a YAML condition file, or a struct as produced by parseYAML. +0023 % The schema is as follows (all keys optional). reset_exchanges is +0024 % passed as the reaction type to getExchangeRxns; the lower bound of +0025 % the matching reactions is set to 0 and the upper bound to 1000. +0026 % charge_balance_met receives the coefficient that makes the charge of +0027 % rxn_id zero, which requires the metCharges field; a participant of +0028 % rxn_id other than charge_balance_met with a NaN charge raises +0029 % applyCondition:unknownCharge. add entries are +0030 % added to the existing coefficients. A bounds entry whose rxn is not +0031 % in the model is skipped with a warning. expected_uptake_count is +0032 % compared with the number of bounds entries with lb of -1000, and a +0033 % warning is issued when they differ. +0034 % +0035 % prelude: +0036 % reset_exchanges: out +0037 % +0038 % cofactor_pseudoreaction: +0039 % rxn_id: r_4598 +0040 % remove_mets: +0041 % - { met: s_3714 } +0042 % charge_balance_met: s_0794 +0043 % +0044 % biomass_stoichiometry_delta: +0045 % rxn_id: r_4041 +0046 % add: +0047 % - { met: s_0689, coef: 0.08 } +0048 % - { met: s_0687, coef: -0.08 } +0049 % - { met: s_0794, coef: -0.16 } +0050 % +0051 % bounds: +0052 % - { rxn: r_1654, lb: -1000 } +0053 % - { rxn: r_1992, lb: 0 } +0054 % - { rxn: r_1663, lb: 0, ub: 0 } +0055 % +0056 % expected_uptake_count: 15 +0057 % +0058 % Returns +0059 % ------- +0060 % model : struct +0061 % Model with the condition applied. +0062 % +0063 % Examples +0064 % -------- +0065 % model = applyCondition(model, 'data/conditions/anaerobic.yml'); +0066 % model = applyCondition(model, parseYAML('data/conditions/anaerobic.yml')); +0067 % +0068 % See also +0069 % -------- +0070 % parseYAML +0071 +0072 if ischar(condition) || isstring(condition) +0073 cond = parseYAML(char(condition)); +0074 elseif isstruct(condition) +0075 cond = condition; +0076 else +0077 error('applyCondition:invalidCondition', ... +0078 'condition must be a YAML file path or a struct.'); +0079 end +0080 +0081 % --- Step 1: prelude --------------------------------------------------- +0082 if isfield(cond, 'prelude') && isfield(cond.prelude, 'reset_exchanges') +0083 [~, exchangeRxns] = getExchangeRxns(model, cond.prelude.reset_exchanges); +0084 model.lb(exchangeRxns) = 0; +0085 model.ub(exchangeRxns) = 1000; +0086 end +0087 +0088 % --- Step 2: cofactor pseudoreaction edits ---------------------------- +0089 if isfield(cond, 'cofactor_pseudoreaction') +0090 cp = cond.cofactor_pseudoreaction; +0091 cofacIdx = getIndexes(model, cp.rxn_id, 'rxns'); +0092 if isfield(cp, 'remove_mets') +0093 for i = 1:numel(cp.remove_mets) +0094 metIdx = getIndexes(model, cp.remove_mets{i}.met, 'mets'); +0095 model.S(metIdx, cofacIdx) = 0; +0096 end +0097 end +0098 if isfield(cp, 'charge_balance_met') +0099 balanceIdx = find(strcmp(model.mets, cp.charge_balance_met)); +0100 model.S(balanceIdx, cofacIdx) = 0; +0101 % Only the participating metabolites may be summed: S is sparse and +0102 % 0*NaN is NaN, while 'omitnan' would treat an unset charge as +0103 % neutral and silently write the wrong balancing coefficient. +0104 metIdx = find(model.S(:, cofacIdx)); +0105 if ~isfield(model, 'metCharges') +0106 error('applyCondition:noCharges', ... +0107 ['Cannot charge balance %s: the model has no metCharges ' ... +0108 'field.'], cp.rxn_id); +0109 end +0110 unknown = metIdx(isnan(model.metCharges(metIdx))); +0111 if ~isempty(unknown) +0112 error('applyCondition:unknownCharge', ... +0113 ['Cannot charge balance %s: the following metabolites have ' ... +0114 'no charge, so the charge balance is unknown rather than ' ... +0115 'zero:\n\t%s'], cp.rxn_id, strjoin(model.mets(unknown), ', ')); +0116 end +0117 model.S(balanceIdx, cofacIdx) = ... +0118 -sum(full(model.S(metIdx, cofacIdx)) .* model.metCharges(metIdx)); +0119 end +0120 end +0121 +0122 % --- Step 3: biomass stoichiometry delta ------------------------------ +0123 if isfield(cond, 'biomass_stoichiometry_delta') +0124 delta = cond.biomass_stoichiometry_delta; +0125 bioIdx = getIndexes(model, delta.rxn_id, 'rxns'); +0126 if isfield(delta, 'add') +0127 for i = 1:numel(delta.add) +0128 entry = delta.add{i}; +0129 metIdx = getIndexes(model, entry.met, 'mets'); +0130 model.S(metIdx, bioIdx) = full(model.S(metIdx, bioIdx)) + entry.coef; +0131 end +0132 end +0133 end +0134 +0135 % --- Step 4: bounds --------------------------------------------------- +0136 nUptake = 0; +0137 if isfield(cond, 'bounds') +0138 for i = 1:numel(cond.bounds) +0139 b = cond.bounds{i}; +0140 rxnIdx = find(strcmp(model.rxns, b.rxn)); +0141 if isempty(rxnIdx) +0142 warning('applyCondition:missingRxn', ... +0143 'Reaction %s not found in model; skipping.', b.rxn); +0144 continue; +0145 end +0146 if isfield(b, 'lb') +0147 model.lb(rxnIdx) = b.lb; +0148 if b.lb == -1000 +0149 nUptake = nUptake + 1; +0150 end +0151 end +0152 if isfield(b, 'ub') +0153 model.ub(rxnIdx) = b.ub; +0154 end +0155 end +0156 end +0157 +0158 % --- Step 5: uptake sanity check -------------------------------------- +0159 if isfield(cond, 'expected_uptake_count') +0160 if nUptake ~= cond.expected_uptake_count +0161 warning('applyCondition:uptakeCountMismatch', ... +0162 'Expected %d uptake reactions, applied %d. Some may be missing from the model.', ... +0163 cond.expected_uptake_count, nUptake); +0164 end +0165 end +0166 +0167 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/conversion/addIdentifierPrefix.html b/docs/html/conversion/addIdentifierPrefix.html index 4c7b682d..307cb0cc 100644 --- a/docs/html/conversion/addIdentifierPrefix.html +++ b/docs/html/conversion/addIdentifierPrefix.html @@ -29,13 +29,13 @@

    SYNOPSIS ^DESCRIPTION ^

     addIdentifierPrefix  Add identifier prefixes required by SBML.
     
    - If reaction, metabolite, compartment, gene or model identifiers do not
    - start with a letter or _, which conflicts with SBML specifications,
    - prefixes are added for all identifiers in the respective model field.
    + If any reaction, metabolite, compartment, gene or model identifier does
    + not start with a letter or _, which conflicts with SBML specifications,
    + a prefix is added to all identifiers in the respective model field.
      The prefixes are:
     
          "R_" for model.rxns,
    -     "M_" for model.mets,
    +     "M_" for model.mets and model.id,
          "C_" for model.comps;
          "G_" for model.genes (and also represented in model.grRules)
     
    @@ -47,16 +47,17 @@ 

    DESCRIPTION ^SOURCE CODE ^
    0001 function [model, hasChanged]=addIdentifierPrefix(model,varargin)
     0002 % addIdentifierPrefix  Add identifier prefixes required by SBML.
     0003 %
    -0004 % If reaction, metabolite, compartment, gene or model identifiers do not
    -0005 % start with a letter or _, which conflicts with SBML specifications,
    -0006 % prefixes are added for all identifiers in the respective model field.
    +0004 % If any reaction, metabolite, compartment, gene or model identifier does
    +0005 % not start with a letter or _, which conflicts with SBML specifications,
    +0006 % a prefix is added to all identifiers in the respective model field.
     0007 % The prefixes are:
     0008 %
     0009 %     "R_" for model.rxns,
    -0010 %     "M_" for model.mets,
    +0010 %     "M_" for model.mets and model.id,
     0011 %     "C_" for model.comps;
     0012 %     "G_" for model.genes (and also represented in model.grRules)
     0013 %
    @@ -98,73 +99,74 @@ 

    SOURCE CODE ^% Name-Value Arguments 0020 % -------------------- 0021 % fields : cell -0022 % cell array with model field names that should be checked if prefixes -0023 % should be added, possible values: 'rxns', 'mets', 'comps', 'genes', -0024 % 'id' (default all listed model fields will be checked). +0022 % cell array with the names of the model fields to check for the need +0023 % of a prefix, possible values: 'rxns', 'mets', 'comps', 'genes', 'id' +0024 % (default all of these fields). 0025 % 0026 % Returns 0027 % ------- 0028 % model : struct 0029 % modified model. 0030 % hasChanged : cell -0031 % cell array with fields and prefixes that are added. -0032 % -0033 % Examples -0034 % -------- -0035 % [model, hasChanged] = addIdentifierPrefix(model, fields); -0036 -0037 p=parseRAVENargs(varargin, {'fields',[]}); -0038 fields=p.fields; -0039 if isempty(fields) -0040 fields = {'rxns','mets','comps','genes','id'}; -0041 end -0042 -0043 modelFields = {'rxns','R_'; -0044 'mets','M_'; -0045 'comps','C_'; -0046 'genes','G_'; -0047 'id','M_'}; -0048 -0049 toChangeIdx = find(ismember(modelFields(:,1),fields)); -0050 hasChanged = false(numel(modelFields(:,1)),1); -0051 for i=1:numel(toChangeIdx) -0052 currName = modelFields{toChangeIdx(i),1}; -0053 currPrefix = modelFields{toChangeIdx(i),2}; -0054 if isfield(model,currName) -0055 currField = model.(currName); -0056 else -0057 continue; -0058 end -0059 if ~all(startsWith(currField,regexpPattern('^[a-zA-Z_]'))) -0060 currField = strcat(currPrefix, currField); -0061 hasChanged(toChangeIdx(i)) = true; -0062 -0063 if strcmp(currName,'genes') && isfield(model,'grRules') -0064 model.grRules = cellfun(@(r) prefixGenes(r,currPrefix), model.grRules, 'UniformOutput', false); -0065 end -0066 model.(currName) = currField; -0067 end -0068 end -0069 -0070 hasChanged = modelFields(hasChanged,:); -0071 hasChanged = append('model.', hasChanged(:,1), ' (', hasChanged(:,2), ' prefix)'); -0072 end -0073 +0031 % cell array of char with one entry per field that received a prefix, +0032 % for example 'model.rxns (R_ prefix)'. +0033 % +0034 % Examples +0035 % -------- +0036 % [model, hasChanged] = addIdentifierPrefix(model, fields); +0037 +0038 p=parseRAVENargs(varargin, {'fields',[]}); +0039 fields=p.fields; +0040 if isempty(fields) +0041 fields = {'rxns','mets','comps','genes','id'}; +0042 end +0043 +0044 modelFields = {'rxns','R_'; +0045 'mets','M_'; +0046 'comps','C_'; +0047 'genes','G_'; +0048 'id','M_'}; +0049 +0050 toChangeIdx = find(ismember(modelFields(:,1),fields)); +0051 hasChanged = false(numel(modelFields(:,1)),1); +0052 for i=1:numel(toChangeIdx) +0053 currName = modelFields{toChangeIdx(i),1}; +0054 currPrefix = modelFields{toChangeIdx(i),2}; +0055 if isfield(model,currName) +0056 currField = model.(currName); +0057 else +0058 continue; +0059 end +0060 if ~all(startsWith(currField,regexpPattern('^[a-zA-Z_]'))) +0061 currField = strcat(currPrefix, currField); +0062 hasChanged(toChangeIdx(i)) = true; +0063 +0064 if strcmp(currName,'genes') && isfield(model,'grRules') +0065 model.grRules = cellfun(@(r) prefixGenes(r,currPrefix), model.grRules, 'UniformOutput', false); +0066 end +0067 model.(currName) = currField; +0068 end +0069 end +0070 +0071 hasChanged = modelFields(hasChanged,:); +0072 hasChanged = append('model.', hasChanged(:,1), ' (', hasChanged(:,2), ' prefix)'); +0073 end 0074 -0075 function rule = prefixGenes(rule, prefix) -0076 % Prefix every gene in a grRule, leaving "and", "or" and brackets alone. A -0077 % gene is everything between spaces and brackets, so ids that contain -0078 % "-", "." or ":" get one prefix, not one per word. -0079 [tokens, separators] = regexp(rule, '[^\s()]+', 'match', 'split'); -0080 out = separators{1}; -0081 for i = 1:numel(tokens) -0082 if ~any(strcmpi(tokens{i}, {'and','or'})) -0083 tokens{i} = [prefix tokens{i}]; -0084 end -0085 out = [out tokens{i} separators{i+1}]; %#ok<AGROW> -0086 end -0087 rule = out; -0088 end

    +0075 +0076 function rule = prefixGenes(rule, prefix) +0077 % Prefix every gene in a grRule, leaving "and", "or" and brackets alone. A +0078 % gene is everything between spaces and brackets, so ids that contain +0079 % "-", "." or ":" get one prefix, not one per word. +0080 [tokens, separators] = regexp(rule, '[^\s()]+', 'match', 'split'); +0081 out = separators{1}; +0082 for i = 1:numel(tokens) +0083 if ~any(strcmpi(tokens{i}, {'and','or'})) +0084 tokens{i} = [prefix tokens{i}]; +0085 end +0086 out = [out tokens{i} separators{i+1}]; %#ok<AGROW> +0087 end +0088 rule = out; +0089 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/conversion/ravenCobraWrapper.html b/docs/html/conversion/ravenCobraWrapper.html index feebf91b..cf6cd078 100644 --- a/docs/html/conversion/ravenCobraWrapper.html +++ b/docs/html/conversion/ravenCobraWrapper.html @@ -29,19 +29,21 @@

    SYNOPSIS ^DESCRIPTION ^

     ravenCobraWrapper  Convert between RAVEN and COBRA structures.
     
    - Recognises a COBRA structure by checking for the field 'rules', found
    - only in a COBRA Toolbox structure. If the COBRA model also has a grRules
    - field, that is used instead of parsing rules.
    + A structure with the field 'rules' is treated as a COBRA Toolbox
    + structure and converted to RAVEN; a structure without it is treated as a
    + RAVEN structure and converted to COBRA. If a COBRA structure also has a
    + grRules field, that field is used and rules is not parsed.
     
      Parameters
      ----------
      model : struct
    -     a RAVEN/COBRA-compatible model structure.
    +     a RAVEN or COBRA model structure.
     
      Returns
      -------
      newModel : struct
    -     a COBRA/RAVEN-compatible model structure.
    +     the model structure in the other format: COBRA for a RAVEN input and
    +     RAVEN for a COBRA input.
     
      Examples
      --------
    @@ -52,17 +54,18 @@ 

    DESCRIPTION ^CROSS-REFERENCE INFORMATION ^

    @@ -82,464 +85,467 @@

    SOURCE CODE ^
    0001 function newModel=ravenCobraWrapper(model)
     0002 % ravenCobraWrapper  Convert between RAVEN and COBRA structures.
     0003 %
    -0004 % Recognises a COBRA structure by checking for the field 'rules', found
    -0005 % only in a COBRA Toolbox structure. If the COBRA model also has a grRules
    -0006 % field, that is used instead of parsing rules.
    -0007 %
    -0008 % Parameters
    -0009 % ----------
    -0010 % model : struct
    -0011 %     a RAVEN/COBRA-compatible model structure.
    -0012 %
    -0013 % Returns
    -0014 % -------
    -0015 % newModel : struct
    -0016 %     a COBRA/RAVEN-compatible model structure.
    -0017 %
    -0018 % Examples
    -0019 % --------
    -0020 %     newModel = ravenCobraWrapper(model);
    -0021 %
    -0022 % Notes
    -0023 % -----
    -0024 % During the RAVEN -> COBRA -> RAVEN conversion cycle the following fields
    -0025 % are lost: annotation, compOutside, compMiriams, rxnComps, geneComps,
    -0026 % unconstrained. Boundary metabolites are lost, because the COBRA
    -0027 % structure does not involve boundary metabolites, so they are removed
    -0028 % using simplifyModel before RAVEN -> COBRA conversion. The field 'rev' is
    -0029 % also partially lost, but during COBRA -> RAVEN conversion it is
    -0030 % reconstructed based on lower bound reaction values.
    -0031 %
    -0032 % During the COBRA -> RAVEN -> COBRA conversion cycle the following fields
    -0033 % are lost: geneEntrezID, modelVersion, proteins.
    +0004 % A structure with the field 'rules' is treated as a COBRA Toolbox
    +0005 % structure and converted to RAVEN; a structure without it is treated as a
    +0006 % RAVEN structure and converted to COBRA. If a COBRA structure also has a
    +0007 % grRules field, that field is used and rules is not parsed.
    +0008 %
    +0009 % Parameters
    +0010 % ----------
    +0011 % model : struct
    +0012 %     a RAVEN or COBRA model structure.
    +0013 %
    +0014 % Returns
    +0015 % -------
    +0016 % newModel : struct
    +0017 %     the model structure in the other format: COBRA for a RAVEN input and
    +0018 %     RAVEN for a COBRA input.
    +0019 %
    +0020 % Examples
    +0021 % --------
    +0022 %     newModel = ravenCobraWrapper(model);
    +0023 %
    +0024 % Notes
    +0025 % -----
    +0026 % During the RAVEN -> COBRA -> RAVEN conversion cycle the following fields
    +0027 % are lost: annotation, compOutside, compMiriams, rxnComps, geneComps,
    +0028 % unconstrained. Boundary metabolites are lost, because the COBRA
    +0029 % structure has no boundary metabolites, and are removed using
    +0030 % simplifyModel before the RAVEN -> COBRA conversion. The field 'rev' is
    +0031 % also partially lost; during the COBRA -> RAVEN conversion it is
    +0032 % reconstructed from the lower bounds (a reaction is reversible if its
    +0033 % lower bound is below zero).
     0034 %
    -0035 % The information about mandatory RAVEN fields was taken from the
    -0036 % checkModelStruct function, whereas the corresponding information about
    -0037 % COBRA fields was fetched from the verifyModel function.
    -0038 
    -0039 if isfield(model,'rules')
    -0040     isRaven=false;
    -0041 else
    -0042     isRaven=true;
    -0043 end
    -0044 
    -0045 ravenPath=findRAVENroot();
    -0046 
    -0047 % Load COBRA field information
    -0048 fid             = fopen(fullfile(ravenPath,'src','conversion','COBRA_structure_fields.csv')); % Taken from https://github.com/opencobra/cobratoolbox/blob/develop/src/base/io/definitions/COBRA_structure_fields.csv
    -0049 fieldFile       = textscan(fid,repmat('%s',1,15),'Delimiter','\t','HeaderLines',1);
    -0050 dbFields        = ~cellfun(@isempty,fieldFile{5}); % Only keep fields with database annotations that should be translated to xxxMiriams
    -0051 dbFields        = dbFields & ~contains(fieldFile{1},{'metInChIString','metKEGGID','metPubChemID','rxnECNumbers'});
    -0052 COBRAnamespace  = fieldFile{5}(dbFields);
    -0053 COBRAnamespace  = regexprep(COBRAnamespace,';.*',''); % Only keep first suggested namespace
    -0054 COBRAfields     = fieldFile{1}(dbFields);
    -0055 fclose(fid);
    -0056 
    -0057 % Load conversion between additional COBRA fields and namespaces:
    -0058 fid             = fopen(fullfile(ravenPath,'src','conversion','cobraNamespaces.csv'));
    -0059 fieldFile       = textscan(fid,'%s %s','Delimiter',',','HeaderLines',0);
    -0060 COBRAfields     = [COBRAfields; fieldFile{1}];
    -0061 COBRAnamespace  = [COBRAnamespace; fieldFile{2}];
    -0062 rxnCOBRAfields  = COBRAfields(startsWith(COBRAfields,'rxn'));
    -0063 rxnNamespaces   = COBRAnamespace(startsWith(COBRAfields,'rxn'));
    -0064 metCOBRAfields  = COBRAfields(startsWith(COBRAfields,'met'));
    -0065 metNamespaces   = COBRAnamespace(startsWith(COBRAfields,'met'));
    -0066 geneCOBRAfields = COBRAfields(startsWith(COBRAfields,'gene'));
    -0067 geneNamespaces  = COBRAnamespace(startsWith(COBRAfields,'gene'));
    -0068 fclose(fid);
    -0069 
    -0070 if isRaven
    -0071     %Remove boundary metabolites
    -0072     model=simplifyModel(model);
    -0073 end
    -0074 
    -0075 % Keep fields that have identical names and content
    -0076 newModel.S=model.S;
    -0077 newModel.lb=model.lb;
    -0078 newModel.ub=model.ub;
    -0079 if isfield(model,'c')
    -0080     newModel.c=model.c;
    -0081 else
    -0082     newModel.c=zeros(numel(model.rxns),1);
    -0083 end
    -0084 newModel.rxns=model.rxns;
    -0085 optFields = {'rxnNames','rxnNotes','rxnConfidenceScores','rxnDeltaG',...
    -0086     'rxnGeneMat','rev','subSystems','comps','compNames','metCharges',...
    -0087     'metDeltaG','metFormulas','metNotes','metSmiles','genes','proteins'};
    -0088 for i=1:length(optFields)
    -0089     if isfield(model,optFields{i})
    -0090         newModel.(optFields{i})=model.(optFields{i});
    -0091     end
    -0092 end
    -0093     
    -0094 % Convert unique fields
    -0095 if isRaven
    -0096     fprintf('Converting RAVEN structure to COBRA..\n');
    -0097     %Convert from RAVEN to COBRA structure
    -0098     
    -0099     %Mandatory COBRA fields
    -0100     newModel.rxns=model.rxns;
    -0101     if all(~cellfun(@isempty,regexp(model.mets,'\[[^\]]+\]$')))
    -0102         newModel.mets=model.mets;
    -0103     else
    -0104         %Check if model has compartment info as "met_c" suffix in all metabolites:
    -0105         BiGGformat = false(size(model.mets));
    -0106         for i=1:numel(model.comps)
    -0107             compPos=model.metComps==i;
    -0108             BiGGformat(compPos)=~cellfun(@isempty,regexp(model.mets(compPos),['_' model.comps{i} '$']));
    -0109         end
    -0110         if all(BiGGformat)
    -0111             newModel.mets=model.mets;
    -0112             for i=1:numel(model.comps)
    -0113                 newModel.mets=regexprep(newModel.mets,['_' model.comps{i} '$'],['[' model.comps{i} ']']);
    -0114             end
    -0115         else
    -0116             newModel.mets=strcat(model.mets,'[',model.comps(model.metComps),']');
    -0117         end
    -0118     end
    -0119 
    -0120     %b, csense, osenseStr, genes, rules are also mandatory, but defined
    -0121     %later to match the order of fields
    -0122     
    -0123     %Optional COBRA fields
    -0124     if isfield(model,'id')
    -0125         newModel.modelID=model.id;
    -0126     end
    -0127     if isfield(model,'name')
    -0128         newModel.modelName=model.name;
    +0035 % During the COBRA -> RAVEN -> COBRA conversion cycle the following fields
    +0036 % are lost: geneEntrezID, modelVersion, proteins.
    +0037 %
    +0038 % The information about mandatory RAVEN fields was taken from the
    +0039 % checkModelStruct function, whereas the corresponding information about
    +0040 % COBRA fields was taken from the verifyModel function.
    +0041 
    +0042 if isfield(model,'rules')
    +0043     isRaven=false;
    +0044 else
    +0045     isRaven=true;
    +0046 end
    +0047 
    +0048 ravenPath=findRAVENroot();
    +0049 
    +0050 % Load COBRA field information
    +0051 fid             = fopen(fullfile(ravenPath,'src','conversion','COBRA_structure_fields.csv')); % Taken from https://github.com/opencobra/cobratoolbox/blob/develop/src/base/io/definitions/COBRA_structure_fields.csv
    +0052 fieldFile       = textscan(fid,repmat('%s',1,15),'Delimiter','\t','HeaderLines',1);
    +0053 dbFields        = ~cellfun(@isempty,fieldFile{5}); % Only keep fields with database annotations that should be translated to xxxMiriams
    +0054 dbFields        = dbFields & ~contains(fieldFile{1},{'metInChIString','metKEGGID','metPubChemID','rxnECNumbers'});
    +0055 COBRAnamespace  = fieldFile{5}(dbFields);
    +0056 COBRAnamespace  = regexprep(COBRAnamespace,';.*',''); % Only keep first suggested namespace
    +0057 COBRAfields     = fieldFile{1}(dbFields);
    +0058 fclose(fid);
    +0059 
    +0060 % Load conversion between additional COBRA fields and namespaces:
    +0061 fid             = fopen(fullfile(ravenPath,'src','conversion','cobraNamespaces.csv'));
    +0062 fieldFile       = textscan(fid,'%s %s','Delimiter',',','HeaderLines',0);
    +0063 COBRAfields     = [COBRAfields; fieldFile{1}];
    +0064 COBRAnamespace  = [COBRAnamespace; fieldFile{2}];
    +0065 rxnCOBRAfields  = COBRAfields(startsWith(COBRAfields,'rxn'));
    +0066 rxnNamespaces   = COBRAnamespace(startsWith(COBRAfields,'rxn'));
    +0067 metCOBRAfields  = COBRAfields(startsWith(COBRAfields,'met'));
    +0068 metNamespaces   = COBRAnamespace(startsWith(COBRAfields,'met'));
    +0069 geneCOBRAfields = COBRAfields(startsWith(COBRAfields,'gene'));
    +0070 geneNamespaces  = COBRAnamespace(startsWith(COBRAfields,'gene'));
    +0071 fclose(fid);
    +0072 
    +0073 if isRaven
    +0074     %Remove boundary metabolites
    +0075     model=simplifyModel(model);
    +0076 end
    +0077 
    +0078 % Keep fields that have identical names and content
    +0079 newModel.S=model.S;
    +0080 newModel.lb=model.lb;
    +0081 newModel.ub=model.ub;
    +0082 if isfield(model,'c')
    +0083     newModel.c=model.c;
    +0084 else
    +0085     newModel.c=zeros(numel(model.rxns),1);
    +0086 end
    +0087 newModel.rxns=model.rxns;
    +0088 optFields = {'rxnNames','rxnNotes','rxnConfidenceScores','rxnDeltaG',...
    +0089     'rxnGeneMat','rev','subSystems','comps','compNames','metCharges',...
    +0090     'metDeltaG','metFormulas','metNotes','metSmiles','genes','proteins'};
    +0091 for i=1:length(optFields)
    +0092     if isfield(model,optFields{i})
    +0093         newModel.(optFields{i})=model.(optFields{i});
    +0094     end
    +0095 end
    +0096     
    +0097 % Convert unique fields
    +0098 if isRaven
    +0099     fprintf('Converting RAVEN structure to COBRA..\n');
    +0100     %Convert from RAVEN to COBRA structure
    +0101     
    +0102     %Mandatory COBRA fields
    +0103     newModel.rxns=model.rxns;
    +0104     if all(~cellfun(@isempty,regexp(model.mets,'\[[^\]]+\]$')))
    +0105         newModel.mets=model.mets;
    +0106     else
    +0107         %Check if model has compartment info as "met_c" suffix in all metabolites:
    +0108         BiGGformat = false(size(model.mets));
    +0109         for i=1:numel(model.comps)
    +0110             compPos=model.metComps==i;
    +0111             BiGGformat(compPos)=~cellfun(@isempty,regexp(model.mets(compPos),['_' model.comps{i} '$']));
    +0112         end
    +0113         if all(BiGGformat)
    +0114             newModel.mets=model.mets;
    +0115             for i=1:numel(model.comps)
    +0116                 newModel.mets=regexprep(newModel.mets,['_' model.comps{i} '$'],['[' model.comps{i} ']']);
    +0117             end
    +0118         else
    +0119             newModel.mets=strcat(model.mets,'[',model.comps(model.metComps),']');
    +0120         end
    +0121     end
    +0122 
    +0123     %b, csense, osenseStr, genes, rules are also mandatory, but defined
    +0124     %later to match the order of fields
    +0125     
    +0126     %Optional COBRA fields
    +0127     if isfield(model,'id')
    +0128         newModel.modelID=model.id;
     0129     end
    -0130     if isfield(model,'eccodes')
    -0131         newModel.rxnECNumbers=model.eccodes;
    +0130     if isfield(model,'name')
    +0131         newModel.modelName=model.name;
     0132     end
    -0133     if isfield(model,'rxnMiriams')
    -0134         [miriams,extractedMiriamNames]=extractMiriam(model.rxnMiriams);
    -0135         for i = 1:length(rxnCOBRAfields)
    -0136             j=ismember(extractedMiriamNames,rxnNamespaces{i});
    -0137             if any(j)
    -0138                 newModel.(rxnCOBRAfields{i}) = miriams(:,j);
    -0139             end
    -0140         end
    -0141     end
    -0142     if isfield(model,'rxnReferences') % Concatenate model.rxnReferences to those extracted from model.rxnMiriams
    -0143         if isfield(newModel,'rxnReferences')
    -0144             newModel.rxnReferences = strcat(newModel.rxnReferences,{'; '},model.rxnReferences);
    -0145             newModel.rxnReferences = regexprep(newModel.rxnReferences,'^; $','');
    -0146         else
    -0147             newModel.rxnReferences = model.rxnReferences;
    -0148         end
    -0149     end
    -0150     if isfield(model,'metNames')
    -0151         newModel.metNames=strcat(model.metNames,' [',model.compNames(model.metComps),']');
    +0133     if isfield(model,'eccodes')
    +0134         newModel.rxnECNumbers=model.eccodes;
    +0135     end
    +0136     if isfield(model,'rxnMiriams')
    +0137         [miriams,extractedMiriamNames]=extractMiriam(model.rxnMiriams);
    +0138         for i = 1:length(rxnCOBRAfields)
    +0139             j=ismember(extractedMiriamNames,rxnNamespaces{i});
    +0140             if any(j)
    +0141                 newModel.(rxnCOBRAfields{i}) = miriams(:,j);
    +0142             end
    +0143         end
    +0144     end
    +0145     if isfield(model,'rxnReferences') % Concatenate model.rxnReferences to those extracted from model.rxnMiriams
    +0146         if isfield(newModel,'rxnReferences')
    +0147             newModel.rxnReferences = strcat(newModel.rxnReferences,{'; '},model.rxnReferences);
    +0148             newModel.rxnReferences = regexprep(newModel.rxnReferences,'^; $','');
    +0149         else
    +0150             newModel.rxnReferences = model.rxnReferences;
    +0151         end
     0152     end
    -0153     if isfield(model,'metMiriams')
    -0154         [miriams,extractedMiriamNames]=extractMiriam(model.metMiriams);
    -0155         %Shorten miriam names for KEGG and PubChem. These shorter names
    -0156         %will be used later to concatenate KEGG COMPOUND/GLYCAN and PubChem
    -0157         %Compound/Substance, into corresponding COBRA model fields
    -0158         extractedMiriamNames=regexprep(extractedMiriamNames,'^kegg\..+','kegg');
    -0159         extractedMiriamNames=regexprep(extractedMiriamNames,'^pubchem\..+','pubchem');
    -0160         i=ismember(extractedMiriamNames,'kegg');
    -0161         if any(i) % Combine KEGG compounds and glycans
    -0162             for j=1:length(i)
    -0163                 if i(j) && isfield(newModel,'metKEGGID')~=1
    -0164                     newModel.metKEGGID=miriams(:,j);
    -0165                 elseif i(j)
    -0166                     newModel.metKEGGID=strcat(newModel.metKEGGID,';',miriams(:,j));
    -0167                 end
    -0168             end
    -0169             newModel.metKEGGID=regexprep(newModel.metKEGGID,'^;|;$','');
    -0170         end
    -0171         i=ismember(extractedMiriamNames,'pubchem');
    -0172         if any(i) % Combine Pubchem compounds and substances
    -0173             for j=1:length(i)
    -0174                 if i(j) && isfield(newModel,'metPubChemID')~=1
    -0175                     newModel.metPubChemID=miriams(:,j);
    -0176                 elseif i(j)
    -0177                     newModel.metPubChemID=strcat(newModel.metPubChemID,';',miriams(:,j));
    -0178                 end
    -0179             end
    -0180             newModel.metPubChemID=regexprep(newModel.metPubChemID,'^;|;$','');
    -0181         end
    -0182         %All other Miriams can be directly parsed with no modifications:
    -0183         for i = 1:length(metCOBRAfields)
    -0184             j=ismember(extractedMiriamNames,metNamespaces{i});
    -0185             if any(j)
    -0186                 newModel.(metCOBRAfields{i}) = miriams(:,j);
    -0187             end
    -0188         end
    -0189     end
    -0190     if isfield(model,'inchis')
    -0191         newModel.metInChIString=regexprep(strcat('InChI=', model.inchis),'^InChI=$','');
    +0153     if isfield(model,'metNames')
    +0154         newModel.metNames=strcat(model.metNames,' [',model.compNames(model.metComps),']');
    +0155     end
    +0156     if isfield(model,'metMiriams')
    +0157         [miriams,extractedMiriamNames]=extractMiriam(model.metMiriams);
    +0158         %Shorten miriam names for KEGG and PubChem. These shorter names
    +0159         %will be used later to concatenate KEGG COMPOUND/GLYCAN and PubChem
    +0160         %Compound/Substance, into corresponding COBRA model fields
    +0161         extractedMiriamNames=regexprep(extractedMiriamNames,'^kegg\..+','kegg');
    +0162         extractedMiriamNames=regexprep(extractedMiriamNames,'^pubchem\..+','pubchem');
    +0163         i=ismember(extractedMiriamNames,'kegg');
    +0164         if any(i) % Combine KEGG compounds and glycans
    +0165             for j=1:length(i)
    +0166                 if i(j) && isfield(newModel,'metKEGGID')~=1
    +0167                     newModel.metKEGGID=miriams(:,j);
    +0168                 elseif i(j)
    +0169                     newModel.metKEGGID=strcat(newModel.metKEGGID,';',miriams(:,j));
    +0170                 end
    +0171             end
    +0172             newModel.metKEGGID=regexprep(newModel.metKEGGID,'^;|;$','');
    +0173         end
    +0174         i=ismember(extractedMiriamNames,'pubchem');
    +0175         if any(i) % Combine Pubchem compounds and substances
    +0176             for j=1:length(i)
    +0177                 if i(j) && isfield(newModel,'metPubChemID')~=1
    +0178                     newModel.metPubChemID=miriams(:,j);
    +0179                 elseif i(j)
    +0180                     newModel.metPubChemID=strcat(newModel.metPubChemID,';',miriams(:,j));
    +0181                 end
    +0182             end
    +0183             newModel.metPubChemID=regexprep(newModel.metPubChemID,'^;|;$','');
    +0184         end
    +0185         %All other Miriams can be directly parsed with no modifications:
    +0186         for i = 1:length(metCOBRAfields)
    +0187             j=ismember(extractedMiriamNames,metNamespaces{i});
    +0188             if any(j)
    +0189                 newModel.(metCOBRAfields{i}) = miriams(:,j);
    +0190             end
    +0191         end
     0192     end
    -0193     newModel.b=zeros(numel(model.mets),1);
    -0194     newModel.csense=repmat('E',size(model.mets));
    -0195     if isfield(model,'geneMiriams')
    -0196         [miriams,extractedMiriamNames]=extractMiriam(model.geneMiriams);
    -0197         for i = 1:length(geneCOBRAfields)
    -0198             j=ismember(extractedMiriamNames,geneNamespaces{i});
    -0199             if any(j)
    -0200                 newModel.(geneCOBRAfields{i}) = miriams(:,j);
    -0201             end
    -0202         end
    -0203     end
    -0204     if isfield(model,'geneShortNames')
    -0205         newModel.geneNames=model.geneShortNames;
    +0193     if isfield(model,'inchis')
    +0194         newModel.metInChIString=regexprep(strcat('InChI=', model.inchis),'^InChI=$','');
    +0195     end
    +0196     newModel.b=zeros(numel(model.mets),1);
    +0197     newModel.csense=repmat('E',size(model.mets));
    +0198     if isfield(model,'geneMiriams')
    +0199         [miriams,extractedMiriamNames]=extractMiriam(model.geneMiriams);
    +0200         for i = 1:length(geneCOBRAfields)
    +0201             j=ismember(extractedMiriamNames,geneNamespaces{i});
    +0202             if any(j)
    +0203                 newModel.(geneCOBRAfields{i}) = miriams(:,j);
    +0204             end
    +0205         end
     0206     end
    -0207     if isfield(model,'genes')
    -0208         newModel.rules=grrulesToRules(model);
    -0209     else
    -0210         fprintf('WARNING: no genes detected. The model therefore may not be exportable to SBML file with writeCbModel\n');
    -0211     end
    -0212     % If needed, normalise subSystems to cell-of-cells format
    -0213     if isfield(newModel,'subSystems') && ~isempty(newModel.subSystems)
    -0214         if ~iscell(newModel.subSystems{1})
    -0215             newModel.subSystems = cellfun(@(x) {x}, newModel.subSystems, 'UniformOutput', false);
    -0216         end
    -0217     end
    -0218 else
    -0219     fprintf('Converting COBRA structure to RAVEN..\n');
    -0220     %Convert from COBRA to RAVEN structure
    -0221 
    -0222     %Mandatory RAVEN fields
    -0223     newModel.mets=model.mets;
    -0224     if ~isfield(model,'comps')
    -0225         %Since 'comps' field is not mandatory in COBRA, it may be required
    -0226         %to obtain the non-redundant list of comps from metabolite ids, if
    -0227         %'comps' field is not available
    -0228         newModel.comps = unique(regexprep(model.mets,'.*\[([^\]]+)\]$','$1'));
    -0229         newModel.compNames = newModel.comps;
    -0230     end
    -0231     for i=1:numel(newModel.comps)
    -0232         newModel.mets=regexprep(newModel.mets,['\[', newModel.comps{i}, '\]$'],'');
    -0233         newModel.mets=regexprep(newModel.mets,['\[', newModel.compNames{i}, '\]$'],'');
    -0234     end
    -0235     
    -0236     %In some cases (e.g. any model that uses BiGG ids as main ids), there
    -0237     %may be overlapping mets due to removal of compartment info. To avoid
    -0238     %this, we change compartments from e.g. [c] into _c
    -0239     if numel(unique(newModel.mets))~=numel(model.mets)
    -0240         newModel.mets=model.mets;
    -0241         for i=1:numel(newModel.comps)
    -0242             newModel.mets=regexprep(newModel.mets,['\[' newModel.comps{i} '\]$'],['_' newModel.comps{i}]);
    -0243         end
    -0244     end
    -0245     %Since COBRA no longer contains rev field it is assumed that rxn is
    -0246     %reversible if its lower bound is set below zero
    -0247     if ~isfield(model,'rev')
    -0248         for i=1:numel(model.rxns)
    -0249             if model.lb(i)<0
    -0250                 newModel.rev(i,1)=1;
    -0251             else
    -0252                 newModel.rev(i,1)=0;
    -0253             end
    -0254         end
    -0255     end
    -0256     newModel.b=zeros(numel(model.mets),1);
    -0257    
    -0258     %metComps is also mandatory, but defined later to match the order of
    -0259     %fields
    -0260     
    -0261     %Fields 'name' and 'id' are also considered as mandatory, but
    -0262     %these are added to the model during exportModel/exportToExcelFormat
    -0263     %anyway, so there is no point to add this information here
    -0264     
    -0265     %Optional RAVEN fields
    -0266     if isfield(model,'modelID')
    -0267         newModel.id=model.modelID;
    -0268     end
    -0269     if isfield(model,'modelName')
    -0270         newModel.name=model.modelName;
    +0207     if isfield(model,'geneShortNames')
    +0208         newModel.geneNames=model.geneShortNames;
    +0209     end
    +0210     if isfield(model,'genes')
    +0211         newModel.rules=grrulesToRules(model);
    +0212     else
    +0213         fprintf('WARNING: no genes detected. The model therefore may not be exportable to SBML file with writeCbModel\n');
    +0214     end
    +0215     % If needed, normalise subSystems to cell-of-cells format
    +0216     if isfield(newModel,'subSystems') && ~isempty(newModel.subSystems)
    +0217         if ~iscell(newModel.subSystems{1})
    +0218             newModel.subSystems = cellfun(@(x) {x}, newModel.subSystems, 'UniformOutput', false);
    +0219         end
    +0220     end
    +0221 else
    +0222     fprintf('Converting COBRA structure to RAVEN..\n');
    +0223     %Convert from COBRA to RAVEN structure
    +0224 
    +0225     %Mandatory RAVEN fields
    +0226     newModel.mets=model.mets;
    +0227     if ~isfield(model,'comps')
    +0228         %Since 'comps' field is not mandatory in COBRA, it may be required
    +0229         %to obtain the non-redundant list of comps from metabolite ids, if
    +0230         %'comps' field is not available
    +0231         newModel.comps = unique(regexprep(model.mets,'.*\[([^\]]+)\]$','$1'));
    +0232         newModel.compNames = newModel.comps;
    +0233     end
    +0234     for i=1:numel(newModel.comps)
    +0235         newModel.mets=regexprep(newModel.mets,['\[', newModel.comps{i}, '\]$'],'');
    +0236         newModel.mets=regexprep(newModel.mets,['\[', newModel.compNames{i}, '\]$'],'');
    +0237     end
    +0238     
    +0239     %In some cases (e.g. any model that uses BiGG ids as main ids), there
    +0240     %may be overlapping mets due to removal of compartment info. To avoid
    +0241     %this, we change compartments from e.g. [c] into _c
    +0242     if numel(unique(newModel.mets))~=numel(model.mets)
    +0243         newModel.mets=model.mets;
    +0244         for i=1:numel(newModel.comps)
    +0245             newModel.mets=regexprep(newModel.mets,['\[' newModel.comps{i} '\]$'],['_' newModel.comps{i}]);
    +0246         end
    +0247     end
    +0248     %Since COBRA no longer contains rev field it is assumed that rxn is
    +0249     %reversible if its lower bound is set below zero
    +0250     if ~isfield(model,'rev')
    +0251         for i=1:numel(model.rxns)
    +0252             if model.lb(i)<0
    +0253                 newModel.rev(i,1)=1;
    +0254             else
    +0255                 newModel.rev(i,1)=0;
    +0256             end
    +0257         end
    +0258     end
    +0259     newModel.b=zeros(numel(model.mets),1);
    +0260    
    +0261     %metComps is also mandatory, but defined later to match the order of
    +0262     %fields
    +0263     
    +0264     %Fields 'name' and 'id' are also considered as mandatory, but
    +0265     %these are added to the model during exportModel/exportToExcelFormat
    +0266     %anyway, so there is no point to add this information here
    +0267     
    +0268     %Optional RAVEN fields
    +0269     if isfield(model,'modelID')
    +0270         newModel.id=model.modelID;
     0271     end
    -0272     if isfield(model,'rules') && ~isfield(model,'grRules')
    -0273         model.grRules        = rulesTogrrules(model);
    +0272     if isfield(model,'modelName')
    +0273         newModel.name=model.modelName;
     0274     end
    -0275     if isfield(model,'grRules')
    -0276         [grRules,rxnGeneMat] = standardizeGrRules(model,true);
    -0277         newModel.grRules     = grRules;
    -0278         newModel.rxnGeneMat  = rxnGeneMat;
    -0279     end
    -0280     if isfield(model,'rxnECNumbers')
    -0281         newModel.eccodes=regexprep(model.rxnECNumbers,'EC|EC:','');
    +0275     if isfield(model,'rules') && ~isfield(model,'grRules')
    +0276         model.grRules        = rulesTogrrules(model);
    +0277     end
    +0278     if isfield(model,'grRules')
    +0279         [grRules,rxnGeneMat] = standardizeGrRules(model,true);
    +0280         newModel.grRules     = grRules;
    +0281         newModel.rxnGeneMat  = rxnGeneMat;
     0282     end
    -0283     if any(isfield(model,rxnCOBRAfields))
    -0284         for i=1:numel(model.rxns)
    -0285             counter=1;
    -0286             newModel.rxnMiriams{i,1}=[];
    -0287             if isfield(model,'rxnReferences')
    -0288                 if ~isempty(model.rxnReferences{i})
    -0289                     pmids = model.rxnReferences{i};
    -0290                     pmids = strsplit(pmids,'; ');
    -0291                     nonPmids = cellfun(@isempty,regexp(pmids,'^\d+$','match','once'));
    -0292                     if any(nonPmids) %Not a pubmed id, keep in rxnReferences instead
    -0293                         newModel.rxnReferences{i,1} = strjoin(pmids(nonPmids),', ');
    -0294                         pmids(nonPmids)=[];
    -0295                     end
    -0296                     for j = 1:length(pmids)
    -0297                         newModel.rxnMiriams{i,1}.name{counter,1} = 'pubmed';
    -0298                         newModel.rxnMiriams{i,1}.value{counter,1} = pmids{j};
    -0299                         counter=counter+1;
    -0300                     end
    -0301                 end
    -0302             end
    -0303             for j = 2:length(rxnCOBRAfields) %Start from 2, as 1 is rxnReferences
    -0304                 if isfield(model,rxnCOBRAfields{j})
    -0305                     rxnAnnotation = model.(rxnCOBRAfields{j}){i};
    -0306                     if ~isempty(rxnAnnotation)
    -0307                         rxnAnnotation = strtrim(strsplit(rxnAnnotation,';'));
    -0308                         for a=1:length(rxnAnnotation)
    -0309                             newModel.rxnMiriams{i,1}.name{counter,1} = rxnNamespaces{j};
    -0310                             newModel.rxnMiriams{i,1}.value{counter,1} = rxnAnnotation{a};
    -0311                             counter=counter+1;
    -0312                         end
    -0313                     end
    -0314                 end
    -0315             end
    -0316         end
    -0317     end
    -0318     if isfield(newModel,'rxnReferences')
    -0319         emptyEntry = cellfun(@isempty,newModel.rxnReferences);
    -0320         newModel.rxnReferences(emptyEntry)={''};
    -0321         diffNumel = numel(newModel.rxns) - numel(newModel.rxnReferences);
    -0322         if diffNumel > 0
    -0323             newModel.rxnReferences(end+1:end+diffNumel) = {''};
    -0324         end
    -0325     end
    -0326     if any(isfield(model,geneCOBRAfields))
    -0327         for i=1:numel(model.genes)
    -0328             counter=1;
    -0329             newModel.geneMiriams{i,1}=[];
    -0330             for j = 1:length(geneCOBRAfields)
    -0331                 if isfield(model,geneCOBRAfields{j})
    -0332                     geneAnnotation = model.(geneCOBRAfields{j}){i};
    -0333                     if ~isempty(geneAnnotation)
    -0334                         geneAnnotation = strtrim(strsplit(geneAnnotation,';'));
    -0335                         for a=1:length(geneAnnotation)
    -0336                             newModel.geneMiriams{i,1}.name{counter,1} = geneNamespaces{j};
    -0337                             newModel.geneMiriams{i,1}.value{counter,1} = geneAnnotation{a};
    -0338                             counter=counter+1;
    -0339                         end
    -0340                     end
    -0341                 end
    -0342             end
    -0343         end
    -0344     end
    -0345     if isfield(model,'geneNames')
    -0346         newModel.geneShortNames=model.geneNames;
    +0283     if isfield(model,'rxnECNumbers')
    +0284         newModel.eccodes=regexprep(model.rxnECNumbers,'EC|EC:','');
    +0285     end
    +0286     if any(isfield(model,rxnCOBRAfields))
    +0287         for i=1:numel(model.rxns)
    +0288             counter=1;
    +0289             newModel.rxnMiriams{i,1}=[];
    +0290             if isfield(model,'rxnReferences')
    +0291                 if ~isempty(model.rxnReferences{i})
    +0292                     pmids = model.rxnReferences{i};
    +0293                     pmids = strsplit(pmids,'; ');
    +0294                     nonPmids = cellfun(@isempty,regexp(pmids,'^\d+$','match','once'));
    +0295                     if any(nonPmids) %Not a pubmed id, keep in rxnReferences instead
    +0296                         newModel.rxnReferences{i,1} = strjoin(pmids(nonPmids),', ');
    +0297                         pmids(nonPmids)=[];
    +0298                     end
    +0299                     for j = 1:length(pmids)
    +0300                         newModel.rxnMiriams{i,1}.name{counter,1} = 'pubmed';
    +0301                         newModel.rxnMiriams{i,1}.value{counter,1} = pmids{j};
    +0302                         counter=counter+1;
    +0303                     end
    +0304                 end
    +0305             end
    +0306             for j = 2:length(rxnCOBRAfields) %Start from 2, as 1 is rxnReferences
    +0307                 if isfield(model,rxnCOBRAfields{j})
    +0308                     rxnAnnotation = model.(rxnCOBRAfields{j}){i};
    +0309                     if ~isempty(rxnAnnotation)
    +0310                         rxnAnnotation = strtrim(strsplit(rxnAnnotation,';'));
    +0311                         for a=1:length(rxnAnnotation)
    +0312                             newModel.rxnMiriams{i,1}.name{counter,1} = rxnNamespaces{j};
    +0313                             newModel.rxnMiriams{i,1}.value{counter,1} = rxnAnnotation{a};
    +0314                             counter=counter+1;
    +0315                         end
    +0316                     end
    +0317                 end
    +0318             end
    +0319         end
    +0320     end
    +0321     if isfield(newModel,'rxnReferences')
    +0322         emptyEntry = cellfun(@isempty,newModel.rxnReferences);
    +0323         newModel.rxnReferences(emptyEntry)={''};
    +0324         diffNumel = numel(newModel.rxns) - numel(newModel.rxnReferences);
    +0325         if diffNumel > 0
    +0326             newModel.rxnReferences(end+1:end+diffNumel) = {''};
    +0327         end
    +0328     end
    +0329     if any(isfield(model,geneCOBRAfields))
    +0330         for i=1:numel(model.genes)
    +0331             counter=1;
    +0332             newModel.geneMiriams{i,1}=[];
    +0333             for j = 1:length(geneCOBRAfields)
    +0334                 if isfield(model,geneCOBRAfields{j})
    +0335                     geneAnnotation = model.(geneCOBRAfields{j}){i};
    +0336                     if ~isempty(geneAnnotation)
    +0337                         geneAnnotation = strtrim(strsplit(geneAnnotation,';'));
    +0338                         for a=1:length(geneAnnotation)
    +0339                             newModel.geneMiriams{i,1}.name{counter,1} = geneNamespaces{j};
    +0340                             newModel.geneMiriams{i,1}.value{counter,1} = geneAnnotation{a};
    +0341                             counter=counter+1;
    +0342                         end
    +0343                     end
    +0344                 end
    +0345             end
    +0346         end
     0347     end
    -0348     newModel.metNames=model.metNames;
    -0349     for i=1:numel(newModel.comps)
    -0350         newModel.metNames=regexprep(newModel.metNames,['\[', newModel.comps{i}, '\]$'],'');
    -0351         newModel.metNames=regexprep(newModel.metNames,['\[', newModel.compNames{i}, '\]$'],'');
    -0352     end
    -0353     newModel.metNames=deblank(newModel.metNames);
    -0354     newModel.metComps=regexprep(model.mets,'^.+\[','');
    -0355     newModel.metComps=regexprep(newModel.metComps,'\]$','');
    -0356     [~, newModel.metComps]=ismember(newModel.metComps,newModel.comps);
    -0357     if isfield(model,'metInChIString')
    -0358         newModel.inchis=regexprep(model.metInChIString,'^InChI=','');
    -0359     end
    -0360     printWarning=false;
    -0361     if any(isfield(model,[metCOBRAfields;'metKEGGID';'metPubChemID']))
    -0362         for i=1:numel(model.mets)
    -0363             counter=1;
    -0364             newModel.metMiriams{i,1}=[];
    -0365             if isfield(model,'metKEGGID')
    -0366                 if ~isempty(model.metKEGGID{i})
    -0367                     if strcmp(model.metKEGGID{i}(1),'C')
    -0368                         newModel.metMiriams{i,1}.name{counter,1} = 'kegg.compound';
    -0369                         newModel.metMiriams{i,1}.value{counter,1} = model.metKEGGID{i};
    -0370                         counter=counter+1;
    -0371                     elseif strcmp(model.metKEGGID{i}(1),'G')
    -0372                         newModel.metMiriams{i,1}.name{counter,1} = 'kegg.glycan';
    -0373                         newModel.metMiriams{i,1}.value{counter,1} = model.metKEGGID{i};
    -0374                         counter=counter+1;
    -0375                     end
    -0376                 end
    -0377             end
    -0378             if isfield(model,'metPubChemID')
    -0379                 if ~isempty(model.metPubChemID{i})
    -0380                     if length(model.metPubChemID{i})>3 && strcmp(model.metPubChemID{i}(1:4),'CID:')
    -0381                         newModel.metMiriams{i,1}.name{counter,1} = 'pubchem.compound';
    -0382                         newModel.metMiriams{i,1}.value{counter,1} = model.metPubChemID{i};
    -0383                         counter=counter+1;
    -0384                     elseif length(model.metPubChemID{i})>3 && strcmp(model.metPubChemID{i}(1:4),'SID:')
    -0385                         newModel.metMiriams{i,1}.name{counter,1} = 'pubchem.substance';
    -0386                         newModel.metMiriams{i,1}.value{counter,1} = model.metPubChemID{i};
    -0387                         counter=counter+1;
    -0388                     else
    -0389                         newModel.metMiriams{i,1}.name{counter,1} = 'pubchem.compound';
    -0390                         newModel.metMiriams{i,1}.value{counter,1} = model.metPubChemID{i};
    -0391                         counter=counter+1;
    -0392                         printWarning=true;
    -0393                     end
    -0394                 end
    -0395             end            
    -0396             for j = 1:length(metCOBRAfields)
    -0397                 if isfield(model,metCOBRAfields{j})
    -0398                     metAnnotation = model.(metCOBRAfields{j}){i};
    -0399                     if ~isempty(metAnnotation)
    -0400                         metAnnotation = strtrim(strsplit(metAnnotation,';'));
    -0401                         for a=1:length(metAnnotation)
    -0402                             newModel.metMiriams{i,1}.name{counter,1} = metNamespaces{j};
    -0403                             newModel.metMiriams{i,1}.value{counter,1} = metAnnotation{a};
    -0404                             counter=counter+1;
    -0405                         end
    -0406                     end
    -0407                 end
    -0408             end
    -0409         end
    -0410     end
    -0411     % Flatten subSystems if every reaction has exactly one subsystem.
    -0412     if isfield(newModel,'subSystems') && ~isempty(newModel.subSystems)
    -0413         if all(cellfun(@(x) iscell(x) && isscalar(x), newModel.subSystems))
    -0414             newModel.subSystems = transpose([newModel.subSystems{:}]);
    -0415         end
    -0416     end
    -0417     if printWarning
    -0418         fprintf('Could not determine whether PubChemIDs are compounds (CID)\n or substances (SID). All annotated PubChemIDs will therefore \n be assigned as compounds (CID).\n');
    +0348     if isfield(model,'geneNames')
    +0349         newModel.geneShortNames=model.geneNames;
    +0350     end
    +0351     newModel.metNames=model.metNames;
    +0352     for i=1:numel(newModel.comps)
    +0353         newModel.metNames=regexprep(newModel.metNames,['\[', newModel.comps{i}, '\]$'],'');
    +0354         newModel.metNames=regexprep(newModel.metNames,['\[', newModel.compNames{i}, '\]$'],'');
    +0355     end
    +0356     newModel.metNames=deblank(newModel.metNames);
    +0357     newModel.metComps=regexprep(model.mets,'^.+\[','');
    +0358     newModel.metComps=regexprep(newModel.metComps,'\]$','');
    +0359     [~, newModel.metComps]=ismember(newModel.metComps,newModel.comps);
    +0360     if isfield(model,'metInChIString')
    +0361         newModel.inchis=regexprep(model.metInChIString,'^InChI=','');
    +0362     end
    +0363     printWarning=false;
    +0364     if any(isfield(model,[metCOBRAfields;'metKEGGID';'metPubChemID']))
    +0365         for i=1:numel(model.mets)
    +0366             counter=1;
    +0367             newModel.metMiriams{i,1}=[];
    +0368             if isfield(model,'metKEGGID')
    +0369                 if ~isempty(model.metKEGGID{i})
    +0370                     if strcmp(model.metKEGGID{i}(1),'C')
    +0371                         newModel.metMiriams{i,1}.name{counter,1} = 'kegg.compound';
    +0372                         newModel.metMiriams{i,1}.value{counter,1} = model.metKEGGID{i};
    +0373                         counter=counter+1;
    +0374                     elseif strcmp(model.metKEGGID{i}(1),'G')
    +0375                         newModel.metMiriams{i,1}.name{counter,1} = 'kegg.glycan';
    +0376                         newModel.metMiriams{i,1}.value{counter,1} = model.metKEGGID{i};
    +0377                         counter=counter+1;
    +0378                     end
    +0379                 end
    +0380             end
    +0381             if isfield(model,'metPubChemID')
    +0382                 if ~isempty(model.metPubChemID{i})
    +0383                     if length(model.metPubChemID{i})>3 && strcmp(model.metPubChemID{i}(1:4),'CID:')
    +0384                         newModel.metMiriams{i,1}.name{counter,1} = 'pubchem.compound';
    +0385                         newModel.metMiriams{i,1}.value{counter,1} = model.metPubChemID{i};
    +0386                         counter=counter+1;
    +0387                     elseif length(model.metPubChemID{i})>3 && strcmp(model.metPubChemID{i}(1:4),'SID:')
    +0388                         newModel.metMiriams{i,1}.name{counter,1} = 'pubchem.substance';
    +0389                         newModel.metMiriams{i,1}.value{counter,1} = model.metPubChemID{i};
    +0390                         counter=counter+1;
    +0391                     else
    +0392                         newModel.metMiriams{i,1}.name{counter,1} = 'pubchem.compound';
    +0393                         newModel.metMiriams{i,1}.value{counter,1} = model.metPubChemID{i};
    +0394                         counter=counter+1;
    +0395                         printWarning=true;
    +0396                     end
    +0397                 end
    +0398             end            
    +0399             for j = 1:length(metCOBRAfields)
    +0400                 if isfield(model,metCOBRAfields{j})
    +0401                     metAnnotation = model.(metCOBRAfields{j}){i};
    +0402                     if ~isempty(metAnnotation)
    +0403                         metAnnotation = strtrim(strsplit(metAnnotation,';'));
    +0404                         for a=1:length(metAnnotation)
    +0405                             newModel.metMiriams{i,1}.name{counter,1} = metNamespaces{j};
    +0406                             newModel.metMiriams{i,1}.value{counter,1} = metAnnotation{a};
    +0407                             counter=counter+1;
    +0408                         end
    +0409                     end
    +0410                 end
    +0411             end
    +0412         end
    +0413     end
    +0414     % Flatten subSystems if every reaction has exactly one subsystem.
    +0415     if isfield(newModel,'subSystems') && ~isempty(newModel.subSystems)
    +0416         if all(cellfun(@(x) iscell(x) && isscalar(x), newModel.subSystems))
    +0417             newModel.subSystems = transpose([newModel.subSystems{:}]);
    +0418         end
     0419     end
    -0420 end
    -0421 
    -0422 % Order fields
    -0423 newModel=standardizeModelFieldOrder(newModel); % Corrects for both RAVEN and COBRA models
    -0424 end
    -0425 
    -0426 function rules=grrulesToRules(model)
    -0427 %Changes all gene names in grRules to 'x(geneNumber)' and changes 'or' and
    -0428 %'and' relations to the corresponding symbols
    -0429 replacingGenes=cell([size(model.genes,1) 1]);
    -0430 for i=1:numel(replacingGenes)
    -0431     replacingGenes{i}=strcat('x(',num2str(i),')');
    -0432 end
    -0433 rules = strcat({' '},model.grRules,{' '});
    -0434 for i=1:length(model.genes)
    -0435     %Escaped: a gene id is arbitrary model text, not a regex pattern, and
    -0436     %commonly contains metacharacters (e.g. "ENSG00000141510.16", where an
    -0437     %unescaped "." would match any character instead of a literal dot).
    -0438     escapedGene=regexptranslate('escape',model.genes{i});
    -0439     rules=regexprep(rules,[' ' escapedGene ' '],[' ' replacingGenes{i} ' ']);
    -0440     rules=regexprep(rules,['(' escapedGene ' '],['(' replacingGenes{i} ' ']);
    -0441     rules=regexprep(rules,[' ' escapedGene ')'],[' ' replacingGenes{i} ')']);
    -0442 end
    -0443 rules=regexprep(rules,' and ',' & ');
    -0444 rules=regexprep(rules,' or ',' | ');
    -0445 rules=strtrim(rules);
    -0446 end
    -0447 
    -0448 function grRules=rulesTogrrules(model)
    -0449 %Replaces &/| with and/or, replaces the x(i) format with the actual gene
    -0450 %ID, and strips the extra whitespace and redundant parenthesis introduced
    -0451 %by COBRA, to create grRules.
    -0452 grRules = strrep(model.rules,'&','and');
    -0453 grRules = strrep(grRules,'|','or');
    -0454 for i = 1:length(model.genes)
    -0455     grRules = strrep(grRules,['x(' num2str(i) ')'],model.genes{i});
    -0456 end
    -0457 grRules = strrep(grRules,'( ','(');
    -0458 grRules = strrep(grRules,' )',')');
    -0459 grRules = regexprep(grRules,'^(',''); %rules that start with a "("
    -0460 grRules = regexprep(grRules,')$',''); %rules that end with a ")"
    -0461 end

    +0420 if printWarning +0421 fprintf('Could not determine whether PubChemIDs are compounds (CID)\n or substances (SID). All annotated PubChemIDs will therefore \n be assigned as compounds (CID).\n'); +0422 end +0423 end +0424 +0425 % Order fields +0426 newModel=standardizeModelFieldOrder(newModel); % Corrects for both RAVEN and COBRA models +0427 end +0428 +0429 function rules=grrulesToRules(model) +0430 %Changes all gene names in grRules to 'x(geneNumber)' and changes 'or' and +0431 %'and' relations to the corresponding symbols +0432 replacingGenes=cell([size(model.genes,1) 1]); +0433 for i=1:numel(replacingGenes) +0434 replacingGenes{i}=strcat('x(',num2str(i),')'); +0435 end +0436 rules = strcat({' '},model.grRules,{' '}); +0437 for i=1:length(model.genes) +0438 %Escaped: a gene id is arbitrary model text, not a regex pattern, and +0439 %commonly contains metacharacters (e.g. "ENSG00000141510.16", where an +0440 %unescaped "." would match any character instead of a literal dot). +0441 escapedGene=regexptranslate('escape',model.genes{i}); +0442 rules=regexprep(rules,[' ' escapedGene ' '],[' ' replacingGenes{i} ' ']); +0443 rules=regexprep(rules,['(' escapedGene ' '],['(' replacingGenes{i} ' ']); +0444 rules=regexprep(rules,[' ' escapedGene ')'],[' ' replacingGenes{i} ')']); +0445 end +0446 rules=regexprep(rules,' and ',' & '); +0447 rules=regexprep(rules,' or ',' | '); +0448 rules=strtrim(rules); +0449 end +0450 +0451 function grRules=rulesTogrrules(model) +0452 %Replaces &/| with and/or, replaces the x(i) format with the actual gene +0453 %ID, and strips the extra whitespace and redundant parenthesis introduced +0454 %by COBRA, to create grRules. +0455 grRules = strrep(model.rules,'&','and'); +0456 grRules = strrep(grRules,'|','or'); +0457 for i = 1:length(model.genes) +0458 grRules = strrep(grRules,['x(' num2str(i) ')'],model.genes{i}); +0459 end +0460 grRules = strrep(grRules,'( ','('); +0461 grRules = strrep(grRules,' )',')'); +0462 grRules = regexprep(grRules,'^(',''); %rules that start with a "(" +0463 grRules = regexprep(grRules,')$',''); %rules that end with a ")" +0464 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/conversion/removeIdentifierPrefix.html b/docs/html/conversion/removeIdentifierPrefix.html index ba99e7a1..11639553 100644 --- a/docs/html/conversion/removeIdentifierPrefix.html +++ b/docs/html/conversion/removeIdentifierPrefix.html @@ -31,15 +31,15 @@

    DESCRIPTION ^DESCRIPTION ^SOURCE CODE ^% 0004 % Removes identifier prefixes: 0005 % -0006 % "R_" for model.rxns, model.rxnNames and model.id, -0007 % "M_" for model.mets and model.metNames, +0006 % "R_" for model.rxns and model.rxnNames, +0007 % "M_" for model.mets, model.metNames and model.id, 0008 % "C_" for model.comps; 0009 % "G_" for model.genes (and also represented in model.grRules). 0010 % 0011 % By default, the prefixes are only removed if all entries in a particular -0012 % field have the prefix. The prefixes might have been present because one -0013 % or more identifiers do not start with a letter or _, which conflicts -0014 % with SBML specifications. +0012 % field have the prefix. The prefixes are required when one or more +0013 % identifiers do not start with a letter or _, which conflicts with SBML +0014 % specifications. 0015 % 0016 % Parameters 0017 % ---------- @@ -104,20 +104,20 @@

    SOURCE CODE ^% Name-Value Arguments 0022 % -------------------- 0023 % fields : cell -0024 % cell array with model field names from which the identifiers should -0025 % be removed, possible values: 'rxns', 'mets', 'comps', 'genes', -0026 % 'metNames', 'rxnNames', 'id' (default all listed model fields will -0027 % be checked). -0028 % forceRemove : logical -0029 % if prefixes should be removed even if not all entries in a model -0030 % field have the prefix (default false). -0031 % -0032 % Returns -0033 % ------- -0034 % model : struct -0035 % modified model. -0036 % hasChanged : cell -0037 % cell array with fields and prefixes that are removed. +0024 % cell array with the names of the model fields from which the prefix +0025 % is removed, possible values: 'rxns', 'mets', 'comps', 'genes', +0026 % 'metNames', 'rxnNames', 'id' (default all of these fields). +0027 % forceRemove : logical +0028 % true to remove the prefix from the entries that have it when not all +0029 % entries in a model field have the prefix (default false). +0030 % +0031 % Returns +0032 % ------- +0033 % model : struct +0034 % modified model. +0035 % hasChanged : cell +0036 % cell array of char with one entry per field from which a prefix is +0037 % removed, for example 'model.rxns (R_ prefix)'. 0038 % 0039 % Examples 0040 % -------- diff --git a/docs/html/conversion/standardizeModelFieldOrder.html b/docs/html/conversion/standardizeModelFieldOrder.html index bd9e1bae..13a7bd13 100644 --- a/docs/html/conversion/standardizeModelFieldOrder.html +++ b/docs/html/conversion/standardizeModelFieldOrder.html @@ -29,12 +29,15 @@

    SYNOPSIS ^DESCRIPTION ^

     standardizeModelFieldOrder  Order RAVEN model structure fields.
     
    - Orders fields of a RAVEN model structure as specified at
    + Orders the fields of a RAVEN or COBRA model structure. The RAVEN order is
    + the one listed in orderRavenFields.csv, described at
      https://raven-docs.readthedocs.io/en/latest/guide/model-structure.html
    + A structure with the field 'rules' is ordered as a COBRA structure, using
    + COBRA_structure_fields.csv. Fields that are not listed in the file are
    + placed last, in alphabetical order.
     
    - The model fields themselves are not changed, only the order is modified.
    - For changing model fields between RAVEN and COBRA format, use
    - ravenCobraWrapper().
    + The content of the fields is unchanged. To convert model fields between
    + RAVEN and COBRA format, use ravenCobraWrapper().
     
      Parameters
      ----------
    @@ -72,76 +75,79 @@ 

    SOURCE CODE ^
    0001 function orderedModel=standardizeModelFieldOrder(model)
     0002 % standardizeModelFieldOrder  Order RAVEN model structure fields.
     0003 %
    -0004 % Orders fields of a RAVEN model structure as specified at
    -0005 % https://raven-docs.readthedocs.io/en/latest/guide/model-structure.html
    -0006 %
    -0007 % The model fields themselves are not changed, only the order is modified.
    -0008 % For changing model fields between RAVEN and COBRA format, use
    -0009 % ravenCobraWrapper().
    +0004 % Orders the fields of a RAVEN or COBRA model structure. The RAVEN order is
    +0005 % the one listed in orderRavenFields.csv, described at
    +0006 % https://raven-docs.readthedocs.io/en/latest/guide/model-structure.html
    +0007 % A structure with the field 'rules' is ordered as a COBRA structure, using
    +0008 % COBRA_structure_fields.csv. Fields that are not listed in the file are
    +0009 % placed last, in alphabetical order.
     0010 %
    -0011 % Parameters
    -0012 % ----------
    -0013 % model : struct
    -0014 %     model structure, either RAVEN or COBRA format.
    -0015 %
    -0016 % Returns
    -0017 % -------
    -0018 % orderedModel : struct
    -0019 %     model structure with ordered fields.
    -0020 %
    -0021 % Examples
    -0022 % --------
    -0023 %     orderedModel = standardizeModelFieldOrder(model);
    -0024 %
    -0025 % See also
    -0026 % --------
    -0027 % ravenCobraWrapper
    -0028 
    -0029 ravenPath=findRAVENroot();
    -0030 
    -0031 if ~isfield(model,'rules') % Check if model is RAVEN
    -0032     fid = fopen(fullfile(ravenPath,'src','conversion','orderRavenFields.csv'));
    -0033     fields = textscan(fid,'%s','Delimiter',',','HeaderLines',0);
    -0034     fields = fields{1};
    -0035     fclose(fid);
    -0036 else % If model is COBRA
    -0037     fid = fopen(fullfile(ravenPath,'src','conversion','COBRA_structure_fields.csv')); % Taken from https://github.com/opencobra/cobratoolbox/blob/develop/src/base/io/definitions/COBRA_structure_fields.csv
    -0038     fields = textscan(fid,repmat('%s',1,15),'Delimiter','\t','HeaderLines',1);
    -0039     fields = fields{1};
    -0040     fclose(fid);
    -0041 end
    -0042 
    -0043 modelfields = fieldnames(model);
    -0044 order = fields(ismember(fields(:,1),modelfields));
    -0045 remainingOrder = sort(setdiff(modelfields,order));
    -0046 overallOrder = [columnVector(order);columnVector(remainingOrder)];
    -0047 orderedModel = orderfields(model,overallOrder);
    -0048 end
    -0049 
    -0050 function vecT = columnVector(vec)
    -0051 % Converts a vector to a column vector
    -0052 %
    -0053 % USAGE:
    -0054 %
    -0055 %   vecT = columnVector(vec)
    -0056 %
    -0057 % INPUT:
    -0058 %   vec:     a vector
    +0011 % The content of the fields is unchanged. To convert model fields between
    +0012 % RAVEN and COBRA format, use ravenCobraWrapper().
    +0013 %
    +0014 % Parameters
    +0015 % ----------
    +0016 % model : struct
    +0017 %     model structure, either RAVEN or COBRA format.
    +0018 %
    +0019 % Returns
    +0020 % -------
    +0021 % orderedModel : struct
    +0022 %     model structure with ordered fields.
    +0023 %
    +0024 % Examples
    +0025 % --------
    +0026 %     orderedModel = standardizeModelFieldOrder(model);
    +0027 %
    +0028 % See also
    +0029 % --------
    +0030 % ravenCobraWrapper
    +0031 
    +0032 ravenPath=findRAVENroot();
    +0033 
    +0034 if ~isfield(model,'rules') % Check if model is RAVEN
    +0035     fid = fopen(fullfile(ravenPath,'src','conversion','orderRavenFields.csv'));
    +0036     fields = textscan(fid,'%s','Delimiter',',','HeaderLines',0);
    +0037     fields = fields{1};
    +0038     fclose(fid);
    +0039 else % If model is COBRA
    +0040     fid = fopen(fullfile(ravenPath,'src','conversion','COBRA_structure_fields.csv')); % Taken from https://github.com/opencobra/cobratoolbox/blob/develop/src/base/io/definitions/COBRA_structure_fields.csv
    +0041     fields = textscan(fid,repmat('%s',1,15),'Delimiter','\t','HeaderLines',1);
    +0042     fields = fields{1};
    +0043     fclose(fid);
    +0044 end
    +0045 
    +0046 modelfields = fieldnames(model);
    +0047 order = fields(ismember(fields(:,1),modelfields));
    +0048 remainingOrder = sort(setdiff(modelfields,order));
    +0049 overallOrder = [columnVector(order);columnVector(remainingOrder)];
    +0050 orderedModel = orderfields(model,overallOrder);
    +0051 end
    +0052 
    +0053 function vecT = columnVector(vec)
    +0054 % Converts a vector to a column vector
    +0055 %
    +0056 % USAGE:
    +0057 %
    +0058 %   vecT = columnVector(vec)
     0059 %
    -0060 % OUTPUT:
    -0061 %   vecT:    a column vector
    +0060 % INPUT:
    +0061 %   vec:     a vector
     0062 %
    -0063 % .. Authors:
    -0064 %     - Markus Herrgard (COBRA Toolbox)
    -0065 
    -0066 [n, m] = size(vec);
    -0067 
    -0068 if (m ~= 1 && n < m) || n == 1
    -0069     vecT = vec';
    -0070 else
    -0071     vecT = vec;
    -0072 end
    -0073 end

    +0063 % OUTPUT: +0064 % vecT: a column vector +0065 % +0066 % .. Authors: +0067 % - Markus Herrgard (COBRA Toolbox) +0068 +0069 [n, m] = size(vec); +0070 +0071 if (m ~= 1 && n < m) || n == 1 +0072 vecT = vec'; +0073 else +0074 vecT = vec; +0075 end +0076 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/curation/curateModelFromTables.html b/docs/html/curation/curateModelFromTables.html index 763c8b41..5193baae 100644 --- a/docs/html/curation/curateModelFromTables.html +++ b/docs/html/curation/curateModelFromTables.html @@ -29,17 +29,16 @@

    SYNOPSIS ^DESCRIPTION ^

     curateModelFromTables  Curate or add mets, rxns and genes from tables.
     
    - Curate existing and/or add new metabolites, reactions and genes from
    + Curates existing metabolites, reactions and genes and adds new ones from
      tabular data files.
     
    - If the *.tsv files contain metabolites, reactions and/or genes that are
    - already present in the model, then information in the model will be
    - overwritten. Note that this includes empty annotations in the *.tsv
    - files, unless 'overwriteWithEmpty' is false! Metabolites are matched by
    - metaboliteName[comp]; reactions by the stoichiometry of its reactants and
    - products; genes by their gene name.
    - This function can therefore be used to add new entities in the model, or
    - curate those already existing in the model.
    + If the *.tsv files contain metabolites, reactions or genes that are
    + already present in the model, the information in the model is
    + overwritten. This includes empty annotations in the *.tsv files, unless
    + 'overwriteWithEmpty' is false. Metabolites are matched by
    + metaboliteName[comp]; reactions by the stoichiometry of their reactants
    + and products; genes by their gene name. Entities that are not present in
    + the model are added.
     
      Columns are identified by their header (case-insensitive, and spaces around
      a header are ignored, also inside its quotes), so their order
    @@ -77,27 +76,30 @@ 

    DESCRIPTION ^DESCRIPTION ^DESCRIPTION ^CROSS-REFERENCE INFORMATION ^

    @@ -167,728 +170,731 @@

    SOURCE CODE ^
    0001 function newModel=curateModelFromTables(model,metsInfo,varargin)
     0002 % curateModelFromTables  Curate or add mets, rxns and genes from tables.
     0003 %
    -0004 % Curate existing and/or add new metabolites, reactions and genes from
    +0004 % Curates existing metabolites, reactions and genes and adds new ones from
     0005 % tabular data files.
     0006 %
    -0007 % If the *.tsv files contain metabolites, reactions and/or genes that are
    -0008 % already present in the model, then information in the model will be
    -0009 % overwritten. Note that this includes empty annotations in the *.tsv
    -0010 % files, unless 'overwriteWithEmpty' is false! Metabolites are matched by
    -0011 % metaboliteName[comp]; reactions by the stoichiometry of its reactants and
    -0012 % products; genes by their gene name.
    -0013 % This function can therefore be used to add new entities in the model, or
    -0014 % curate those already existing in the model.
    -0015 %
    -0016 % Columns are identified by their header (case-insensitive, and spaces around
    -0017 % a header are ignored, also inside its quotes), so their order
    -0018 % does not matter and optional columns can be left out. Every field that a
    -0019 % table supplies is written to new and existing entities alike; a field the
    -0020 % model does not have yet is created, with its default value for the other
    -0021 % entities, unless all supplied values for it are empty. An empty grRules
    -0022 % cell leaves the rule of an existing reaction unchanged, and so does an
    -0023 % empty lb, ub or rev cell: an empty bound means that no bound is given.
    -0024 % A new reaction without a lb gets a lower bound of -Inf, and one without an
    -0025 % ub gets model.annotation.defaultUB (Inf if the model has none).
    -0026 %
    -0027 % Parameters
    -0028 % ----------
    -0029 % model : struct
    -0030 %     RAVEN model structure to be curated.
    -0031 % metsInfo : char
    -0032 %     Path to a *.tsv file with metabolite information, or 'none' to skip
    -0033 %     metabolite curation. Columns: metNames, comps (both required),
    -0034 %     formula, charge, inchi, metNotes, and any number of MIRIAM-namespace
    -0035 %     columns. Singular forms and the RAVEN field names (metFormulas,
    -0036 %     metCharges, inchis) are accepted as headers too.
    -0037 %
    -0038 % Name-Value Arguments
    -0039 % --------------------
    -0040 % genesInfo : char
    -0041 %     Path to a *.tsv file with gene information, or 'none'. Columns:
    -0042 %     genes (required), geneShortNames (or geneShortName), and MIRIAM.
    -0043 % rxnsCoeffs : char
    -0044 %     Path to a *.tsv file with reaction stoichiometric coefficients, or
    -0045 %     'none'. Columns (all required): rxnIdx (or index), rxnNames,
    -0046 %     metNames, comps, coefficient. One row per (reaction, metabolite)
    -0047 %     pair.
    -0048 % rxnsInfo : char
    -0049 %     Path to a *.tsv file with reaction information, or 'none'. Columns:
    -0050 %     rxnIdx (or index), rxnNames (both required), grRules, lb, ub, rev,
    -0051 %     subSystems, eccodes, rxnNotes, rxnReferences, rxnConfidenceScores,
    -0052 %     and MIRIAM. Each rxnIdx may be used by one row only (the rxnsCoeffs
    -0053 %     file has one row per metabolite of the reaction and repeats it).
    -0054 % metPrefix : char
    -0055 %     Prefix used to mint fresh metabolite ids (e.g. 's_' for yeast-GEM,
    -0056 %     'M_' for the BiGG default) (default 'M_').
    -0057 % rxnPrefix : char
    -0058 %     Prefix used to mint fresh reaction ids (default 'R_').
    -0059 % overwriteWithEmpty : logical
    -0060 %     if true, an empty cell in a row of an existing metabolite, gene or
    -0061 %     reaction empties the corresponding field of the model. If false, an
    -0062 %     empty cell leaves the existing value unchanged and only the cells with a
    -0063 %     value are written (default true; [] also means the default). Must be a
    -0064 %     scalar logical or 0/1. A cell with only blanks counts as empty in both
    -0065 %     modes. Empty grRules cells never overwrite. An empty or NaN lb, ub or
    -0066 %     rev cell always keeps the existing value. For the charge and the
    -0067 %     confidence score, an empty or NaN cell is treated alike: when
    -0068 %     overwriteWithEmpty is true and at least one other row of the column has
    -0069 %     a value, it becomes 0; a column in which no row has a value is not
    -0070 %     written, and the existing values stay. The same holds for a MIRIAM
    -0071 %     column: a column in which no row has a value does not clear the
    -0072 %     annotations of existing entities.
    -0073 %
    -0074 % Returns
    -0075 % -------
    -0076 % newModel : struct
    -0077 %     Curated RAVEN model structure.
    -0078 %
    -0079 % Examples
    -0080 % --------
    -0081 %     newModel = curateModelFromTables(model, metsInfo, genesInfo, ...
    -0082 %                     rxnsCoeffs, rxnsInfo, metPrefix, rxnPrefix);
    -0083 %
    -0084 % Notes
    -0085 % -----
    -0086 % In all three info tables, any column whose header is not one of the
    -0087 % listed core fields is treated as a MIRIAM annotation namespace and
    -0088 % stored on the matching entity.
    -0089 %
    -0090 % Cell values: the charge, confidence score, lb, ub, rev, rxnIdx and
    -0091 % coefficient cells are read as plain decimal numbers (digits with an
    -0092 % optional sign, decimal point and exponent), Inf or NaN in any case. Only
    -0093 % the lb, ub and rev cells also accept true and false in any case (read as
    -0094 % 1 and 0). Any other text, including text with a comma, hexadecimal numbers
    -0095 % and expressions, raises RAVEN:badInput; no expression is evaluated. A
    -0096 % charge must be a finite whole number (or empty or NaN). A rev must be 0, 1,
    -0097 % true or false (or empty or NaN). The rev column sets the reversibility of
    -0098 % existing reactions only, independently of lb and ub, which are written as
    -0099 % given; for new reactions the reversibility is derived from lb and ub.
    -0100 %
    -0101 % Gene rules: a grRules cell must be a well-formed gene rule (genes joined by
    -0102 % and/or in any case, or by &, &&, | and ||, with balanced parentheses); a
    -0103 % rule with an operator that lacks an operand, empty parentheses or two genes
    -0104 % without an operator between them raises RAVEN:badInput before the model is
    -0105 % changed. A valid rule is stored in RAVEN's standard format. Whether the
    -0106 % genes exist is checked when the rule is applied.
    -0107 %
    -0108 % Genes: a gene id that the genesInfo table lists more than once raises
    -0109 % RAVEN:badInput, whether the model has the gene or not.
    +0007 % If the *.tsv files contain metabolites, reactions or genes that are
    +0008 % already present in the model, the information in the model is
    +0009 % overwritten. This includes empty annotations in the *.tsv files, unless
    +0010 % 'overwriteWithEmpty' is false. Metabolites are matched by
    +0011 % metaboliteName[comp]; reactions by the stoichiometry of their reactants
    +0012 % and products; genes by their gene name. Entities that are not present in
    +0013 % the model are added.
    +0014 %
    +0015 % Columns are identified by their header (case-insensitive, and spaces around
    +0016 % a header are ignored, also inside its quotes), so their order
    +0017 % does not matter and optional columns can be left out. Every field that a
    +0018 % table supplies is written to new and existing entities alike; a field the
    +0019 % model does not have yet is created, with its default value for the other
    +0020 % entities, unless all supplied values for it are empty. An empty grRules
    +0021 % cell leaves the rule of an existing reaction unchanged, and so does an
    +0022 % empty lb, ub or rev cell: an empty bound means that no bound is given.
    +0023 % A new reaction without a lb gets a lower bound of -Inf, and one without an
    +0024 % ub gets model.annotation.defaultUB (Inf if the model has none).
    +0025 %
    +0026 % Parameters
    +0027 % ----------
    +0028 % model : struct
    +0029 %     RAVEN model structure to be curated.
    +0030 % metsInfo : char
    +0031 %     Path to a *.tsv file with metabolite information, or 'none' to skip
    +0032 %     metabolite curation. Columns: metNames, comps (both required),
    +0033 %     formula, charge, inchi, metNotes, and any number of MIRIAM-namespace
    +0034 %     columns. Singular forms and the RAVEN field names (metFormulas,
    +0035 %     metCharges, inchis) are accepted as headers too.
    +0036 %
    +0037 % Name-Value Arguments
    +0038 % --------------------
    +0039 % genesInfo : char
    +0040 %     Path to a *.tsv file with gene information, or 'none'. Columns:
    +0041 %     genes (required), geneShortNames (or geneShortName), and MIRIAM.
    +0042 % rxnsCoeffs : char
    +0043 %     Path to a *.tsv file with reaction stoichiometric coefficients, or
    +0044 %     'none'. Columns (all required): rxnIdx (or index), rxnNames,
    +0045 %     metNames, comps, coefficient. One row per (reaction, metabolite)
    +0046 %     pair.
    +0047 % rxnsInfo : char
    +0048 %     Path to a *.tsv file with reaction information, or 'none'. Columns:
    +0049 %     rxnIdx (or index), rxnNames (both required), grRules, lb, ub, rev,
    +0050 %     subSystems, eccodes, rxnNotes, rxnReferences, rxnConfidenceScores,
    +0051 %     and MIRIAM. An index appears in at most one row of this table,
    +0052 %     otherwise RAVEN:badInput is raised (the rxnsCoeffs file has one row per
    +0053 %     metabolite of the reaction and repeats it). The two reaction tables
    +0054 %     are matched on index and name, and reactions or indices that appear
    +0055 %     in only one of them raise RAVEN:badInput.
    +0056 % metPrefix : char
    +0057 %     Prefix used to create new metabolite ids (for example 's_' for
    +0058 %     yeast-GEM, 'M_' for the BiGG default) (default 'M_').
    +0059 % rxnPrefix : char
    +0060 %     Prefix used to create new reaction ids (default 'R_').
    +0061 % overwriteWithEmpty : logical
    +0062 %     if true, an empty cell in a row of an existing metabolite, gene or
    +0063 %     reaction empties the corresponding field of the model. If false, an
    +0064 %     empty cell leaves the existing value unchanged and only the cells with a
    +0065 %     value are written (default true; [] also means the default). Must be a
    +0066 %     scalar logical, 0 or 1. A cell with only blanks counts as empty in both
    +0067 %     modes. Empty grRules cells never overwrite. An empty or NaN lb, ub or
    +0068 %     rev cell always keeps the existing value. For the charge and the
    +0069 %     confidence score, an empty or NaN cell is treated alike: when
    +0070 %     overwriteWithEmpty is true and at least one other row of the column has
    +0071 %     a value, it becomes 0; a column in which no row has a value is not
    +0072 %     written, and the existing values stay. The same holds for a MIRIAM
    +0073 %     column: a column in which no row of an existing entity has a value
    +0074 %     does not clear the annotations.
    +0075 %
    +0076 % Returns
    +0077 % -------
    +0078 % newModel : struct
    +0079 %     Curated RAVEN model structure.
    +0080 %
    +0081 % Examples
    +0082 % --------
    +0083 %     newModel = curateModelFromTables(model, metsInfo, genesInfo, ...
    +0084 %                     rxnsCoeffs, rxnsInfo, metPrefix, rxnPrefix);
    +0085 %
    +0086 % Notes
    +0087 % -----
    +0088 % In all three info tables, any column whose header is not one of the
    +0089 % listed core fields is treated as a MIRIAM annotation namespace and
    +0090 % stored on the matching entity.
    +0091 %
    +0092 % Cell values: the charge, confidence score, lb, ub, rev, rxnIdx and
    +0093 % coefficient cells are read as plain decimal numbers (digits with an
    +0094 % optional sign, decimal point and exponent), Inf or NaN in any case. Only
    +0095 % the lb, ub and rev cells also accept true and false in any case (read as
    +0096 % 1 and 0). Any other text, including text with a comma, hexadecimal numbers
    +0097 % and expressions, raises RAVEN:badInput; no expression is evaluated. A
    +0098 % charge must be a finite whole number (or empty or NaN). A rev must be 0, 1,
    +0099 % true or false (or empty or NaN). The rev column sets the reversibility of
    +0100 % existing reactions only, independently of lb and ub, which are written as
    +0101 % given; for new reactions the reversibility is derived from lb and ub.
    +0102 %
    +0103 % Gene rules: a grRules cell must be a well-formed gene rule (genes joined by
    +0104 % the words and or or in any case, or by &, &&, | or ||, with balanced
    +0105 % parentheses); a rule with an operator that lacks an operand, empty
    +0106 % parentheses or two genes without an operator between them raises
    +0107 % RAVEN:badInput before the model is changed. A valid rule is stored in
    +0108 % RAVEN's standard format. Whether the genes exist is checked when the rule
    +0109 % is applied.
     0110 %
    -0111 % Bounds: a lower bound above the upper bound is written as given. solveLP
    -0112 % rejects such a model when it is run ("Invalid set of bounds for
    -0113 % reaction(s): ...").
    -0114 %
    -0115 % Reaction tables: a row of the rxnsCoeffs table without a rxnIdx, rxnNames,
    -0116 % metNames and comps value is ignored; a row with any of them needs a
    -0117 % rxnIdx and a coefficient that is a finite number other than 0. A row of
    -0118 % the rxnsInfo table without any value is ignored; a row with a value needs
    -0119 % a rxnIdx. A rxnIdx is a whole number of at least 1; the indices (and
    -0120 % names) of the two reaction tables must match exactly, compared by their
    -0121 % numeric value (1, 1.0 and 1e0 are the same index). Otherwise RAVEN:badInput
    -0122 % is raised.
    -0123 
    -0124 p=parseRAVENargs(varargin, {'genesInfo','none'; 'rxnsCoeffs','none'; 'rxnsInfo','none'; 'metPrefix',[]; 'rxnPrefix',[]; 'overwriteWithEmpty',true});
    -0125 overwriteWithEmpty=p.overwriteWithEmpty;
    -0126 if isempty(overwriteWithEmpty)
    -0127     overwriteWithEmpty=true;
    -0128 end
    -0129 if ~((islogical(overwriteWithEmpty) || isnumeric(overwriteWithEmpty)) && isscalar(overwriteWithEmpty) ...
    -0130         && isreal(overwriteWithEmpty) && ~isnan(overwriteWithEmpty) && ismember(overwriteWithEmpty,[0 1]))
    -0131     error('RAVEN:badInput','overwriteWithEmpty must be a scalar logical or 0/1');
    -0132 end
    -0133 overwriteWithEmpty=logical(overwriteWithEmpty);
    -0134 genesInfo=p.genesInfo;
    -0135 rxnsCoeffs=p.rxnsCoeffs;
    -0136 rxnsInfo=p.rxnsInfo;
    -0137 metPrefix=p.metPrefix;
    -0138 if isempty(metPrefix)
    -0139     metPrefix = 'M_';
    -0140 end
    -0141 rxnPrefix=p.rxnPrefix;
    -0142 if isempty(rxnPrefix)
    -0143     rxnPrefix = 'R_';
    -0144 end
    -0145 if xor(strcmp(rxnsCoeffs,'none'), strcmp(rxnsInfo,'none'))
    -0146     error('RAVEN:badInput','Provide both a ''rxnsInfo'' and a ''rxnsCoeffs'' file')
    +0111 % Genes: a gene id that the genesInfo table lists more than once raises
    +0112 % RAVEN:badInput, whether the model has the gene or not.
    +0113 %
    +0114 % Bounds: a lower bound above the upper bound is written as given. solveLP
    +0115 % rejects such a model when it is run ("Invalid set of bounds for
    +0116 % reaction(s): ...").
    +0117 %
    +0118 % Reaction tables: a row of the rxnsCoeffs table without a rxnIdx, rxnNames,
    +0119 % metNames and comps value is ignored; a row with any of them needs a
    +0120 % rxnIdx and a coefficient that is a finite number other than 0. A row of
    +0121 % the rxnsInfo table without any value is ignored; a row with a value needs
    +0122 % a rxnIdx. A rxnIdx is a whole number of at least 1; the indices (and
    +0123 % names) of the two reaction tables must match, with indices compared by
    +0124 % their numeric value (1, 1.0 and 1e0 are the same index). Otherwise
    +0125 % RAVEN:badInput is raised.
    +0126 
    +0127 p=parseRAVENargs(varargin, {'genesInfo','none'; 'rxnsCoeffs','none'; 'rxnsInfo','none'; 'metPrefix',[]; 'rxnPrefix',[]; 'overwriteWithEmpty',true});
    +0128 overwriteWithEmpty=p.overwriteWithEmpty;
    +0129 if isempty(overwriteWithEmpty)
    +0130     overwriteWithEmpty=true;
    +0131 end
    +0132 if ~((islogical(overwriteWithEmpty) || isnumeric(overwriteWithEmpty)) && isscalar(overwriteWithEmpty) ...
    +0133         && isreal(overwriteWithEmpty) && ~isnan(overwriteWithEmpty) && ismember(overwriteWithEmpty,[0 1]))
    +0134     error('RAVEN:badInput','overwriteWithEmpty must be a scalar logical or 0/1');
    +0135 end
    +0136 overwriteWithEmpty=logical(overwriteWithEmpty);
    +0137 genesInfo=p.genesInfo;
    +0138 rxnsCoeffs=p.rxnsCoeffs;
    +0139 rxnsInfo=p.rxnsInfo;
    +0140 metPrefix=p.metPrefix;
    +0141 if isempty(metPrefix)
    +0142     metPrefix = 'M_';
    +0143 end
    +0144 rxnPrefix=p.rxnPrefix;
    +0145 if isempty(rxnPrefix)
    +0146     rxnPrefix = 'R_';
     0147 end
    -0148 newModel=model;
    -0149 
    -0150 %% Metabolites
    -0151 if ~strcmp(metsInfo,'none')
    -0152     [header,data] = readCurationTable(metsInfo);
    -0153     [col,miriamCols] = matchColumns(header,metsInfo,{'metNames','comps'},{ ...
    -0154         'metNames',    {'metNames','metName'}; ...
    -0155         'comps',       {'comps','comp'}; ...
    -0156         'metFormulas', {'formula','formulas','metFormula','metFormulas'}; ...
    -0157         'metCharges',  {'charge','charges','metCharge','metCharges'}; ...
    -0158         'inchis',      {'inchi','inchis'}; ...
    -0159         'metNotes',    {'metNotes','metNote'}});
    -0160     metsToAdd.metNames      = data(:,col.metNames);
    -0161     metsToAdd.compartments  = data(:,col.comps);
    -0162     textFields = {'metFormulas','inchis','metNotes'};
    -0163     for f = textFields(isfield(col,textFields))
    -0164         metsToAdd.(f{1}) = data(:,col.(f{1}));
    -0165     end
    -0166     if isfield(col,'metCharges')
    -0167         metsToAdd.metCharges = cellfun(@(x) parseChargeCell(x,'metsInfo'),data(:,col.metCharges),'UniformOutput',false);
    -0168         isNanCharge = cellfun(@(x) isscalar(x) && isnan(x),metsToAdd.metCharges);
    -0169         metsToAdd.metCharges(isNanCharge) = {[]}; % a NaN charge counts as not given
    -0170         emptyEntry = cellfun(@isempty,metsToAdd.metCharges);
    -0171         emptyCharge = emptyEntry;
    -0172         if all(emptyEntry)
    -0173             metsToAdd       = rmfield(metsToAdd,'metCharges');
    -0174         else % If some charges are given, assume 0 for those without charges specified
    -0175             metsToAdd.metCharges(emptyEntry) = {0};
    -0176             metsToAdd.metCharges = cell2mat(metsToAdd.metCharges);
    -0177         end
    -0178     end
    -0179     optFields = intersect({'metFormulas','metCharges','inchis','metNotes'},fieldnames(metsToAdd)','stable');
    -0180 
    -0181     % Check if metabolite already exists (check by metName[comp])
    -0182     existingMets            = [];
    -0183     existingMetsIdx         = [];
    -0184     newMets                 = [];
    -0185     for i=1:numel(metsToAdd.metNames)
    -0186         metIdx              = find(strcmp(newModel.metNames,metsToAdd.metNames{i}));
    -0187         existMet            = strcmp(metsToAdd.compartments{i},newModel.comps(newModel.metComps(metIdx)));
    -0188         if any(existMet)
    -0189             existingMets    = [existingMets, i];
    -0190             existingMetsIdx = [existingMetsIdx, metIdx(existMet)];
    -0191         else
    -0192             newMets    = [newMets, i];
    -0193         end
    -0194     end
    -0195 
    -0196     % Overwrite annotation of existing entries
    -0197     if any(existingMets)
    -0198         for f = optFields
    -0199             skip = [];
    -0200             if ~overwriteWithEmpty
    -0201                 if strcmp(f{1},'metCharges')
    -0202                     skip = emptyCharge(existingMets);
    -0203                 else
    -0204                     skip = emptyCells(metsToAdd.(f{1})(existingMets));
    -0205                 end
    -0206             end
    -0207             newModel = writeExistingField(newModel,f{1},existingMetsIdx,metsToAdd.(f{1})(existingMets),skip);
    -0208         end
    -0209         newModel = extractAndAddMiriam(newModel,header(miriamCols),data(:,miriamCols),existingMets,existingMetsIdx,'met',~overwriteWithEmpty);
    -0210         warning(['The following metabolites are already present in the model, '...
    -0211             'their annotation will be overwritten to match the metsInfo file. '...
    -0212             'If you do not particularly want to curate their annotations, it '...
    -0213             'would be better to remove these metabolites from metsInfo:\n\t\t%s'],...
    -0214             strjoin(strcat(metsToAdd.metNames(existingMets),'[',metsToAdd.compartments(existingMets),']'),'\n\t\t'));
    -0215     end
    -0216 
    -0217     % Continue with new metabolites
    -0218     if numel(newMets)>0
    -0219         metsToAdd.metNames      = metsToAdd.metNames(newMets);
    -0220         metsToAdd.compartments  = metsToAdd.compartments(newMets);
    -0221         for f = optFields
    -0222             metsToAdd.(f{1}) = metsToAdd.(f{1})(newMets);
    -0223             if iscell(metsToAdd.(f{1})) && all(cellfun(@isempty,metsToAdd.(f{1})))
    -0224                 metsToAdd = rmfield(metsToAdd,f{1});
    -0225             end
    -0226         end
    -0227         % Add metabolites
    -0228         newModel        = addMets(newModel,metsToAdd,true,metPrefix);
    -0229         addedIdx        = numel(newModel.mets)-numel(newMets)+1:numel(newModel.mets);
    -0230         newModel        = extractAndAddMiriam(newModel,header(miriamCols),data(:,miriamCols),newMets,addedIdx,'met');
    -0231     end
    -0232 end
    -0233 %% Genes
    -0234 if ~strcmp(genesInfo,'none')
    -0235     [header,data] = readCurationTable(genesInfo);
    -0236     [col,miriamCols] = matchColumns(header,genesInfo,{'genes'},{ ...
    -0237         'genes',          {'genes','gene'}; ...
    -0238         'geneShortNames', {'geneShortNames','geneShortName'}});
    -0239     genesToAdd.genes            = data(:,col.genes);
    -0240     checkGenesListedOnce(genesToAdd.genes);
    -0241     if isfield(col,'geneShortNames')
    -0242         genesToAdd.geneShortNames   = data(:,col.geneShortNames);
    -0243     end
    -0244 
    -0245     [~,notNewGene,existingGene] = intersect(genesToAdd.genes,newModel.genes);
    -0246     if ~isempty(notNewGene)
    -0247         warning(['The following genes are already present in the model, their annotation '...
    -0248             'will be overwritten to match the genesInfo file: \n\t\t%s'],...
    -0249             strjoin(genesToAdd.genes(notNewGene),'\n\t\t'));
    -0250         if isfield(genesToAdd,'geneShortNames')
    -0251             skip = [];
    -0252             if ~overwriteWithEmpty
    -0253                 skip = emptyCells(genesToAdd.geneShortNames(notNewGene));
    -0254             end
    -0255             newModel = writeExistingField(newModel,'geneShortNames',existingGene,genesToAdd.geneShortNames(notNewGene),skip);
    -0256         end
    -0257         newModel = extractAndAddMiriam(newModel,header(miriamCols),data(:,miriamCols),notNewGene,existingGene,'gene',~overwriteWithEmpty);
    -0258     end
    -0259 
    -0260     % Continue with new genes
    -0261     toAdd               = 1:numel(genesToAdd.genes);
    -0262     toAdd(notNewGene)   = [];
    -0263     if ~isempty(toAdd)
    -0264         genesToAdd.genes            = genesToAdd.genes(toAdd);
    -0265         if isfield(genesToAdd,'geneShortNames')
    -0266             genesToAdd.geneShortNames   = genesToAdd.geneShortNames(toAdd);
    -0267             if all(cellfun(@isempty,genesToAdd.geneShortNames))
    -0268                 genesToAdd = rmfield(genesToAdd,'geneShortNames');
    -0269             end
    -0270         end
    -0271         % Add genes
    -0272         newModel = addGenesRaven(newModel,genesToAdd);
    -0273         addedIdx = numel(newModel.genes)-numel(toAdd)+1:numel(newModel.genes);
    -0274         newModel = extractAndAddMiriam(newModel,header(miriamCols),data(:,miriamCols),toAdd,addedIdx,'gene');
    -0275     end
    -0276 end
    -0277 %% Reactions
    -0278 if ~any(strcmp({rxnsCoeffs,rxnsInfo},'none'))
    -0279     % Gather all data, first about reaction stoichiometries...
    -0280     [header,data] = readCurationTable(rxnsCoeffs);
    -0281     col = matchColumns(header,rxnsCoeffs,{'rxnIdx','rxnNames','metNames','comps','coefficient'},{ ...
    -0282         'rxnIdx',      {'rxnIdx','index'}; ...
    -0283         'rxnNames',    {'rxnNames','rxnName'}; ...
    -0284         'metNames',    {'metNames','metName'}; ...
    -0285         'comps',       {'comps','comp'}; ...
    -0286         'coefficient', {'coefficient','coefficients','coeffs','stoichCoeffs'}});
    -0287     % A row without a rxnIdx, rxnNames, metNames and comps value is ignored
    -0288     rowNo = (1:size(data,1))';
    -0289     keyCells = data(:,[col.rxnIdx,col.rxnNames,col.metNames,col.comps]);
    -0290     ignoredRow = all(reshape(emptyCells(keyCells(:)),size(keyCells)),2);
    -0291     data(ignoredRow,:) = [];
    -0292     rowNo(ignoredRow) = [];
    -0293     rxnCheck.coeffsIdx = parseIndexCells(data(:,col.rxnIdx),'rxnIdx','rxnsCoeffs');
    -0294     rxns.rxnNames   = data(:,col.rxnNames);
    -0295     rxns.metNames   = data(:,col.metNames);
    -0296     rxns.comps      = data(:,col.comps);
    -0297     rxns.coeffs     = parseNumericCells(data(:,col.coefficient),'coefficient','rxnsCoeffs');
    -0298     checkCoefficientsGiven(rxnCheck.coeffsIdx,rxns.coeffs,rowNo);
    -0299     rxnCheck.coeffs = indexKeys(rxnCheck.coeffsIdx,rxns.rxnNames);
    -0300 
    -0301     % ... and then additional reaction-specific data.
    -0302     [header,data] = readCurationTable(rxnsInfo);
    -0303     [col,miriamCols] = matchColumns(header,rxnsInfo,{'rxnIdx','rxnNames'},{ ...
    -0304         'rxnIdx',              {'rxnIdx','index'}; ...
    -0305         'rxnNames',            {'rxnNames','rxnName'}; ...
    -0306         'grRules',             {'grRules'}; ...
    -0307         'lb',                  {'lb'}; ...
    -0308         'ub',                  {'ub'}; ...
    -0309         'rev',                 {'rev'}; ...
    -0310         'subSystems',          {'subSystems'}; ...
    -0311         'eccodes',             {'eccodes'}; ...
    -0312         'rxnNotes',            {'rxnNotes'}; ...
    -0313         'rxnReferences',       {'rxnReferences'}; ...
    -0314         'rxnConfidenceScores', {'rxnConfidenceScores'}});
    -0315     % A row without any value is ignored; a row with a value needs a rxnIdx
    -0316     rowNo = (1:size(data,1))';
    -0317     ignoredRow = all(reshape(emptyCells(data(:)),size(data)),2);
    -0318     data(ignoredRow,:) = [];
    -0319     rowNo(ignoredRow) = [];
    -0320     rxnCheck.rxnsIdx = parseIndexCells(data(:,col.rxnIdx),'rxnIdx','rxnsInfo');
    -0321     noIdx = find(isnan(rxnCheck.rxnsIdx),1);
    -0322     if ~isempty(noIdx)
    -0323         error('RAVEN:badInput','Data row %d of the rxnsInfo file has values but its rxnIdx is empty or NaN',rowNo(noIdx));
    -0324     end
    -0325     [uniqueIdx,~,idxGroup] = unique(rxnCheck.rxnsIdx);
    -0326     repeated = find(accumarray(idxGroup,1)>1,1);
    -0327     if ~isempty(repeated)
    -0328         error('RAVEN:badInput','The rxnIdx %s is used by data rows %s of the rxnsInfo file; each reaction needs its own index', ...
    -0329             num2str(uniqueIdx(repeated)),strjoin(arrayfun(@num2str,rowNo(idxGroup==repeated)','UniformOutput',false),', '));
    -0330     end
    -0331     rxnCheck.rxns = indexKeys(rxnCheck.rxnsIdx,data(:,col.rxnNames));
    -0332 
    -0333     notMatching = setxor(rxnCheck.coeffs,rxnCheck.rxns);
    -0334     if ~isempty(notMatching)
    -0335         error('RAVEN:badInput',['The following reactions and/or their indices are not matched '...
    -0336             'between the rxnsInfo and rxnsCoeffs files:\n\t\t%s'],...
    -0337             strjoin(regexprep(notMatching,'^(\d+)\*\*\*(.*)$','index $1 ($2)'),'\n\t\t'))
    -0338     end
    -0339 
    -0340     rxnsToAdd.rxnNames              = data(:,col.rxnNames);
    -0341     for f = intersect({'grRules','subSystems','eccodes','rxnNotes','rxnReferences'},fieldnames(col)','stable')
    -0342         rxnsToAdd.(f{1}) = data(:,col.(f{1}));
    -0343     end
    -0344     if isfield(col,'grRules')
    -0345         rxnsToAdd.grRules = standardGrRules(rxnsToAdd.grRules,rxnsToAdd.rxnNames,rowNo);
    +0148 if xor(strcmp(rxnsCoeffs,'none'), strcmp(rxnsInfo,'none'))
    +0149     error('RAVEN:badInput','Provide both a ''rxnsInfo'' and a ''rxnsCoeffs'' file')
    +0150 end
    +0151 newModel=model;
    +0152 
    +0153 %% Metabolites
    +0154 if ~strcmp(metsInfo,'none')
    +0155     [header,data] = readCurationTable(metsInfo);
    +0156     [col,miriamCols] = matchColumns(header,metsInfo,{'metNames','comps'},{ ...
    +0157         'metNames',    {'metNames','metName'}; ...
    +0158         'comps',       {'comps','comp'}; ...
    +0159         'metFormulas', {'formula','formulas','metFormula','metFormulas'}; ...
    +0160         'metCharges',  {'charge','charges','metCharge','metCharges'}; ...
    +0161         'inchis',      {'inchi','inchis'}; ...
    +0162         'metNotes',    {'metNotes','metNote'}});
    +0163     metsToAdd.metNames      = data(:,col.metNames);
    +0164     metsToAdd.compartments  = data(:,col.comps);
    +0165     textFields = {'metFormulas','inchis','metNotes'};
    +0166     for f = textFields(isfield(col,textFields))
    +0167         metsToAdd.(f{1}) = data(:,col.(f{1}));
    +0168     end
    +0169     if isfield(col,'metCharges')
    +0170         metsToAdd.metCharges = cellfun(@(x) parseChargeCell(x,'metsInfo'),data(:,col.metCharges),'UniformOutput',false);
    +0171         isNanCharge = cellfun(@(x) isscalar(x) && isnan(x),metsToAdd.metCharges);
    +0172         metsToAdd.metCharges(isNanCharge) = {[]}; % a NaN charge counts as not given
    +0173         emptyEntry = cellfun(@isempty,metsToAdd.metCharges);
    +0174         emptyCharge = emptyEntry;
    +0175         if all(emptyEntry)
    +0176             metsToAdd       = rmfield(metsToAdd,'metCharges');
    +0177         else % If some charges are given, assume 0 for those without charges specified
    +0178             metsToAdd.metCharges(emptyEntry) = {0};
    +0179             metsToAdd.metCharges = cell2mat(metsToAdd.metCharges);
    +0180         end
    +0181     end
    +0182     optFields = intersect({'metFormulas','metCharges','inchis','metNotes'},fieldnames(metsToAdd)','stable');
    +0183 
    +0184     % Check if metabolite already exists (check by metName[comp])
    +0185     existingMets            = [];
    +0186     existingMetsIdx         = [];
    +0187     newMets                 = [];
    +0188     for i=1:numel(metsToAdd.metNames)
    +0189         metIdx              = find(strcmp(newModel.metNames,metsToAdd.metNames{i}));
    +0190         existMet            = strcmp(metsToAdd.compartments{i},newModel.comps(newModel.metComps(metIdx)));
    +0191         if any(existMet)
    +0192             existingMets    = [existingMets, i];
    +0193             existingMetsIdx = [existingMetsIdx, metIdx(existMet)];
    +0194         else
    +0195             newMets    = [newMets, i];
    +0196         end
    +0197     end
    +0198 
    +0199     % Overwrite annotation of existing entries
    +0200     if any(existingMets)
    +0201         for f = optFields
    +0202             skip = [];
    +0203             if ~overwriteWithEmpty
    +0204                 if strcmp(f{1},'metCharges')
    +0205                     skip = emptyCharge(existingMets);
    +0206                 else
    +0207                     skip = emptyCells(metsToAdd.(f{1})(existingMets));
    +0208                 end
    +0209             end
    +0210             newModel = writeExistingField(newModel,f{1},existingMetsIdx,metsToAdd.(f{1})(existingMets),skip);
    +0211         end
    +0212         newModel = extractAndAddMiriam(newModel,header(miriamCols),data(:,miriamCols),existingMets,existingMetsIdx,'met',~overwriteWithEmpty);
    +0213         warning(['The following metabolites are already present in the model, '...
    +0214             'their annotation will be overwritten to match the metsInfo file. '...
    +0215             'If you do not particularly want to curate their annotations, it '...
    +0216             'would be better to remove these metabolites from metsInfo:\n\t\t%s'],...
    +0217             strjoin(strcat(metsToAdd.metNames(existingMets),'[',metsToAdd.compartments(existingMets),']'),'\n\t\t'));
    +0218     end
    +0219 
    +0220     % Continue with new metabolites
    +0221     if numel(newMets)>0
    +0222         metsToAdd.metNames      = metsToAdd.metNames(newMets);
    +0223         metsToAdd.compartments  = metsToAdd.compartments(newMets);
    +0224         for f = optFields
    +0225             metsToAdd.(f{1}) = metsToAdd.(f{1})(newMets);
    +0226             if iscell(metsToAdd.(f{1})) && all(cellfun(@isempty,metsToAdd.(f{1})))
    +0227                 metsToAdd = rmfield(metsToAdd,f{1});
    +0228             end
    +0229         end
    +0230         % Add metabolites
    +0231         newModel        = addMets(newModel,metsToAdd,true,metPrefix);
    +0232         addedIdx        = numel(newModel.mets)-numel(newMets)+1:numel(newModel.mets);
    +0233         newModel        = extractAndAddMiriam(newModel,header(miriamCols),data(:,miriamCols),newMets,addedIdx,'met');
    +0234     end
    +0235 end
    +0236 %% Genes
    +0237 if ~strcmp(genesInfo,'none')
    +0238     [header,data] = readCurationTable(genesInfo);
    +0239     [col,miriamCols] = matchColumns(header,genesInfo,{'genes'},{ ...
    +0240         'genes',          {'genes','gene'}; ...
    +0241         'geneShortNames', {'geneShortNames','geneShortName'}});
    +0242     genesToAdd.genes            = data(:,col.genes);
    +0243     checkGenesListedOnce(genesToAdd.genes);
    +0244     if isfield(col,'geneShortNames')
    +0245         genesToAdd.geneShortNames   = data(:,col.geneShortNames);
    +0246     end
    +0247 
    +0248     [~,notNewGene,existingGene] = intersect(genesToAdd.genes,newModel.genes);
    +0249     if ~isempty(notNewGene)
    +0250         warning(['The following genes are already present in the model, their annotation '...
    +0251             'will be overwritten to match the genesInfo file: \n\t\t%s'],...
    +0252             strjoin(genesToAdd.genes(notNewGene),'\n\t\t'));
    +0253         if isfield(genesToAdd,'geneShortNames')
    +0254             skip = [];
    +0255             if ~overwriteWithEmpty
    +0256                 skip = emptyCells(genesToAdd.geneShortNames(notNewGene));
    +0257             end
    +0258             newModel = writeExistingField(newModel,'geneShortNames',existingGene,genesToAdd.geneShortNames(notNewGene),skip);
    +0259         end
    +0260         newModel = extractAndAddMiriam(newModel,header(miriamCols),data(:,miriamCols),notNewGene,existingGene,'gene',~overwriteWithEmpty);
    +0261     end
    +0262 
    +0263     % Continue with new genes
    +0264     toAdd               = 1:numel(genesToAdd.genes);
    +0265     toAdd(notNewGene)   = [];
    +0266     if ~isempty(toAdd)
    +0267         genesToAdd.genes            = genesToAdd.genes(toAdd);
    +0268         if isfield(genesToAdd,'geneShortNames')
    +0269             genesToAdd.geneShortNames   = genesToAdd.geneShortNames(toAdd);
    +0270             if all(cellfun(@isempty,genesToAdd.geneShortNames))
    +0271                 genesToAdd = rmfield(genesToAdd,'geneShortNames');
    +0272             end
    +0273         end
    +0274         % Add genes
    +0275         newModel = addGenesRaven(newModel,genesToAdd);
    +0276         addedIdx = numel(newModel.genes)-numel(toAdd)+1:numel(newModel.genes);
    +0277         newModel = extractAndAddMiriam(newModel,header(miriamCols),data(:,miriamCols),toAdd,addedIdx,'gene');
    +0278     end
    +0279 end
    +0280 %% Reactions
    +0281 if ~any(strcmp({rxnsCoeffs,rxnsInfo},'none'))
    +0282     % Gather all data, first about reaction stoichiometries...
    +0283     [header,data] = readCurationTable(rxnsCoeffs);
    +0284     col = matchColumns(header,rxnsCoeffs,{'rxnIdx','rxnNames','metNames','comps','coefficient'},{ ...
    +0285         'rxnIdx',      {'rxnIdx','index'}; ...
    +0286         'rxnNames',    {'rxnNames','rxnName'}; ...
    +0287         'metNames',    {'metNames','metName'}; ...
    +0288         'comps',       {'comps','comp'}; ...
    +0289         'coefficient', {'coefficient','coefficients','coeffs','stoichCoeffs'}});
    +0290     % A row without a rxnIdx, rxnNames, metNames and comps value is ignored
    +0291     rowNo = (1:size(data,1))';
    +0292     keyCells = data(:,[col.rxnIdx,col.rxnNames,col.metNames,col.comps]);
    +0293     ignoredRow = all(reshape(emptyCells(keyCells(:)),size(keyCells)),2);
    +0294     data(ignoredRow,:) = [];
    +0295     rowNo(ignoredRow) = [];
    +0296     rxnCheck.coeffsIdx = parseIndexCells(data(:,col.rxnIdx),'rxnIdx','rxnsCoeffs');
    +0297     rxns.rxnNames   = data(:,col.rxnNames);
    +0298     rxns.metNames   = data(:,col.metNames);
    +0299     rxns.comps      = data(:,col.comps);
    +0300     rxns.coeffs     = parseNumericCells(data(:,col.coefficient),'coefficient','rxnsCoeffs');
    +0301     checkCoefficientsGiven(rxnCheck.coeffsIdx,rxns.coeffs,rowNo);
    +0302     rxnCheck.coeffs = indexKeys(rxnCheck.coeffsIdx,rxns.rxnNames);
    +0303 
    +0304     % ... and then additional reaction-specific data.
    +0305     [header,data] = readCurationTable(rxnsInfo);
    +0306     [col,miriamCols] = matchColumns(header,rxnsInfo,{'rxnIdx','rxnNames'},{ ...
    +0307         'rxnIdx',              {'rxnIdx','index'}; ...
    +0308         'rxnNames',            {'rxnNames','rxnName'}; ...
    +0309         'grRules',             {'grRules'}; ...
    +0310         'lb',                  {'lb'}; ...
    +0311         'ub',                  {'ub'}; ...
    +0312         'rev',                 {'rev'}; ...
    +0313         'subSystems',          {'subSystems'}; ...
    +0314         'eccodes',             {'eccodes'}; ...
    +0315         'rxnNotes',            {'rxnNotes'}; ...
    +0316         'rxnReferences',       {'rxnReferences'}; ...
    +0317         'rxnConfidenceScores', {'rxnConfidenceScores'}});
    +0318     % A row without any value is ignored; a row with a value needs a rxnIdx
    +0319     rowNo = (1:size(data,1))';
    +0320     ignoredRow = all(reshape(emptyCells(data(:)),size(data)),2);
    +0321     data(ignoredRow,:) = [];
    +0322     rowNo(ignoredRow) = [];
    +0323     rxnCheck.rxnsIdx = parseIndexCells(data(:,col.rxnIdx),'rxnIdx','rxnsInfo');
    +0324     noIdx = find(isnan(rxnCheck.rxnsIdx),1);
    +0325     if ~isempty(noIdx)
    +0326         error('RAVEN:badInput','Data row %d of the rxnsInfo file has values but its rxnIdx is empty or NaN',rowNo(noIdx));
    +0327     end
    +0328     [uniqueIdx,~,idxGroup] = unique(rxnCheck.rxnsIdx);
    +0329     repeated = find(accumarray(idxGroup,1)>1,1);
    +0330     if ~isempty(repeated)
    +0331         error('RAVEN:badInput','The rxnIdx %s is used by data rows %s of the rxnsInfo file; each reaction needs its own index', ...
    +0332             num2str(uniqueIdx(repeated)),strjoin(arrayfun(@num2str,rowNo(idxGroup==repeated)','UniformOutput',false),', '));
    +0333     end
    +0334     rxnCheck.rxns = indexKeys(rxnCheck.rxnsIdx,data(:,col.rxnNames));
    +0335 
    +0336     notMatching = setxor(rxnCheck.coeffs,rxnCheck.rxns);
    +0337     if ~isempty(notMatching)
    +0338         error('RAVEN:badInput',['The following reactions and/or their indices are not matched '...
    +0339             'between the rxnsInfo and rxnsCoeffs files:\n\t\t%s'],...
    +0340             strjoin(regexprep(notMatching,'^(\d+)\*\*\*(.*)$','index $1 ($2)'),'\n\t\t'))
    +0341     end
    +0342 
    +0343     rxnsToAdd.rxnNames              = data(:,col.rxnNames);
    +0344     for f = intersect({'grRules','subSystems','eccodes','rxnNotes','rxnReferences'},fieldnames(col)','stable')
    +0345         rxnsToAdd.(f{1}) = data(:,col.(f{1}));
     0346     end
    -0347     for f = intersect({'lb','ub','rev'},fieldnames(col)','stable')
    -0348         rxnsToAdd.(f{1}) = parseNumericCells(data(:,col.(f{1})),f{1},'rxnsInfo');
    +0347     if isfield(col,'grRules')
    +0348         rxnsToAdd.grRules = standardGrRules(rxnsToAdd.grRules,rxnsToAdd.rxnNames,rowNo);
     0349     end
    -0350     if isfield(rxnsToAdd,'rev')
    -0351         checkRevValues(rxnsToAdd.rev,data(:,col.rev));
    +0350     for f = intersect({'lb','ub','rev'},fieldnames(col)','stable')
    +0351         rxnsToAdd.(f{1}) = parseNumericCells(data(:,col.(f{1})),f{1},'rxnsInfo');
     0352     end
    -0353     if isfield(col,'rxnConfidenceScores')
    -0354         rxnsToAdd.rxnConfidenceScores   = cellfun(@(x) parseNumberCell(x,'rxnConfidenceScores','rxnsInfo'),data(:,col.rxnConfidenceScores),'UniformOutput',false);
    -0355         isNanScore = cellfun(@(x) isscalar(x) && isnan(x),rxnsToAdd.rxnConfidenceScores);
    -0356         rxnsToAdd.rxnConfidenceScores(isNanScore) = {[]}; % a NaN score counts as not given
    -0357         emptyEntry = cellfun(@isempty,rxnsToAdd.rxnConfidenceScores);
    -0358         emptyScore = emptyEntry;
    -0359         if all(emptyEntry)
    -0360             rxnsToAdd           = rmfield(rxnsToAdd,'rxnConfidenceScores');
    -0361         else % If some rxnConfidenceScores are given, assume 0 for those without rxnConfidenceScores specified
    -0362             rxnsToAdd.rxnConfidenceScores(emptyEntry) = {0};
    -0363             rxnsToAdd.rxnConfidenceScores = cell2mat(rxnsToAdd.rxnConfidenceScores);
    -0364         end
    -0365     end
    -0366     optFields = intersect({'subSystems','eccodes','rxnNotes','rxnReferences','rxnConfidenceScores'},fieldnames(rxnsToAdd)','stable');
    -0367 
    -0368     existingRxn=[];
    -0369     notNewRxn=[];
    -0370     for i=1:numel(rxnCheck.rxnsIdx)
    -0371         rxnRows                     = find(rxnCheck.rxnsIdx(i)==rxnCheck.coeffsIdx);
    -0372         rxnsToAdd.mets{i,1}         = cell(1,1);
    -0373         rxnsToAdd.stoichCoeffs{i,1} = cell(1,1);
    -0374         for j=1:numel(rxnRows)
    -0375             newMetComp = [rxns.metNames{rxnRows(j)}, '[', rxns.comps{rxnRows(j)}, ']'];
    -0376             try
    -0377                 newMetComp = getIndexes(newModel,newMetComp,'metcomps');
    -0378             catch
    -0379                 error('RAVEN:badInput',['Not all metabolites in reaction "%s" are present in the '...
    -0380                     'model or in the provided table with new metabolites.'],rxnsToAdd.rxnNames{i})
    -0381             end
    -0382             rxnsToAdd.mets{i}(j) = newModel.mets(newMetComp);
    -0383             rxnsToAdd.stoichCoeffs{i}{j} = rxns.coeffs(rxnRows(j));
    -0384         end
    -0385         rxnsToAdd.stoichCoeffs{i}=cell2mat(rxnsToAdd.stoichCoeffs{i});
    -0386         %Check if the reaction not already exists, by stoichiometry of its
    -0387         %products and reactants
    -0388         metIdx              = getIndexes(newModel,rxnsToAdd.mets{i},'mets');
    -0389         modelCoeffs         = transpose(newModel.S(metIdx,:));
    -0390         modelCoeffs2        = find(~all(modelCoeffs==0,2));
    -0391         % Only reactions built from exactly these metabolites can be
    -0392         % duplicates. Any model reaction that also involves other
    -0393         % metabolites projects onto the same row of coefficients, but is a
    -0394         % different reaction and must not be overwritten.
    -0395         sameSupport         = full(sum(newModel.S(:,modelCoeffs2)~=0,1))' == numel(metIdx);
    -0396         modelCoeffs2        = modelCoeffs2(sameSupport);
    -0397         modelCoeffs         = modelCoeffs(modelCoeffs2,:);
    -0398         [~, duplicateRxn]   = intersect(modelCoeffs,rxnsToAdd.stoichCoeffs{i},'rows');
    -0399         if ~isempty(duplicateRxn)
    -0400             existingRxn     = [existingRxn,modelCoeffs2(duplicateRxn)];
    -0401             notNewRxn       = [notNewRxn,i];
    -0402         end
    -0403     end
    -0404 
    -0405     % If some reactions already existed, then replace its information
    -0406     if ~isempty(existingRxn)
    -0407         warning(['The following reactions are the same as existing model '...
    -0408             'reactions, their annotation will be overwritten to match the '...
    -0409             'rxnCoeffs file: \n\t\t(Existing reaction ID): New reaction name\n\t\t%s'],...
    -0410             strjoin(strcat('(',newModel.rxns(existingRxn),{'): '},rxnsToAdd.rxnNames(notNewRxn)),'\n\t\t'));
    -0411         nameValues = rxnsToAdd.rxnNames(notNewRxn);
    -0412         nameIdx    = existingRxn;
    -0413         if ~overwriteWithEmpty
    -0414             nameIdx    = nameIdx(~emptyCells(nameValues));
    -0415             nameValues = nameValues(~emptyCells(nameValues));
    -0416         end
    -0417         newModel.rxnNames(nameIdx) = nameValues;
    -0418         for f = intersect({'lb','ub','rev'},fieldnames(rxnsToAdd)','stable')
    -0419             values = rxnsToAdd.(f{1})(notNewRxn);
    -0420             given  = ~isnan(values);
    -0421             newModel.(f{1})(existingRxn(given)) = values(given);
    -0422         end
    -0423         for f = optFields
    -0424             values = rxnsToAdd.(f{1})(notNewRxn);
    -0425             skip = [];
    -0426             if ~overwriteWithEmpty
    -0427                 if strcmp(f{1},'rxnConfidenceScores')
    -0428                     skip = emptyScore(notNewRxn);
    -0429                 else
    -0430                     skip = emptyCells(values);
    -0431                 end
    -0432             end
    -0433             if strcmp(f{1},'subSystems') && isfield(newModel,'subSystems') ...
    -0434                     && any(cellfun(@iscell,newModel.subSystems))
    -0435                 values = cellfun(@(x) {x}, values, 'UniformOutput', false);
    -0436             end
    -0437             newModel = writeExistingField(newModel,f{1},existingRxn,values,skip);
    -0438         end
    -0439         if isfield(rxnsToAdd,'grRules')
    -0440             emptyEntries = emptyCells(rxnsToAdd.grRules(notNewRxn));
    -0441             if ~all(emptyEntries)
    -0442                 newModel    = changeGrRules(newModel,newModel.rxns(existingRxn(~emptyEntries)),rxnsToAdd.grRules(notNewRxn(~emptyEntries)),true);
    -0443             end
    -0444         end
    -0445         newModel    = extractAndAddMiriam(newModel,header(miriamCols),data(:,miriamCols),notNewRxn,existingRxn,'rxn',~overwriteWithEmpty);
    -0446     end
    -0447 
    -0448     % Continue with new reactions
    -0449     toAdd               = 1:numel(rxnsToAdd.rxnNames);
    -0450     toAdd(notNewRxn)    = [];
    -0451     if ~isempty(toAdd)
    -0452         rxnsToAdd.rxnNames      = rxnsToAdd.rxnNames(toAdd);
    -0453         for f = intersect({'lb','ub','rev','grRules'},fieldnames(rxnsToAdd)','stable')
    -0454             rxnsToAdd.(f{1}) = rxnsToAdd.(f{1})(toAdd);
    -0455         end
    -0456         if isfield(rxnsToAdd,'lb')
    -0457             rxnsToAdd.lb(isnan(rxnsToAdd.lb)) = -inf;
    +0353     if isfield(rxnsToAdd,'rev')
    +0354         checkRevValues(rxnsToAdd.rev,data(:,col.rev));
    +0355     end
    +0356     if isfield(col,'rxnConfidenceScores')
    +0357         rxnsToAdd.rxnConfidenceScores   = cellfun(@(x) parseNumberCell(x,'rxnConfidenceScores','rxnsInfo'),data(:,col.rxnConfidenceScores),'UniformOutput',false);
    +0358         isNanScore = cellfun(@(x) isscalar(x) && isnan(x),rxnsToAdd.rxnConfidenceScores);
    +0359         rxnsToAdd.rxnConfidenceScores(isNanScore) = {[]}; % a NaN score counts as not given
    +0360         emptyEntry = cellfun(@isempty,rxnsToAdd.rxnConfidenceScores);
    +0361         emptyScore = emptyEntry;
    +0362         if all(emptyEntry)
    +0363             rxnsToAdd           = rmfield(rxnsToAdd,'rxnConfidenceScores');
    +0364         else % If some rxnConfidenceScores are given, assume 0 for those without rxnConfidenceScores specified
    +0365             rxnsToAdd.rxnConfidenceScores(emptyEntry) = {0};
    +0366             rxnsToAdd.rxnConfidenceScores = cell2mat(rxnsToAdd.rxnConfidenceScores);
    +0367         end
    +0368     end
    +0369     optFields = intersect({'subSystems','eccodes','rxnNotes','rxnReferences','rxnConfidenceScores'},fieldnames(rxnsToAdd)','stable');
    +0370 
    +0371     existingRxn=[];
    +0372     notNewRxn=[];
    +0373     for i=1:numel(rxnCheck.rxnsIdx)
    +0374         rxnRows                     = find(rxnCheck.rxnsIdx(i)==rxnCheck.coeffsIdx);
    +0375         rxnsToAdd.mets{i,1}         = cell(1,1);
    +0376         rxnsToAdd.stoichCoeffs{i,1} = cell(1,1);
    +0377         for j=1:numel(rxnRows)
    +0378             newMetComp = [rxns.metNames{rxnRows(j)}, '[', rxns.comps{rxnRows(j)}, ']'];
    +0379             try
    +0380                 newMetComp = getIndexes(newModel,newMetComp,'metcomps');
    +0381             catch
    +0382                 error('RAVEN:badInput',['Not all metabolites in reaction "%s" are present in the '...
    +0383                     'model or in the provided table with new metabolites.'],rxnsToAdd.rxnNames{i})
    +0384             end
    +0385             rxnsToAdd.mets{i}(j) = newModel.mets(newMetComp);
    +0386             rxnsToAdd.stoichCoeffs{i}{j} = rxns.coeffs(rxnRows(j));
    +0387         end
    +0388         rxnsToAdd.stoichCoeffs{i}=cell2mat(rxnsToAdd.stoichCoeffs{i});
    +0389         %Check if the reaction not already exists, by stoichiometry of its
    +0390         %products and reactants
    +0391         metIdx              = getIndexes(newModel,rxnsToAdd.mets{i},'mets');
    +0392         modelCoeffs         = transpose(newModel.S(metIdx,:));
    +0393         modelCoeffs2        = find(~all(modelCoeffs==0,2));
    +0394         % Only reactions built from exactly these metabolites can be
    +0395         % duplicates. Any model reaction that also involves other
    +0396         % metabolites projects onto the same row of coefficients, but is a
    +0397         % different reaction and must not be overwritten.
    +0398         sameSupport         = full(sum(newModel.S(:,modelCoeffs2)~=0,1))' == numel(metIdx);
    +0399         modelCoeffs2        = modelCoeffs2(sameSupport);
    +0400         modelCoeffs         = modelCoeffs(modelCoeffs2,:);
    +0401         [~, duplicateRxn]   = intersect(modelCoeffs,rxnsToAdd.stoichCoeffs{i},'rows');
    +0402         if ~isempty(duplicateRxn)
    +0403             existingRxn     = [existingRxn,modelCoeffs2(duplicateRxn)];
    +0404             notNewRxn       = [notNewRxn,i];
    +0405         end
    +0406     end
    +0407 
    +0408     % If some reactions already existed, then replace its information
    +0409     if ~isempty(existingRxn)
    +0410         warning(['The following reactions are the same as existing model '...
    +0411             'reactions, their annotation will be overwritten to match the '...
    +0412             'rxnCoeffs file: \n\t\t(Existing reaction ID): New reaction name\n\t\t%s'],...
    +0413             strjoin(strcat('(',newModel.rxns(existingRxn),{'): '},rxnsToAdd.rxnNames(notNewRxn)),'\n\t\t'));
    +0414         nameValues = rxnsToAdd.rxnNames(notNewRxn);
    +0415         nameIdx    = existingRxn;
    +0416         if ~overwriteWithEmpty
    +0417             nameIdx    = nameIdx(~emptyCells(nameValues));
    +0418             nameValues = nameValues(~emptyCells(nameValues));
    +0419         end
    +0420         newModel.rxnNames(nameIdx) = nameValues;
    +0421         for f = intersect({'lb','ub','rev'},fieldnames(rxnsToAdd)','stable')
    +0422             values = rxnsToAdd.(f{1})(notNewRxn);
    +0423             given  = ~isnan(values);
    +0424             newModel.(f{1})(existingRxn(given)) = values(given);
    +0425         end
    +0426         for f = optFields
    +0427             values = rxnsToAdd.(f{1})(notNewRxn);
    +0428             skip = [];
    +0429             if ~overwriteWithEmpty
    +0430                 if strcmp(f{1},'rxnConfidenceScores')
    +0431                     skip = emptyScore(notNewRxn);
    +0432                 else
    +0433                     skip = emptyCells(values);
    +0434                 end
    +0435             end
    +0436             if strcmp(f{1},'subSystems') && isfield(newModel,'subSystems') ...
    +0437                     && any(cellfun(@iscell,newModel.subSystems))
    +0438                 values = cellfun(@(x) {x}, values, 'UniformOutput', false);
    +0439             end
    +0440             newModel = writeExistingField(newModel,f{1},existingRxn,values,skip);
    +0441         end
    +0442         if isfield(rxnsToAdd,'grRules')
    +0443             emptyEntries = emptyCells(rxnsToAdd.grRules(notNewRxn));
    +0444             if ~all(emptyEntries)
    +0445                 newModel    = changeGrRules(newModel,newModel.rxns(existingRxn(~emptyEntries)),rxnsToAdd.grRules(notNewRxn(~emptyEntries)),true);
    +0446             end
    +0447         end
    +0448         newModel    = extractAndAddMiriam(newModel,header(miriamCols),data(:,miriamCols),notNewRxn,existingRxn,'rxn',~overwriteWithEmpty);
    +0449     end
    +0450 
    +0451     % Continue with new reactions
    +0452     toAdd               = 1:numel(rxnsToAdd.rxnNames);
    +0453     toAdd(notNewRxn)    = [];
    +0454     if ~isempty(toAdd)
    +0455         rxnsToAdd.rxnNames      = rxnsToAdd.rxnNames(toAdd);
    +0456         for f = intersect({'lb','ub','rev','grRules'},fieldnames(rxnsToAdd)','stable')
    +0457             rxnsToAdd.(f{1}) = rxnsToAdd.(f{1})(toAdd);
     0458         end
    -0459         if isfield(rxnsToAdd,'ub')
    -0460             defaultUB = inf;
    -0461             if isfield(newModel,'annotation') && isfield(newModel.annotation,'defaultUB')
    -0462                 defaultUB = newModel.annotation.defaultUB;
    -0463             end
    -0464             rxnsToAdd.ub(isnan(rxnsToAdd.ub)) = defaultUB;
    -0465         end
    -0466         for f = optFields
    -0467             rxnsToAdd.(f{1}) = rxnsToAdd.(f{1})(toAdd);
    -0468             if iscell(rxnsToAdd.(f{1})) && all(cellfun(@isempty,rxnsToAdd.(f{1})))
    -0469                 rxnsToAdd = rmfield(rxnsToAdd,f{1});
    -0470             end
    -0471         end
    -0472         rxnsToAdd.mets          = rxnsToAdd.mets(toAdd);
    -0473         rxnsToAdd.stoichCoeffs  = rxnsToAdd.stoichCoeffs(toAdd);
    -0474         rxnsToAdd.rxns          = generateNewIds(model,'rxns',rxnPrefix,numel(rxnsToAdd.rxnNames));
    -0475 
    -0476         newModel        = addRxns(newModel,rxnsToAdd,1,[],false,false);
    -0477         rxnsModelIdx    = numel(newModel.rxns)-numel(toAdd)+1:numel(newModel.rxns);
    -0478         newModel        = extractAndAddMiriam(newModel,header(miriamCols),data(:,miriamCols),toAdd,rxnsModelIdx,'rxn');
    -0479     end
    -0480 end
    -0481 end
    -0482 
    -0483 function [header,data] = readCurationTable(fileName)
    -0484 % Read a tab-delimited curation table into its header row and a cell
    -0485 % array of text with one column per header field.
    -0486 fid = fopen(fileName);
    -0487 if fid==-1
    -0488     error('RAVEN:badInput','Could not open the file "%s"',fileName);
    -0489 end
    -0490 headerLine = fgetl(fid);
    -0491 if ~ischar(headerLine)
    -0492     fclose(fid);
    -0493     error('RAVEN:badInput','The file "%s" is empty',fileName);
    -0494 end
    -0495 headerLine = strrep(headerLine,char(65279),''); % byte order mark
    -0496 header = strtrim(strsplit(headerLine,'\t'));
    -0497 header = strtrim(regexprep(header,'^"(.*)"$','$1'));
    -0498 nCols  = numel(header);
    -0499 raw    = textscan(fid,repmat('%q',1,nCols),'Delimiter','\t');
    -0500 fclose(fid);
    -0501 nRows  = max(cellfun(@numel,raw));
    -0502 data   = repmat({''},nRows,nCols);
    -0503 for i=1:nCols
    -0504     data(1:numel(raw{i}),i) = raw{i};
    -0505 end
    -0506 blank = cellfun(@(x) ischar(x) && isempty(strtrim(x)),data);
    -0507 data(blank) = {''};
    +0459         if isfield(rxnsToAdd,'lb')
    +0460             rxnsToAdd.lb(isnan(rxnsToAdd.lb)) = -inf;
    +0461         end
    +0462         if isfield(rxnsToAdd,'ub')
    +0463             defaultUB = inf;
    +0464             if isfield(newModel,'annotation') && isfield(newModel.annotation,'defaultUB')
    +0465                 defaultUB = newModel.annotation.defaultUB;
    +0466             end
    +0467             rxnsToAdd.ub(isnan(rxnsToAdd.ub)) = defaultUB;
    +0468         end
    +0469         for f = optFields
    +0470             rxnsToAdd.(f{1}) = rxnsToAdd.(f{1})(toAdd);
    +0471             if iscell(rxnsToAdd.(f{1})) && all(cellfun(@isempty,rxnsToAdd.(f{1})))
    +0472                 rxnsToAdd = rmfield(rxnsToAdd,f{1});
    +0473             end
    +0474         end
    +0475         rxnsToAdd.mets          = rxnsToAdd.mets(toAdd);
    +0476         rxnsToAdd.stoichCoeffs  = rxnsToAdd.stoichCoeffs(toAdd);
    +0477         rxnsToAdd.rxns          = generateNewIds(model,'rxns',rxnPrefix,numel(rxnsToAdd.rxnNames));
    +0478 
    +0479         newModel        = addRxns(newModel,rxnsToAdd,1,[],false,false);
    +0480         rxnsModelIdx    = numel(newModel.rxns)-numel(toAdd)+1:numel(newModel.rxns);
    +0481         newModel        = extractAndAddMiriam(newModel,header(miriamCols),data(:,miriamCols),toAdd,rxnsModelIdx,'rxn');
    +0482     end
    +0483 end
    +0484 end
    +0485 
    +0486 function [header,data] = readCurationTable(fileName)
    +0487 % Read a tab-delimited curation table into its header row and a cell
    +0488 % array of text with one column per header field.
    +0489 fid = fopen(fileName);
    +0490 if fid==-1
    +0491     error('RAVEN:badInput','Could not open the file "%s"',fileName);
    +0492 end
    +0493 headerLine = fgetl(fid);
    +0494 if ~ischar(headerLine)
    +0495     fclose(fid);
    +0496     error('RAVEN:badInput','The file "%s" is empty',fileName);
    +0497 end
    +0498 headerLine = strrep(headerLine,char(65279),''); % byte order mark
    +0499 header = strtrim(strsplit(headerLine,'\t'));
    +0500 header = strtrim(regexprep(header,'^"(.*)"$','$1'));
    +0501 nCols  = numel(header);
    +0502 raw    = textscan(fid,repmat('%q',1,nCols),'Delimiter','\t');
    +0503 fclose(fid);
    +0504 nRows  = max(cellfun(@numel,raw));
    +0505 data   = repmat({''},nRows,nCols);
    +0506 for i=1:nCols
    +0507     data(1:numel(raw{i}),i) = raw{i};
     0508 end
    -0509 
    -0510 function [col,miriamCols] = matchColumns(header,fileName,required,spec)
    -0511 % Locate the core columns of a curation table by header name (case
    -0512 % insensitive). spec has one row per core field: {field, accepted headers}.
    -0513 % Every other column is a MIRIAM namespace.
    -0514 col        = struct();
    -0515 isCore     = false(1,numel(header));
    -0516 for i=1:size(spec,1)
    -0517     match = find(ismember(lower(header),lower(spec{i,2})));
    -0518     if ~isempty(match)
    -0519         col.(spec{i,1}) = match(1);
    -0520         isCore(match)   = true;
    -0521     end
    -0522 end
    -0523 missing = setdiff(required,fieldnames(col)','stable');
    -0524 if ~isempty(missing)
    -0525     error('RAVEN:badInput','The file "%s" has no column with header: %s',fileName,strjoin(missing,', '));
    -0526 end
    -0527 miriamCols = find(~isCore & ~cellfun(@isempty,header));
    -0528 end
    -0529 
    -0530 function model = writeExistingField(model,field,modelIdx,values,skip)
    -0531 % Write the values of an optional field onto existing entities. A field
    -0532 % the model does not have is created from its ravenModelFields default,
    -0533 % unless all supplied values are empty. Entries flagged in the logical
    -0534 % vector skip are not written.
    -0535 if nargin>4 && ~isempty(skip)
    -0536     modelIdx = modelIdx(~skip(:)');
    -0537     values   = values(~skip(:));
    -0538     if isempty(modelIdx)
    -0539         return
    -0540     end
    -0541 end
    -0542 if ~isfield(model,field)
    -0543     if iscell(values) && all(cellfun(@isempty,values))
    -0544         return
    -0545     end
    -0546     reg   = ravenModelFields();
    -0547     entry = reg(strcmp({reg.name},field));
    -0548     n     = numel(model.([entry.type 's']));
    -0549     if iscell(values)
    -0550         model.(field) = repmat({entry.default},n,1);
    -0551     else
    -0552         model.(field) = repmat(entry.default,n,1);
    -0553     end
    -0554 end
    -0555 model.(field)(modelIdx) = values;
    -0556 end
    -0557 
    -0558 function newModel = extractAndAddMiriam(model,miriamName,miriamData,inputIndex,modelIndex,type,skipEmpty)
    -0559 % Replace the MIRIAM annotations of the entities in modelIndex with the
    -0560 % values of the table rows in inputIndex. With skipEmpty, an empty cell
    -0561 % leaves the annotation of that entity unchanged.
    -0562 newModel=model;
    -0563 miriamValues    = miriamData(inputIndex,:);
    -0564 if nargin>6 && skipEmpty
    -0565     for i=1:numel(miriamName)
    -0566         given = ~emptyCells(miriamValues(:,i));
    -0567         if any(given)
    -0568             if ~isfield(newModel,[type 'Miriams'])
    -0569                 newModel.([type 'Miriams'])=cell(numel(newModel.([type 's'])),1);
    -0570             end
    -0571             idx = modelIndex(:);
    -0572             newModel = editMiriam(newModel,type,idx(given),miriamName{i},miriamValues(given,i),'replace');
    -0573         end
    -0574     end
    -0575     return
    -0576 end
    -0577 emptyMiriam     = all(cellfun(@isempty,miriamValues),1);
    -0578 miriamName(emptyMiriam)     = [];
    -0579 miriamValues(:,emptyMiriam) = [];
    -0580 if ~isfield(newModel,[type 'Miriams'])
    -0581     newModel.([type 'Miriams'])=cell(numel(newModel.([type 's'])),1);
    -0582 end
    -0583 for i=1:numel(miriamName)
    -0584     newModel = editMiriam(newModel,type,modelIndex,miriamName{i},miriamValues(:,i),'replace');
    +0509 blank = cellfun(@(x) ischar(x) && isempty(strtrim(x)),data);
    +0510 data(blank) = {''};
    +0511 end
    +0512 
    +0513 function [col,miriamCols] = matchColumns(header,fileName,required,spec)
    +0514 % Locate the core columns of a curation table by header name (case
    +0515 % insensitive). spec has one row per core field: {field, accepted headers}.
    +0516 % Every other column is a MIRIAM namespace.
    +0517 col        = struct();
    +0518 isCore     = false(1,numel(header));
    +0519 for i=1:size(spec,1)
    +0520     match = find(ismember(lower(header),lower(spec{i,2})));
    +0521     if ~isempty(match)
    +0522         col.(spec{i,1}) = match(1);
    +0523         isCore(match)   = true;
    +0524     end
    +0525 end
    +0526 missing = setdiff(required,fieldnames(col)','stable');
    +0527 if ~isempty(missing)
    +0528     error('RAVEN:badInput','The file "%s" has no column with header: %s',fileName,strjoin(missing,', '));
    +0529 end
    +0530 miriamCols = find(~isCore & ~cellfun(@isempty,header));
    +0531 end
    +0532 
    +0533 function model = writeExistingField(model,field,modelIdx,values,skip)
    +0534 % Write the values of an optional field onto existing entities. A field
    +0535 % the model does not have is created from its ravenModelFields default,
    +0536 % unless all supplied values are empty. Entries flagged in the logical
    +0537 % vector skip are not written.
    +0538 if nargin>4 && ~isempty(skip)
    +0539     modelIdx = modelIdx(~skip(:)');
    +0540     values   = values(~skip(:));
    +0541     if isempty(modelIdx)
    +0542         return
    +0543     end
    +0544 end
    +0545 if ~isfield(model,field)
    +0546     if iscell(values) && all(cellfun(@isempty,values))
    +0547         return
    +0548     end
    +0549     reg   = ravenModelFields();
    +0550     entry = reg(strcmp({reg.name},field));
    +0551     n     = numel(model.([entry.type 's']));
    +0552     if iscell(values)
    +0553         model.(field) = repmat({entry.default},n,1);
    +0554     else
    +0555         model.(field) = repmat(entry.default,n,1);
    +0556     end
    +0557 end
    +0558 model.(field)(modelIdx) = values;
    +0559 end
    +0560 
    +0561 function newModel = extractAndAddMiriam(model,miriamName,miriamData,inputIndex,modelIndex,type,skipEmpty)
    +0562 % Replace the MIRIAM annotations of the entities in modelIndex with the
    +0563 % values of the table rows in inputIndex. With skipEmpty, an empty cell
    +0564 % leaves the annotation of that entity unchanged.
    +0565 newModel=model;
    +0566 miriamValues    = miriamData(inputIndex,:);
    +0567 if nargin>6 && skipEmpty
    +0568     for i=1:numel(miriamName)
    +0569         given = ~emptyCells(miriamValues(:,i));
    +0570         if any(given)
    +0571             if ~isfield(newModel,[type 'Miriams'])
    +0572                 newModel.([type 'Miriams'])=cell(numel(newModel.([type 's'])),1);
    +0573             end
    +0574             idx = modelIndex(:);
    +0575             newModel = editMiriam(newModel,type,idx(given),miriamName{i},miriamValues(given,i),'replace');
    +0576         end
    +0577     end
    +0578     return
    +0579 end
    +0580 emptyMiriam     = all(cellfun(@isempty,miriamValues),1);
    +0581 miriamName(emptyMiriam)     = [];
    +0582 miriamValues(:,emptyMiriam) = [];
    +0583 if ~isfield(newModel,[type 'Miriams'])
    +0584     newModel.([type 'Miriams'])=cell(numel(newModel.([type 's'])),1);
     0585 end
    -0586 end
    -0587 
    -0588 function empty = emptyCells(values)
    -0589 % Logical column vector that is true for the empty entries of a cell array
    -0590 % of text (a cell with only blanks counts as empty) or of a numeric cell.
    -0591 empty = cellfun(@(x) isempty(x) || (ischar(x) && isempty(strtrim(x))),values(:));
    -0592 end
    -0593 
    -0594 function values = parseIndexCells(cells,field,fileLabel)
    -0595 % Reaction indices from table cells: NaN where the cell is empty or says NaN
    -0596 % (the row is ignored), otherwise a finite whole number of at least 1.
    -0597 values = parseNumericCells(cells,field,fileLabel);
    -0598 bad = find(~isnan(values) & (~isfinite(values) | values~=round(values) | values<1),1);
    -0599 if ~isempty(bad)
    -0600     error('RAVEN:badInput','The %s value "%s" in the %s file is not a whole number of at least 1',field,strtrim(cells{bad}),fileLabel);
    -0601 end
    -0602 end
    -0603 
    -0604 function values = parseNumericCells(cells,field,fileLabel)
    -0605 % Numbers from table cells, NaN where the cell is empty or says NaN. A cell
    -0606 % that is not a number (or true/false) raises an error.
    -0607 values = nan(numel(cells),1);
    -0608 for i=1:numel(cells)
    -0609     v = parseNumberCell(cells{i},field,fileLabel);
    -0610     if ~isempty(v)
    -0611         values(i) = v;
    -0612     end
    -0613 end
    -0614 end
    -0615 
    -0616 function v = parseChargeCell(text,fileLabel)
    -0617 % Charge from a table cell: [] for an empty cell, NaN for NaN, otherwise a
    -0618 % finite whole number. A fractional or infinite charge raises an error.
    -0619 v = parseNumberCell(text,'charge',fileLabel);
    -0620 if ~isempty(v) && ~isnan(v) && (~isfinite(v) || v~=round(v))
    -0621     error('RAVEN:badInput','The charge value "%s" in the %s file is not a finite whole number',strtrim(text),fileLabel);
    -0622 end
    -0623 end
    -0624 
    -0625 function checkCoefficientsGiven(idx,coeffs,rowNo)
    -0626 % Every row of the rxnsCoeffs table that is kept has a rxnIdx and a
    -0627 % coefficient that is a finite number other than 0.
    -0628 noIdx = find(isnan(idx),1);
    -0629 if ~isempty(noIdx)
    -0630     error('RAVEN:badInput',['Data row %d of the rxnsCoeffs file has values but its rxnIdx is empty or NaN'],rowNo(noIdx));
    -0631 end
    -0632 missing = find(isnan(coeffs),1);
    -0633 if ~isempty(missing)
    -0634     error('RAVEN:badInput',['The coefficient in data row %d of the rxnsCoeffs file is empty or NaN; ' ...
    -0635         'every row with a reaction, metabolite or compartment needs a coefficient'],rowNo(missing));
    -0636 end
    -0637 bad = find(~isfinite(coeffs) | coeffs==0,1);
    -0638 if ~isempty(bad)
    -0639     error('RAVEN:badInput','The coefficient in data row %d of the rxnsCoeffs file must be a finite non-zero number',rowNo(bad));
    -0640 end
    -0641 end
    -0642 
    -0643 function keys = indexKeys(idx,names)
    -0644 % One key per table row from the numeric reaction index and the name, so that
    -0645 % the indices of two tables are compared by value.
    -0646 keys = cell(numel(idx),1);
    -0647 for i=1:numel(idx)
    -0648     keys{i} = sprintf('%.0f***%s',idx(i),names{i});
    -0649 end
    -0650 end
    -0651 
    -0652 function checkGenesListedOnce(genes)
    -0653 % Raise RAVEN:badInput for a gene id that the genesInfo table lists more than
    -0654 % once, whether the model has the gene or not: two rows for one gene would
    -0655 % say two things about it, and the second row would add a second gene with
    -0656 % the same id.
    -0657 [ids,~,j] = unique(genes);
    -0658 repeated = ids(accumarray(j,1) > 1);
    -0659 if ~isempty(repeated)
    -0660     error('RAVEN:badInput',['The following genes are listed more than once '...
    -0661         'in the genesInfo file:\n\t\t%s'],strjoin(repeated,'\n\t\t'));
    -0662 end
    -0663 end
    -0664 
    -0665 
    -0666 function rules = standardGrRules(rules,rxnNames,rowNo)
    -0667 % Raise RAVEN:badInput for a grRules cell that is not a well-formed gene rule
    -0668 % (unbalanced parentheses, an operator without two operands, empty
    -0669 % parentheses, two genes without an operator between them) and write each
    -0670 % other rule in RAVEN's standard format (" and " / " or ", parentheses only
    -0671 % where needed), so that addRxns and changeGrRules read every spelling that
    -0672 % parseGrRule reads. Empty cells are left as they are.
    -0673 for i = find(~emptyCells(rules))'
    -0674     try
    -0675         rules{i} = grRuleToString(parseGrRule(rules{i}));
    -0676     catch err
    -0677         if strcmp(err.identifier,'RAVEN:badGrRule')
    -0678             error('RAVEN:badInput','The grRules value "%s" of reaction "%s" (data row %d of the rxnsInfo file) is not a valid gene rule: %s',...
    -0679                 rules{i},rxnNames{i},rowNo(i),err.message);
    -0680         end
    -0681         rethrow(err);
    -0682     end
    -0683 end
    -0684 end
    -0685 
    -0686 
    -0687 function checkRevValues(values,cells)
    -0688 % A rev cell is 0, 1, true or false; an empty or NaN cell is not given.
    -0689 bad = find(~isnan(values) & values~=0 & values~=1,1);
    -0690 if ~isempty(bad)
    -0691     error('RAVEN:badInput','The rev value "%s" in the rxnsInfo file is not 0, 1, true or false',strtrim(cells{bad}));
    -0692 end
    -0693 end
    -0694 
    -0695 function v = parseNumberCell(text,field,fileLabel)
    -0696 % Real number from a table cell: [] for an empty cell, 1 or 0 for true or
    -0697 % false in any case (lb, ub and rev cells only), NaN or +/-Inf for those words in any case, otherwise a
    -0698 % plain decimal number (digits with an optional sign, decimal point and
    -0699 % exponent). Any other text raises an error; nothing is evaluated.
    -0700 text = strtrim(text);
    -0701 if isempty(text)
    -0702     v = [];
    -0703     return
    -0704 end
    -0705 word = lower(text);
    -0706 if any(strcmp(word,{'true','false'})) && ~ismember(field,{'lb','ub','rev'})
    -0707     error('RAVEN:badInput','The %s value "%s" in the %s file is not a number',field,text,fileLabel);
    -0708 end
    -0709 if strcmp(word,'true')
    -0710     v = 1;
    -0711 elseif strcmp(word,'false')
    -0712     v = 0;
    -0713 elseif ~isempty(regexp(word,'^[+-]?nan$','once'))
    -0714     v = NaN;
    -0715 elseif ~isempty(regexp(word,'^[+-]?inf$','once'))
    -0716     v = Inf;
    -0717     if word(1)=='-'
    -0718         v = -Inf;
    -0719     end
    -0720 elseif ~isempty(regexp(text,'^[+-]?([0-9]+\.?[0-9]*|\.[0-9]+)([eE][+-]?[0-9]+)?$','once'))
    -0721     v = str2double(text);
    -0722 else
    -0723     error('RAVEN:badInput','The %s value "%s" in the %s file is not a number',field,text,fileLabel);
    -0724 end
    -0725 end

    +0586 for i=1:numel(miriamName) +0587 newModel = editMiriam(newModel,type,modelIndex,miriamName{i},miriamValues(:,i),'replace'); +0588 end +0589 end +0590 +0591 function empty = emptyCells(values) +0592 % Logical column vector that is true for the empty entries of a cell array +0593 % of text (a cell with only blanks counts as empty) or of a numeric cell. +0594 empty = cellfun(@(x) isempty(x) || (ischar(x) && isempty(strtrim(x))),values(:)); +0595 end +0596 +0597 function values = parseIndexCells(cells,field,fileLabel) +0598 % Reaction indices from table cells: NaN where the cell is empty or says NaN +0599 % (the row is ignored), otherwise a finite whole number of at least 1. +0600 values = parseNumericCells(cells,field,fileLabel); +0601 bad = find(~isnan(values) & (~isfinite(values) | values~=round(values) | values<1),1); +0602 if ~isempty(bad) +0603 error('RAVEN:badInput','The %s value "%s" in the %s file is not a whole number of at least 1',field,strtrim(cells{bad}),fileLabel); +0604 end +0605 end +0606 +0607 function values = parseNumericCells(cells,field,fileLabel) +0608 % Numbers from table cells, NaN where the cell is empty or says NaN. A cell +0609 % that is not a number (or true/false) raises an error. +0610 values = nan(numel(cells),1); +0611 for i=1:numel(cells) +0612 v = parseNumberCell(cells{i},field,fileLabel); +0613 if ~isempty(v) +0614 values(i) = v; +0615 end +0616 end +0617 end +0618 +0619 function v = parseChargeCell(text,fileLabel) +0620 % Charge from a table cell: [] for an empty cell, NaN for NaN, otherwise a +0621 % finite whole number. A fractional or infinite charge raises an error. +0622 v = parseNumberCell(text,'charge',fileLabel); +0623 if ~isempty(v) && ~isnan(v) && (~isfinite(v) || v~=round(v)) +0624 error('RAVEN:badInput','The charge value "%s" in the %s file is not a finite whole number',strtrim(text),fileLabel); +0625 end +0626 end +0627 +0628 function checkCoefficientsGiven(idx,coeffs,rowNo) +0629 % Every row of the rxnsCoeffs table that is kept has a rxnIdx and a +0630 % coefficient that is a finite number other than 0. +0631 noIdx = find(isnan(idx),1); +0632 if ~isempty(noIdx) +0633 error('RAVEN:badInput',['Data row %d of the rxnsCoeffs file has values but its rxnIdx is empty or NaN'],rowNo(noIdx)); +0634 end +0635 missing = find(isnan(coeffs),1); +0636 if ~isempty(missing) +0637 error('RAVEN:badInput',['The coefficient in data row %d of the rxnsCoeffs file is empty or NaN; ' ... +0638 'every row with a reaction, metabolite or compartment needs a coefficient'],rowNo(missing)); +0639 end +0640 bad = find(~isfinite(coeffs) | coeffs==0,1); +0641 if ~isempty(bad) +0642 error('RAVEN:badInput','The coefficient in data row %d of the rxnsCoeffs file must be a finite non-zero number',rowNo(bad)); +0643 end +0644 end +0645 +0646 function keys = indexKeys(idx,names) +0647 % One key per table row from the numeric reaction index and the name, so that +0648 % the indices of two tables are compared by value. +0649 keys = cell(numel(idx),1); +0650 for i=1:numel(idx) +0651 keys{i} = sprintf('%.0f***%s',idx(i),names{i}); +0652 end +0653 end +0654 +0655 function checkGenesListedOnce(genes) +0656 % Raise RAVEN:badInput for a gene id that the genesInfo table lists more than +0657 % once, whether the model has the gene or not: two rows for one gene would +0658 % say two things about it, and the second row would add a second gene with +0659 % the same id. +0660 [ids,~,j] = unique(genes); +0661 repeated = ids(accumarray(j,1) > 1); +0662 if ~isempty(repeated) +0663 error('RAVEN:badInput',['The following genes are listed more than once '... +0664 'in the genesInfo file:\n\t\t%s'],strjoin(repeated,'\n\t\t')); +0665 end +0666 end +0667 +0668 +0669 function rules = standardGrRules(rules,rxnNames,rowNo) +0670 % Raise RAVEN:badInput for a grRules cell that is not a well-formed gene rule +0671 % (unbalanced parentheses, an operator without two operands, empty +0672 % parentheses, two genes without an operator between them) and write each +0673 % other rule in RAVEN's standard format (" and " / " or ", parentheses only +0674 % where needed), so that addRxns and changeGrRules read every spelling that +0675 % parseGrRule reads. Empty cells are left as they are. +0676 for i = find(~emptyCells(rules))' +0677 try +0678 rules{i} = grRuleToString(parseGrRule(rules{i})); +0679 catch err +0680 if strcmp(err.identifier,'RAVEN:badGrRule') +0681 error('RAVEN:badInput','The grRules value "%s" of reaction "%s" (data row %d of the rxnsInfo file) is not a valid gene rule: %s',... +0682 rules{i},rxnNames{i},rowNo(i),err.message); +0683 end +0684 rethrow(err); +0685 end +0686 end +0687 end +0688 +0689 +0690 function checkRevValues(values,cells) +0691 % A rev cell is 0, 1, true or false; an empty or NaN cell is not given. +0692 bad = find(~isnan(values) & values~=0 & values~=1,1); +0693 if ~isempty(bad) +0694 error('RAVEN:badInput','The rev value "%s" in the rxnsInfo file is not 0, 1, true or false',strtrim(cells{bad})); +0695 end +0696 end +0697 +0698 function v = parseNumberCell(text,field,fileLabel) +0699 % Real number from a table cell: [] for an empty cell, 1 or 0 for true or +0700 % false in any case (lb, ub and rev cells only), NaN or +/-Inf for those words in any case, otherwise a +0701 % plain decimal number (digits with an optional sign, decimal point and +0702 % exponent). Any other text raises an error; nothing is evaluated. +0703 text = strtrim(text); +0704 if isempty(text) +0705 v = []; +0706 return +0707 end +0708 word = lower(text); +0709 if any(strcmp(word,{'true','false'})) && ~ismember(field,{'lb','ub','rev'}) +0710 error('RAVEN:badInput','The %s value "%s" in the %s file is not a number',field,text,fileLabel); +0711 end +0712 if strcmp(word,'true') +0713 v = 1; +0714 elseif strcmp(word,'false') +0715 v = 0; +0716 elseif ~isempty(regexp(word,'^[+-]?nan$','once')) +0717 v = NaN; +0718 elseif ~isempty(regexp(word,'^[+-]?inf$','once')) +0719 v = Inf; +0720 if word(1)=='-' +0721 v = -Inf; +0722 end +0723 elseif ~isempty(regexp(text,'^[+-]?([0-9]+\.?[0-9]*|\.[0-9]+)([eE][+-]?[0-9]+)?$','once')) +0724 v = str2double(text); +0725 else +0726 error('RAVEN:badInput','The %s value "%s" in the %s file is not a number',field,text,fileLabel); +0727 end +0728 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/curation/downloadGenomeData.html b/docs/html/curation/downloadGenomeData.html index 62aea358..4a6aab10 100644 --- a/docs/html/curation/downloadGenomeData.html +++ b/docs/html/curation/downloadGenomeData.html @@ -29,16 +29,17 @@

    SYNOPSIS ^DESCRIPTION ^

     downloadGenomeData  Download genome annotation files from NCBI.
     
    - Retrieves the GFF3 annotation and protein FASTA (.faa) files for a given
    - NCBI genome assembly accession using the NCBI Datasets v2 API, which
    - returns both files in a single archive. The files are saved locally and
    - can be used directly by getGeneData to build a gene-mapping table.
    + Downloads the GFF3 annotation and protein FASTA (.faa) files for an NCBI
    + genome assembly accession using the NCBI Datasets v2 API, which returns
    + both files in a single archive. The files are saved as
    + <accession>_genomic.gff and <accession>_protein.faa in outputDir. They
    + are input files for getGeneData.
     
      Parameters
      ----------
      accession : char
    -     NCBI genome assembly accession, e.g. 'GCF_000002595.2'.
    -     Both RefSeq (GCF_) and GenBank (GCA_) prefixes are accepted.
    +     NCBI genome assembly accession, for example 'GCF_000002595.2'. The
    +     prefix is 'GCF_' (RefSeq) or 'GCA_' (GenBank).
     
      Name-Value Arguments
      --------------------
    @@ -68,8 +69,10 @@ 

    DESCRIPTION ^CROSS-REFERENCE INFORMATION ^

    @@ -87,119 +90,122 @@

    SOURCE CODE ^
    0001 function [gffFile, faaFile] = downloadGenomeData(accession, varargin)
     0002 % downloadGenomeData  Download genome annotation files from NCBI.
     0003 %
    -0004 % Retrieves the GFF3 annotation and protein FASTA (.faa) files for a given
    -0005 % NCBI genome assembly accession using the NCBI Datasets v2 API, which
    -0006 % returns both files in a single archive. The files are saved locally and
    -0007 % can be used directly by getGeneData to build a gene-mapping table.
    -0008 %
    -0009 % Parameters
    -0010 % ----------
    -0011 % accession : char
    -0012 %     NCBI genome assembly accession, e.g. 'GCF_000002595.2'.
    -0013 %     Both RefSeq (GCF_) and GenBank (GCA_) prefixes are accepted.
    -0014 %
    -0015 % Name-Value Arguments
    -0016 % --------------------
    -0017 % outputDir : char
    -0018 %     Directory where downloaded files are saved (default: current working
    -0019 %     directory).
    -0020 % verbose : logical
    -0021 %     Print download progress to the command window (default: true).
    -0022 %
    -0023 % Returns
    -0024 % -------
    -0025 % gffFile : char
    -0026 %     Full path to the downloaded GFF3 annotation file.
    -0027 % faaFile : char
    -0028 %     Full path to the downloaded protein FASTA file.
    -0029 %
    -0030 % Examples
    -0031 % --------
    -0032 %     % Download to current directory
    -0033 %     [gff, faa] = downloadGenomeData('GCF_000002595.2');
    -0034 %
    -0035 %     % Save to a specific directory
    -0036 %     [gff, faa] = downloadGenomeData('GCF_000002595.2', 'data/');
    -0037 %
    -0038 %     % Suppress progress messages
    -0039 %     [gff, faa] = downloadGenomeData('GCF_000002595.2', 'data/', false);
    -0040 %
    -0041 % Notes
    -0042 % -----
    -0043 % Requires an active internet connection. Files already present in
    -0044 % outputDir are not re-fetched; delete them manually to force a refresh.
    -0045 
    -0046     if nargin < 1 || isempty(accession)
    -0047     error('downloadGenomeData:missingInput', ...
    -0048         ['accession is required. ', ...
    -0049          'Provide an NCBI assembly accession, e.g. ''GCF_000002595.2''.']);
    -0050     end
    -0051     accession = char(accession);
    -0052     
    -0053     if ~any(strncmpi(accession, {'GCF_', 'GCA_'}, 4))
    -0054         error('downloadGenomeData:invalidAccession', ...
    -0055         ['Accession must start with ''GCF_'' (RefSeq) or ''GCA_'' (GenBank). ', ...
    -0056          'Received: ''%s''.'], accession);
    -0057     end
    -0058 
    -0059     p = parseRAVENargs(varargin, {'outputDir', pwd; 'verbose', true});
    -0060     downloadDir = char(p.outputDir);
    -0061     verbose = p.verbose;
    -0062 
    -0063     if ~exist(downloadDir, 'dir')
    -0064         mkdir(downloadDir);
    -0065     end
    -0066 
    -0067     gffFile = fullfile(downloadDir, [accession '_genomic.gff']);
    -0068     faaFile = fullfile(downloadDir, [accession '_protein.faa']);
    +0004 % Downloads the GFF3 annotation and protein FASTA (.faa) files for an NCBI
    +0005 % genome assembly accession using the NCBI Datasets v2 API, which returns
    +0006 % both files in a single archive. The files are saved as
    +0007 % <accession>_genomic.gff and <accession>_protein.faa in outputDir. They
    +0008 % are input files for getGeneData.
    +0009 %
    +0010 % Parameters
    +0011 % ----------
    +0012 % accession : char
    +0013 %     NCBI genome assembly accession, for example 'GCF_000002595.2'. The
    +0014 %     prefix is 'GCF_' (RefSeq) or 'GCA_' (GenBank).
    +0015 %
    +0016 % Name-Value Arguments
    +0017 % --------------------
    +0018 % outputDir : char
    +0019 %     Directory where downloaded files are saved (default: current working
    +0020 %     directory).
    +0021 % verbose : logical
    +0022 %     Print download progress to the command window (default: true).
    +0023 %
    +0024 % Returns
    +0025 % -------
    +0026 % gffFile : char
    +0027 %     Full path to the downloaded GFF3 annotation file.
    +0028 % faaFile : char
    +0029 %     Full path to the downloaded protein FASTA file.
    +0030 %
    +0031 % Examples
    +0032 % --------
    +0033 %     % Download to current directory
    +0034 %     [gff, faa] = downloadGenomeData('GCF_000002595.2');
    +0035 %
    +0036 %     % Save to a specific directory
    +0037 %     [gff, faa] = downloadGenomeData('GCF_000002595.2', 'data/');
    +0038 %
    +0039 %     % Suppress progress messages
    +0040 %     [gff, faa] = downloadGenomeData('GCF_000002595.2', 'data/', false);
    +0041 %
    +0042 % Notes
    +0043 % -----
    +0044 % An internet connection is required. When both files are already present
    +0045 % in outputDir, no download takes place; deleting them forces a new
    +0046 % download. An error is raised when NCBI Datasets does not return both a
    +0047 % GFF3 file and a protein FASTA file for the accession.
    +0048 
    +0049     if nargin < 1 || isempty(accession)
    +0050     error('downloadGenomeData:missingInput', ...
    +0051         ['accession is required. ', ...
    +0052          'Provide an NCBI assembly accession, e.g. ''GCF_000002595.2''.']);
    +0053     end
    +0054     accession = char(accession);
    +0055     
    +0056     if ~any(strncmpi(accession, {'GCF_', 'GCA_'}, 4))
    +0057         error('downloadGenomeData:invalidAccession', ...
    +0058         ['Accession must start with ''GCF_'' (RefSeq) or ''GCA_'' (GenBank). ', ...
    +0059          'Received: ''%s''.'], accession);
    +0060     end
    +0061 
    +0062     p = parseRAVENargs(varargin, {'outputDir', pwd; 'verbose', true});
    +0063     downloadDir = char(p.outputDir);
    +0064     verbose = p.verbose;
    +0065 
    +0066     if ~exist(downloadDir, 'dir')
    +0067         mkdir(downloadDir);
    +0068     end
     0069 
    -0070     % Skip the download if both files are already present
    -0071     if isfile(gffFile) && isfile(faaFile)
    -0072         if verbose; fprintf('Genome data for %s already present, skipping download.\n', accession); end
    -0073         return
    -0074     end
    -0075 
    -0076     % --- NCBI Datasets v2 API: download GFF3 and protein FASTA as one zip ---
    -0077     if verbose; fprintf('Downloading genome data for %s from NCBI Datasets...\n', accession); end
    -0078     url = ['https://api.ncbi.nlm.nih.gov/datasets/v2alpha/genome/accession/', ...
    -0079            accession, '/download?include_annotation_type=GENOME_GFF,PROT_FASTA'];
    -0080     zipFile    = [tempname '.zip'];
    -0081     extractDir = tempname;
    -0082     try
    -0083         websave(zipFile, url);
    -0084     catch ME
    -0085         error('downloadGenomeData:download', 'Download failed for %s: %s', accession, ME.message);
    -0086     end
    -0087     try
    -0088         unzip(zipFile, extractDir);
    -0089     catch ME
    -0090         delete(zipFile);
    -0091         error('downloadGenomeData:extract', 'Extraction failed for %s: %s', accession, ME.message);
    -0092     end
    -0093     delete(zipFile);
    -0094 
    -0095     % The archive stores the files under ncbi_dataset/data/<assembly>/
    -0096     dataParent = fullfile(extractDir, 'ncbi_dataset', 'data');
    -0097     sub = dir(dataParent);
    -0098     sub = sub([sub.isdir] & ~startsWith({sub.name}, '.'));
    -0099     if ~isempty(sub)
    -0100         assemblyDir = fullfile(dataParent, sub(1).name);
    -0101         srcGff = fullfile(assemblyDir, 'genomic.gff');
    -0102         srcFaa = fullfile(assemblyDir, 'protein.faa');
    -0103     else
    -0104         srcGff = '';
    -0105         srcFaa = '';
    -0106     end
    -0107     if ~isfile(srcGff) || ~isfile(srcFaa)
    -0108         rmdir(extractDir, 's');
    -0109         error('downloadGenomeData:noAnnotation', ...
    -0110             ['NCBI Datasets did not return both a GFF3 annotation and a protein ', ...
    -0111              'FASTA for %s. The assembly may lack annotation.'], accession);
    -0112     end
    -0113     movefile(srcGff, gffFile, 'f');
    -0114     movefile(srcFaa, faaFile, 'f');
    -0115     rmdir(extractDir, 's');
    -0116 end

    +0070 gffFile = fullfile(downloadDir, [accession '_genomic.gff']); +0071 faaFile = fullfile(downloadDir, [accession '_protein.faa']); +0072 +0073 % Skip the download if both files are already present +0074 if isfile(gffFile) && isfile(faaFile) +0075 if verbose; fprintf('Genome data for %s already present, skipping download.\n', accession); end +0076 return +0077 end +0078 +0079 % --- NCBI Datasets v2 API: download GFF3 and protein FASTA as one zip --- +0080 if verbose; fprintf('Downloading genome data for %s from NCBI Datasets...\n', accession); end +0081 url = ['https://api.ncbi.nlm.nih.gov/datasets/v2alpha/genome/accession/', ... +0082 accession, '/download?include_annotation_type=GENOME_GFF,PROT_FASTA']; +0083 zipFile = [tempname '.zip']; +0084 extractDir = tempname; +0085 try +0086 websave(zipFile, url); +0087 catch ME +0088 error('downloadGenomeData:download', 'Download failed for %s: %s', accession, ME.message); +0089 end +0090 try +0091 unzip(zipFile, extractDir); +0092 catch ME +0093 delete(zipFile); +0094 error('downloadGenomeData:extract', 'Extraction failed for %s: %s', accession, ME.message); +0095 end +0096 delete(zipFile); +0097 +0098 % The archive stores the files under ncbi_dataset/data/<assembly>/ +0099 dataParent = fullfile(extractDir, 'ncbi_dataset', 'data'); +0100 sub = dir(dataParent); +0101 sub = sub([sub.isdir] & ~startsWith({sub.name}, '.')); +0102 if ~isempty(sub) +0103 assemblyDir = fullfile(dataParent, sub(1).name); +0104 srcGff = fullfile(assemblyDir, 'genomic.gff'); +0105 srcFaa = fullfile(assemblyDir, 'protein.faa'); +0106 else +0107 srcGff = ''; +0108 srcFaa = ''; +0109 end +0110 if ~isfile(srcGff) || ~isfile(srcFaa) +0111 rmdir(extractDir, 's'); +0112 error('downloadGenomeData:noAnnotation', ... +0113 ['NCBI Datasets did not return both a GFF3 annotation and a protein ', ... +0114 'FASTA for %s. The assembly may lack annotation.'], accession); +0115 end +0116 movefile(srcGff, gffFile, 'f'); +0117 movefile(srcFaa, faaFile, 'f'); +0118 rmdir(extractDir, 's'); +0119 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/curation/getGeneData.html b/docs/html/curation/getGeneData.html index f9c134b0..277bc9bc 100644 --- a/docs/html/curation/getGeneData.html +++ b/docs/html/curation/getGeneData.html @@ -29,39 +29,46 @@

    SYNOPSIS ^DESCRIPTION ^

     getGeneData  Build a gene-mapping table from NCBI genome annotation files.
     
    - Parses the GFF3 annotation and protein FASTA (.faa) files for a given
    - NCBI genome assembly and produces a table mapping locus tags to gene
    - symbols, protein IDs, and other identifiers. The resulting table can be
    - passed directly to renameModelGenes to update gene identifiers in a
    - RAVEN model. If no local files are provided, downloadGenomeData is
    - called automatically to fetch them.
    + Parses the GFF3 annotation file of an NCBI genome assembly and produces
    + a table mapping locus tags to gene symbols, protein IDs and other
    + identifiers. The table can be passed to renameModelGenes to change gene
    + identifiers in a RAVEN model. If the input is an assembly accession,
    + downloadGenomeData downloads the GFF3 and protein FASTA (.faa) files to
    + downloadDir, unless both are already present there.
     
      Parameters
      ----------
      accession : char
    -     NCBI genome assembly accession, e.g. 'GCF_000002595.2', or a path to a
    -     local GFF3 annotation file.
    +     NCBI genome assembly accession, for example 'GCF_000002595.2', or the
    +     path to a local GFF3 annotation file. An error is raised if the
    +     input is neither an existing file nor an accession of the form
    +     'GCF_' or 'GCA_' followed by digits and an optional version.
     
      Name-Value Arguments
      --------------------
      outputFile : char
    -     Path to save the resulting table as a tab-delimited .tsv file. If
    -     omitted or empty, no file is written and the table is only returned.
    +     Path of the tab-delimited .tsv file to which the table is written.
    +     No file is written if omitted or empty (default '').
      downloadDir : char
    -     Directory where genome files are downloaded if not already present
    -     (default: current working directory).
    +     Directory where genome files are downloaded if not already present.
    +     Only used when the input is an accession (default: current working
    +     directory).
     
      Returns
      -------
      geneTable : table
    -     MATLAB table with one row per gene containing:
    -       locus_tag       — stable locus identifier (e.g. 'Cre01.g000001')
    -       old_locus_tag   — previous locus identifier when available
    -       GeneID          — NCBI Gene ID (e.g. '5723799')
    -       gene_name       — common gene symbol when available (e.g. 'rbcL')
    -       GenBank_protein — protein accession (e.g. 'XP_001698190.2'), matching
    -                         the protein FASTA headers
    -       UniProt         — UniProt accession from Dbxref when available
    +     MATLAB table with one row per distinct combination of gene and
    +     protein accession (a CDS feature with a protein accession, repeated
    +     CDS rows are merged), with the columns below. A column entry is empty
    +     when the annotation lacks the value.
    +
    +     - locus_tag : locus identifier (for example 'Cre01.g000001')
    +     - old_locus_tag : earlier locus identifier
    +     - GeneID : NCBI Gene ID (for example '5723799')
    +     - gene_name : gene symbol (for example 'rbcL')
    +     - GenBank_protein : protein accession (for example 'XP_001698190.2'),
    +       as in the protein FASTA headers
    +     - UniProt : UniProt accession from the Dbxref attribute of the gene
     
      Examples
      --------
    @@ -77,8 +84,9 @@ 

    DESCRIPTION ^CROSS-REFERENCE INFORMATION ^

    @@ -98,249 +106,257 @@

    SOURCE CODE ^
    0001 function geneTable = getGeneData(accession, varargin)
     0002 % getGeneData  Build a gene-mapping table from NCBI genome annotation files.
     0003 %
    -0004 % Parses the GFF3 annotation and protein FASTA (.faa) files for a given
    -0005 % NCBI genome assembly and produces a table mapping locus tags to gene
    -0006 % symbols, protein IDs, and other identifiers. The resulting table can be
    -0007 % passed directly to renameModelGenes to update gene identifiers in a
    -0008 % RAVEN model. If no local files are provided, downloadGenomeData is
    -0009 % called automatically to fetch them.
    +0004 % Parses the GFF3 annotation file of an NCBI genome assembly and produces
    +0005 % a table mapping locus tags to gene symbols, protein IDs and other
    +0006 % identifiers. The table can be passed to renameModelGenes to change gene
    +0007 % identifiers in a RAVEN model. If the input is an assembly accession,
    +0008 % downloadGenomeData downloads the GFF3 and protein FASTA (.faa) files to
    +0009 % downloadDir, unless both are already present there.
     0010 %
     0011 % Parameters
     0012 % ----------
     0013 % accession : char
    -0014 %     NCBI genome assembly accession, e.g. 'GCF_000002595.2', or a path to a
    -0015 %     local GFF3 annotation file.
    -0016 %
    -0017 % Name-Value Arguments
    -0018 % --------------------
    -0019 % outputFile : char
    -0020 %     Path to save the resulting table as a tab-delimited .tsv file. If
    -0021 %     omitted or empty, no file is written and the table is only returned.
    -0022 % downloadDir : char
    -0023 %     Directory where genome files are downloaded if not already present
    -0024 %     (default: current working directory).
    -0025 %
    -0026 % Returns
    -0027 % -------
    -0028 % geneTable : table
    -0029 %     MATLAB table with one row per gene containing:
    -0030 %       locus_tag       — stable locus identifier (e.g. 'Cre01.g000001')
    -0031 %       old_locus_tag   — previous locus identifier when available
    -0032 %       GeneID          — NCBI Gene ID (e.g. '5723799')
    -0033 %       gene_name       — common gene symbol when available (e.g. 'rbcL')
    -0034 %       GenBank_protein — protein accession (e.g. 'XP_001698190.2'), matching
    -0035 %                         the protein FASTA headers
    -0036 %       UniProt         — UniProt accession from Dbxref when available
    -0037 %
    -0038 % Examples
    -0039 % --------
    -0040 %     % Fetch and parse gene data for Chlamydomonas reinhardtii
    -0041 %     geneTable = getGeneData('GCF_000002595.2');
    -0042 %
    -0043 %     % Save the mapping table to a specific file
    -0044 %     geneTable = getGeneData('GCF_000002595.2', 'chlamy.tsv');
    -0045 %
    -0046 %     % Save to a file and download to a specific directory
    -0047 %     geneTable = getGeneData('GCF_000002595.2', 'chlamy.tsv', 'data/');
    -0048 %
    -0049 % Notes
    -0050 % -----
    -0051 % When outputFile is provided the table is written as UTF-8 encoded
    -0052 % tab-delimited text with a header row, compatible with readtable and
    -0053 % renameModelGenes. Rows without a locus_tag are silently discarded.
    -0054 
    -0055     if nargin < 1 || isempty(accession)
    -0056         error('getGeneData:missingInput', ...
    -0057             'accession is required. Provide an NCBI assembly accession, e.g. ''GCF_000002595.2''.');
    -0058     end
    -0059 
    -0060     p = parseRAVENargs(varargin, {'outputFile', ''; 'downloadDir', pwd});
    -0061     outputFile = char(p.outputFile);
    -0062     downloadDir = char(p.downloadDir);
    -0063 
    -0064     % Resolve input → local GFF3 path. Decide whether input is a file path or an NCBI accession.
    -0065     if ischar(accession) || isstring(accession)
    -0066         inp = char(accession);
    -0067     else
    -0068         error('getGeneData:badInputType', 'Input must be a character vector or string.');
    -0069     end
    -0070     
    -0071     gffPath = '';
    -0072     % If input is an existing file, use it.
    -0073     if isfile(inp)
    -0074         gffPath = inp;
    +0014 %     NCBI genome assembly accession, for example 'GCF_000002595.2', or the
    +0015 %     path to a local GFF3 annotation file. An error is raised if the
    +0016 %     input is neither an existing file nor an accession of the form
    +0017 %     'GCF_' or 'GCA_' followed by digits and an optional version.
    +0018 %
    +0019 % Name-Value Arguments
    +0020 % --------------------
    +0021 % outputFile : char
    +0022 %     Path of the tab-delimited .tsv file to which the table is written.
    +0023 %     No file is written if omitted or empty (default '').
    +0024 % downloadDir : char
    +0025 %     Directory where genome files are downloaded if not already present.
    +0026 %     Only used when the input is an accession (default: current working
    +0027 %     directory).
    +0028 %
    +0029 % Returns
    +0030 % -------
    +0031 % geneTable : table
    +0032 %     MATLAB table with one row per distinct combination of gene and
    +0033 %     protein accession (a CDS feature with a protein accession, repeated
    +0034 %     CDS rows are merged), with the columns below. A column entry is empty
    +0035 %     when the annotation lacks the value.
    +0036 %
    +0037 %     - locus_tag : locus identifier (for example 'Cre01.g000001')
    +0038 %     - old_locus_tag : earlier locus identifier
    +0039 %     - GeneID : NCBI Gene ID (for example '5723799')
    +0040 %     - gene_name : gene symbol (for example 'rbcL')
    +0041 %     - GenBank_protein : protein accession (for example 'XP_001698190.2'),
    +0042 %       as in the protein FASTA headers
    +0043 %     - UniProt : UniProt accession from the Dbxref attribute of the gene
    +0044 %
    +0045 % Examples
    +0046 % --------
    +0047 %     % Fetch and parse gene data for Chlamydomonas reinhardtii
    +0048 %     geneTable = getGeneData('GCF_000002595.2');
    +0049 %
    +0050 %     % Save the mapping table to a specific file
    +0051 %     geneTable = getGeneData('GCF_000002595.2', 'chlamy.tsv');
    +0052 %
    +0053 %     % Save to a file and download to a specific directory
    +0054 %     geneTable = getGeneData('GCF_000002595.2', 'chlamy.tsv', 'data/');
    +0055 %
    +0056 % Notes
    +0057 % -----
    +0058 % When outputFile is provided the table is written as UTF-8 encoded
    +0059 % tab-delimited text with a header row, which readtable and
    +0060 % renameModelGenes can read. A CDS whose gene has no locus_tag, or whose
    +0061 % gene feature is missing, gives a row with an empty locus_tag.
    +0062 
    +0063     if nargin < 1 || isempty(accession)
    +0064         error('getGeneData:missingInput', ...
    +0065             'accession is required. Provide an NCBI assembly accession, e.g. ''GCF_000002595.2''.');
    +0066     end
    +0067 
    +0068     p = parseRAVENargs(varargin, {'outputFile', ''; 'downloadDir', pwd});
    +0069     outputFile = char(p.outputFile);
    +0070     downloadDir = char(p.downloadDir);
    +0071 
    +0072     % Resolve input → local GFF3 path. Decide whether input is a file path or an NCBI accession.
    +0073     if ischar(accession) || isstring(accession)
    +0074         inp = char(accession);
     0075     else
    -0076         % Check if it looks like an NCBI assembly accession (GCF_... or GCA_...)
    -0077         if ~isempty(regexp(inp, '^GC[FA]_\d+(\.\d+)?$', 'once'))
    -0078             gffPath = downloadGenomeData(inp, downloadDir);
    -0079         else
    -0080             error('getGeneData:badInput', ...
    -0081                 'Input "%s" is neither an existing file nor a recognised NCBI assembly accession.', inp);
    -0082         end
    -0083     end
    -0084 
    -0085     % Parse the GFF3 annotation into a gene-mapping table
    -0086     geneTable = parseGFF(gffPath);
    -0087 
    -0088     % Save result only if an output file was requested
    -0089     if ~isempty(outputFile)
    -0090         writetable(geneTable, outputFile, 'Delimiter', '\t', 'FileType', 'text');
    -0091         fprintf('Gene table saved to: %s  (%d rows)\n', outputFile, height(geneTable));
    -0092     end
    -0093 end
    -0094 
    -0095 %--------------------------------------------------------------------------
    -0096 % Helper functions
    -0097 
    -0098 function T = parseGFF(gffPath)
    -0099 % Parse a GFF3 file extracting gene + CDS pairs. The protein accession is
    -0100 % taken from each CDS protein_id, and the owning gene is resolved through
    -0101 % the Parent chain (CDS → mRNA → gene for eukaryotes, CDS → gene for
    -0102 % prokaryotes), so the GenBank_protein column matches the protein FASTA.
    -0103 %
    -0104 % Returns a table with columns:
    -0105 %   locus_tag | old_locus_tag | GeneID | gene_name | GenBank_protein | UniProt
    -0106 
    -0107     batchSize = 10000;
    -0108     rows  = cell(batchSize, 6);
    -0109     nRows = 0;
    -0110     % Use a containers.Map to hold the last gene record keyed by gene ID,
    -0111     % and an mRNA → gene map to resolve eukaryote CDS parents.
    -0112     geneMap    = containers.Map('KeyType', 'char', 'ValueType', 'any');
    -0113     mrnaToGene = containers.Map('KeyType', 'char', 'ValueType', 'char');
    -0114     emptyGene = struct('locus_tag','','old_locus_tag','','geneID','','name','','uniProt','');
    -0115 
    -0116     fid = fopen(gffPath, 'r');
    -0117     while ~feof(fid)
    -0118         line = fgetl(fid);
    -0119         if ~ischar(line) || isempty(line) || line(1) == '#', continue, end
    -0120         fields = strsplit(line, '\t');
    -0121         if numel(fields) < 9, continue, end
    -0122 
    -0123         featureType = fields{3};
    -0124         attrs       = fields{9};
    -0125 
    -0126         if strcmp(featureType, 'gene')
    -0127             g = parseGeneAttrs(attrs);
    -0128             if ~isempty(g.id)
    -0129                 geneMap(g.id) = g;
    -0130             end
    -0131 
    -0132         elseif strcmp(featureType, 'mRNA')
    -0133             id     = extractAttr(attrs, 'ID');
    -0134             parent = extractAttr(attrs, 'Parent');
    -0135             if ~isempty(id) && ~isempty(parent)
    -0136                 mrnaToGene(id) = parent;
    -0137             end
    -0138 
    -0139         elseif strcmp(featureType, 'CDS')
    -0140             child     = parseCDSAttrs(attrs);
    -0141             proteinID = child.protein_id;
    -0142             if isempty(child.parent) || isempty(proteinID), continue, end
    -0143 
    -0144             % Resolve the owning gene through the Parent chain
    -0145             if isKey(mrnaToGene, child.parent)
    -0146                 geneKey = mrnaToGene(child.parent);
    -0147             else
    -0148                 geneKey = child.parent;  % prokaryote: CDS Parent is the gene
    -0149             end
    -0150             if isKey(geneMap, geneKey)
    -0151                 g = geneMap(geneKey);
    -0152             else
    -0153                 g = emptyGene;
    -0154             end
    -0155 
    -0156             nRows = nRows + 1;
    -0157             if nRows > size(rows, 1)
    -0158                 rows = [rows; cell(batchSize, 6)];
    -0159             end
    -0160             rows(nRows, :) = {g.locus_tag, g.old_locus_tag, ...
    -0161                                g.geneID,    g.name,          proteinID, g.uniProt};
    -0162         end
    -0163     end
    -0164     fclose(fid);
    -0165 
    -0166     rows = rows(1:nRows, :);
    -0167 
    -0168     % Deduplicate rows (CDS repeats per exon).
    -0169     rowKeys = cellfun(@(a,b,c,d,e) strjoin({a,b,c,d,e}, '|'), ...
    -0170         rows(:,1), rows(:,2), rows(:,3), rows(:,4), rows(:,5), ...
    -0171         'UniformOutput', false);
    -0172     [~, ia] = unique(rowKeys, 'stable');
    -0173     rows = rows(ia, :);
    -0174 
    -0175     T = cell2table(rows, 'VariableNames', ...
    -0176         {'locus_tag','old_locus_tag','GeneID','gene_name','GenBank_protein','UniProt'});
    -0177 end
    -0178 
    -0179 % Attibute parsing utilities
    -0180 function value = extractAttr(attrStr, key)
    -0181 % Extract the value of a GFF3 attribute by key.
    -0182 % Handles URL-encoded characters (%2C etc.) for robustness.
    -0183 % Returns '' if key is not present.
    -0184     pattern = sprintf('(?:^|;)%s=([^;]+)', regexptranslate('escape', key));
    -0185     tok = regexp(attrStr, pattern, 'tokens', 'once');
    -0186     if isempty(tok)
    -0187         value = '';
    -0188     else
    -0189         value = gff3Decode(tok{1});
    -0190     end
    -0191 end
    -0192 
    -0193 function out = gff3Decode(str)
    -0194 % Decode GFF3 percent-encoded characters (%XX). Unlike urldecode, '+' is
    -0195 % left untouched, as GFF3 does not use it to encode spaces.
    -0196     out = regexprep(str, '%([0-9A-Fa-f]{2})', '${char(hex2dec($1))}');
    -0197 end
    -0198 
    -0199 function value = extractDbxrefField(dbxref, prefix)
    -0200 % From a comma-separated Dbxref value like
    -0201 %   "Phytozome:Cre16.g651050,GeneID:5723799,GenBank:XM_001698190.2"
    -0202 % extract the part after "prefix:".
    -0203 % Returns '' if not found.
    -0204     if isempty(dbxref)
    -0205         value = '';
    -0206         return
    -0207     end
    -0208     parts   = strsplit(dbxref, ',');
    -0209     pattern = sprintf('^%s:(.+)$', regexptranslate('escape', prefix));
    -0210     value   = '';
    -0211     for i = 1:numel(parts)
    -0212         tok = regexp(strtrim(parts{i}), pattern, 'tokens', 'once');
    -0213         if ~isempty(tok)
    -0214             value = tok{1};
    -0215             return
    -0216         end
    -0217     end
    -0218 end
    -0219 
    -0220 function g = parseGeneAttrs(attrs)
    -0221     g.id            = extractAttr(attrs, 'ID');
    -0222     g.name          = extractAttr(attrs, 'Name');
    -0223     g.locus_tag     = extractAttr(attrs, 'locus_tag');
    -0224     g.old_locus_tag = extractAttr(attrs, 'old_locus_tag');
    -0225 
    -0226     % Dbxref is a comma-separated list like:
    -0227     %   GeneID:5723799,GenBank:XM_001698190.2
    -0228     dbxref          = extractAttr(attrs, 'Dbxref');
    -0229     g.geneID        = extractDbxrefField(dbxref, 'GeneID');
    -0230 
    -0231     % UniProt accession appears as UniProtKB/Swiss-Prot:P12345 or UniProtKB:P12345
    -0232     g.uniProt       = extractDbxrefField(dbxref, 'UniProtKB/Swiss-Prot');
    -0233     if isempty(g.uniProt)
    -0234         g.uniProt   = extractDbxrefField(dbxref, 'UniProtKB');
    -0235     end
    -0236 end
    -0237 
    -0238 function c = parseCDSAttrs(attrs)
    -0239     c.parent     = extractAttr(attrs, 'Parent');
    -0240     c.protein_id = extractAttr(attrs, 'protein_id');
    -0241     if isempty(c.protein_id)
    -0242         % Some files put it in Dbxref as GenBank:WP_...
    -0243         dbxref       = extractAttr(attrs, 'Dbxref');
    -0244         c.protein_id = extractDbxrefField(dbxref, 'GenBank');
    -0245     end
    -0246 end

    +0076 error('getGeneData:badInputType', 'Input must be a character vector or string.'); +0077 end +0078 +0079 gffPath = ''; +0080 % If input is an existing file, use it. +0081 if isfile(inp) +0082 gffPath = inp; +0083 else +0084 % Check if it looks like an NCBI assembly accession (GCF_... or GCA_...) +0085 if ~isempty(regexp(inp, '^GC[FA]_\d+(\.\d+)?$', 'once')) +0086 gffPath = downloadGenomeData(inp, downloadDir); +0087 else +0088 error('getGeneData:badInput', ... +0089 'Input "%s" is neither an existing file nor a recognised NCBI assembly accession.', inp); +0090 end +0091 end +0092 +0093 % Parse the GFF3 annotation into a gene-mapping table +0094 geneTable = parseGFF(gffPath); +0095 +0096 % Save result only if an output file was requested +0097 if ~isempty(outputFile) +0098 writetable(geneTable, outputFile, 'Delimiter', '\t', 'FileType', 'text'); +0099 fprintf('Gene table saved to: %s (%d rows)\n', outputFile, height(geneTable)); +0100 end +0101 end +0102 +0103 %-------------------------------------------------------------------------- +0104 % Helper functions +0105 +0106 function T = parseGFF(gffPath) +0107 % Parse a GFF3 file extracting gene + CDS pairs. The protein accession is +0108 % taken from each CDS protein_id, and the owning gene is resolved through +0109 % the Parent chain (CDS → mRNA → gene for eukaryotes, CDS → gene for +0110 % prokaryotes), so the GenBank_protein column matches the protein FASTA. +0111 % +0112 % Returns a table with columns: +0113 % locus_tag | old_locus_tag | GeneID | gene_name | GenBank_protein | UniProt +0114 +0115 batchSize = 10000; +0116 rows = cell(batchSize, 6); +0117 nRows = 0; +0118 % Use a containers.Map to hold the last gene record keyed by gene ID, +0119 % and an mRNA → gene map to resolve eukaryote CDS parents. +0120 geneMap = containers.Map('KeyType', 'char', 'ValueType', 'any'); +0121 mrnaToGene = containers.Map('KeyType', 'char', 'ValueType', 'char'); +0122 emptyGene = struct('locus_tag','','old_locus_tag','','geneID','','name','','uniProt',''); +0123 +0124 fid = fopen(gffPath, 'r'); +0125 while ~feof(fid) +0126 line = fgetl(fid); +0127 if ~ischar(line) || isempty(line) || line(1) == '#', continue, end +0128 fields = strsplit(line, '\t'); +0129 if numel(fields) < 9, continue, end +0130 +0131 featureType = fields{3}; +0132 attrs = fields{9}; +0133 +0134 if strcmp(featureType, 'gene') +0135 g = parseGeneAttrs(attrs); +0136 if ~isempty(g.id) +0137 geneMap(g.id) = g; +0138 end +0139 +0140 elseif strcmp(featureType, 'mRNA') +0141 id = extractAttr(attrs, 'ID'); +0142 parent = extractAttr(attrs, 'Parent'); +0143 if ~isempty(id) && ~isempty(parent) +0144 mrnaToGene(id) = parent; +0145 end +0146 +0147 elseif strcmp(featureType, 'CDS') +0148 child = parseCDSAttrs(attrs); +0149 proteinID = child.protein_id; +0150 if isempty(child.parent) || isempty(proteinID), continue, end +0151 +0152 % Resolve the owning gene through the Parent chain +0153 if isKey(mrnaToGene, child.parent) +0154 geneKey = mrnaToGene(child.parent); +0155 else +0156 geneKey = child.parent; % prokaryote: CDS Parent is the gene +0157 end +0158 if isKey(geneMap, geneKey) +0159 g = geneMap(geneKey); +0160 else +0161 g = emptyGene; +0162 end +0163 +0164 nRows = nRows + 1; +0165 if nRows > size(rows, 1) +0166 rows = [rows; cell(batchSize, 6)]; +0167 end +0168 rows(nRows, :) = {g.locus_tag, g.old_locus_tag, ... +0169 g.geneID, g.name, proteinID, g.uniProt}; +0170 end +0171 end +0172 fclose(fid); +0173 +0174 rows = rows(1:nRows, :); +0175 +0176 % Deduplicate rows (CDS repeats per exon). +0177 rowKeys = cellfun(@(a,b,c,d,e) strjoin({a,b,c,d,e}, '|'), ... +0178 rows(:,1), rows(:,2), rows(:,3), rows(:,4), rows(:,5), ... +0179 'UniformOutput', false); +0180 [~, ia] = unique(rowKeys, 'stable'); +0181 rows = rows(ia, :); +0182 +0183 T = cell2table(rows, 'VariableNames', ... +0184 {'locus_tag','old_locus_tag','GeneID','gene_name','GenBank_protein','UniProt'}); +0185 end +0186 +0187 % Attibute parsing utilities +0188 function value = extractAttr(attrStr, key) +0189 % Extract the value of a GFF3 attribute by key. +0190 % Handles URL-encoded characters (%2C etc.) for robustness. +0191 % Returns '' if key is not present. +0192 pattern = sprintf('(?:^|;)%s=([^;]+)', regexptranslate('escape', key)); +0193 tok = regexp(attrStr, pattern, 'tokens', 'once'); +0194 if isempty(tok) +0195 value = ''; +0196 else +0197 value = gff3Decode(tok{1}); +0198 end +0199 end +0200 +0201 function out = gff3Decode(str) +0202 % Decode GFF3 percent-encoded characters (%XX). Unlike urldecode, '+' is +0203 % left untouched, as GFF3 does not use it to encode spaces. +0204 out = regexprep(str, '%([0-9A-Fa-f]{2})', '${char(hex2dec($1))}'); +0205 end +0206 +0207 function value = extractDbxrefField(dbxref, prefix) +0208 % From a comma-separated Dbxref value like +0209 % "Phytozome:Cre16.g651050,GeneID:5723799,GenBank:XM_001698190.2" +0210 % extract the part after "prefix:". +0211 % Returns '' if not found. +0212 if isempty(dbxref) +0213 value = ''; +0214 return +0215 end +0216 parts = strsplit(dbxref, ','); +0217 pattern = sprintf('^%s:(.+)$', regexptranslate('escape', prefix)); +0218 value = ''; +0219 for i = 1:numel(parts) +0220 tok = regexp(strtrim(parts{i}), pattern, 'tokens', 'once'); +0221 if ~isempty(tok) +0222 value = tok{1}; +0223 return +0224 end +0225 end +0226 end +0227 +0228 function g = parseGeneAttrs(attrs) +0229 g.id = extractAttr(attrs, 'ID'); +0230 g.name = extractAttr(attrs, 'Name'); +0231 g.locus_tag = extractAttr(attrs, 'locus_tag'); +0232 g.old_locus_tag = extractAttr(attrs, 'old_locus_tag'); +0233 +0234 % Dbxref is a comma-separated list like: +0235 % GeneID:5723799,GenBank:XM_001698190.2 +0236 dbxref = extractAttr(attrs, 'Dbxref'); +0237 g.geneID = extractDbxrefField(dbxref, 'GeneID'); +0238 +0239 % UniProt accession appears as UniProtKB/Swiss-Prot:P12345 or UniProtKB:P12345 +0240 g.uniProt = extractDbxrefField(dbxref, 'UniProtKB/Swiss-Prot'); +0241 if isempty(g.uniProt) +0242 g.uniProt = extractDbxrefField(dbxref, 'UniProtKB'); +0243 end +0244 end +0245 +0246 function c = parseCDSAttrs(attrs) +0247 c.parent = extractAttr(attrs, 'Parent'); +0248 c.protein_id = extractAttr(attrs, 'protein_id'); +0249 if isempty(c.protein_id) +0250 % Some files put it in Dbxref as GenBank:WP_... +0251 dbxref = extractAttr(attrs, 'Dbxref'); +0252 c.protein_id = extractDbxrefField(dbxref, 'GenBank'); +0253 end +0254 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/curation/processProteinFastaFile.html b/docs/html/curation/processProteinFastaFile.html index 4984f47d..16e79f92 100644 --- a/docs/html/curation/processProteinFastaFile.html +++ b/docs/html/curation/processProteinFastaFile.html @@ -29,11 +29,13 @@

    SYNOPSIS ^DESCRIPTION ^

     processProteinFastaFile  Rename protein FASTA headers using a gene mapping table.
     
    - Reads a protein FASTA file (as downloaded by downloadGenomeData) and
    - replaces each sequence header with the value from the specified geneTable
    - column, matched via the GenBank_protein accession present in the original
    - FASTA header.  Sequences whose accession is not found in geneTable are
    - kept with their original header unchanged.
    + Reads a protein FASTA file (such as one downloaded by downloadGenomeData)
    + and replaces each sequence header with the value from the specified
    + geneTable column. A sequence is matched to a row through the
    + GenBank_protein accession, which is the first token of the original
    + FASTA header. Sequences whose accession is not found in geneTable keep
    + their original header. The numbers of renamed and kept sequences are
    + printed.
     
      Parameters
      ----------
    @@ -41,18 +43,19 @@ 

    DESCRIPTION ^DESCRIPTION ^CROSS-REFERENCE INFORMATION ^

    @@ -95,143 +99,147 @@

    SOURCE CODE ^
    0001 function processedFaaFile = processProteinFastaFile(faaFile, geneTable, headerCol, varargin)
     0002 % processProteinFastaFile  Rename protein FASTA headers using a gene mapping table.
     0003 %
    -0004 % Reads a protein FASTA file (as downloaded by downloadGenomeData) and
    -0005 % replaces each sequence header with the value from the specified geneTable
    -0006 % column, matched via the GenBank_protein accession present in the original
    -0007 % FASTA header.  Sequences whose accession is not found in geneTable are
    -0008 % kept with their original header unchanged.
    -0009 %
    -0010 % Parameters
    -0011 % ----------
    -0012 % faaFile : char
    -0013 %     Path to the protein FASTA file (.faa) to process.
    -0014 % geneTable : char | table
    -0015 %     Either a path to a tab-delimited .tsv file produced by getGeneData,
    -0016 %     or a MATLAB table variable.  Must contain at least the columns
    -0017 %     'GenBank_protein' and the column named by headerCol.
    -0018 % headerCol : char
    -0019 %     Name of the geneTable column whose values will replace each FASTA
    -0020 %     header (e.g. 'locus_tag', 'gene_name', 'GenBank_protein').
    -0021 %
    -0022 % Name-Value Arguments
    -0023 % --------------------
    -0024 % outputDir : char
    -0025 %     Directory where the processed FASTA file is saved.  The output file
    -0026 %     name is the original base name with '_processed' appended before the
    -0027 %     extension (default: current working directory).
    -0028 %
    -0029 % Returns
    -0030 % -------
    -0031 % processedFaaFile : char
    -0032 %     Full path to the written processed FASTA file.
    -0033 %
    -0034 % Examples
    -0035 % --------
    -0036 %     % Rename headers using locus_tag
    -0037 %     [~, faa]  = downloadGenomeData('GCF_000002595.2');
    -0038 %     geneTable = getGeneData('GCF_000002595.2', 'chlamy.tsv');
    -0039 %     outFile   = processProteinFastaFile(faa, geneTable, 'locus_tag');
    -0040 %
    -0041 %     % Load both inputs from saved files, use gene_name as header
    -0042 %     outFile = processProteinFastaFile('GCF_000002595.2_protein.faa', 'chlamy.tsv', 'gene_name');
    +0004 % Reads a protein FASTA file (such as one downloaded by downloadGenomeData)
    +0005 % and replaces each sequence header with the value from the specified
    +0006 % geneTable column. A sequence is matched to a row through the
    +0007 % GenBank_protein accession, which is the first token of the original
    +0008 % FASTA header. Sequences whose accession is not found in geneTable keep
    +0009 % their original header. The numbers of renamed and kept sequences are
    +0010 % printed.
    +0011 %
    +0012 % Parameters
    +0013 % ----------
    +0014 % faaFile : char
    +0015 %     Path to the protein FASTA file (.faa) to process.
    +0016 % geneTable : char | table
    +0017 %     Either a path to a tab-delimited .tsv file produced by getGeneData,
    +0018 %     or a MATLAB table variable. It must contain the columns
    +0019 %     'GenBank_protein' and the column named by headerCol.
    +0020 % headerCol : char
    +0021 %     Name of the geneTable column whose values will replace each FASTA
    +0022 %     header (for example 'locus_tag', 'gene_name', 'GenBank_protein').
    +0023 %
    +0024 % Name-Value Arguments
    +0025 % --------------------
    +0026 % outputDir : char
    +0027 %     Directory where the processed FASTA file is saved. It is created if
    +0028 %     missing. The output file name is the original base name with
    +0029 %     '_processed' appended before the extension (default: current working
    +0030 %     directory).
    +0031 %
    +0032 % Returns
    +0033 % -------
    +0034 % processedFaaFile : char
    +0035 %     Full path to the written processed FASTA file.
    +0036 %
    +0037 % Examples
    +0038 % --------
    +0039 %     % Rename headers using locus_tag
    +0040 %     [~, faa]  = downloadGenomeData('GCF_000002595.2');
    +0041 %     geneTable = getGeneData('GCF_000002595.2', 'chlamy.tsv');
    +0042 %     outFile   = processProteinFastaFile(faa, geneTable, 'locus_tag');
     0043 %
    -0044 %     % Save the processed file to a specific directory
    -0045 %     outFile = processProteinFastaFile(faa, 'chlamy.tsv', 'locus_tag', 'results/');
    +0044 %     % Load both inputs from saved files, use gene_name as header
    +0045 %     outFile = processProteinFastaFile('GCF_000002595.2_protein.faa', 'chlamy.tsv', 'gene_name');
     0046 %
    -0047 % Notes
    -0048 % -----
    -0049 % GenBank_protein must contain accessions matching the first token of each
    -0050 % FASTA header (after '>').  For prokaryotes this is the protein_id
    -0051 % (e.g. WP_012345678.1); for eukaryotes it is the GenBank accession stored
    -0052 % in the GFF3 annotation by getGeneData.
    -0053     
    -0054     if nargin < 1 || isempty(faaFile)
    -0055         error('processProteinFastaFile:missingInput', ...
    -0056             'faaFile is required. Provide a path to a protein FASTA (.faa) file.');
    -0057     end
    -0058     faaFile = char(faaFile);
    -0059     if ~isfile(faaFile)
    -0060         error('processProteinFastaFile:fileNotFound', ...
    -0061             'FASTA file not found: ''%s''.', faaFile);
    -0062     end
    -0063 
    -0064     if nargin < 2 || isempty(geneTable)
    -0065         error('processProteinFastaFile:missingInput', ...
    -0066             'geneTable is required. Provide a table variable or a path to a .tsv file.');
    -0067     end
    -0068 
    -0069     if nargin < 3 || isempty(headerCol)
    -0070         error('processProteinFastaFile:missingInput', ...
    -0071             'headerCol is required. Specify the geneTable column to use as the new FASTA header (e.g. ''locus_tag'').');
    -0072     end
    -0073     headerCol = char(headerCol);
    -0074 
    -0075     p = parseRAVENargs(varargin, {'outputDir', pwd});
    -0076     outputDir = char(p.outputDir);
    -0077 
    -0078     % Load gene table
    -0079     if ischar(geneTable) || isstring(geneTable)
    -0080         tsvPath = char(geneTable);
    -0081         if ~isfile(tsvPath)
    -0082             error('processProteinFastaFile:fileNotFound', ...
    -0083                 'Gene table file not found: ''%s''.', tsvPath);
    -0084         end
    -0085         geneTable = readtable(tsvPath, 'FileType', 'text', 'Delimiter', '\t', 'TextType', 'char');
    -0086     end
    -0087 
    -0088     if ~istable(geneTable)
    -0089         error('processProteinFastaFile:badInput', ...
    -0090             'geneTable must be a MATLAB table or a path to a .tsv file.');
    -0091     end
    -0092 
    -0093     for col = {'GenBank_protein', headerCol}
    -0094         if ~ismember(col{1}, geneTable.Properties.VariableNames)
    -0095             error('processProteinFastaFile:missingColumn', ...
    -0096                 'geneTable is missing required column: ''%s''.', col{1});
    -0097         end
    -0098     end
    -0099 
    -0100     % Build protein-accession → header-value lookup map
    -0101     proteinIDs = geneTable.GenBank_protein;
    -0102     if ~iscell(proteinIDs), proteinIDs = cellstr(proteinIDs); end
    +0047 %     % Save the processed file to a specific directory
    +0048 %     outFile = processProteinFastaFile(faa, 'chlamy.tsv', 'locus_tag', 'results/');
    +0049 %
    +0050 % Notes
    +0051 % -----
    +0052 % GenBank_protein must contain accessions equal to the first
    +0053 % whitespace-delimited token of each FASTA header (after '>'), for example
    +0054 % WP_012345678.1. getGeneData takes the accession from the protein_id
    +0055 % attribute of the CDS features of the GFF3 annotation, or from a GenBank
    +0056 % entry of the Dbxref attribute when protein_id is empty.
    +0057     
    +0058     if nargin < 1 || isempty(faaFile)
    +0059         error('processProteinFastaFile:missingInput', ...
    +0060             'faaFile is required. Provide a path to a protein FASTA (.faa) file.');
    +0061     end
    +0062     faaFile = char(faaFile);
    +0063     if ~isfile(faaFile)
    +0064         error('processProteinFastaFile:fileNotFound', ...
    +0065             'FASTA file not found: ''%s''.', faaFile);
    +0066     end
    +0067 
    +0068     if nargin < 2 || isempty(geneTable)
    +0069         error('processProteinFastaFile:missingInput', ...
    +0070             'geneTable is required. Provide a table variable or a path to a .tsv file.');
    +0071     end
    +0072 
    +0073     if nargin < 3 || isempty(headerCol)
    +0074         error('processProteinFastaFile:missingInput', ...
    +0075             'headerCol is required. Specify the geneTable column to use as the new FASTA header (e.g. ''locus_tag'').');
    +0076     end
    +0077     headerCol = char(headerCol);
    +0078 
    +0079     p = parseRAVENargs(varargin, {'outputDir', pwd});
    +0080     outputDir = char(p.outputDir);
    +0081 
    +0082     % Load gene table
    +0083     if ischar(geneTable) || isstring(geneTable)
    +0084         tsvPath = char(geneTable);
    +0085         if ~isfile(tsvPath)
    +0086             error('processProteinFastaFile:fileNotFound', ...
    +0087                 'Gene table file not found: ''%s''.', tsvPath);
    +0088         end
    +0089         geneTable = readtable(tsvPath, 'FileType', 'text', 'Delimiter', '\t', 'TextType', 'char');
    +0090     end
    +0091 
    +0092     if ~istable(geneTable)
    +0093         error('processProteinFastaFile:badInput', ...
    +0094             'geneTable must be a MATLAB table or a path to a .tsv file.');
    +0095     end
    +0096 
    +0097     for col = {'GenBank_protein', headerCol}
    +0098         if ~ismember(col{1}, geneTable.Properties.VariableNames)
    +0099             error('processProteinFastaFile:missingColumn', ...
    +0100                 'geneTable is missing required column: ''%s''.', col{1});
    +0101         end
    +0102     end
     0103 
    -0104     headerValues = geneTable.(headerCol);
    -0105     if ~iscell(headerValues), headerValues = cellstr(headerValues); end
    -0106 
    -0107     % Keep only rows with a non-empty protein accession
    -0108     validMask  = ~cellfun(@isempty, proteinIDs);
    -0109     proteinMap = containers.Map(proteinIDs(validMask), num2cell(find(validMask)));
    +0104     % Build protein-accession → header-value lookup map
    +0105     proteinIDs = geneTable.GenBank_protein;
    +0106     if ~iscell(proteinIDs), proteinIDs = cellstr(proteinIDs); end
    +0107 
    +0108     headerValues = geneTable.(headerCol);
    +0109     if ~iscell(headerValues), headerValues = cellstr(headerValues); end
     0110 
    -0111 
    -0112     % Prepare output path
    -0113     if ~exist(outputDir, 'dir')
    -0114         mkdir(outputDir);
    -0115     end
    -0116     [~, baseName, ext] = fileparts(faaFile);
    -0117     processedFaaFile   = fullfile(outputDir, [baseName '_processed' ext]);
    -0118 
    -0119 
    -0120     % Read the FASTA, rename headers, write the result
    -0121     fastaStruct = readFasta(faaFile);
    -0122     nMatched    = 0;
    -0123     for i = 1:numel(fastaStruct)
    -0124         % The protein accession is the first whitespace-delimited token of
    -0125         % the header (readFasta strips the leading '>')
    -0126         tokens     = strsplit(fastaStruct(i).Header);
    -0127         proteinAcc = tokens{1};
    -0128         if isKey(proteinMap, proteinAcc)
    -0129             fastaStruct(i).Header = headerValues{proteinMap(proteinAcc)};
    -0130             nMatched = nMatched + 1;
    -0131         end
    -0132         % Sequences with no match keep their original header
    -0133     end
    -0134     writeFasta(processedFaaFile, fastaStruct);
    -0135 
    -0136     fprintf('Processed FASTA written to: %s\n', processedFaaFile);
    -0137     fprintf('  Total sequences  : %d\n', numel(fastaStruct));
    -0138     fprintf('  Renamed          : %d\n', nMatched);
    -0139     fprintf('  Kept (no match)  : %d\n', numel(fastaStruct) - nMatched);
    -0140 end

    +0111 % Keep only rows with a non-empty protein accession +0112 validMask = ~cellfun(@isempty, proteinIDs); +0113 proteinMap = containers.Map(proteinIDs(validMask), num2cell(find(validMask))); +0114 +0115 +0116 % Prepare output path +0117 if ~exist(outputDir, 'dir') +0118 mkdir(outputDir); +0119 end +0120 [~, baseName, ext] = fileparts(faaFile); +0121 processedFaaFile = fullfile(outputDir, [baseName '_processed' ext]); +0122 +0123 +0124 % Read the FASTA, rename headers, write the result +0125 fastaStruct = readFasta(faaFile); +0126 nMatched = 0; +0127 for i = 1:numel(fastaStruct) +0128 % The protein accession is the first whitespace-delimited token of +0129 % the header (readFasta strips the leading '>') +0130 tokens = strsplit(fastaStruct(i).Header); +0131 proteinAcc = tokens{1}; +0132 if isKey(proteinMap, proteinAcc) +0133 fastaStruct(i).Header = headerValues{proteinMap(proteinAcc)}; +0134 nMatched = nMatched + 1; +0135 end +0136 % Sequences with no match keep their original header +0137 end +0138 writeFasta(processedFaaFile, fastaStruct); +0139 +0140 fprintf('Processed FASTA written to: %s\n', processedFaaFile); +0141 fprintf(' Total sequences : %d\n', numel(fastaStruct)); +0142 fprintf(' Renamed : %d\n', nMatched); +0143 fprintf(' Kept (no match) : %d\n', numel(fastaStruct) - nMatched); +0144 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/curation/renameModelGenes.html b/docs/html/curation/renameModelGenes.html index 54a6c461..7fbd3bef 100644 --- a/docs/html/curation/renameModelGenes.html +++ b/docs/html/curation/renameModelGenes.html @@ -29,34 +29,35 @@

    SYNOPSIS ^DESCRIPTION ^

     renameModelGenes  Replace gene identifiers in a RAVEN model.
     
    - Updates model.genes and model.grRules by substituting the identifiers
    - currently used in the model (fromCol) with new identifiers (toCol) from
    - a mapping table produced by getGeneData or supplied manually. After
    - renaming, model.rxnGeneMat is rebuilt automatically.
    + Replaces the identifiers in model.genes and model.grRules that are listed
    + in column fromCol of a mapping table with the identifiers in column
    + toCol. The table is produced by getGeneData or supplied manually. After
    + renaming, model.rxnGeneMat is rebuilt.
     
      Parameters
      ----------
      model : struct
    -     RAVEN model struct. Must contain model.genes and model.grRules;
    -     model.rxnGeneMat is rebuilt automatically.
    +     RAVEN model struct. It must contain model.genes and model.grRules.
      geneTable : table | char
    -     Gene-mapping table. Can be supplied as:
    -       (a) A MATLAB table variable already loaded in the workspace, or
    -       (b) A path to a tab-delimited .tsv file — loaded automatically.
    +     Gene-mapping table, either
    +       (a) a MATLAB table variable, or
    +       (b) the path to a tab-delimited .tsv file, which is read with
    +           readtable.
      fromCol : char
    -     Column in geneTable whose values match the identifiers currently in
    -     model.genes (e.g. 'locus_tag').
    +     Name of the column in geneTable whose values match the identifiers
    +     in model.genes (for example 'locus_tag').
      toCol : char
    -     Column in geneTable whose values will replace the current
    -     identifiers (e.g. 'gene_name').
    +     Name of the column in geneTable with the new identifiers (for
    +     example 'gene_name').
     
      Returns
      -------
      model : struct
    -     Updated model struct with:
    -       model.genes      — renamed gene list.
    -       model.grRules    — gene-reaction rules using new identifiers.
    -       model.rxnGeneMat — rebuilt sparse matrix matching the new gene list.
    +     Model struct with these fields changed:
    +
    +     - model.genes : gene list with the new identifiers.
    +     - model.grRules : gene-reaction rules with the new identifiers.
    +     - model.rxnGeneMat : sparse matrix rebuilt for the new gene list.
     
      Examples
      --------
    @@ -70,11 +71,17 @@ 

    DESCRIPTION ^CROSS-REFERENCE INFORMATION ^

    @@ -94,239 +101,246 @@

    SOURCE CODE ^
    0001 function model = renameModelGenes(model, geneTable, fromCol, toCol)
     0002 % renameModelGenes  Replace gene identifiers in a RAVEN model.
     0003 %
    -0004 % Updates model.genes and model.grRules by substituting the identifiers
    -0005 % currently used in the model (fromCol) with new identifiers (toCol) from
    -0006 % a mapping table produced by getGeneData or supplied manually. After
    -0007 % renaming, model.rxnGeneMat is rebuilt automatically.
    +0004 % Replaces the identifiers in model.genes and model.grRules that are listed
    +0005 % in column fromCol of a mapping table with the identifiers in column
    +0006 % toCol. The table is produced by getGeneData or supplied manually. After
    +0007 % renaming, model.rxnGeneMat is rebuilt.
     0008 %
     0009 % Parameters
     0010 % ----------
     0011 % model : struct
    -0012 %     RAVEN model struct. Must contain model.genes and model.grRules;
    -0013 %     model.rxnGeneMat is rebuilt automatically.
    -0014 % geneTable : table | char
    -0015 %     Gene-mapping table. Can be supplied as:
    -0016 %       (a) A MATLAB table variable already loaded in the workspace, or
    -0017 %       (b) A path to a tab-delimited .tsv file — loaded automatically.
    +0012 %     RAVEN model struct. It must contain model.genes and model.grRules.
    +0013 % geneTable : table | char
    +0014 %     Gene-mapping table, either
    +0015 %       (a) a MATLAB table variable, or
    +0016 %       (b) the path to a tab-delimited .tsv file, which is read with
    +0017 %           readtable.
     0018 % fromCol : char
    -0019 %     Column in geneTable whose values match the identifiers currently in
    -0020 %     model.genes (e.g. 'locus_tag').
    +0019 %     Name of the column in geneTable whose values match the identifiers
    +0020 %     in model.genes (for example 'locus_tag').
     0021 % toCol : char
    -0022 %     Column in geneTable whose values will replace the current
    -0023 %     identifiers (e.g. 'gene_name').
    +0022 %     Name of the column in geneTable with the new identifiers (for
    +0023 %     example 'gene_name').
     0024 %
     0025 % Returns
     0026 % -------
     0027 % model : struct
    -0028 %     Updated model struct with:
    -0029 %       model.genes      — renamed gene list.
    -0030 %       model.grRules    — gene-reaction rules using new identifiers.
    -0031 %       model.rxnGeneMat — rebuilt sparse matrix matching the new gene list.
    -0032 %
    -0033 % Examples
    -0034 % --------
    -0035 %     model     = readYAMLmodel('iCre1355.yml');
    -0036 %     geneTable = getGeneData('GCF_000002595.2', 'chlamy.tsv');
    -0037 %     model     = renameModelGenes(model, geneTable, 'locus_tag', 'gene_name');
    -0038 %
    -0039 %     % Supply a TSV file path directly
    -0040 %     model = renameModelGenes(model, 'chlamy.tsv', 'locus_tag', 'gene_name');
    -0041 %
    -0042 % Notes
    -0043 % -----
    -0044 % * Genes with no entry in fromCol, or whose toCol value is empty, are left
    -0045 %   unchanged; a warning lists them for investigation.
    -0046 % * Word-boundary matching prevents partial substitution (e.g. 'gene1'
    -0047 %   is not replaced inside 'gene10').
    -0048 % * grRules are standardized via standardizeGrRules before and after
    -0049 %   renaming to ensure consistent formatting.
    -0050 
    -0051     if nargin < 1 || isempty(model)
    -0052         error('renameModelGenes:missingInput', ...
    -0053             'model is required. Provide a RAVEN model struct.');
    -0054     end
    -0055 
    -0056     if nargin < 2 || isempty(geneTable)
    -0057         error('renameModelGenes:missingInput', ...
    -0058             'geneTable is required. Provide a table variable or a path to a .tsv file.');
    -0059     end
    -0060 
    -0061     if nargin < 3 || isempty(fromCol)
    -0062         error('renameModelGenes:missingInput', ...
    -0063             'fromCol is required. Specify the column in geneTable whose values match model.genes.');
    -0064     end
    -0065 
    -0066     if nargin < 4 || isempty(toCol)
    -0067         error('renameModelGenes:missingInput', ...
    -0068             'toCol is required. Specify the column in geneTable with the replacement identifiers.');
    -0069     end
    -0070 
    -0071     fromCol = char(fromCol);
    -0072     toCol   = char(toCol);
    -0073 
    -0074     % Load table if a file path was supplied
    -0075     if ischar(geneTable) || isstring(geneTable)
    -0076         if ~isfile(geneTable)
    -0077             error('renameModelGenes:fileNotFound', ...
    -0078                   'Table file not found: %s', geneTable);
    -0079         end
    -0080         geneTable = readtable(geneTable, 'Delimiter', '\t', 'FileType', 'text', ...
    -0081                               'TextType', 'char');
    -0082         fprintf('Loaded gene table from file: %s  (%d rows)\n', ...
    -0083                 geneTable, height(geneTable));
    -0084     end
    -0085 
    -0086     % Validate column names
    -0087     if ~ismember(fromCol, geneTable.Properties.VariableNames)
    -0088         error('renameModelGenes:badColumn', ...
    -0089               'Column "%s" not found in geneTable.\nAvailable columns: %s', ...
    -0090               fromCol, strjoin(geneTable.Properties.VariableNames, ', '));
    +0028 %     Model struct with these fields changed:
    +0029 %
    +0030 %     - model.genes : gene list with the new identifiers.
    +0031 %     - model.grRules : gene-reaction rules with the new identifiers.
    +0032 %     - model.rxnGeneMat : sparse matrix rebuilt for the new gene list.
    +0033 %
    +0034 % Examples
    +0035 % --------
    +0036 %     model     = readYAMLmodel('iCre1355.yml');
    +0037 %     geneTable = getGeneData('GCF_000002595.2', 'chlamy.tsv');
    +0038 %     model     = renameModelGenes(model, geneTable, 'locus_tag', 'gene_name');
    +0039 %
    +0040 %     % Supply a TSV file path directly
    +0041 %     model = renameModelGenes(model, 'chlamy.tsv', 'locus_tag', 'gene_name');
    +0042 %
    +0043 % Notes
    +0044 % -----
    +0045 % * Genes with no entry in fromCol, or whose toCol value is empty, are left
    +0046 %   unchanged, and a warning lists them.
    +0047 % * If a fromCol value occurs in several rows, the first row with a
    +0048 %   non-empty toCol value is used. Values are trimmed of surrounding
    +0049 %   whitespace.
    +0050 % * An identifier is replaced only where it is delimited by whitespace,
    +0051 %   parentheses or the start or end of the rule, so 'gene1' is not
    +0052 %   replaced inside 'gene10'.
    +0053 % * Unnecessary outer parentheses are removed from the grRules and the
    +0054 %   grRules are standardised with standardizeGrRules before and after
    +0055 %   renaming. A warning is issued when standardizeGrRules reports rules
    +0056 %   with potential issues.
    +0057 
    +0058     if nargin < 1 || isempty(model)
    +0059         error('renameModelGenes:missingInput', ...
    +0060             'model is required. Provide a RAVEN model struct.');
    +0061     end
    +0062 
    +0063     if nargin < 2 || isempty(geneTable)
    +0064         error('renameModelGenes:missingInput', ...
    +0065             'geneTable is required. Provide a table variable or a path to a .tsv file.');
    +0066     end
    +0067 
    +0068     if nargin < 3 || isempty(fromCol)
    +0069         error('renameModelGenes:missingInput', ...
    +0070             'fromCol is required. Specify the column in geneTable whose values match model.genes.');
    +0071     end
    +0072 
    +0073     if nargin < 4 || isempty(toCol)
    +0074         error('renameModelGenes:missingInput', ...
    +0075             'toCol is required. Specify the column in geneTable with the replacement identifiers.');
    +0076     end
    +0077 
    +0078     fromCol = char(fromCol);
    +0079     toCol   = char(toCol);
    +0080 
    +0081     % Load table if a file path was supplied
    +0082     if ischar(geneTable) || isstring(geneTable)
    +0083         if ~isfile(geneTable)
    +0084             error('renameModelGenes:fileNotFound', ...
    +0085                   'Table file not found: %s', geneTable);
    +0086         end
    +0087         geneTable = readtable(geneTable, 'Delimiter', '\t', 'FileType', 'text', ...
    +0088                               'TextType', 'char');
    +0089         fprintf('Loaded gene table from file: %s  (%d rows)\n', ...
    +0090                 geneTable, height(geneTable));
     0091     end
    -0092     if ~ismember(toCol, geneTable.Properties.VariableNames)
    -0093         error('renameModelGenes:badColumn', ...
    -0094               'Column "%s" not found in geneTable.\nAvailable columns: %s', ...
    -0095               toCol, strjoin(geneTable.Properties.VariableNames, ', '));
    -0096     end
    -0097 
    -0098     % Build mapping: oldName → newName
    -0099     % Use only rows where both fromCol and toCol are non-empty. If a fromCol value maps to
    -0100     % multiple toCol values (isoforms), take the first non-empty one — gene names are
    -0101     % per-gene, not per-isoform.
    -0102 
    -0103     fromVals = geneTable.(fromCol);
    -0104     toVals   = geneTable.(toCol);
    -0105 
    -0106     % Convert to char cell if needed
    -0107     if ~iscell(fromVals), fromVals = cellstr(fromVals); end
    -0108     if ~iscell(toVals),   toVals   = cellstr(toVals);   end
    +0092 
    +0093     % Validate column names
    +0094     if ~ismember(fromCol, geneTable.Properties.VariableNames)
    +0095         error('renameModelGenes:badColumn', ...
    +0096               'Column "%s" not found in geneTable.\nAvailable columns: %s', ...
    +0097               fromCol, strjoin(geneTable.Properties.VariableNames, ', '));
    +0098     end
    +0099     if ~ismember(toCol, geneTable.Properties.VariableNames)
    +0100         error('renameModelGenes:badColumn', ...
    +0101               'Column "%s" not found in geneTable.\nAvailable columns: %s', ...
    +0102               toCol, strjoin(geneTable.Properties.VariableNames, ', '));
    +0103     end
    +0104 
    +0105     % Build mapping: oldName → newName
    +0106     % Use only rows where both fromCol and toCol are non-empty. If a fromCol value maps to
    +0107     % multiple toCol values (isoforms), take the first non-empty one — gene names are
    +0108     % per-gene, not per-isoform.
     0109 
    -0110     % Build map (first occurrence wins for duplicates). Only genes with a
    -0111     % non-empty toCol value are mapped; the rest are left unchanged.
    -0112     nameMap = containers.Map('KeyType', 'char', 'ValueType', 'char');
    -0113     for i = 1:numel(fromVals)
    -0114         k = strtrim(fromVals{i});
    -0115         v = strtrim(toVals{i});
    -0116         if ~isempty(k) && ~isempty(v) && ~isKey(nameMap, k)
    -0117             nameMap(k) = v;
    -0118         end
    -0119     end
    -0120 
    -0121     % Standardize grRules before renaming to ensure consistent formatting for accurate replacement.
    -0122     fprintf('Removing unnecessary outer parentheses ...\n');
    -0123     model.grRules = removeUnnecessaryParentheses(model.grRules);
    -0124     
    -0125     fprintf('Standardizing grRules ...\n');
    -0126     [model.grRules, ~, indexes2check] = standardizeGrRules(model, true);
    -0127     if ~isempty(indexes2check)
    -0128         warning('renameModelGenes:potentialErrors', ...
    -0129             '%d grRules have potential issues. Review them before proceeding.', ...
    -0130             numel(indexes2check));
    -0131     end
    -0132 
    -0133     % Rename model.genes; genes without a mapping are left unchanged
    -0134     nGenes   = numel(model.genes);
    -0135     notFound = {};
    -0136     for i = 1:nGenes
    -0137         oldName = model.genes{i};
    -0138         if isKey(nameMap, oldName)
    -0139             model.genes{i} = nameMap(oldName);
    -0140         else
    -0141             notFound{end+1} = oldName; %#ok<AGROW>
    -0142         end
    -0143     end
    -0144 
    -0145     % Report genes that had no mapping and were left unchanged
    -0146     if ~isempty(notFound)
    -0147         warning('renameModelGenes:noMapping', ...
    -0148             '%d gene(s) in the model had no mapping in column "%s" and were left unchanged:\n  %s', ...
    -0149             numel(notFound), fromCol, strjoin(unique(notFound), ', '));
    +0110     fromVals = geneTable.(fromCol);
    +0111     toVals   = geneTable.(toCol);
    +0112 
    +0113     % Convert to char cell if needed
    +0114     if ~iscell(fromVals), fromVals = cellstr(fromVals); end
    +0115     if ~iscell(toVals),   toVals   = cellstr(toVals);   end
    +0116 
    +0117     % Build map (first occurrence wins for duplicates). Only genes with a
    +0118     % non-empty toCol value are mapped; the rest are left unchanged.
    +0119     nameMap = containers.Map('KeyType', 'char', 'ValueType', 'char');
    +0120     for i = 1:numel(fromVals)
    +0121         k = strtrim(fromVals{i});
    +0122         v = strtrim(toVals{i});
    +0123         if ~isempty(k) && ~isempty(v) && ~isKey(nameMap, k)
    +0124             nameMap(k) = v;
    +0125         end
    +0126     end
    +0127 
    +0128     % Standardize grRules before renaming to ensure consistent formatting for accurate replacement.
    +0129     fprintf('Removing unnecessary outer parentheses ...\n');
    +0130     model.grRules = removeUnnecessaryParentheses(model.grRules);
    +0131     
    +0132     fprintf('Standardizing grRules ...\n');
    +0133     [model.grRules, ~, indexes2check] = standardizeGrRules(model, true);
    +0134     if ~isempty(indexes2check)
    +0135         warning('renameModelGenes:potentialErrors', ...
    +0136             '%d grRules have potential issues. Review them before proceeding.', ...
    +0137             numel(indexes2check));
    +0138     end
    +0139 
    +0140     % Rename model.genes; genes without a mapping are left unchanged
    +0141     nGenes   = numel(model.genes);
    +0142     notFound = {};
    +0143     for i = 1:nGenes
    +0144         oldName = model.genes{i};
    +0145         if isKey(nameMap, oldName)
    +0146             model.genes{i} = nameMap(oldName);
    +0147         else
    +0148             notFound{end+1} = oldName; %#ok<AGROW>
    +0149         end
     0150     end
     0151 
    -0152     % Rename genes inside grRules Sort keys longest-first to avoid partial substitutions
    -0153     % (e.g. replace 'gene10' before 'gene1').
    -0154     allKeys    = keys(nameMap);
    -0155     allVals    = values(nameMap);
    -0156     [~, order] = sort(cellfun(@numel, allKeys), 'descend');
    -0157     allKeys    = allKeys(order);
    -0158     allVals    = allVals(order);
    -0159     
    -0160     nRxns = numel(model.grRules);
    -0161     for i = 1:nRxns
    -0162         rule = model.grRules{i};
    -0163         if isempty(rule)
    -0164             continue
    -0165         end
    -0166         for j = 1:numel(allKeys)
    -0167             oldG = allKeys{j};
    -0168             newG = allVals{j};
    -0169             % Use word-boundary replacement: only match oldG when surrounded
    -0170             % by non-word characters (spaces, parentheses, start/end of string)
    -0171             % to avoid replacing 'gene1' inside 'gene10'.
    -0172             rule = regexprep(rule, ['(?<![^\s()])' regexptranslate('escape', oldG) ...
    -0173                                     '(?![^\s()])'], newG);
    -0174         end
    -0175         model.grRules{i} = rule;
    -0176     end
    -0177 
    -0178     % Rebuild rxnGeneMat to match the updated gene list
    -0179     fprintf('Rebuilding rxnGeneMat ...\n');
    -0180     [model.grRules, model.rxnGeneMat, ~] = standardizeGrRules(model, true);
    -0181     
    -0182     fprintf('Done. %d genes renamed, %d left unchanged.\n', ...
    -0183             nGenes - numel(notFound), numel(notFound));
    -0184 end
    -0185 
    -0186 %--------------------------------------------------------------------------
    -0187 %  Helper functions
    -0188 
    -0189 function grRules = removeUnnecessaryParentheses(grRules)
    -0190 % Remove unnecessary outer parentheses from grRules.
    -0191 % e.g., '(A and (B or C))' → 'A and (B or C)'
    -0192 %       '(A or B)' → 'A or B'
    -0193 % but preserves '(A and B) or C' (outer parens are needed here)
    -0194 %
    -0195 % Checks if the opening parenthesis at position 1 closes before the end
    -0196 % of the string. If yes, outer parens are necessary. If no, they can be
    -0197 % removed recursively.
    -0198 
    -0199     nRxns = numel(grRules);
    -0200     for i = 1:nRxns
    -0201         rule = grRules{i};
    -0202         if isempty(rule)
    -0203             continue
    -0204         end
    -0205     
    -0206         % Recursively remove outer parentheses while they are unnecessary
    -0207         while length(rule) >= 2 && rule(1) == '(' && rule(end) == ')'
    -0208             % Count brackets to see when the opening paren closes
    -0209             parenCount = 0;
    -0210             closedEarly = false;
    -0211     
    -0212             for j = 1:length(rule)
    -0213                 if rule(j) == '('
    -0214                     parenCount = parenCount + 1;
    -0215                 elseif rule(j) == ')'
    -0216                     parenCount = parenCount - 1;
    -0217                     % If it reaches 0 before the end, outer parens are necessary
    -0218                     if parenCount == 0 && j < length(rule)
    -0219                         closedEarly = true;
    -0220                         break
    -0221                     end
    -0222                 end
    -0223             end
    -0224     
    -0225             if closedEarly
    -0226                 % Opening paren closes early → outer parens are needed
    -0227                 break
    -0228             else
    -0229                 % Opening paren only closes at the end → they're unnecessary
    -0230                 rule = rule(2:end-1);
    -0231             end
    -0232         end
    -0233     
    -0234         grRules{i} = rule;
    -0235     end
    -0236 end

    +0152 % Report genes that had no mapping and were left unchanged +0153 if ~isempty(notFound) +0154 warning('renameModelGenes:noMapping', ... +0155 '%d gene(s) in the model had no mapping in column "%s" and were left unchanged:\n %s', ... +0156 numel(notFound), fromCol, strjoin(unique(notFound), ', ')); +0157 end +0158 +0159 % Rename genes inside grRules Sort keys longest-first to avoid partial substitutions +0160 % (e.g. replace 'gene10' before 'gene1'). +0161 allKeys = keys(nameMap); +0162 allVals = values(nameMap); +0163 [~, order] = sort(cellfun(@numel, allKeys), 'descend'); +0164 allKeys = allKeys(order); +0165 allVals = allVals(order); +0166 +0167 nRxns = numel(model.grRules); +0168 for i = 1:nRxns +0169 rule = model.grRules{i}; +0170 if isempty(rule) +0171 continue +0172 end +0173 for j = 1:numel(allKeys) +0174 oldG = allKeys{j}; +0175 newG = allVals{j}; +0176 % Use word-boundary replacement: only match oldG when surrounded +0177 % by non-word characters (spaces, parentheses, start/end of string) +0178 % to avoid replacing 'gene1' inside 'gene10'. +0179 rule = regexprep(rule, ['(?<![^\s()])' regexptranslate('escape', oldG) ... +0180 '(?![^\s()])'], newG); +0181 end +0182 model.grRules{i} = rule; +0183 end +0184 +0185 % Rebuild rxnGeneMat to match the updated gene list +0186 fprintf('Rebuilding rxnGeneMat ...\n'); +0187 [model.grRules, model.rxnGeneMat, ~] = standardizeGrRules(model, true); +0188 +0189 fprintf('Done. %d genes renamed, %d left unchanged.\n', ... +0190 nGenes - numel(notFound), numel(notFound)); +0191 end +0192 +0193 %-------------------------------------------------------------------------- +0194 % Helper functions +0195 +0196 function grRules = removeUnnecessaryParentheses(grRules) +0197 % Remove unnecessary outer parentheses from grRules. +0198 % e.g., '(A and (B or C))' → 'A and (B or C)' +0199 % '(A or B)' → 'A or B' +0200 % but preserves '(A and B) or C' (outer parens are needed here) +0201 % +0202 % Checks if the opening parenthesis at position 1 closes before the end +0203 % of the string. If yes, outer parens are necessary. If no, they can be +0204 % removed recursively. +0205 +0206 nRxns = numel(grRules); +0207 for i = 1:nRxns +0208 rule = grRules{i}; +0209 if isempty(rule) +0210 continue +0211 end +0212 +0213 % Recursively remove outer parentheses while they are unnecessary +0214 while length(rule) >= 2 && rule(1) == '(' && rule(end) == ')' +0215 % Count brackets to see when the opening paren closes +0216 parenCount = 0; +0217 closedEarly = false; +0218 +0219 for j = 1:length(rule) +0220 if rule(j) == '(' +0221 parenCount = parenCount + 1; +0222 elseif rule(j) == ')' +0223 parenCount = parenCount - 1; +0224 % If it reaches 0 before the end, outer parens are necessary +0225 if parenCount == 0 && j < length(rule) +0226 closedEarly = true; +0227 break +0228 end +0229 end +0230 end +0231 +0232 if closedEarly +0233 % Opening paren closes early → outer parens are needed +0234 break +0235 else +0236 % Opening paren only closes at the end → they're unnecessary +0237 rule = rule(2:end-1); +0238 end +0239 end +0240 +0241 grRules{i} = rule; +0242 end +0243 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/gapfilling/canExchange.html b/docs/html/gapfilling/canExchange.html index c739cc7c..95d70536 100644 --- a/docs/html/gapfilling/canExchange.html +++ b/docs/html/gapfilling/canExchange.html @@ -30,12 +30,14 @@

    DESCRIPTION ^
     canExchange  Check which metabolites a model can produce or consume.
     
      Adds an exchange reaction for each of the specified metabolites and
    - checks which of them can carry a flux. This is the unified replacement
    - for canProduce ('produce') and canConsume ('consume').
    + checks which of them can carry an absolute flux above 1e-5. The direction
    + 'produce' corresponds to the deprecated function canProduce and 'consume'
    + to canConsume.
     
    - This is a less advanced but faster version of checkProduction, which
    - additionally reports which metabolites must be connected in order to make
    - the remaining ones producible.
    + canExchange only tests whether each added exchange reaction can carry flux.
    + checkProduction also reports which metabolites must be connected in order
    + to make the remaining ones producible, which requires further problems to be
    + solved.
     
      Parameters
      ----------
    @@ -74,7 +76,7 @@ 

    CROSS-REFERENCE INFORMATION ^
 <li><a href=canExchange canExchange Check which metabolites a model can produce or consume. This function is called by:
      -
    • canExchange canExchange Check which metabolites a model can produce or consume.
    • gapReport gapReport Perform a gap analysis and summarize the results.
    +
  • canExchange canExchange Check which metabolites a model can produce or consume.
  • gapReport gapReport Perform a gap analysis and summarise the results.
  • @@ -84,66 +86,68 @@

    SOURCE CODE ^% canExchange Check which metabolites a model can produce or consume. 0003 % 0004 % Adds an exchange reaction for each of the specified metabolites and -0005 % checks which of them can carry a flux. This is the unified replacement -0006 % for canProduce ('produce') and canConsume ('consume'). -0007 % -0008 % This is a less advanced but faster version of checkProduction, which -0009 % additionally reports which metabolites must be connected in order to make -0010 % the remaining ones producible. -0011 % -0012 % Parameters -0013 % ---------- -0014 % model : struct -0015 % a model structure. -0016 % direction : char -0017 % 'produce' to check for net production of each metabolite, or -0018 % 'consume' to check for net consumption. -0019 % -0020 % Name-Value Arguments -0021 % -------------------- -0022 % mets : cell or logical or double -0023 % either a cell array of metabolite IDs, a logical vector with the same -0024 % number of elements as metabolites in the model, or a vector of -0025 % indexes to check for (default model.mets). -0026 % -0027 % Returns -0028 % ------- -0029 % canExchange : logical -0030 % vector with true if the corresponding metabolite could be produced -0031 % (direction 'produce') or consumed (direction 'consume'). -0032 % -0033 % Examples -0034 % -------- -0035 % produced = canExchange(model, 'produce'); -0036 % consumed = canExchange(model, 'consume', mets); -0037 % -0038 % See also -0039 % -------- -0040 % checkProduction, findLeakMetabolite -0041 -0042 direction=char(direction); -0043 if ~any(strcmp(direction,{'produce','consume'})) -0044 error('RAVEN:badInput','direction must be ''produce'' or ''consume'''); -0045 end -0046 -0047 p=parseRAVENargs(varargin, {'mets',[]}); -0048 mets=p.mets; -0049 -0050 if isempty(mets) -0051 mets=model.mets; -0052 elseif ~islogical(mets) && ~isnumeric(mets) -0053 mets=convertCharArray(mets); -0054 end -0055 -0056 if strcmp(direction,'produce') -0057 exchangeDir='out'; -0058 else -0059 exchangeDir='in'; -0060 end -0061 -0062 [model, rxns]=addExchangeRxns(model,exchangeDir,mets); -0063 canExchange=haveFlux(model,10^-5,rxns); -0064 end

    +0005 % checks which of them can carry an absolute flux above 1e-5. The direction +0006 % 'produce' corresponds to the deprecated function canProduce and 'consume' +0007 % to canConsume. +0008 % +0009 % canExchange only tests whether each added exchange reaction can carry flux. +0010 % checkProduction also reports which metabolites must be connected in order +0011 % to make the remaining ones producible, which requires further problems to be +0012 % solved. +0013 % +0014 % Parameters +0015 % ---------- +0016 % model : struct +0017 % a model structure. +0018 % direction : char +0019 % 'produce' to check for net production of each metabolite, or +0020 % 'consume' to check for net consumption. +0021 % +0022 % Name-Value Arguments +0023 % -------------------- +0024 % mets : cell or logical or double +0025 % either a cell array of metabolite IDs, a logical vector with the same +0026 % number of elements as metabolites in the model, or a vector of +0027 % indexes to check for (default model.mets). +0028 % +0029 % Returns +0030 % ------- +0031 % canExchange : logical +0032 % vector with true if the corresponding metabolite could be produced +0033 % (direction 'produce') or consumed (direction 'consume'). +0034 % +0035 % Examples +0036 % -------- +0037 % produced = canExchange(model, 'produce'); +0038 % consumed = canExchange(model, 'consume', mets); +0039 % +0040 % See also +0041 % -------- +0042 % checkProduction, findLeakMetabolite +0043 +0044 direction=char(direction); +0045 if ~any(strcmp(direction,{'produce','consume'})) +0046 error('RAVEN:badInput','direction must be ''produce'' or ''consume'''); +0047 end +0048 +0049 p=parseRAVENargs(varargin, {'mets',[]}); +0050 mets=p.mets; +0051 +0052 if isempty(mets) +0053 mets=model.mets; +0054 elseif ~islogical(mets) && ~isnumeric(mets) +0055 mets=convertCharArray(mets); +0056 end +0057 +0058 if strcmp(direction,'produce') +0059 exchangeDir='out'; +0060 else +0061 exchangeDir='in'; +0062 end +0063 +0064 [model, rxns]=addExchangeRxns(model,exchangeDir,mets); +0065 canExchange=haveFlux(model,10^-5,rxns); +0066 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/gapfilling/checkProduction.html b/docs/html/gapfilling/checkProduction.html index 6cdd7b37..62003a79 100644 --- a/docs/html/gapfilling/checkProduction.html +++ b/docs/html/gapfilling/checkProduction.html @@ -29,16 +29,16 @@

    SYNOPSIS ^DESCRIPTION ^

     checkProduction  Check which metabolites can be produced from a model.
     
    - Checks which metabolites that can be produced from a model using the
    - specified constraints.
    + Checks which metabolites can be produced from a model under the current
    + constraints.
     
    - The function is intended to be used to identify which metabolites must be
    - connected in order to have a fully connected network. It does so by first
    - identifying which metabolites could have a net production in the network.
    - Then it calculates which other metabolites must be able to have net
    - production in order to have production of all metabolites in the network.
    - So, if a network contains the equations A[external]->B, C->D, and D->E it
    - will identify that production of C will connect the metabolites D and E.
    + The function identifies which metabolites must be connected in order to
    + have a fully connected network. It first identifies which metabolites
    + can have a net production in the network. It then calculates which other
    + metabolites must be able to have net production in order to have
    + production of all metabolites in the network. If a network contains the
    + equations A[external]->B, C->D, and D->E, production of C connects the
    + metabolites D and E.
     
      Parameters
      ----------
    @@ -48,31 +48,43 @@ 

    DESCRIPTION ^CROSS-REFERENCE INFORMATION ^
 </ul>
 This function is called by:
 <ul style= -
  • gapReport gapReport Perform a gap analysis and summarize the results.
  • +
  • gapReport gapReport Perform a gap analysis and summarise the results.
  • @@ -95,16 +107,16 @@

    SOURCE CODE ^
    0001 function [notProduced, notProducedNames, neededForProductionMat,minToConnect,model]=checkProduction(model,varargin)
     0002 % checkProduction  Check which metabolites can be produced from a model.
     0003 %
    -0004 % Checks which metabolites that can be produced from a model using the
    -0005 % specified constraints.
    +0004 % Checks which metabolites can be produced from a model under the current
    +0005 % constraints.
     0006 %
    -0007 % The function is intended to be used to identify which metabolites must be
    -0008 % connected in order to have a fully connected network. It does so by first
    -0009 % identifying which metabolites could have a net production in the network.
    -0010 % Then it calculates which other metabolites must be able to have net
    -0011 % production in order to have production of all metabolites in the network.
    -0012 % So, if a network contains the equations A[external]->B, C->D, and D->E it
    -0013 % will identify that production of C will connect the metabolites D and E.
    +0007 % The function identifies which metabolites must be connected in order to
    +0008 % have a fully connected network. It first identifies which metabolites
    +0009 % can have a net production in the network. It then calculates which other
    +0010 % metabolites must be able to have net production in order to have
    +0011 % production of all metabolites in the network. If a network contains the
    +0012 % equations A[external]->B, C->D, and D->E, production of C connects the
    +0013 % metabolites D and E.
     0014 %
     0015 % Parameters
     0016 % ----------
    @@ -114,146 +126,158 @@ 

    SOURCE CODE ^% Name-Value Arguments 0021 % -------------------- 0022 % checkNeededForProduction : logical -0023 % for each of the metabolites that could not be produced, include an -0024 % artificial production reaction and calculate which new metabolites that -0025 % could be produced as an effect of this (default false). +0023 % true to calculate, for each metabolite that could not be produced, +0024 % which of the other such metabolites can be produced when an uptake +0025 % reaction for it is added (default false). 0026 % excretionFromCompartments : cell 0027 % cell array with compartment ids from which metabolites can be excreted 0028 % (default model.comps). 0029 % printDetails : logical -0030 % print details to the screen (default true). -0031 % -0032 % Returns -0033 % ------- -0034 % notProduced : double -0035 % indices of metabolites that could not be produced. -0036 % notProducedNames : cell -0037 % cell array with names and compartments for metabolites that could not -0038 % be produced. -0039 % neededForProductionMat : logical -0040 % matrix where n x m is true if metabolite n allows for production of -0041 % metabolite m. -0042 % minToConnect : cell -0043 % the minimal number of metabolites that need to be connected in order to -0044 % be able to produce all other metabolites, and which metabolites each of -0045 % them connects. -0046 % model : struct -0047 % updated model structure with excretion reactions added. -0048 % -0049 % Examples -0050 % -------- -0051 % [notProduced, notProducedNames, neededForProductionMat, minToConnect, model] = ... -0052 % checkProduction(model, checkNeededForProduction, excretionFromCompartments, printDetails); -0053 -0054 p=parseRAVENargs(varargin, {'checkNeededForProduction',false; ... -0055 'excretionFromCompartments',[]; ... -0056 'printDetails',true}); -0057 checkNeededForProduction=p.checkNeededForProduction; -0058 excretionFromCompartments=p.excretionFromCompartments; -0059 printDetails=p.printDetails; -0060 -0061 if isempty(excretionFromCompartments) -0062 excretionFromCompartments=model.comps; -0063 else -0064 excretionFromCompartments=convertCharArray(excretionFromCompartments); -0065 end -0066 -0067 %Add an exchange reaction for each metabolite in the allowed compartments -0068 %and see if it can carry a flux -0069 allowedMetIds=ismember(model.comps(model.metComps),excretionFromCompartments); -0070 allowedMetIndexes=find(allowedMetIds); -0071 [model, addedRxns]=addExchangeRxns(model,'out',allowedMetIndexes); +0030 % true to print the metabolites that could not be produced to the command +0031 % window (default true). +0032 % +0033 % Returns +0034 % ------- +0035 % notProduced : double +0036 % indices of metabolites that could not be produced, counted among the +0037 % metabolites in the compartments of excretionFromCompartments, in model +0038 % order. +0039 % notProducedNames : cell +0040 % cell array with names and compartments for metabolites that could not +0041 % be produced. +0042 % neededForProductionMat : logical +0043 % matrix where n x m is true if metabolite n allows for production of +0044 % metabolite m. The rows and columns follow the order of notProduced. +0045 % It is empty if checkNeededForProduction is false. +0046 % minToConnect : cell +0047 % metabolites that need to be connected in order to be able to produce +0048 % all other metabolites, each with the number of metabolites it +0049 % connects. The metabolites are selected iteratively, each time the one +0050 % that connects the most remaining metabolites, so the number of +0051 % metabolites need not be the global minimum. It is empty if +0052 % checkNeededForProduction is false. +0053 % model : struct +0054 % model structure with an excretion reaction added for each metabolite +0055 % in the compartments of excretionFromCompartments. When +0056 % checkNeededForProduction is true, the excretion reaction of the last +0057 % metabolite in notProduced is returned as an uptake reaction, because +0058 % the loop that flips each reaction into an uptake reaction does not +0059 % reset the last one. +0060 % +0061 % Examples +0062 % -------- +0063 % [notProduced, notProducedNames, neededForProductionMat, minToConnect, model] = ... +0064 % checkProduction(model, checkNeededForProduction, excretionFromCompartments, printDetails); +0065 +0066 p=parseRAVENargs(varargin, {'checkNeededForProduction',false; ... +0067 'excretionFromCompartments',[]; ... +0068 'printDetails',true}); +0069 checkNeededForProduction=p.checkNeededForProduction; +0070 excretionFromCompartments=p.excretionFromCompartments; +0071 printDetails=p.printDetails; 0072 -0073 canProduce=haveFlux(model,10^-5,addedRxns); -0074 -0075 notProduced=find(~canProduce); -0076 minToConnect={}; -0077 if checkNeededForProduction==true -0078 %For each of the metabolites that couldn't be produced allow uptake and -0079 %check which of the other metabolites that couldn't be produced that -0080 %can be produced -0081 neededForProductionMat=false(numel(notProduced)); -0082 for i=1:numel(notProduced) -0083 %Add uptake for this metabolite -0084 if i>1 -0085 %Reset last iteration -0086 model.S(:,numel(model.rxns)-numel(addedRxns)+notProduced(i-1))=model.S(:,numel(model.rxns)-numel(addedRxns)+notProduced(i-1))*-1; -0087 end -0088 %Change the production reaction to an uptake reaction -0089 model.S(:,numel(model.rxns)-numel(addedRxns)+notProduced(i))=model.S(:,numel(model.rxns)-numel(addedRxns)+notProduced(i))*-1; -0090 -0091 %Test which of the metabolites that couldn't be produced that can -0092 %be produced now -0093 neededForProductionMat(i,:)=haveFlux(model,10^-5,addedRxns(notProduced)); -0094 end -0095 %Calculate the smallest number of metabolites that must be connected to -0096 %make everything connected and return their names -0097 -0098 %The algorithm is relatively straight forward. It finds the metabolite -0099 %that connects the most unconnected metabolites (iteratively), adds it -0100 %and removes the now connected metabolites until all are connected. -0101 %This is not guaranteed to find the global minimum -0102 neededForProdTemp=neededForProductionMat; -0103 while 1==1 -0104 %Get all metabolites a metabolite makes connected -0105 totalConnected=false(size(neededForProdTemp)); -0106 for i=1:numel(notProduced) -0107 totalConnected(i,:)=neededForProdTemp(i,:); -0108 -0109 lastIter=0; -0110 while 1==1 -0111 [~, a]=find(neededForProdTemp(totalConnected(i,:),:)); -0112 totalConnected(i,a)=true; -0113 if numel(a)==lastIter -0114 break; %No more connections were possible -0115 else -0116 lastIter=numel(a); -0117 end -0118 end -0119 end -0120 [connections, mostConnected]=max(sum(totalConnected,2)); -0121 -0122 if connections>0 -0123 %Add the most connected metabolite to the list and remove all -0124 %metabolites that it's connected to -0125 metID=allowedMetIndexes(notProduced(mostConnected)); -0126 entry=[model.metNames{metID},'[',model.comps{model.metComps(metID)},'] (connects ' num2str(connections) ' metabolites)']; -0127 minToConnect=[minToConnect;entry]; -0128 neededForProdTemp(totalConnected(mostConnected,:),:)=false; -0129 neededForProdTemp(:,totalConnected(mostConnected,:))=false; -0130 else -0131 break; -0132 end -0133 end -0134 else -0135 neededForProductionMat=[]; -0136 end -0137 -0138 notProducedNames=strcat(model.metNames(allowedMetIndexes(notProduced)),'[',model.comps(model.metComps(allowedMetIndexes(notProduced))),']'); -0139 -0140 if printDetails==true -0141 fprintf('The following metabolites could not be produced:\n'); -0142 [notProducedNamesTemp,perm]=sort(notProducedNames); -0143 -0144 if checkNeededForProduction==true -0145 neededForProdTemp=neededForProductionMat(:,perm); -0146 neededForProdTemp=neededForProdTemp(perm,:); -0147 fprintf('\tIf the production of a metabolite is dependent on some other metabolites then those are printed under the name\n\n'); -0148 end -0149 for i=1:numel(notProducedNamesTemp) -0150 fprintf([notProducedNamesTemp{i} '\n']); -0151 neededForProdTemp(i,i)=false; %Not neat to do this here. Prevent printing itself -0152 if checkNeededForProduction==true -0153 enablesProduction=find(neededForProdTemp(:,i)); -0154 if any(enablesProduction) -0155 for j=1:numel(enablesProduction) -0156 fprintf(['\t' notProducedNamesTemp{enablesProduction(j)} '\n']); -0157 end -0158 end -0159 end +0073 if isempty(excretionFromCompartments) +0074 excretionFromCompartments=model.comps; +0075 else +0076 excretionFromCompartments=convertCharArray(excretionFromCompartments); +0077 end +0078 +0079 %Add an exchange reaction for each metabolite in the allowed compartments +0080 %and see if it can carry a flux +0081 allowedMetIds=ismember(model.comps(model.metComps),excretionFromCompartments); +0082 allowedMetIndexes=find(allowedMetIds); +0083 [model, addedRxns]=addExchangeRxns(model,'out',allowedMetIndexes); +0084 +0085 canProduce=haveFlux(model,10^-5,addedRxns); +0086 +0087 notProduced=find(~canProduce); +0088 minToConnect={}; +0089 if checkNeededForProduction==true +0090 %For each of the metabolites that couldn't be produced allow uptake and +0091 %check which of the other metabolites that couldn't be produced that +0092 %can be produced +0093 neededForProductionMat=false(numel(notProduced)); +0094 for i=1:numel(notProduced) +0095 %Add uptake for this metabolite +0096 if i>1 +0097 %Reset last iteration +0098 model.S(:,numel(model.rxns)-numel(addedRxns)+notProduced(i-1))=model.S(:,numel(model.rxns)-numel(addedRxns)+notProduced(i-1))*-1; +0099 end +0100 %Change the production reaction to an uptake reaction +0101 model.S(:,numel(model.rxns)-numel(addedRxns)+notProduced(i))=model.S(:,numel(model.rxns)-numel(addedRxns)+notProduced(i))*-1; +0102 +0103 %Test which of the metabolites that couldn't be produced that can +0104 %be produced now +0105 neededForProductionMat(i,:)=haveFlux(model,10^-5,addedRxns(notProduced)); +0106 end +0107 %Calculate the smallest number of metabolites that must be connected to +0108 %make everything connected and return their names +0109 +0110 %The algorithm is relatively straight forward. It finds the metabolite +0111 %that connects the most unconnected metabolites (iteratively), adds it +0112 %and removes the now connected metabolites until all are connected. +0113 %This is not guaranteed to find the global minimum +0114 neededForProdTemp=neededForProductionMat; +0115 while 1==1 +0116 %Get all metabolites a metabolite makes connected +0117 totalConnected=false(size(neededForProdTemp)); +0118 for i=1:numel(notProduced) +0119 totalConnected(i,:)=neededForProdTemp(i,:); +0120 +0121 lastIter=0; +0122 while 1==1 +0123 [~, a]=find(neededForProdTemp(totalConnected(i,:),:)); +0124 totalConnected(i,a)=true; +0125 if numel(a)==lastIter +0126 break; %No more connections were possible +0127 else +0128 lastIter=numel(a); +0129 end +0130 end +0131 end +0132 [connections, mostConnected]=max(sum(totalConnected,2)); +0133 +0134 if connections>0 +0135 %Add the most connected metabolite to the list and remove all +0136 %metabolites that it's connected to +0137 metID=allowedMetIndexes(notProduced(mostConnected)); +0138 entry=[model.metNames{metID},'[',model.comps{model.metComps(metID)},'] (connects ' num2str(connections) ' metabolites)']; +0139 minToConnect=[minToConnect;entry]; +0140 neededForProdTemp(totalConnected(mostConnected,:),:)=false; +0141 neededForProdTemp(:,totalConnected(mostConnected,:))=false; +0142 else +0143 break; +0144 end +0145 end +0146 else +0147 neededForProductionMat=[]; +0148 end +0149 +0150 notProducedNames=strcat(model.metNames(allowedMetIndexes(notProduced)),'[',model.comps(model.metComps(allowedMetIndexes(notProduced))),']'); +0151 +0152 if printDetails==true +0153 fprintf('The following metabolites could not be produced:\n'); +0154 [notProducedNamesTemp,perm]=sort(notProducedNames); +0155 +0156 if checkNeededForProduction==true +0157 neededForProdTemp=neededForProductionMat(:,perm); +0158 neededForProdTemp=neededForProdTemp(perm,:); +0159 fprintf('\tIf the production of a metabolite is dependent on some other metabolites then those are printed under the name\n\n'); 0160 end -0161 end -0162 end

    +0161 for i=1:numel(notProducedNamesTemp) +0162 fprintf([notProducedNamesTemp{i} '\n']); +0163 neededForProdTemp(i,i)=false; %Not neat to do this here. Prevent printing itself +0164 if checkNeededForProduction==true +0165 enablesProduction=find(neededForProdTemp(:,i)); +0166 if any(enablesProduction) +0167 for j=1:numel(enablesProduction) +0168 fprintf(['\t' notProducedNamesTemp{enablesProduction(j)} '\n']); +0169 end +0170 end +0171 end +0172 end +0173 end +0174 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/gapfilling/fillGaps.html b/docs/html/gapfilling/fillGaps.html index 439e4ecc..1cc3b3b4 100644 --- a/docs/html/gapfilling/fillGaps.html +++ b/docs/html/gapfilling/fillGaps.html @@ -29,54 +29,57 @@

    SYNOPSIS ^DESCRIPTION ^

     fillGaps  Use template model(s) to fill gaps in a model.
     
    - This method works by merging the model with the reference model(s) and
    - checking which reactions can carry flux. All reactions that cannot carry
    - flux are removed (cannotConnect). If useModelConstraints is false it
    - then solves the MILP problem of minimizing the number of active
    - reactions from the reference models that are required to have flux in
    - all the reactions in model. This requires that the input model has
    - exchange reactions present for the nutrients that are needed for its
    - metabolism. If useModelConstraints is true then the problem is to
    - include as few reactions as possible from the reference models in order
    - to satisfy the model constraints.
    + The model is merged with the reference model(s). If useModelConstraints
    + is false, the reactions of the model that cannot carry flux in the model
    + are tested for flux in the merged model. The non-reversible reactions that
    + carry flux in the merged model are the newConnected reactions. The other
    + reactions are returned as cannotConnect: the reactions that cannot carry
    + flux in the merged model and the reversible reactions that can, which are
    + left out of the MILP target set. A MILP then minimises the number of
    + active reactions from the reference models that are required for flux in
    + the reactions that can be connected (newConnected). This requires that
    + the input model has exchange reactions for the nutrients that its
    + metabolism needs. If useModelConstraints is true, the flux test and the
    + split into newConnected and cannotConnect are not run, and the MILP
    + includes as few reactions as possible from the reference models to
    + satisfy the model constraints.
     
    - The intended use is that the user can attempt a general gap-filling
    - using useModelConstraints=false, or a more targeted gap-filling by
    - setting constraints in the model structure and then using
    - useModelConstraints=true. For example, to include reactions so that all
    - biomass components can be synthesized, the user could define a biomass
    - equation and set its lower bound to >0. Running this function with
    - useModelConstraints=true would then give the smallest set of reactions
    - that have to be included in order for the model to produce biomass.
    + A general gap-filling uses useModelConstraints=false. A targeted
    + gap-filling sets constraints in the model structure and uses
    + useModelConstraints=true. For example, a model with a biomass equation
    + whose lower bound is set above 0 gets the smallest set of reactions from
    + the reference models that allows it to produce biomass.
     
      Parameters
      ----------
      model : struct
          a model structure that may contain gaps to be filled.
      models : cell or struct
    -     a cell array of reference models or a model structure. The gaps will
    -     be filled using reactions from these models.
    +     a cell array of reference models or a model structure. Reactions
    +     from these models are used to fill the gaps.
     
      Name-Value Arguments
      --------------------
      allowNetProduction : logical
    -     true if net production of all metabolites is allowed. A reaction can
    -     be unable to carry flux because one of the reactants is unavailable
    -     or because one of the products cannot be further processed. If true,
    -     only the first type of unconnectivity is considered (default false).
    +     true to allow net production of all metabolites. A reaction can be
    +     unable to carry flux because one of the reactants is unavailable or
    +     because one of the products cannot be further processed. If true,
    +     only the first cause is considered (default false).
      useModelConstraints : logical
    -     true if the constraints specified in the model structure should be
    -     used. If false then reactions are included from the template
    -     model(s) so that as many reactions as possible in model can carry
    -     flux (default false).
    +     true to use the constraints specified in the model structure. If
    +     false, reactions are included from the template model(s) so that as
    +     many reactions as possible in model can carry flux (default false).
      supressWarnings : logical
    -     false if warnings should be displayed (default false).
    +     true to suppress the warning and the note about missing exchange
    +     reactions (default false).
      rxnScores : double or cell
    -     array with scores for each of the reactions in the reference
    -     model(s). If more than one model is supplied, then rxnScores should
    -     be a cell array of vectors. The solver will try to maximize the sum
    -     of the scores for the included reactions (default is -1 for all
    -     reactions).
    +     scores for each of the reactions in the reference model(s). If more
    +     than one model is supplied, rxnScores is a cell array of vectors. The
    +     solver maximises the sum of the scores of the included reactions.
    +     Used by the 'reference' algorithm (default -1 for all reactions).
    + params : struct
    +     solver parameters passed to getMinNrFluxes (algorithm 'reference') or
    +     to gapFillMILP (algorithm 'gapfillMILP') (default empty struct).
      algorithm : char
          gap-filling algorithm to use (default 'reference'):
     
    @@ -90,20 +93,26 @@ 

    DESCRIPTION ^DESCRIPTION ^ftINITFillGaps : the ftINIT-side counterpart, used when the reference - model already contains the model's own reactions, so no per-task - merge is needed.

    + ftINITFillGaps : the variant of fillGaps for ftINIT, used when the + reference model already contains the reactions of the model. No merge + per task is needed in that case.

    CROSS-REFERENCE INFORMATION ^

    This function calls:
      -
    • gapFillFastLP gapFillFastLP LP-based gap-filling (fastGapFill / swiftGapFill).
    • gapFillMILP gapFillMILP Objective-based gap-filling via a global growth-floor MILP.
    • gapFillTopological gapFillTopological BFS metabolite-producibility pre-screening.
    +
  • gapFillFastLP gapFillFastLP LP-based gap-filling (fastGapFill or swiftGapFill).
  • gapFillMILP gapFillMILP Objective-based gap-filling via a global growth-floor MILP.
  • gapFillTopological gapFillTopological BFS metabolite-producibility pre-screening.
  • This function is called by:
      -
    • fitTasks fitTasks Fill gaps in a model so it can perform a list of tasks.
    • gapReport gapReport Perform a gap analysis and summarize the results.
    +
  • fitTasks fitTasks Fill gaps in a model so it can perform a list of tasks.
  • gapReport gapReport Perform a gap analysis and summarise the results.
  • @@ -150,312 +160,322 @@

    SOURCE CODE ^
    0001 function [newConnected, cannotConnect, addedRxns, newModel, exitFlag]=fillGaps(model,models,varargin)
     0002 % fillGaps  Use template model(s) to fill gaps in a model.
     0003 %
    -0004 % This method works by merging the model with the reference model(s) and
    -0005 % checking which reactions can carry flux. All reactions that cannot carry
    -0006 % flux are removed (cannotConnect). If useModelConstraints is false it
    -0007 % then solves the MILP problem of minimizing the number of active
    -0008 % reactions from the reference models that are required to have flux in
    -0009 % all the reactions in model. This requires that the input model has
    -0010 % exchange reactions present for the nutrients that are needed for its
    -0011 % metabolism. If useModelConstraints is true then the problem is to
    -0012 % include as few reactions as possible from the reference models in order
    -0013 % to satisfy the model constraints.
    -0014 %
    -0015 % The intended use is that the user can attempt a general gap-filling
    -0016 % using useModelConstraints=false, or a more targeted gap-filling by
    -0017 % setting constraints in the model structure and then using
    -0018 % useModelConstraints=true. For example, to include reactions so that all
    -0019 % biomass components can be synthesized, the user could define a biomass
    -0020 % equation and set its lower bound to >0. Running this function with
    -0021 % useModelConstraints=true would then give the smallest set of reactions
    -0022 % that have to be included in order for the model to produce biomass.
    -0023 %
    -0024 % Parameters
    -0025 % ----------
    -0026 % model : struct
    -0027 %     a model structure that may contain gaps to be filled.
    -0028 % models : cell or struct
    -0029 %     a cell array of reference models or a model structure. The gaps will
    -0030 %     be filled using reactions from these models.
    -0031 %
    -0032 % Name-Value Arguments
    -0033 % --------------------
    -0034 % allowNetProduction : logical
    -0035 %     true if net production of all metabolites is allowed. A reaction can
    -0036 %     be unable to carry flux because one of the reactants is unavailable
    -0037 %     or because one of the products cannot be further processed. If true,
    -0038 %     only the first type of unconnectivity is considered (default false).
    -0039 % useModelConstraints : logical
    -0040 %     true if the constraints specified in the model structure should be
    -0041 %     used. If false then reactions are included from the template
    -0042 %     model(s) so that as many reactions as possible in model can carry
    -0043 %     flux (default false).
    +0004 % The model is merged with the reference model(s). If useModelConstraints
    +0005 % is false, the reactions of the model that cannot carry flux in the model
    +0006 % are tested for flux in the merged model. The non-reversible reactions that
    +0007 % carry flux in the merged model are the newConnected reactions. The other
    +0008 % reactions are returned as cannotConnect: the reactions that cannot carry
    +0009 % flux in the merged model and the reversible reactions that can, which are
    +0010 % left out of the MILP target set. A MILP then minimises the number of
    +0011 % active reactions from the reference models that are required for flux in
    +0012 % the reactions that can be connected (newConnected). This requires that
    +0013 % the input model has exchange reactions for the nutrients that its
    +0014 % metabolism needs. If useModelConstraints is true, the flux test and the
    +0015 % split into newConnected and cannotConnect are not run, and the MILP
    +0016 % includes as few reactions as possible from the reference models to
    +0017 % satisfy the model constraints.
    +0018 %
    +0019 % A general gap-filling uses useModelConstraints=false. A targeted
    +0020 % gap-filling sets constraints in the model structure and uses
    +0021 % useModelConstraints=true. For example, a model with a biomass equation
    +0022 % whose lower bound is set above 0 gets the smallest set of reactions from
    +0023 % the reference models that allows it to produce biomass.
    +0024 %
    +0025 % Parameters
    +0026 % ----------
    +0027 % model : struct
    +0028 %     a model structure that may contain gaps to be filled.
    +0029 % models : cell or struct
    +0030 %     a cell array of reference models or a model structure. Reactions
    +0031 %     from these models are used to fill the gaps.
    +0032 %
    +0033 % Name-Value Arguments
    +0034 % --------------------
    +0035 % allowNetProduction : logical
    +0036 %     true to allow net production of all metabolites. A reaction can be
    +0037 %     unable to carry flux because one of the reactants is unavailable or
    +0038 %     because one of the products cannot be further processed. If true,
    +0039 %     only the first cause is considered (default false).
    +0040 % useModelConstraints : logical
    +0041 %     true to use the constraints specified in the model structure. If
    +0042 %     false, reactions are included from the template model(s) so that as
    +0043 %     many reactions as possible in model can carry flux (default false).
     0044 % supressWarnings : logical
    -0045 %     false if warnings should be displayed (default false).
    -0046 % rxnScores : double or cell
    -0047 %     array with scores for each of the reactions in the reference
    -0048 %     model(s). If more than one model is supplied, then rxnScores should
    -0049 %     be a cell array of vectors. The solver will try to maximize the sum
    -0050 %     of the scores for the included reactions (default is -1 for all
    -0051 %     reactions).
    -0052 % algorithm : char
    -0053 %     gap-filling algorithm to use (default 'reference'):
    -0054 %
    -0055 %     - 'reference'   : MILP-based connectivity maximisation
    -0056 %     - 'fastLP'      : L1-norm LP relaxation via FASTCORE (gapFillFastLP)
    -0057 %     - 'swiftLP'     : SWIFTCORE single-LP variant (gapFillFastLP)
    -0058 %     - 'gapfillMILP' : growth-floor MILP adding database reactions
    -0059 %                       (gapFillMILP). Uses only models{1} as the database.
    -0060 %     - 'topological' : BFS metabolite producibility pre-screening
    -0061 %                       (gapFillTopological); no model modification.
    -0062 % epsilon : double
    -0063 %     minimum flux threshold used by fastLP and swiftLP (default 1e-4).
    -0064 % minGrowth : double
    -0065 %     minimum growth rate for gapfillMILP (default [] = 10% of max FBA).
    -0066 % verbose : logical
    -0067 %     print progress messages for new algorithms (default true).
    -0068 %
    -0069 % Returns
    -0070 % -------
    -0071 % newConnected : cell
    -0072 %     cell array with the reactions that could be connected. This is not
    -0073 %     calculated if useModelConstraints is true.
    -0074 % cannotConnect : cell
    -0075 %     cell array with reactions that could not be connected. This is not
    -0076 %     calculated if useModelConstraints is true.
    -0077 % addedRxns : cell
    -0078 %     cell array with the reactions that were added from "models".
    -0079 % newModel : struct
    -0080 %     the model with reactions added to fill gaps.
    -0081 % exitFlag : double
    -0082 %     exit status:
    -0083 %
    -0084 %     - 1 : optimal solution found
    -0085 %     - -1 : no solution found, either because the problem is infeasible or
    -0086 %       because the solver reached its time limit before finding one
    -0087 %     - -2 : a solution was found but is not proven optimal, because the
    -0088 %       solver stopped on its time limit
    -0089 %
    -0090 % Examples
    -0091 % --------
    -0092 %     [newConnected, cannotConnect, addedRxns, newModel, exitFlag]=...
    -0093 %         fillGaps(model,models,allowNetProduction,useModelConstraints,...
    -0094 %         supressWarnings,rxnScores,params);
    -0095 %
    -0096 % Notes
    -0097 % -----
    -0098 % The 'reference' and 'gapfillMILP' algorithms solve a MILP with the
    -0099 % solver set by setRavenSolver. RAVEN's bundled GLPK solves it for small
    -0100 % models (with a RAVEN:glpkMILP warning); for larger models Gurobi is much
    -0101 % faster and more reliable (setRavenSolver('gurobi')).
    -0102 %
    -0103 % See Also
    -0104 % --------
    -0105 % ftINITFillGaps : the ftINIT-side counterpart, used when the reference
    -0106 %     model already contains the model's own reactions, so no per-task
    -0107 %     merge is needed.
    -0108 
    -0109 %If the user only supplied a single template model
    -0110 if ~iscell(models)
    -0111     models={models};
    -0112 end
    -0113 
    -0114 p=parseRAVENargs(varargin, {'allowNetProduction',false; ...
    -0115     'useModelConstraints',false; ...
    -0116     'supressWarnings',false; ...
    -0117     'rxnScores',[]; ...
    -0118     'params',[]; ...
    -0119     'algorithm','reference'; ...
    -0120     'epsilon',1e-4; ...
    -0121     'minGrowth',[]; ...
    -0122     'verbose',true});
    -0123 allowNetProduction=p.allowNetProduction;
    -0124 useModelConstraints=p.useModelConstraints;
    -0125 supressWarnings=p.supressWarnings;
    -0126 rxnScores=p.rxnScores;
    -0127 params=p.params;
    -0128 
    -0129 % ---- Dispatch to alternative gap-filling algorithms ----
    -0130 switch lower(p.algorithm)
    -0131     case {'fastlp', 'swiftlp'}
    -0132         if numel(models) < 1
    -0133             error('RAVEN:badInput', 'fillGaps: fastLP/swiftLP requires at least one reference model.');
    -0134         end
    -0135         variant = strrep(lower(p.algorithm), 'lp', '');
    -0136         [addedRxns, newModel, cannotConnect] = gapFillFastLP(model, models{1}, ...
    -0137             'epsilon', p.epsilon, 'verbose', p.verbose, 'variant', variant);
    -0138         newConnected = {};
    -0139         exitFlag = 1;
    -0140         return;
    -0141     case 'gapfillmilp'
    +0045 %     true to suppress the warning and the note about missing exchange
    +0046 %     reactions (default false).
    +0047 % rxnScores : double or cell
    +0048 %     scores for each of the reactions in the reference model(s). If more
    +0049 %     than one model is supplied, rxnScores is a cell array of vectors. The
    +0050 %     solver maximises the sum of the scores of the included reactions.
    +0051 %     Used by the 'reference' algorithm (default -1 for all reactions).
    +0052 % params : struct
    +0053 %     solver parameters passed to getMinNrFluxes (algorithm 'reference') or
    +0054 %     to gapFillMILP (algorithm 'gapfillMILP') (default empty struct).
    +0055 % algorithm : char
    +0056 %     gap-filling algorithm to use (default 'reference'):
    +0057 %
    +0058 %     - 'reference'   : MILP-based connectivity maximisation
    +0059 %     - 'fastLP'      : L1-norm LP relaxation via FASTCORE (gapFillFastLP)
    +0060 %     - 'swiftLP'     : SWIFTCORE single-LP variant (gapFillFastLP)
    +0061 %     - 'gapfillMILP' : growth-floor MILP adding database reactions
    +0062 %                       (gapFillMILP). Uses only models{1} as the database.
    +0063 %     - 'topological' : BFS metabolite producibility pre-screening
    +0064 %                       (gapFillTopological); no model modification.
    +0065 % epsilon : double
    +0066 %     minimum flux threshold used by fastLP and swiftLP (default 1e-4).
    +0067 % minGrowth : double
    +0068 %     minimum growth rate for gapfillMILP (default [], which is 10% of the
    +0069 %     maximum growth rate found by FBA on the merged model).
    +0070 % verbose : logical
    +0071 %     true to print progress messages for the algorithms other than
    +0072 %     'reference' (default true).
    +0073 %
    +0074 % Returns
    +0075 % -------
    +0076 % newConnected : cell
    +0077 %     cell array with the non-reversible reactions of model that cannot
    +0078 %     carry flux in model but can in the merged model. Empty if
    +0079 %     useModelConstraints is true or if the algorithm is not 'reference'.
    +0080 % cannotConnect : cell
    +0081 %     cell array with the other reactions of model that cannot carry flux
    +0082 %     in model. For the algorithms 'fastLP' and 'swiftLP' it holds the
    +0083 %     reactions that remain blocked after merging (from gapFillFastLP), and
    +0084 %     for 'topological' the ids of the metabolites that cannot be reached.
    +0085 %     Empty if useModelConstraints is true or for 'gapfillMILP'.
    +0086 % addedRxns : cell
    +0087 %     cell array with the reactions added from "models".
    +0088 % newModel : struct
    +0089 %     the model with reactions added to fill gaps.
    +0090 % exitFlag : double
    +0091 %     exit status:
    +0092 %
    +0093 %     - 1 : optimal solution found
    +0094 %     - -1 : no solution found, either because the problem is infeasible or
    +0095 %       because the solver reached its time limit before finding one
    +0096 %     - -2 : a solution was found but is not proven optimal, because the
    +0097 %       solver stopped on its time limit
    +0098 %
    +0099 % Examples
    +0100 % --------
    +0101 %     [newConnected, cannotConnect, addedRxns, newModel, exitFlag]=...
    +0102 %         fillGaps(model,models,allowNetProduction,useModelConstraints,...
    +0103 %         supressWarnings,rxnScores,params);
    +0104 %
    +0105 % Notes
    +0106 % -----
    +0107 % The 'reference' and 'gapfillMILP' algorithms solve a MILP with the
    +0108 % solver set by setRavenSolver. RAVEN's bundled GLPK solves it for small
    +0109 % models. The RAVEN:glpkMILP warning issued with GLPK states that GLPK works
    +0110 % for small problems and that Gurobi is much faster and more reliable for
    +0111 % larger ones (setRavenSolver('gurobi')).
    +0112 %
    +0113 % See also
    +0114 % --------
    +0115 % ftINITFillGaps : the variant of fillGaps for ftINIT, used when the
    +0116 %     reference model already contains the reactions of the model. No merge
    +0117 %     per task is needed in that case.
    +0118 
    +0119 %If the user only supplied a single template model
    +0120 if ~iscell(models)
    +0121     models={models};
    +0122 end
    +0123 
    +0124 p=parseRAVENargs(varargin, {'allowNetProduction',false; ...
    +0125     'useModelConstraints',false; ...
    +0126     'supressWarnings',false; ...
    +0127     'rxnScores',[]; ...
    +0128     'params',[]; ...
    +0129     'algorithm','reference'; ...
    +0130     'epsilon',1e-4; ...
    +0131     'minGrowth',[]; ...
    +0132     'verbose',true});
    +0133 allowNetProduction=p.allowNetProduction;
    +0134 useModelConstraints=p.useModelConstraints;
    +0135 supressWarnings=p.supressWarnings;
    +0136 rxnScores=p.rxnScores;
    +0137 params=p.params;
    +0138 
    +0139 % ---- Dispatch to alternative gap-filling algorithms ----
    +0140 switch lower(p.algorithm)
    +0141     case {'fastlp', 'swiftlp'}
     0142         if numel(models) < 1
    -0143             error('RAVEN:badInput', 'fillGaps: gapfillMILP requires at least one reference model.');
    +0143             error('RAVEN:badInput', 'fillGaps: fastLP/swiftLP requires at least one reference model.');
     0144         end
    -0145         [addedRxns, newModel, exitFlag] = gapFillMILP(model, models{1}, ...
    -0146             'minGrowth', p.minGrowth, 'verbose', p.verbose, 'params', params);
    -0147         newConnected = {};
    -0148         cannotConnect = {};
    -0149         return;
    -0150     case 'topological'
    -0151         if numel(models) < 1
    -0152             error('RAVEN:badInput', 'fillGaps: topological requires at least one reference model.');
    -0153         end
    -0154         result = gapFillTopological(model, models{1}, 'verbose', p.verbose);
    -0155         addedRxns    = {};
    -0156         newModel     = model;
    -0157         cannotConnect = result.blockedMets;
    -0158         newConnected = {};
    -0159         exitFlag = 1;
    -0160         return;
    -0161 end
    -0162 % Falls through to the 'reference' algorithm for all other values.
    -0163 
    -0164 if isempty(rxnScores)
    -0165     rxnScores=cell(numel(models),1);
    -0166     for i=1:numel(models)
    -0167         rxnScores{i}=ones(numel(models{i}.rxns),1)*-1;
    -0168     end
    -0169 end
    -0170 if isempty(params)
    -0171     params=struct();
    -0172 end
    +0145         variant = strrep(lower(p.algorithm), 'lp', '');
    +0146         [addedRxns, newModel, cannotConnect] = gapFillFastLP(model, models{1}, ...
    +0147             'epsilon', p.epsilon, 'verbose', p.verbose, 'variant', variant);
    +0148         newConnected = {};
    +0149         exitFlag = 1;
    +0150         return;
    +0151     case 'gapfillmilp'
    +0152         if numel(models) < 1
    +0153             error('RAVEN:badInput', 'fillGaps: gapfillMILP requires at least one reference model.');
    +0154         end
    +0155         [addedRxns, newModel, exitFlag] = gapFillMILP(model, models{1}, ...
    +0156             'minGrowth', p.minGrowth, 'verbose', p.verbose, 'params', params);
    +0157         newConnected = {};
    +0158         cannotConnect = {};
    +0159         return;
    +0160     case 'topological'
    +0161         if numel(models) < 1
    +0162             error('RAVEN:badInput', 'fillGaps: topological requires at least one reference model.');
    +0163         end
    +0164         result = gapFillTopological(model, models{1}, 'verbose', p.verbose);
    +0165         addedRxns    = {};
    +0166         newModel     = model;
    +0167         cannotConnect = result.blockedMets;
    +0168         newConnected = {};
    +0169         exitFlag = 1;
    +0170         return;
    +0171 end
    +0172 % Falls through to the 'reference' algorithm for all other values.
     0173 
    -0174 if ~iscell(rxnScores)
    -0175     rxnScores={rxnScores};
    -0176 end
    -0177 
    -0178 models=models(:);
    -0179 rxnScores=rxnScores(:);
    -0180 
    -0181 %Check if the original model has an unconstrained field. If so, give a
    -0182 %warning
    -0183 if supressWarnings==false
    -0184     if isfield(model,'unconstrained')
    -0185         EM='This algorithm is meant to function on a model with exchange reactions for uptake and excretion of metabolites. The current model still has the "unconstrained" field';
    -0186         warning('RAVEN:warning', '%s', EM);
    -0187     else
    -0188         if isempty(getExchangeRxns(model,'both'))
    -0189             fprintf('NOTE: This algorithm is meant to function on a model with exchange reactions for uptake and excretion of metabolites. The current model does not seem to contain any such reactions.\n');
    -0190         end
    -0191     end
    -0192 end
    -0193 
    -0194 %Simplify the template models to remove constrained rxns. At the same time,
    -0195 %check that the id of the template models is not the same as the model. That
    -0196 %would cause an error further down
    -0197 for i=1:numel(models)
    -0198     models{i}.rxnScores=rxnScores{i};
    -0199     models{i}=simplifyModel(models{i},false,false,true);
    -0200     if strcmpi(models{i}.id,model.id)
    -0201         EM='The reference model(s) cannot have the same id as the model';
    -0202         error('RAVEN:badInput', '%s', EM);
    -0203     end
    -0204 end
    -0205 
    -0206 %This is a rather ugly solution to the issue that it is a bit tricky to keep
    -0207 %track of which scores belong to which reactions. This requires that
    -0208 %removeReactions and mergeModels are modified to check for the new field.
    -0209 model.rxnScores=zeros(numel(model.rxns),1);
    -0210 
    -0211 %First merge all models into one big one
    -0212 allModels=mergeModels([{model};models],'metNames',true);
    -0213 
    -0214 %Add that net production is ok
    -0215 if allowNetProduction==true
    -0216     %A second column in model.b means that the b field is lower and upper
    -0217     %bound on the RHS.
    -0218     model.b=[model.b(:,1) inf(numel(model.mets),1)];
    -0219     allModels.b=[allModels.b(:,1) inf(numel(allModels.mets),1)];
    -0220 end
    -0221 
    -0222 if useModelConstraints==true
    -0223     newConnected={};
    -0224     cannotConnect={};
    -0225     
    -0226     %Check that the input model is not solveable without any input
    -0227     sol=solveLP(model);
    -0228     if ~isempty(sol.f)
    -0229         addedRxns={};
    -0230         newModel=model;
    -0231         exitFlag=1;
    -0232         return;
    -0233     end
    -0234     
    -0235     %Then check that the merged model is solveable
    -0236     sol=solveLP(allModels);
    -0237     if isempty(sol.f)
    -0238         EM='There are no reactions in the template model(s) that can make the model constraints satisfied';
    -0239         error('RAVEN:badInput', '%s', EM);
    -0240     end
    -0241     
    -0242     %Remove dead ends for speed reasons. This has to be done here and
    -0243     %duplicate below because there is otherwise a risk that a reaction
    -0244     %which is constrained to something relevant is removed
    -0245     allModels=simplifyModel(allModels,false,false,false,true,false,false,false,[],true);
    -0246     allModels.c(:)=0;
    -0247 else
    -0248     %Remove dead ends for speed reasons
    -0249     allModels=simplifyModel(allModels,false,false,false,true,false,false,false,[],true);
    -0250     allModels.c(:)=0;
    +0174 if isempty(rxnScores)
    +0175     rxnScores=cell(numel(models),1);
    +0176     for i=1:numel(models)
    +0177         rxnScores{i}=ones(numel(models{i}.rxns),1)*-1;
    +0178     end
    +0179 end
    +0180 if isempty(params)
    +0181     params=struct();
    +0182 end
    +0183 
    +0184 if ~iscell(rxnScores)
    +0185     rxnScores={rxnScores};
    +0186 end
    +0187 
    +0188 models=models(:);
    +0189 rxnScores=rxnScores(:);
    +0190 
    +0191 %Check if the original model has an unconstrained field. If so, give a
    +0192 %warning
    +0193 if supressWarnings==false
    +0194     if isfield(model,'unconstrained')
    +0195         EM='This algorithm is meant to function on a model with exchange reactions for uptake and excretion of metabolites. The current model still has the "unconstrained" field';
    +0196         warning('RAVEN:warning', '%s', EM);
    +0197     else
    +0198         if isempty(getExchangeRxns(model,'both'))
    +0199             fprintf('NOTE: This algorithm is meant to function on a model with exchange reactions for uptake and excretion of metabolites. The current model does not seem to contain any such reactions.\n');
    +0200         end
    +0201     end
    +0202 end
    +0203 
    +0204 %Simplify the template models to remove constrained rxns. At the same time,
    +0205 %check that the id of the template models is not the same as the model. That
    +0206 %would cause an error further down
    +0207 for i=1:numel(models)
    +0208     models{i}.rxnScores=rxnScores{i};
    +0209     models{i}=simplifyModel(models{i},false,false,true);
    +0210     if strcmpi(models{i}.id,model.id)
    +0211         EM='The reference model(s) cannot have the same id as the model';
    +0212         error('RAVEN:badInput', '%s', EM);
    +0213     end
    +0214 end
    +0215 
    +0216 %This is a rather ugly solution to the issue that it is a bit tricky to keep
    +0217 %track of which scores belong to which reactions. This requires that
    +0218 %removeReactions and mergeModels are modified to check for the new field.
    +0219 model.rxnScores=zeros(numel(model.rxns),1);
    +0220 
    +0221 %First merge all models into one big one
    +0222 allModels=mergeModels([{model};models],'metNames',true);
    +0223 
    +0224 %Add that net production is ok
    +0225 if allowNetProduction==true
    +0226     %A second column in model.b means that the b field is lower and upper
    +0227     %bound on the RHS.
    +0228     model.b=[model.b(:,1) inf(numel(model.mets),1)];
    +0229     allModels.b=[allModels.b(:,1) inf(numel(allModels.mets),1)];
    +0230 end
    +0231 
    +0232 if useModelConstraints==true
    +0233     newConnected={};
    +0234     cannotConnect={};
    +0235     
    +0236     %Check that the input model is not solveable without any input
    +0237     sol=solveLP(model);
    +0238     if ~isempty(sol.f)
    +0239         addedRxns={};
    +0240         newModel=model;
    +0241         exitFlag=1;
    +0242         return;
    +0243     end
    +0244     
    +0245     %Then check that the merged model is solveable
    +0246     sol=solveLP(allModels);
    +0247     if isempty(sol.f)
    +0248         EM='There are no reactions in the template model(s) that can make the model constraints satisfied';
    +0249         error('RAVEN:badInput', '%s', EM);
    +0250     end
     0251     
    -0252     %If model constraints should not be used, then determine which reactions
    -0253     %to force to have flux
    -0254     
    -0255     %Get the reactions that can carry flux in the original model
    -0256     originalFlux=haveFlux(model,1);
    -0257     
    -0258     %For the ones that cannot carry flux, see if they can do so in the
    -0259     %merged model. Reactions from model keep their id unchanged in
    -0260     %allModels (mergeModels renames colliding ids in the other input
    -0261     %models, never in model itself), so intersecting with allModels.rxns
    -0262     %both identifies them and drops any that simplifyModel has since
    -0263     %removed as dead ends. The previous strcmp(allModels.rxnFrom,model.id)
    -0264     %failed whenever model already carried a rxnFrom field of its own, as
    -0265     %getModelFromHomology output does, tagging each reaction with its
    -0266     %template rather than model.id, so mergeModels leaves it untouched and
    -0267     %it never matches model.id here.
    -0268     toCheck=intersect(model.rxns(~originalFlux),allModels.rxns);
    -0269     
    -0270     %Get the ones that still cannot carry flux. Guard against an empty
    -0271     %toCheck, since haveFlux interprets an empty reaction list as "all
    -0272     %reactions" rather than "none"
    -0273     if isempty(toCheck)
    -0274         K=toCheck;
    -0275     else
    -0276         I=haveFlux(allModels,1,toCheck);
    -0277         %Get the reactions that cannot carry flux in the original model, but can
    -0278         %in the merged one
    -0279         K=toCheck(I);
    -0280     end
    -0281     
    -0282     %This is a temporary thing to only look at the non-reversible rxns.
    -0283     %This is because all reversible rxns can have a flux in the
    -0284     %irreversible model format that is used by getMinNrFluxes
    -0285     [~, I]=ismember(K,model.rxns);
    -0286     K(model.rev(I)~=0)=[];
    -0287     
    -0288     %Constrain all reactions in the original model to have a flux
    -0289     allModels.lb(ismember(allModels.rxns,K))=0.1;
    -0290     
    -0291     newConnected=K;
    -0292     cannotConnect=setdiff(model.rxns(~originalFlux),newConnected);
    -0293 end
    -0294 
    -0295 %Then minimize for the number of fluxes used. The fixed rxns (model's own)
    -0296 %do not need to participate; see the comment above on why this is
    -0297 %identified by rxn ID rather than allModels.rxnFrom.
    -0298 templateRxns=find(~ismember(allModels.rxns,model.rxns));
    -0299 [~, J, exitFlag]=getMinNrFluxes(allModels,templateRxns,params,allModels.rxnScores(templateRxns));
    -0300 
    -0301 %Remove everything except for the added ones
    -0302 I=true(numel(allModels.rxns),1);
    -0303 I(templateRxns(J))=false;
    -0304 addedModel=removeReactions(allModels,I,true,true,true);
    -0305 
    -0306 newModel=mergeModels({model;addedModel},'metNames',true);
    -0307 addedRxns=setdiff(newModel.rxns,model.rxns);
    -0308 newModel=rmfield(newModel,'rxnScores');
    -0309 end
    +0252 %Remove dead ends for speed reasons. This has to be done here and +0253 %duplicate below because there is otherwise a risk that a reaction +0254 %which is constrained to something relevant is removed +0255 allModels=simplifyModel(allModels,false,false,false,true,false,false,false,[],true); +0256 allModels.c(:)=0; +0257 else +0258 %Remove dead ends for speed reasons +0259 allModels=simplifyModel(allModels,false,false,false,true,false,false,false,[],true); +0260 allModels.c(:)=0; +0261 +0262 %If model constraints should not be used, then determine which reactions +0263 %to force to have flux +0264 +0265 %Get the reactions that can carry flux in the original model +0266 originalFlux=haveFlux(model,1); +0267 +0268 %For the ones that cannot carry flux, see if they can do so in the +0269 %merged model. Reactions from model keep their id unchanged in +0270 %allModels (mergeModels renames colliding ids in the other input +0271 %models, never in model itself), so intersecting with allModels.rxns +0272 %both identifies them and drops any that simplifyModel has since +0273 %removed as dead ends. The previous strcmp(allModels.rxnFrom,model.id) +0274 %failed whenever model already carried a rxnFrom field of its own, as +0275 %getModelFromHomology output does, tagging each reaction with its +0276 %template rather than model.id, so mergeModels leaves it untouched and +0277 %it never matches model.id here. +0278 toCheck=intersect(model.rxns(~originalFlux),allModels.rxns); +0279 +0280 %Get the ones that still cannot carry flux. Guard against an empty +0281 %toCheck, since haveFlux interprets an empty reaction list as "all +0282 %reactions" rather than "none" +0283 if isempty(toCheck) +0284 K=toCheck; +0285 else +0286 I=haveFlux(allModels,1,toCheck); +0287 %Get the reactions that cannot carry flux in the original model, but can +0288 %in the merged one +0289 K=toCheck(I); +0290 end +0291 +0292 %This is a temporary thing to only look at the non-reversible rxns. +0293 %This is because all reversible rxns can have a flux in the +0294 %irreversible model format that is used by getMinNrFluxes +0295 [~, I]=ismember(K,model.rxns); +0296 K(model.rev(I)~=0)=[]; +0297 +0298 %Constrain all reactions in the original model to have a flux +0299 allModels.lb(ismember(allModels.rxns,K))=0.1; +0300 +0301 newConnected=K; +0302 cannotConnect=setdiff(model.rxns(~originalFlux),newConnected); +0303 end +0304 +0305 %Then minimize for the number of fluxes used. The fixed rxns (model's own) +0306 %do not need to participate; see the comment above on why this is +0307 %identified by rxn ID rather than allModels.rxnFrom. +0308 templateRxns=find(~ismember(allModels.rxns,model.rxns)); +0309 [~, J, exitFlag]=getMinNrFluxes(allModels,templateRxns,params,allModels.rxnScores(templateRxns)); +0310 +0311 %Remove everything except for the added ones +0312 I=true(numel(allModels.rxns),1); +0313 I(templateRxns(J))=false; +0314 addedModel=removeReactions(allModels,I,true,true,true); +0315 +0316 newModel=mergeModels({model;addedModel},'metNames',true); +0317 addedRxns=setdiff(newModel.rxns,model.rxns); +0318 newModel=rmfield(newModel,'rxnScores'); +0319 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/gapfilling/findLeakMetabolite.html b/docs/html/gapfilling/findLeakMetabolite.html index 1a61626c..bd52aa65 100644 --- a/docs/html/gapfilling/findLeakMetabolite.html +++ b/docs/html/gapfilling/findLeakMetabolite.html @@ -30,10 +30,10 @@

    DESCRIPTION ^
     findLeakMetabolite  Find a metabolite that can be freely made or consumed.
     
      Tests whether the model can produce (excrete) or consume (take up) any
    - metabolite without balanced stoichiometric constraints — a sign that the
    - model contains a stoichiometric leak, futile cycle, or unconstrained
    - exchange. This is the unified replacement for makeSomething ('produce')
    - and consumeSomething ('consume').
    + metabolite without balanced stoichiometric constraints. A positive result
    + indicates that the model contains a stoichiometric leak, a futile cycle or
    + an unconstrained exchange. The direction 'produce' corresponds to the
    + deprecated function makeSomething and 'consume' to consumeSomething.
     
      Parameters
      ----------
    @@ -50,33 +50,33 @@ 

    DESCRIPTION ^DESCRIPTION ^canExchange

    @@ -111,10 +111,10 @@

    SOURCE CODE ^% findLeakMetabolite Find a metabolite that can be freely made or consumed. 0003 % 0004 % Tests whether the model can produce (excrete) or consume (take up) any -0005 % metabolite without balanced stoichiometric constraints — a sign that the -0006 % model contains a stoichiometric leak, futile cycle, or unconstrained -0007 % exchange. This is the unified replacement for makeSomething ('produce') -0008 % and consumeSomething ('consume'). +0005 % metabolite without balanced stoichiometric constraints. A positive result +0006 % indicates that the model contains a stoichiometric leak, a futile cycle or +0007 % an unconstrained exchange. The direction 'produce' corresponds to the +0008 % deprecated function makeSomething and 'consume' to consumeSomething. 0009 % 0010 % Parameters 0011 % ---------- @@ -131,33 +131,33 @@

    SOURCE CODE ^% number of elements as metabolites in the model, or a vector of indexes 0023 % for metabolites to exclude from this analysis (default []). 0024 % isNames : logical -0025 % true if the supplied mets represent metabolite names (as opposed to -0026 % IDs). This is a way to delete metabolites in several compartments at -0027 % once without knowing the exact IDs. This only works if ignoreMets is a +0025 % true if the supplied mets are metabolite names and false if they are +0026 % IDs. Names exclude the metabolites of all compartments that carry the +0027 % name, so the exact IDs are not needed. Only works if ignoreMets is a 0028 % cell array (default false). 0029 % minNrFluxes : logical -0030 % solves the MILP problem of minimizing the number of fluxes instead of -0031 % the sum. Slower, but can be used if the sum gives too many fluxes -0032 % (default false). +0030 % true to solve the MILP problem of minimising the number of fluxes. If +0031 % false, the sum of fluxes is minimised. The MILP is slower and applies +0032 % when the sum gives too many fluxes (default false). 0033 % allowExcretion : logical -0034 % allow for excretion of all metabolites. Only used when direction is +0034 % true to allow excretion of all metabolites. Only used when direction is 0035 % 'produce' (default true). 0036 % params : struct -0037 % *obsolete option*. -0038 % ignoreIntBounds : logical -0039 % true if internal bounds (including reversibility) should be ignored. -0040 % Exchange reactions are not affected. This can be used to find -0041 % unbalanced solutions which are not possible using the default -0042 % constraints (default false). +0037 % solver parameters, passed to solveLP when minNrFluxes is true (default +0038 % a structure with relGap 0.8). +0039 % ignoreIntBounds : logical +0040 % true to ignore internal bounds (including reversibility). Exchange +0041 % reactions are not affected. Finds unbalanced solutions that the +0042 % default constraints do not allow (default false). 0043 % 0044 % Returns 0045 % ------- 0046 % solution : double 0047 % flux vector for the solution. 0048 % metabolite : double -0049 % the index of the metabolite(s) which was produced or consumed. If -0050 % possible only one metabolite is reported, but there are situations -0051 % where metabolites can only be exchanged in pairs (or more). +0049 % the index of the metabolite(s) produced or consumed. A single +0050 % metabolite is reported when one suffices; some metabolites can only be +0051 % exchanged in pairs or larger groups. 0052 % 0053 % Examples 0054 % -------- @@ -166,12 +166,12 @@

    SOURCE CODE ^% 0058 % Notes 0059 % ----- -0060 % Works by forcing at least 1 unit of "any metabolite" to be produced or -0061 % consumed, then minimising for the sum of fluxes. If more than one -0062 % metabolite is produced/consumed, it tries to find a single metabolite -0063 % that satisfies the constraint on its own. +0060 % The function forces a flux of at least 1 through a pooled exchange of all +0061 % metabolites, then minimises the sum of fluxes. If more than one metabolite +0062 % is produced or consumed, it searches for a single metabolite that +0063 % satisfies the constraint on its own. 0064 % -0065 % See Also +0065 % See also 0066 % -------- 0067 % canExchange 0068 diff --git a/docs/html/gapfilling/fitTasks.html b/docs/html/gapfilling/fitTasks.html index badf68f6..5ffc8264 100644 --- a/docs/html/gapfilling/fitTasks.html +++ b/docs/html/gapfilling/fitTasks.html @@ -31,9 +31,9 @@

    DESCRIPTION ^DESCRIPTION ^DESCRIPTION ^DESCRIPTION ^DESCRIPTION ^SOURCE CODE ^% 0004 % Fills gaps in a model by including reactions from a reference model, so 0005 % that the resulting model can perform all the tasks in a task list. The -0006 % gap-filling is done in a task-by-task manner, rather than solving for -0007 % all tasks at once. This means that the order of the tasks could -0008 % influence the result. +0006 % tasks are gap-filled one at a time in list order, and the reactions added +0007 % for one task are kept for the following tasks. The order of the tasks can +0008 % therefore influence the result. 0009 % 0010 % Parameters 0011 % ---------- @@ -128,7 +128,7 @@

    SOURCE CODE ^% reference model from which to include reactions. 0016 % inputFile : char, optional 0017 % a task list in Excel format. See the function parseTaskList for -0018 % details (optional if taskStructure is supplied). +0018 % details. Optional if taskStructure is supplied. 0019 % 0020 % Name-Value Arguments 0021 % -------------------- @@ -136,30 +136,30 @@

    SOURCE CODE ^% true if the results of the test should be displayed (default true). 0024 % rxnScores : double 0025 % scores for each of the reactions in the reference model. Only -0026 % negative scores are allowed. The solver will try to maximize the sum -0027 % of the scores for the included reactions (default is -1 for all -0028 % reactions). -0029 % taskStructure : struct -0030 % structure with the tasks, as from parseTaskList. If supplied then -0031 % inputFile is ignored. -0032 % params : struct -0033 % solver parameters, forwarded to the gap-filling back-end and from there -0034 % to optimizeProb for each per-task MILP (default []). -0035 % gapFillMode : char -0036 % which gap-filling back-end to use (default 'merge'): -0037 % -0038 % - 'merge' : refModel holds only the candidate reactions, and fillGaps -0039 % merges it with the model for every task. -0040 % - 'preMerged' : refModel already contains the model's own reactions, -0041 % so no merge is needed per task. The task constraints are written -0042 % into refModel as well, and ftINITFillGaps does the gap-filling. -0043 % verbose : logical -0044 % if true, the MILP progression will be shown. Only read when -0045 % gapFillMode is 'preMerged' (default false). -0046 % resolveTies : logical -0047 % if true, pin the gap-fill MILP's degenerate optimum to a canonical answer -0048 % instead of relying on the solver Seed alone. Only read when gapFillMode is -0049 % 'preMerged' -- see ftINITFillGaps' resolveTies (default false). +0026 % negative scores are allowed. The solver maximises the sum of the +0027 % scores of the included reactions (default is -1 for all reactions). +0028 % taskStructure : struct +0029 % structure with the tasks, as returned by parseTaskList. If supplied, +0030 % inputFile is ignored. +0031 % params : struct +0032 % solver parameters, passed to the gap-filling function and from there to +0033 % optimizeProb for each per-task MILP (default []). +0034 % gapFillMode : char +0035 % which gap-filling function to use (default 'merge'): +0036 % +0037 % - 'merge' : refModel holds only the candidate reactions, and fillGaps +0038 % merges it with the model for every task. +0039 % - 'preMerged' : refModel already contains the model's own reactions, +0040 % so no merge is needed per task. The task constraints are written +0041 % into refModel as well, and ftINITFillGaps does the gap-filling. +0042 % verbose : logical +0043 % true to show the MILP progression. Only read when gapFillMode is +0044 % 'preMerged' (default false). +0045 % resolveTies : logical +0046 % true to fix the degenerate optimum of the gap-fill MILP to a canonical +0047 % answer, which otherwise depends on the solver Seed. Only read when +0048 % gapFillMode is 'preMerged'. See resolveTies of ftINITFillGaps +0049 % (default false). 0050 % 0051 % Returns 0052 % ------- @@ -173,8 +173,8 @@

    SOURCE CODE ^% failedTasks : logical 0061 % Nx1, true for each task that could not be gap-filled, either because 0062 % no feasible solution exists using the reference model or because the -0063 % attempt threw. Such tasks add no reactions, so they are otherwise -0064 % indistinguishable from tasks that already worked. +0063 % attempt raised an error. Such tasks add no reactions, and the same +0064 % holds for tasks that are feasible without gap-filling. 0065 % 0066 % Examples 0067 % -------- diff --git a/docs/html/gapfilling/ftINITFillGaps.html b/docs/html/gapfilling/ftINITFillGaps.html index 114a2fa0..fab58607 100644 --- a/docs/html/gapfilling/ftINITFillGaps.html +++ b/docs/html/gapfilling/ftINITFillGaps.html @@ -28,30 +28,30 @@

    SYNOPSIS ^DESCRIPTION ^

     ftINITFillGaps
    -   Variant of fillGaps specially adapted to speed up generation of ftINIT models.
    +   Variant of fillGaps adapted to speed up the generation of ftINIT models.
     
        tModel              model that contains the task-specific rxns.
        origModel           model without task-specific rxns.         
    -   tRefModel           reference tModel - the full tModel, containing all rxns
    +   tRefModel           reference tModel: the full tModel, containing all rxns
                            used for gap-filling of tModel + the task-specific rxns
        allowNetProduction  true if net production of all metabolites is
                            allowed. A reaction can be unable to carry flux because one of
                            the reactants is unavailable or because one of the
    -                       products can't be further processed. If this
    -                       parameter is true, only the first type of
    -                       unconnectivity is considered (optional, default false)
    +                       products cannot be processed further. If this
    +                       parameter is true, only the first cause of blocked
    +                       flux is considered (optional, default false)
        supressWarnings     false if warnings should be displayed (optional, default
                            false)
        rxnScores           scores for each of the reactions in the
    -                       reference tModel. 
    -                       The solver will try to maximize the sum of the
    -                       scores for the included reactions
    -   params              *obsolete option*
    -   verbose             if true, the MILP progression will be shown.
    -   resolveTies         if true, pin the gap-fill MILP's degenerate optimum to a
    -                       canonical answer instead of relying on the solver Seed
    -                       alone -- see getMinNrFluxes' resolveTies (optional,
    -                       default false)
    +                       reference tModel. The solver maximises the sum of
    +                       the scores of the included reactions (optional,
    +                       default -1 for all reactions)
    +   params              solver parameters, passed to getMinNrFluxes (optional)
    +   verbose             true to show the MILP progression.
    +   resolveTies         true to fix the degenerate optimum of the gap-fill MILP
    +                       to a canonical answer, which otherwise depends on the
    +                       solver Seed. See resolveTies of getMinNrFluxes
    +                       (optional, default false)
     
        addedRxns           the rxns added
        newModel            the tModel with reactions added to fill gaps
    @@ -63,16 +63,18 @@ 

    DESCRIPTION ^fillGaps, the tINIT-side counterpart, which merges a - reference model with the model being gap-filled on every call instead - of expecting them pre-merged. + fillGaps merges the reference model with the model on every call, so it + does not need a pre-merged reference model. + + See also: fillGaps Usage: [addedRxns, newModel, exitFlag]=... ftINITFillGaps(tModel,origModel,tRefModel,allowNetProduction,... @@ -93,30 +95,30 @@

    CROSS-REFERENCE INFORMATION ^
 <h2><a name=SOURCE CODE ^

    0001 function [addedRxns, newModel, exitFlag]=ftINITFillGaps(tModel, origModel, tRefModel,allowNetProduction,supressWarnings,rxnScores,params,verbose,resolveTies)
     0002 % ftINITFillGaps
    -0003 %   Variant of fillGaps specially adapted to speed up generation of ftINIT models.
    +0003 %   Variant of fillGaps adapted to speed up the generation of ftINIT models.
     0004 %
     0005 %   tModel              model that contains the task-specific rxns.
     0006 %   origModel           model without task-specific rxns.
    -0007 %   tRefModel           reference tModel - the full tModel, containing all rxns
    +0007 %   tRefModel           reference tModel: the full tModel, containing all rxns
     0008 %                       used for gap-filling of tModel + the task-specific rxns
     0009 %   allowNetProduction  true if net production of all metabolites is
     0010 %                       allowed. A reaction can be unable to carry flux because one of
     0011 %                       the reactants is unavailable or because one of the
    -0012 %                       products can't be further processed. If this
    -0013 %                       parameter is true, only the first type of
    -0014 %                       unconnectivity is considered (optional, default false)
    +0012 %                       products cannot be processed further. If this
    +0013 %                       parameter is true, only the first cause of blocked
    +0014 %                       flux is considered (optional, default false)
     0015 %   supressWarnings     false if warnings should be displayed (optional, default
     0016 %                       false)
     0017 %   rxnScores           scores for each of the reactions in the
    -0018 %                       reference tModel.
    -0019 %                       The solver will try to maximize the sum of the
    -0020 %                       scores for the included reactions
    -0021 %   params              *obsolete option*
    -0022 %   verbose             if true, the MILP progression will be shown.
    -0023 %   resolveTies         if true, pin the gap-fill MILP's degenerate optimum to a
    -0024 %                       canonical answer instead of relying on the solver Seed
    -0025 %                       alone -- see getMinNrFluxes' resolveTies (optional,
    -0026 %                       default false)
    +0018 %                       reference tModel. The solver maximises the sum of
    +0019 %                       the scores of the included reactions (optional,
    +0020 %                       default -1 for all reactions)
    +0021 %   params              solver parameters, passed to getMinNrFluxes (optional)
    +0022 %   verbose             true to show the MILP progression.
    +0023 %   resolveTies         true to fix the degenerate optimum of the gap-fill MILP
    +0024 %                       to a canonical answer, which otherwise depends on the
    +0025 %                       solver Seed. See resolveTies of getMinNrFluxes
    +0026 %                       (optional, default false)
     0027 %
     0028 %   addedRxns           the rxns added
     0029 %   newModel            the tModel with reactions added to fill gaps
    @@ -128,92 +130,94 @@ 

    SOURCE CODE ^% because the solver stopped before reaching 0036 % optimality 0037 % -0038 % This method works by merging the tModel to the reference model and -0039 % checking which reactions can carry flux. All reactions that can't -0040 % carry flux are removed. It then solves the MILP problem of including -0041 % as few additional reactions as possible from the reference model so -0042 % that the constraints already set on tModel (e.g. a required biomass -0043 % flux, or exchange bounds) can be satisfied. -0044 % -0045 % See also: fillGaps, the tINIT-side counterpart, which merges a -0046 % reference model with the model being gap-filled on every call instead -0047 % of expecting them pre-merged. +0038 % This method works on the reference model, which already contains the +0039 % tModel reactions. Reactions constrained to zero flux are removed +0040 % (simplifyModel(tRefModel,false,false,true), deleteZeroInterval). It then +0041 % solves the MILP problem of including as few additional reactions as +0042 % possible from the reference model so that the constraints already set +0043 % on tModel (for example a required biomass flux, or exchange bounds) can +0044 % be satisfied. +0045 % +0046 % fillGaps merges the reference model with the model on every call, so it +0047 % does not need a pre-merged reference model. 0048 % -0049 % Usage: [addedRxns, newModel, exitFlag]=... -0050 % ftINITFillGaps(tModel,origModel,tRefModel,allowNetProduction,... -0051 % supressWarnings,rxnScores,params,verbose,resolveTies) -0052 -0053 if nargin<9 || isempty(resolveTies) -0054 resolveTies=false; -0055 end -0056 if isempty(rxnScores) -0057 rxnScores=ones(numel(tRefModel.rxns),1)*-1; -0058 end -0059 -0060 %Simplify the template models to remove constrained rxns. At the same time, -0061 %check that the id of the template models isn't the same as the tModel. That -0062 %would cause an error further down -0063 tRefModel.rxnScores=rxnScores; -0064 tRefModel=simplifyModel(tRefModel,false,false,true); -0065 -0066 %This is a rather ugly solution to the issue that it's a bit tricky to keep -0067 %track of which scores belong to which reactions. This requires that -0068 %removeReactions and mergeModels are modified to check for the new field. -0069 tModel.rxnScores=zeros(numel(tModel.rxns),1); -0070 -0071 %First merge all models into one big one -0072 fullModel = tRefModel;%mergeModels([{tModel};models],'metNames',true); -0073 -0074 %Add that net production is ok -0075 if allowNetProduction==true -0076 %A second column in tModel.b means that the b field is lower and upper -0077 %bound on the RHS. -0078 tModel.b=[tModel.b(:,1) inf(numel(tModel.mets),1)]; -0079 fullModel.b=[fullModel.b(:,1) inf(numel(fullModel.mets),1)]; -0080 end -0081 -0082 -0083 fullModel.c(:)=0; +0049 % See also: fillGaps +0050 % +0051 % Usage: [addedRxns, newModel, exitFlag]=... +0052 % ftINITFillGaps(tModel,origModel,tRefModel,allowNetProduction,... +0053 % supressWarnings,rxnScores,params,verbose,resolveTies) +0054 +0055 if nargin<9 || isempty(resolveTies) +0056 resolveTies=false; +0057 end +0058 if isempty(rxnScores) +0059 rxnScores=ones(numel(tRefModel.rxns),1)*-1; +0060 end +0061 +0062 %Simplify the template models to remove constrained rxns. At the same time, +0063 %check that the id of the template models isn't the same as the tModel. That +0064 %would cause an error further down +0065 tRefModel.rxnScores=rxnScores; +0066 tRefModel=simplifyModel(tRefModel,false,false,true); +0067 +0068 %This is a rather ugly solution to the issue that it's a bit tricky to keep +0069 %track of which scores belong to which reactions. This requires that +0070 %removeReactions and mergeModels are modified to check for the new field. +0071 tModel.rxnScores=zeros(numel(tModel.rxns),1); +0072 +0073 %First merge all models into one big one +0074 fullModel = tRefModel;%mergeModels([{tModel};models],'metNames',true); +0075 +0076 %Add that net production is ok +0077 if allowNetProduction==true +0078 %A second column in tModel.b means that the b field is lower and upper +0079 %bound on the RHS. +0080 tModel.b=[tModel.b(:,1) inf(numel(tModel.mets),1)]; +0081 fullModel.b=[fullModel.b(:,1) inf(numel(fullModel.mets),1)]; +0082 end +0083 0084 -0085 %Then minimize for the number of fluxes used. The fixed rxns doesn't need -0086 %to participate -0087 templateRxns = find(~ismember(fullModel.rxns, tModel.rxns)); %Check if this is slow, in that case keep track of this in fitTasksOpt and send it in -0088 -0089 if isempty(templateRxns) -0090 %The reference model holds no reaction that the model does not already -0091 %have, so no set of additions can satisfy the constraints. This has to -0092 %be caught first: an empty toMinimize means "all reactions" to -0093 %getMinNrFluxes, and its solution would then be indexed against an -0094 %empty templateRxns. -0095 addedRxns = {}; -0096 newModel = origModel; -0097 exitFlag = -1; -0098 return; -0099 end -0100 -0101 %The reversible formulation uses one binary per reaction rather than one -0102 %per irreversible reaction, which is what makes this fast enough to run -0103 %once per task on a genome-scale reference model -0104 [~, J, exitFlag]=getMinNrFluxes(fullModel,templateRxns,params,fullModel.rxnScores(templateRxns),'formulation','reversible','verbose',verbose,'resolveTies',resolveTies);%only the scores from the template rxns are used, so the others doesn't matter -0105 -0106 %Remove everything except for the added ones -0107 addedRxns = fullModel.rxns(templateRxns(J)); -0108 if isempty(addedRxns) -0109 %No reactions were selected, either because the MILP found no solution -0110 %or because none were needed. Handing an empty set to addRxns below -0111 %errors out, which would reach the caller as a thrown exception rather -0112 %than as the exitFlag it keys on. -0113 addedRxns = {}; -0114 newModel = origModel; -0115 return; -0116 end -0117 rxnsToAdd.rxns = addedRxns; -0118 rxnsToAdd.equations = constructEquations(fullModel, addedRxns); -0119 rxnsToAdd.ub = fullModel.ub(templateRxns(J)); -0120 rxnsToAdd.lb = fullModel.lb(templateRxns(J)); -0121 newModel = addRxns(origModel,rxnsToAdd, 3, [], true); %we ignore gene rules etc here, they are not needed - we regenerate the model in the end by subtracting rxns from the full model. -0122 -0123 end

    +0085 fullModel.c(:)=0; +0086 +0087 %Then minimize for the number of fluxes used. The fixed rxns doesn't need +0088 %to participate +0089 templateRxns = find(~ismember(fullModel.rxns, tModel.rxns)); %Check if this is slow, in that case keep track of this in fitTasksOpt and send it in +0090 +0091 if isempty(templateRxns) +0092 %The reference model holds no reaction that the model does not already +0093 %have, so no set of additions can satisfy the constraints. This has to +0094 %be caught first: an empty toMinimize means "all reactions" to +0095 %getMinNrFluxes, and its solution would then be indexed against an +0096 %empty templateRxns. +0097 addedRxns = {}; +0098 newModel = origModel; +0099 exitFlag = -1; +0100 return; +0101 end +0102 +0103 %The reversible formulation uses one binary per reaction rather than one +0104 %per irreversible reaction, which is what makes this fast enough to run +0105 %once per task on a genome-scale reference model +0106 [~, J, exitFlag]=getMinNrFluxes(fullModel,templateRxns,params,fullModel.rxnScores(templateRxns),'formulation','reversible','verbose',verbose,'resolveTies',resolveTies);%only the scores from the template rxns are used, so the others doesn't matter +0107 +0108 %Remove everything except for the added ones +0109 addedRxns = fullModel.rxns(templateRxns(J)); +0110 if isempty(addedRxns) +0111 %No reactions were selected, either because the MILP found no solution +0112 %or because none were needed. Handing an empty set to addRxns below +0113 %errors out, which would reach the caller as a thrown exception rather +0114 %than as the exitFlag it keys on. +0115 addedRxns = {}; +0116 newModel = origModel; +0117 return; +0118 end +0119 rxnsToAdd.rxns = addedRxns; +0120 rxnsToAdd.equations = constructEquations(fullModel, addedRxns); +0121 rxnsToAdd.ub = fullModel.ub(templateRxns(J)); +0122 rxnsToAdd.lb = fullModel.lb(templateRxns(J)); +0123 newModel = addRxns(origModel,rxnsToAdd, 3, [], true); %we ignore gene rules etc here, they are not needed - we regenerate the model in the end by subtracting rxns from the full model. +0124 +0125 end

    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/gapfilling/gapFillFastCore.html b/docs/html/gapfilling/gapFillFastCore.html index d7e6d2e4..e7f0aa16 100644 --- a/docs/html/gapfilling/gapFillFastCore.html +++ b/docs/html/gapfilling/gapFillFastCore.html @@ -30,8 +30,9 @@

    DESCRIPTION ^
     gapFillFastCore  L1-norm LP subroutine for fastGapFill.
     
      Finds the minimum-L1-flux extension of the model that activates all
    - reactions in the core set. Any reaction with non-zero flux in the
    - solution is "consistent" with the core. Used internally by gapFillFastLP.
    + reactions in the core set. Any reaction with a flux of at least
    + epsilon / 2 in the solution is "consistent" with the core. gapFillFastLP
    + calls this function.
     
      Based on FASTCORE (Vlassis et al. 2014, PLoS Comput Biol 10:e1003424).
     
    @@ -49,9 +50,10 @@ 

    DESCRIPTION ^CROSS-REFERENCE INFORMATION ^
 </ul>
 This function is called by:
 <ul style= -
  • gapFillFastLP gapFillFastLP LP-based gap-filling (fastGapFill / swiftGapFill).
  • +
  • gapFillFastLP gapFillFastLP LP-based gap-filling (fastGapFill or swiftGapFill).
  • @@ -74,126 +76,128 @@

    SOURCE CODE ^% gapFillFastCore L1-norm LP subroutine for fastGapFill. 0003 % 0004 % Finds the minimum-L1-flux extension of the model that activates all -0005 % reactions in the core set. Any reaction with non-zero flux in the -0006 % solution is "consistent" with the core. Used internally by gapFillFastLP. -0007 % -0008 % Based on FASTCORE (Vlassis et al. 2014, PLoS Comput Biol 10:e1003424). -0009 % -0010 % Parameters -0011 % ---------- -0012 % model : struct -0013 % a RAVEN model structure. -0014 % coreRxns : cell or logical or double -0015 % core reaction set: cell array of reaction IDs, a logical vector, or a -0016 % vector of reaction indices into model.rxns. All core reactions are -0017 % forced to carry flux >= epsilon in the LP solution. -0018 % epsilon : double -0019 % minimum flux threshold for a reaction to be considered active. -0020 % -0021 % Returns -0022 % ------- -0023 % activeRxns : logical -0024 % logical vector (length numel(model.rxns)) where true indicates the -0025 % reaction carries flux in the L1-minimal solution. Empty if the LP is -0026 % infeasible (core reactions cannot all carry flux simultaneously). -0027 % -0028 % See also -0029 % -------- -0030 % gapFillSwiftCore, gapFillFastLP -0031 -0032 if nargin < 3 || isempty(epsilon) -0033 epsilon = 1e-4; -0034 end -0035 -0036 % ---- Resolve core reaction indices into original model ---- -0037 % coreOrigIdx: numeric indices into model.rxns -0038 if iscell(coreRxns) -0039 coreOrigIdx = find(getIndexes(model, coreRxns, 'rxns', true)); -0040 elseif islogical(coreRxns) -0041 coreOrigIdx = find(coreRxns); -0042 else -0043 coreOrigIdx = coreRxns(:); -0044 end -0045 -0046 % ---- Convert to irreversible form ---- -0047 % All fluxes become non-negative → L1 norm = sum of fluxes. -0048 [irrevModel, matchRev, ~, irrev2rev] = convertToIrrev(model); -0049 nIrrev = numel(irrevModel.rxns); -0050 nMets = numel(irrevModel.mets); -0051 -0052 % Map core reactions to their irreversible equivalents. convertToIrrev -0053 % keeps the forward copy at the reaction's original index, so coreOrigIdx -0054 % already are that copy's indices. -0055 coreIdxIrrev = false(nIrrev, 1); -0056 coreIdxIrrev(coreOrigIdx) = true; -0057 -0058 % A reversible core reaction also has a reverse copy elsewhere in -0059 % irrevModel, with identical but oppositely-signed stoichiometry. Forcing -0060 % both copies to >= epsilon would let them cancel out in S_irrev*v=0 -0061 % (v_fwd=v_bwd=epsilon nets to zero for every metabolite the reaction -0062 % touches), satisfying the "core must carry flux" requirement with a -0063 % self-contained loop that needs no support from the rest of the network, -0064 % regardless of whether the reaction has any real connectivity. Instead, -0065 % require flux on one copy at a time: try forward first, and if that is -0066 % infeasible, retry with reverse forced instead, so a reaction whose only -0067 % genuine support is in the reverse direction is still recognised. -0068 isRevCore = matchRev(coreOrigIdx) > 0; -0069 fwdCoreCopy = coreOrigIdx(isRevCore); % forward copy of reversible core rxns -0070 revCoreCopy = matchRev(coreOrigIdx(isRevCore)); % their reverse copy -0071 irrCoreCopy = coreOrigIdx(~isRevCore); % irreversible core rxns: single copy -0072 -0073 c = ones(nIrrev, 1); -0074 c(coreIdxIrrev) = 0; % core reactions are not in the objective -0075 -0076 prob.A = irrevModel.S; -0077 prob.a = prob.A; -0078 prob.b = zeros(nMets, 1); -0079 prob.csense = repmat('E', 1, nMets); -0080 prob.c = c; -0081 prob.osense = 1; % minimise -0082 prob.vartype = repmat('C', 1, nIrrev); -0083 -0084 % ---- Formulate LP ---- -0085 % Minimize sum of non-core fluxes (all fluxes >= 0 in irreversible form, -0086 % so this is the L1 norm over non-core reactions). -0087 % -0088 % min sum_{j not in C} v_j -0089 % s.t. S_irrev * v = 0 -0090 % v_j >= epsilon for j in C -0091 % lb <= v <= ub -0092 % -0093 forceCopy = {fwdCoreCopy, revCoreCopy}; -0094 disableCopy = {revCoreCopy, fwdCoreCopy}; -0095 nPasses = 1 + ~isempty(fwdCoreCopy); % a second pass only differs when a reversible core rxn exists -0096 for pass = 1:nPasses -0097 lb = irrevModel.lb; -0098 lb(irrCoreCopy) = max(lb(irrCoreCopy), epsilon); -0099 lb(forceCopy{pass}) = max(lb(forceCopy{pass}), epsilon); -0100 ub = irrevModel.ub; -0101 ub(disableCopy{pass}) = 0; -0102 prob.lb = lb; -0103 prob.ub = ub; -0104 -0105 sol = optimizeProb(prob, [], false); -0106 if checkSolution(sol) -0107 break; -0108 end -0109 end -0110 -0111 if ~checkSolution(sol) -0112 activeRxns = false(numel(model.rxns), 1); -0113 return; -0114 end -0115 -0116 % ---- Map solution back to original model ---- -0117 irrevActive = sol.full(1:nIrrev) >= epsilon / 2; -0118 activeRxns = false(numel(model.rxns), 1); -0119 for k = 1:nIrrev -0120 if irrevActive(k) -0121 activeRxns(irrev2rev(k)) = true; -0122 end -0123 end -0124 end

    +0005 % reactions in the core set. Any reaction with a flux of at least +0006 % epsilon / 2 in the solution is "consistent" with the core. gapFillFastLP +0007 % calls this function. +0008 % +0009 % Based on FASTCORE (Vlassis et al. 2014, PLoS Comput Biol 10:e1003424). +0010 % +0011 % Parameters +0012 % ---------- +0013 % model : struct +0014 % a RAVEN model structure. +0015 % coreRxns : cell or logical or double +0016 % core reaction set: cell array of reaction IDs, a logical vector, or a +0017 % vector of reaction indices into model.rxns. All core reactions are +0018 % forced to carry flux >= epsilon in the LP solution. +0019 % epsilon : double +0020 % minimum flux threshold for a reaction to be considered active. +0021 % +0022 % Returns +0023 % ------- +0024 % activeRxns : logical +0025 % logical vector (length numel(model.rxns)) where true indicates that the +0026 % reaction carries flux in the L1-minimal solution. All elements are +0027 % false if the LP is infeasible (the core reactions cannot all carry flux +0028 % simultaneously). +0029 % +0030 % See also +0031 % -------- +0032 % gapFillSwiftCore, gapFillFastLP +0033 +0034 if nargin < 3 || isempty(epsilon) +0035 epsilon = 1e-4; +0036 end +0037 +0038 % ---- Resolve core reaction indices into original model ---- +0039 % coreOrigIdx: numeric indices into model.rxns +0040 if iscell(coreRxns) +0041 coreOrigIdx = find(getIndexes(model, coreRxns, 'rxns', true)); +0042 elseif islogical(coreRxns) +0043 coreOrigIdx = find(coreRxns); +0044 else +0045 coreOrigIdx = coreRxns(:); +0046 end +0047 +0048 % ---- Convert to irreversible form ---- +0049 % All fluxes become non-negative → L1 norm = sum of fluxes. +0050 [irrevModel, matchRev, ~, irrev2rev] = convertToIrrev(model); +0051 nIrrev = numel(irrevModel.rxns); +0052 nMets = numel(irrevModel.mets); +0053 +0054 % Map core reactions to their irreversible equivalents. convertToIrrev +0055 % keeps the forward copy at the reaction's original index, so coreOrigIdx +0056 % already are that copy's indices. +0057 coreIdxIrrev = false(nIrrev, 1); +0058 coreIdxIrrev(coreOrigIdx) = true; +0059 +0060 % A reversible core reaction also has a reverse copy elsewhere in +0061 % irrevModel, with identical but oppositely-signed stoichiometry. Forcing +0062 % both copies to >= epsilon would let them cancel out in S_irrev*v=0 +0063 % (v_fwd=v_bwd=epsilon nets to zero for every metabolite the reaction +0064 % touches), satisfying the "core must carry flux" requirement with a +0065 % self-contained loop that needs no support from the rest of the network, +0066 % regardless of whether the reaction has any real connectivity. Instead, +0067 % require flux on one copy at a time: try forward first, and if that is +0068 % infeasible, retry with reverse forced instead, so a reaction whose only +0069 % genuine support is in the reverse direction is still recognised. +0070 isRevCore = matchRev(coreOrigIdx) > 0; +0071 fwdCoreCopy = coreOrigIdx(isRevCore); % forward copy of reversible core rxns +0072 revCoreCopy = matchRev(coreOrigIdx(isRevCore)); % their reverse copy +0073 irrCoreCopy = coreOrigIdx(~isRevCore); % irreversible core rxns: single copy +0074 +0075 c = ones(nIrrev, 1); +0076 c(coreIdxIrrev) = 0; % core reactions are not in the objective +0077 +0078 prob.A = irrevModel.S; +0079 prob.a = prob.A; +0080 prob.b = zeros(nMets, 1); +0081 prob.csense = repmat('E', 1, nMets); +0082 prob.c = c; +0083 prob.osense = 1; % minimise +0084 prob.vartype = repmat('C', 1, nIrrev); +0085 +0086 % ---- Formulate LP ---- +0087 % Minimize sum of non-core fluxes (all fluxes >= 0 in irreversible form, +0088 % so this is the L1 norm over non-core reactions). +0089 % +0090 % min sum_{j not in C} v_j +0091 % s.t. S_irrev * v = 0 +0092 % v_j >= epsilon for j in C +0093 % lb <= v <= ub +0094 % +0095 forceCopy = {fwdCoreCopy, revCoreCopy}; +0096 disableCopy = {revCoreCopy, fwdCoreCopy}; +0097 nPasses = 1 + ~isempty(fwdCoreCopy); % a second pass only differs when a reversible core rxn exists +0098 for pass = 1:nPasses +0099 lb = irrevModel.lb; +0100 lb(irrCoreCopy) = max(lb(irrCoreCopy), epsilon); +0101 lb(forceCopy{pass}) = max(lb(forceCopy{pass}), epsilon); +0102 ub = irrevModel.ub; +0103 ub(disableCopy{pass}) = 0; +0104 prob.lb = lb; +0105 prob.ub = ub; +0106 +0107 sol = optimizeProb(prob, [], false); +0108 if checkSolution(sol) +0109 break; +0110 end +0111 end +0112 +0113 if ~checkSolution(sol) +0114 activeRxns = false(numel(model.rxns), 1); +0115 return; +0116 end +0117 +0118 % ---- Map solution back to original model ---- +0119 irrevActive = sol.full(1:nIrrev) >= epsilon / 2; +0120 activeRxns = false(numel(model.rxns), 1); +0121 for k = 1:nIrrev +0122 if irrevActive(k) +0123 activeRxns(irrev2rev(k)) = true; +0124 end +0125 end +0126 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/gapfilling/gapFillFastLP.html b/docs/html/gapfilling/gapFillFastLP.html index af091c6e..61c38098 100644 --- a/docs/html/gapfilling/gapFillFastLP.html +++ b/docs/html/gapfilling/gapFillFastLP.html @@ -4,7 +4,7 @@ Description of gapFillFastLP - + @@ -21,13 +21,13 @@

    gapFillFastLP

    PURPOSE ^

    -
    gapFillFastLP LP-based gap-filling (fastGapFill / swiftGapFill).
    +
    gapFillFastLP LP-based gap-filling (fastGapFill or swiftGapFill).

    SYNOPSIS ^

    function [addedRxns, newModel, cannotConnect] = gapFillFastLP(model, universalModel, varargin)

    DESCRIPTION ^

    -
     gapFillFastLP  LP-based gap-filling (fastGapFill / swiftGapFill).
    +
     gapFillFastLP  LP-based gap-filling (fastGapFill or swiftGapFill).
     
      Merges the draft model with a universal reaction database, then for each
      blocked draft reaction calls an LP subroutine (FASTCORE or SWIFTCORE) to
    @@ -43,19 +43,23 @@ 

    DESCRIPTION ^DESCRIPTION ^CROSS-REFERENCE INFORMATION ^
 
 <h2><a name=SOURCE CODE ^

    0001 function [addedRxns, newModel, cannotConnect] = gapFillFastLP(model, universalModel, varargin)
    -0002 % gapFillFastLP  LP-based gap-filling (fastGapFill / swiftGapFill).
    +0002 % gapFillFastLP  LP-based gap-filling (fastGapFill or swiftGapFill).
     0003 %
     0004 % Merges the draft model with a universal reaction database, then for each
     0005 % blocked draft reaction calls an LP subroutine (FASTCORE or SWIFTCORE) to
    @@ -102,154 +106,158 @@ 

    SOURCE CODE ^% model : struct 0016 % draft RAVEN model to be gap-filled. 0017 % universalModel : struct -0018 % universal reaction database (RAVEN model). The caller is responsible -0019 % for building this, e.g. via getKEGGModelForOrganism with broad scope. +0018 % universal reaction database (RAVEN model), for example built with +0019 % getKEGGModelForOrganism with broad scope. 0020 % -0021 % Optional name-value parameters -0022 % -------------------------------- -0023 % 'epsilon' (default 1e-4) -0024 % minimum flux threshold for a reaction to be considered active. -0025 % 'variant' (default 'fast') -0026 % 'fast' — use FASTCORE L1-norm LP (gapFillFastCore) -0027 % 'swift' — use SWIFTCORE single-LP (gapFillSwiftCore); faster but -0028 % stochastic (results may vary between runs/solvers). -0029 % 'verbose' (default true) -0030 % print progress messages. -0031 % -0032 % Returns -0033 % ------- -0034 % addedRxns : cell -0035 % reaction IDs from universalModel that should be added to rescue -0036 % blocked reactions. -0037 % newModel : struct -0038 % copy of model with addedRxns incorporated. -0039 % cannotConnect : cell -0040 % blocked draft reaction IDs that remain blocked even after augmenting -0041 % with the full universalModel (cannot be rescued by the database). -0042 % -0043 % See also -0044 % -------- -0045 % gapFillFastCore, gapFillSwiftCore, gapFillMILP, fillGaps -0046 -0047 % ---- Parse optional arguments ---- -0048 p = inputParser(); -0049 addParameter(p, 'epsilon', 1e-4, @isnumeric); -0050 addParameter(p, 'variant', 'fast', @ischar); -0051 addParameter(p, 'verbose', true, @islogical); -0052 parse(p, varargin{:}); -0053 epsilon = p.Results.epsilon; -0054 variant = lower(p.Results.variant); -0055 verbose = p.Results.verbose; -0056 -0057 % ---- Initialise outputs ---- -0058 addedRxns = {}; -0059 cannotConnect = {}; -0060 newModel = model; -0061 -0062 % ---- Identify blocked reactions in the draft model ---- -0063 blocked = find(~haveFlux(model, epsilon)); -0064 if isempty(blocked) -0065 if verbose -0066 fprintf('gapFillFastLP: all reactions carry flux; nothing to repair.\n'); -0067 end -0068 return; -0069 end -0070 nBlocked = numel(blocked); -0071 if verbose -0072 fprintf('gapFillFastLP: %d blocked reaction(s) found in draft model.\n', nBlocked); +0021 % Name-Value Arguments +0022 % -------------------- +0023 % epsilon : double +0024 % minimum flux threshold for a reaction to be considered active +0025 % (default 1e-4). +0026 % variant : char +0027 % the LP subroutine (default 'fast'): +0028 % +0029 % - 'fast' : use the FASTCORE L1-norm LP (gapFillFastCore). +0030 % - 'swift' : use the SWIFTCORE single LP (gapFillSwiftCore), which +0031 % maximises the sum of the non-core fluxes. The LP is degenerate, so +0032 % results can vary between runs and solvers. +0033 % verbose : logical +0034 % true to print progress messages (default true). +0035 % +0036 % Returns +0037 % ------- +0038 % addedRxns : cell +0039 % reaction IDs from universalModel that should be added to rescue +0040 % blocked reactions. +0041 % newModel : struct +0042 % copy of model with addedRxns incorporated. +0043 % cannotConnect : cell +0044 % IDs of blocked draft reactions that remain blocked after merging with +0045 % the full universalModel (the database cannot rescue them). +0046 % +0047 % See also +0048 % -------- +0049 % gapFillFastCore, gapFillSwiftCore, gapFillMILP, fillGaps +0050 +0051 % ---- Parse optional arguments ---- +0052 p = inputParser(); +0053 addParameter(p, 'epsilon', 1e-4, @isnumeric); +0054 addParameter(p, 'variant', 'fast', @ischar); +0055 addParameter(p, 'verbose', true, @islogical); +0056 parse(p, varargin{:}); +0057 epsilon = p.Results.epsilon; +0058 variant = lower(p.Results.variant); +0059 verbose = p.Results.verbose; +0060 +0061 % ---- Initialise outputs ---- +0062 addedRxns = {}; +0063 cannotConnect = {}; +0064 newModel = model; +0065 +0066 % ---- Identify blocked reactions in the draft model ---- +0067 blocked = find(~haveFlux(model, epsilon)); +0068 if isempty(blocked) +0069 if verbose +0070 fprintf('gapFillFastLP: all reactions carry flux; nothing to repair.\n'); +0071 end +0072 return; 0073 end -0074 -0075 % ---- Merge draft + universal database ---- -0076 mergedModel = mergeModels({model; universalModel}); -0077 -0078 % For each draft reaction, find its position in the merged model. -0079 [inMerged, draftPosInMerged] = ismember(model.rxns, mergedModel.rxns); -0080 if any(~inMerged) -0081 warning('gapFillFastLP: %d draft reaction(s) not found in merged model — skipped.', sum(~inMerged)); -0082 end -0083 -0084 % ---- Determine which blocked reactions can carry flux in the merged model ---- -0085 mergedHasFlux = haveFlux(mergedModel, epsilon); -0086 draftCanCarry = mergedHasFlux(draftPosInMerged); % indexed by draft position +0074 nBlocked = numel(blocked); +0075 if verbose +0076 fprintf('gapFillFastLP: %d blocked reaction(s) found in draft model.\n', nBlocked); +0077 end +0078 +0079 % ---- Merge draft + universal database ---- +0080 mergedModel = mergeModels({model; universalModel}); +0081 +0082 % For each draft reaction, find its position in the merged model. +0083 [inMerged, draftPosInMerged] = ismember(model.rxns, mergedModel.rxns); +0084 if any(~inMerged) +0085 warning('gapFillFastLP: %d draft reaction(s) not found in merged model — skipped.', sum(~inMerged)); +0086 end 0087 -0088 % Blocked reactions that still cannot carry flux even with the full DB. -0089 blockedCanCarry = draftCanCarry(blocked); -0090 cannotConnectIdx = blocked(~blockedCanCarry); -0091 cannotConnect = model.rxns(cannotConnectIdx); -0092 rescuable = blocked(blockedCanCarry); -0093 -0094 if verbose && ~isempty(cannotConnect) -0095 fprintf('gapFillFastLP: %d reaction(s) cannot be rescued by the universal database.\n', ... -0096 numel(cannotConnect)); -0097 end -0098 -0099 if isempty(rescuable) -0100 if verbose -0101 fprintf('gapFillFastLP: no rescuable blocked reactions; nothing to add.\n'); -0102 end -0103 return; -0104 end -0105 if verbose -0106 fprintf('gapFillFastLP: %d/%d blocked reaction(s) are rescuable; running %s...\n', ... -0107 numel(rescuable), nBlocked, variant); +0088 % ---- Determine which blocked reactions can carry flux in the merged model ---- +0089 mergedHasFlux = haveFlux(mergedModel, epsilon); +0090 draftCanCarry = mergedHasFlux(draftPosInMerged); % indexed by draft position +0091 +0092 % Blocked reactions that still cannot carry flux even with the full DB. +0093 blockedCanCarry = draftCanCarry(blocked); +0094 cannotConnectIdx = blocked(~blockedCanCarry); +0095 cannotConnect = model.rxns(cannotConnectIdx); +0096 rescuable = blocked(blockedCanCarry); +0097 +0098 if verbose && ~isempty(cannotConnect) +0099 fprintf('gapFillFastLP: %d reaction(s) cannot be rescued by the universal database.\n', ... +0100 numel(cannotConnect)); +0101 end +0102 +0103 if isempty(rescuable) +0104 if verbose +0105 fprintf('gapFillFastLP: no rescuable blocked reactions; nothing to add.\n'); +0106 end +0107 return; 0108 end -0109 -0110 % ---- For each rescuable blocked reaction, run the LP subroutine ---- -0111 % Accumulate which merged-model reactions are activated. -0112 isUniversal = ~ismember(mergedModel.rxns, model.rxns); -0113 candidateSet = false(numel(mergedModel.rxns), 1); -0114 -0115 nSkipped = 0; -0116 for i = 1:numel(rescuable) -0117 coreIdxInMerged = draftPosInMerged(rescuable(i)); -0118 if coreIdxInMerged == 0 -0119 nSkipped = nSkipped + 1; -0120 continue; -0121 end -0122 -0123 if strcmp(variant, 'swift') -0124 active = gapFillSwiftCore(mergedModel, coreIdxInMerged, epsilon); -0125 else -0126 active = gapFillFastCore(mergedModel, coreIdxInMerged, epsilon); -0127 end -0128 -0129 if any(active) -0130 candidateSet = candidateSet | active; -0131 else -0132 nSkipped = nSkipped + 1; -0133 if verbose -0134 fprintf('gapFillFastLP: LP infeasible for %s; skipping.\n', model.rxns{rescuable(i)}); -0135 end -0136 end -0137 end -0138 -0139 if verbose && nSkipped > 0 -0140 fprintf('gapFillFastLP: %d reaction(s) skipped (infeasible LP).\n', nSkipped); +0109 if verbose +0110 fprintf('gapFillFastLP: %d/%d blocked reaction(s) are rescuable; running %s...\n', ... +0111 numel(rescuable), nBlocked, variant); +0112 end +0113 +0114 % ---- For each rescuable blocked reaction, run the LP subroutine ---- +0115 % Accumulate which merged-model reactions are activated. +0116 isUniversal = ~ismember(mergedModel.rxns, model.rxns); +0117 candidateSet = false(numel(mergedModel.rxns), 1); +0118 +0119 nSkipped = 0; +0120 for i = 1:numel(rescuable) +0121 coreIdxInMerged = draftPosInMerged(rescuable(i)); +0122 if coreIdxInMerged == 0 +0123 nSkipped = nSkipped + 1; +0124 continue; +0125 end +0126 +0127 if strcmp(variant, 'swift') +0128 active = gapFillSwiftCore(mergedModel, coreIdxInMerged, epsilon); +0129 else +0130 active = gapFillFastCore(mergedModel, coreIdxInMerged, epsilon); +0131 end +0132 +0133 if any(active) +0134 candidateSet = candidateSet | active; +0135 else +0136 nSkipped = nSkipped + 1; +0137 if verbose +0138 fprintf('gapFillFastLP: LP infeasible for %s; skipping.\n', model.rxns{rescuable(i)}); +0139 end +0140 end 0141 end 0142 -0143 % ---- Extract universal reactions that appear in any solution ---- -0144 addedMask = candidateSet & isUniversal; -0145 addedRxns = mergedModel.rxns(addedMask); +0143 if verbose && nSkipped > 0 +0144 fprintf('gapFillFastLP: %d reaction(s) skipped (infeasible LP).\n', nSkipped); +0145 end 0146 -0147 if isempty(addedRxns) -0148 if verbose -0149 fprintf('gapFillFastLP: no universal reactions needed (all gaps already resolved).\n'); -0150 end -0151 return; -0152 end -0153 -0154 % ---- Build new model: draft + selected universal reactions ---- -0155 toRemove = find(isUniversal & ~addedMask); -0156 if ~isempty(toRemove) -0157 newModel = removeReactions(mergedModel, mergedModel.rxns(toRemove)); -0158 else -0159 newModel = mergedModel; -0160 end -0161 -0162 if verbose -0163 fprintf('gapFillFastLP: added %d reaction(s) from universal database.\n', numel(addedRxns)); +0147 % ---- Extract universal reactions that appear in any solution ---- +0148 addedMask = candidateSet & isUniversal; +0149 addedRxns = mergedModel.rxns(addedMask); +0150 +0151 if isempty(addedRxns) +0152 if verbose +0153 fprintf('gapFillFastLP: no universal reactions needed (all gaps already resolved).\n'); +0154 end +0155 return; +0156 end +0157 +0158 % ---- Build new model: draft + selected universal reactions ---- +0159 toRemove = find(isUniversal & ~addedMask); +0160 if ~isempty(toRemove) +0161 newModel = removeReactions(mergedModel, mergedModel.rxns(toRemove)); +0162 else +0163 newModel = mergedModel; 0164 end -0165 end

    +0165 +0166 if verbose +0167 fprintf('gapFillFastLP: added %d reaction(s) from universal database.\n', numel(addedRxns)); +0168 end +0169 end

    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/gapfilling/gapFillMILP.html b/docs/html/gapfilling/gapFillMILP.html index 4b867b58..33903605 100644 --- a/docs/html/gapfilling/gapFillMILP.html +++ b/docs/html/gapfilling/gapFillMILP.html @@ -33,14 +33,15 @@

    DESCRIPTION ^DESCRIPTION ^DESCRIPTION ^SOURCE CODE ^% the model's objective (biomass) >= minGrowth, following the database 0006 % repair of Kumar et al. 2007 (BMC Bioinformatics 8:212, 0007 % doi:10.1186/1471-2105-8-212). Reactions already in the draft model keep -0008 % their bounds: a reaction with the wrong direction is a curation issue and -0009 % is not changed here. -0010 % -0011 % The universal model should contain all candidate reactions, including -0012 % exchange and transport reactions if those repair classes are desired. +0008 % their bounds, including a wrong reaction direction. +0009 % +0010 % The universal model contains all candidate reactions. Exchange and +0011 % transport reactions are candidates only if the universal model includes +0012 % them. 0013 % -0014 % This is a single-level MILP. It is computationally more expensive than -0015 % gapFillFastLP but directly optimises for objective feasibility. -0016 % -0017 % Parameters -0018 % ---------- -0019 % model : struct -0020 % draft RAVEN model to be gap-filled. -0021 % universalModel : struct -0022 % universal reaction database (RAVEN model). -0023 % -0024 % Optional name-value parameters -0025 % -------------------------------- -0026 % 'minGrowth' (default []) -0027 % minimum objective value to achieve. If empty, runs FBA on the merged -0028 % model and uses 10% of the maximum as the floor. -0029 % 'bigM' (default 1000) -0030 % Big-M constant for the coupling constraints of universal reactions -0031 % with an infinite bound. A finite bound is used as it is. -0032 % 'params' (default []) -0033 % solver parameter struct passed to optimizeProb. Fields such as -0034 % params.TimeLimit and params.intTol can be used to tune the MILP. -0035 % 'verbose' (default true) -0036 % print progress messages. -0037 % -0038 % Returns -0039 % ------- -0040 % addedRxns : cell -0041 % reaction IDs from universalModel that are selected by the MILP. -0042 % newModel : struct -0043 % model with addedRxns incorporated. -0044 % exitFlag : double -0045 % 1 = optimal, -1 = infeasible, -2 = timeout or other failure. -0046 % -0047 % Notes -0048 % ----- -0049 % The MILP is solved with the solver set by setRavenSolver. RAVEN's bundled GLPK solves it for small -0050 % models (with a RAVEN:glpkMILP warning); for larger models Gurobi is much -0051 % faster and more reliable (setRavenSolver('gurobi')). -0052 % -0053 % See also -0054 % -------- -0055 % gapFillFastLP, gapFillTopological, fillGaps -0056 -0057 % ---- Parse optional arguments ---- -0058 p = inputParser(); -0059 addParameter(p, 'minGrowth', [], @(x) isempty(x) || isnumeric(x)); -0060 addParameter(p, 'bigM', 1000, @isnumeric); -0061 addParameter(p, 'params', [], @(x) isempty(x) || isstruct(x)); -0062 addParameter(p, 'verbose', true, @islogical); -0063 parse(p, varargin{:}); -0064 minGrowth = p.Results.minGrowth; -0065 M = p.Results.bigM; -0066 params = p.Results.params; -0067 verbose = p.Results.verbose; -0068 -0069 % ---- Initialise outputs ---- -0070 addedRxns = {}; -0071 newModel = model; -0072 exitFlag = -1; -0073 -0074 % Check that the model has an objective -0075 if all(model.c == 0) -0076 error('gapFillMILP: model has no objective function. Set model.c.'); -0077 end -0078 -0079 % ---- Merge draft + universal database ---- -0080 mergedModel = mergeModels({model; universalModel}); -0081 nMergedMets = numel(mergedModel.mets); -0082 nMergedRxns = numel(mergedModel.rxns); -0083 -0084 % Identify draft reactions in the merged model -0085 isDraft = ismember(mergedModel.rxns, model.rxns); -0086 isUniv = ~isDraft; -0087 -0088 % Universal reactions that can carry flux (non-trivially blocked) -0089 canCarry = (mergedModel.ub > 0 | mergedModel.lb < 0); -0090 isUniv = isUniv & canCarry; -0091 univIdx = find(isUniv); % positions in merged model -0092 nUniv = numel(univIdx); +0014 % The function solves a single-level MILP that minimises the number of added +0015 % reactions subject to the objective floor. Compared with gapFillFastLP, the +0016 % MILP takes more computation time. +0017 % +0018 % Parameters +0019 % ---------- +0020 % model : struct +0021 % draft RAVEN model to be gap-filled. +0022 % universalModel : struct +0023 % universal reaction database (RAVEN model). +0024 % +0025 % Name-Value Arguments +0026 % -------------------- +0027 % minGrowth : double +0028 % minimum objective value to achieve. If empty, FBA on the merged model +0029 % gives the maximum objective value and 10% of it is the floor +0030 % (default []). +0031 % bigM : double +0032 % Big-M constant for the coupling constraints of universal reactions +0033 % with an infinite bound. A finite bound is used as it is (default 1000). +0034 % params : struct +0035 % solver parameter struct passed to optimizeProb. Fields such as +0036 % params.TimeLimit and params.intTol tune the MILP. If empty, intTol is +0037 % 1e-9 and TimeLimit is 600 (default []). +0038 % verbose : logical +0039 % true to print progress messages (default true). +0040 % +0041 % Returns +0042 % ------- +0043 % addedRxns : cell +0044 % reaction IDs from universalModel that are selected by the MILP. +0045 % newModel : struct +0046 % model with addedRxns incorporated. +0047 % exitFlag : double +0048 % 1 = the MILP returned a solution, -1 = no gap-filling result: the FBA +0049 % or MILP is infeasible, the objective of the merged model is not +0050 % positive, or the solver stops without a solution. +0051 % +0052 % Notes +0053 % ----- +0054 % The MILP is solved with the solver set by setRavenSolver. RAVEN's bundled +0055 % GLPK solves it for small models. The RAVEN:glpkMILP warning issued with +0056 % GLPK states that GLPK works for small problems and that Gurobi is much +0057 % faster and more reliable for larger ones (setRavenSolver('gurobi')). +0058 % +0059 % See also +0060 % -------- +0061 % gapFillFastLP, gapFillTopological, fillGaps +0062 +0063 % ---- Parse optional arguments ---- +0064 p = inputParser(); +0065 addParameter(p, 'minGrowth', [], @(x) isempty(x) || isnumeric(x)); +0066 addParameter(p, 'bigM', 1000, @isnumeric); +0067 addParameter(p, 'params', [], @(x) isempty(x) || isstruct(x)); +0068 addParameter(p, 'verbose', true, @islogical); +0069 parse(p, varargin{:}); +0070 minGrowth = p.Results.minGrowth; +0071 M = p.Results.bigM; +0072 params = p.Results.params; +0073 verbose = p.Results.verbose; +0074 +0075 % ---- Initialise outputs ---- +0076 addedRxns = {}; +0077 newModel = model; +0078 exitFlag = -1; +0079 +0080 % Check that the model has an objective +0081 if all(model.c == 0) +0082 error('gapFillMILP: model has no objective function. Set model.c.'); +0083 end +0084 +0085 % ---- Merge draft + universal database ---- +0086 mergedModel = mergeModels({model; universalModel}); +0087 nMergedMets = numel(mergedModel.mets); +0088 nMergedRxns = numel(mergedModel.rxns); +0089 +0090 % Identify draft reactions in the merged model +0091 isDraft = ismember(mergedModel.rxns, model.rxns); +0092 isUniv = ~isDraft; 0093 -0094 if verbose -0095 fprintf('gapFillMILP: merged model has %d mets, %d rxns (%d draft, %d universal).\n', ... -0096 nMergedMets, nMergedRxns, sum(isDraft), nUniv); -0097 fprintf('gapFillMILP: %d database candidates.\n', nUniv); -0098 end +0094 % Universal reactions that can carry flux (non-trivially blocked) +0095 canCarry = (mergedModel.ub > 0 | mergedModel.lb < 0); +0096 isUniv = isUniv & canCarry; +0097 univIdx = find(isUniv); % positions in merged model +0098 nUniv = numel(univIdx); 0099 -0100 % ---- Determine minGrowth ---- -0101 if isempty(minGrowth) -0102 % Run FBA on merged model (unconstrained) to get max growth. -0103 probFBA.A = mergedModel.S; -0104 probFBA.a = probFBA.A; -0105 probFBA.b = zeros(nMergedMets, 1); -0106 probFBA.csense = repmat('E', 1, nMergedMets); -0107 probFBA.c = -mergedModel.c; % negate: osense=1 minimises -0108 probFBA.osense = 1; -0109 probFBA.lb = mergedModel.lb; -0110 probFBA.ub = mergedModel.ub; -0111 probFBA.vartype = repmat('C', 1, nMergedRxns); -0112 solFBA = optimizeProb(probFBA, params, false); -0113 if ~checkSolution(solFBA) -0114 if verbose -0115 fprintf('gapFillMILP: FBA on merged model is infeasible — cannot gap-fill.\n'); -0116 end -0117 return; -0118 end -0119 maxGrowth = mergedModel.c' * solFBA.full(1:nMergedRxns); -0120 if maxGrowth <= 0 -0121 if verbose -0122 fprintf('gapFillMILP: merged model objective is non-positive (%.4g). Cannot gap-fill.\n', ... -0123 maxGrowth); -0124 end -0125 return; -0126 end -0127 minGrowth = 0.1 * maxGrowth; -0128 if verbose -0129 fprintf('gapFillMILP: setting minGrowth = %.4g (10%% of max %.4g).\n', minGrowth, maxGrowth); -0130 end -0131 end -0132 -0133 % ---- Build MILP ---- -0134 % Variable ordering: -0135 % cols 1 : nMergedRxns → v (all fluxes, continuous) -0136 % cols nMergedRxns+1 : end → y_db (binary, universal reactions) -0137 nVar = nMergedRxns + nUniv; +0100 if verbose +0101 fprintf('gapFillMILP: merged model has %d mets, %d rxns (%d draft, %d universal).\n', ... +0102 nMergedMets, nMergedRxns, sum(isDraft), nUniv); +0103 fprintf('gapFillMILP: %d database candidates.\n', nUniv); +0104 end +0105 +0106 % ---- Determine minGrowth ---- +0107 if isempty(minGrowth) +0108 % Run FBA on merged model (unconstrained) to get max growth. +0109 probFBA.A = mergedModel.S; +0110 probFBA.a = probFBA.A; +0111 probFBA.b = zeros(nMergedMets, 1); +0112 probFBA.csense = repmat('E', 1, nMergedMets); +0113 probFBA.c = -mergedModel.c; % negate: osense=1 minimises +0114 probFBA.osense = 1; +0115 probFBA.lb = mergedModel.lb; +0116 probFBA.ub = mergedModel.ub; +0117 probFBA.vartype = repmat('C', 1, nMergedRxns); +0118 solFBA = optimizeProb(probFBA, params, false); +0119 if ~checkSolution(solFBA) +0120 if verbose +0121 fprintf('gapFillMILP: FBA on merged model is infeasible — cannot gap-fill.\n'); +0122 end +0123 return; +0124 end +0125 maxGrowth = mergedModel.c' * solFBA.full(1:nMergedRxns); +0126 if maxGrowth <= 0 +0127 if verbose +0128 fprintf('gapFillMILP: merged model objective is non-positive (%.4g). Cannot gap-fill.\n', ... +0129 maxGrowth); +0130 end +0131 return; +0132 end +0133 minGrowth = 0.1 * maxGrowth; +0134 if verbose +0135 fprintf('gapFillMILP: setting minGrowth = %.4g (10%% of max %.4g).\n', minGrowth, maxGrowth); +0136 end +0137 end 0138 -0139 % --- Variable bounds --- -0140 lb = mergedModel.lb; -0141 ub = mergedModel.ub; -0142 -0143 % Universal reactions: allow them to be forced to 0 (extend lb toward 0) -0144 lb(univIdx) = min(mergedModel.lb(univIdx), 0); -0145 -0146 % Binary variable bounds -0147 lb = [lb; zeros(nUniv, 1)]; -0148 ub = [ub; ones(nUniv, 1)]; -0149 -0150 % --- Objective: number of added universal reactions --- -0151 c = zeros(nVar, 1); -0152 c(nMergedRxns + 1 : end) = 1; -0153 -0154 % --- Constraint blocks --- -0155 % (1) Steady state: S * v = 0 [nMergedMets rows, 'E'] -0156 A_SS = [mergedModel.S, sparse(nMergedMets, nUniv)]; -0157 -0158 % (2) Growth floor: c' * v >= minGrowth [1 row, 'G'] -0159 A_growth = [sparse(mergedModel.c'), sparse(1, nUniv)]; -0160 -0161 % (3) Universal upper coupling: v_k - ub_k * y_db_k <= 0 [nUniv rows, 'L'] -0162 % (forces v_k <= 0 when y_db_k = 0) -0163 % Coupling coefficients are the reaction bounds. An infinite bound is replaced -0164 % by the Big-M constant: an unbounded universal reaction (ub = Inf, RAVEN's own -0165 % default when no upper-bound annotation is set) would otherwise put an Inf -0166 % coefficient into the constraint matrix, and Inf*0 (y_db_k = 0) is NaN, not 0. -0167 yDbCols = nMergedRxns + (1:nUniv); -0168 uniUb = mergedModel.ub(univIdx); -0169 uniUb(isinf(uniUb)) = M; -0170 uniLb = mergedModel.lb(univIdx); -0171 uniLb(isinf(uniLb)) = -M; -0172 A_uniUp = sparse(... -0173 [1:nUniv, 1:nUniv], ... -0174 [univIdx(:)', yDbCols], ... -0175 [ones(1, nUniv), -uniUb'], ... -0176 nUniv, nVar); -0177 -0178 % (4) Universal lower coupling: v_k - lb_k * y_db_k >= 0 [nUniv rows, 'G'] -0179 % (forces v_k >= 0 when y_db_k = 0; together with (3): v_k = 0 when y_db_k = 0) -0180 A_uniLo = sparse(... -0181 [1:nUniv, 1:nUniv], ... -0182 [univIdx(:)', yDbCols], ... -0183 [ones(1, nUniv), -uniLb'], ... -0184 nUniv, nVar); -0185 -0186 % --- Assemble --- -0187 prob.A = [A_SS; A_growth; A_uniUp; A_uniLo]; -0188 prob.a = prob.A; -0189 prob.b = [zeros(nMergedMets, 1); minGrowth; zeros(2 * nUniv, 1)]; -0190 prob.csense = [repmat('E', 1, nMergedMets), 'G', ... -0191 repmat('L', 1, nUniv), ... -0192 repmat('G', 1, nUniv)]; -0193 prob.c = c; -0194 prob.osense = 1; % minimise the number of added reactions -0195 prob.lb = lb; -0196 prob.ub = ub; -0197 prob.vartype = repmat('C', 1, nVar); -0198 prob.vartype(nMergedRxns + 1 : end) = 'B'; % y_db is binary -0199 -0200 if isempty(params) -0201 params.intTol = 1e-9; -0202 params.TimeLimit = 600; -0203 end -0204 -0205 % ---- Solve ---- -0206 if verbose -0207 fprintf('gapFillMILP: solving MILP (%d vars, %d binary, %d constraints)...\n', ... -0208 nVar, nUniv, size(prob.A, 1)); +0139 % ---- Build MILP ---- +0140 % Variable ordering: +0141 % cols 1 : nMergedRxns → v (all fluxes, continuous) +0142 % cols nMergedRxns+1 : end → y_db (binary, universal reactions) +0143 nVar = nMergedRxns + nUniv; +0144 +0145 % --- Variable bounds --- +0146 lb = mergedModel.lb; +0147 ub = mergedModel.ub; +0148 +0149 % Universal reactions: allow them to be forced to 0 (extend lb toward 0) +0150 lb(univIdx) = min(mergedModel.lb(univIdx), 0); +0151 +0152 % Binary variable bounds +0153 lb = [lb; zeros(nUniv, 1)]; +0154 ub = [ub; ones(nUniv, 1)]; +0155 +0156 % --- Objective: number of added universal reactions --- +0157 c = zeros(nVar, 1); +0158 c(nMergedRxns + 1 : end) = 1; +0159 +0160 % --- Constraint blocks --- +0161 % (1) Steady state: S * v = 0 [nMergedMets rows, 'E'] +0162 A_SS = [mergedModel.S, sparse(nMergedMets, nUniv)]; +0163 +0164 % (2) Growth floor: c' * v >= minGrowth [1 row, 'G'] +0165 A_growth = [sparse(mergedModel.c'), sparse(1, nUniv)]; +0166 +0167 % (3) Universal upper coupling: v_k - ub_k * y_db_k <= 0 [nUniv rows, 'L'] +0168 % (forces v_k <= 0 when y_db_k = 0) +0169 % Coupling coefficients are the reaction bounds. An infinite bound is replaced +0170 % by the Big-M constant: an unbounded universal reaction (ub = Inf, RAVEN's own +0171 % default when no upper-bound annotation is set) would otherwise put an Inf +0172 % coefficient into the constraint matrix, and Inf*0 (y_db_k = 0) is NaN, not 0. +0173 yDbCols = nMergedRxns + (1:nUniv); +0174 uniUb = mergedModel.ub(univIdx); +0175 uniUb(isinf(uniUb)) = M; +0176 uniLb = mergedModel.lb(univIdx); +0177 uniLb(isinf(uniLb)) = -M; +0178 A_uniUp = sparse(... +0179 [1:nUniv, 1:nUniv], ... +0180 [univIdx(:)', yDbCols], ... +0181 [ones(1, nUniv), -uniUb'], ... +0182 nUniv, nVar); +0183 +0184 % (4) Universal lower coupling: v_k - lb_k * y_db_k >= 0 [nUniv rows, 'G'] +0185 % (forces v_k >= 0 when y_db_k = 0; together with (3): v_k = 0 when y_db_k = 0) +0186 A_uniLo = sparse(... +0187 [1:nUniv, 1:nUniv], ... +0188 [univIdx(:)', yDbCols], ... +0189 [ones(1, nUniv), -uniLb'], ... +0190 nUniv, nVar); +0191 +0192 % --- Assemble --- +0193 prob.A = [A_SS; A_growth; A_uniUp; A_uniLo]; +0194 prob.a = prob.A; +0195 prob.b = [zeros(nMergedMets, 1); minGrowth; zeros(2 * nUniv, 1)]; +0196 prob.csense = [repmat('E', 1, nMergedMets), 'G', ... +0197 repmat('L', 1, nUniv), ... +0198 repmat('G', 1, nUniv)]; +0199 prob.c = c; +0200 prob.osense = 1; % minimise the number of added reactions +0201 prob.lb = lb; +0202 prob.ub = ub; +0203 prob.vartype = repmat('C', 1, nVar); +0204 prob.vartype(nMergedRxns + 1 : end) = 'B'; % y_db is binary +0205 +0206 if isempty(params) +0207 params.intTol = 1e-9; +0208 params.TimeLimit = 600; 0209 end -0210 sol = optimizeProb(prob, params, verbose); -0211 -0212 if ~checkSolution(sol) -0213 if verbose -0214 fprintf('gapFillMILP: MILP infeasible or timed out.\n'); -0215 end -0216 exitFlag = -1; -0217 return; -0218 end -0219 -0220 % ---- Extract solution ---- -0221 binThreshold = 1e-3; -0222 yDb = sol.full(nMergedRxns + 1 : end); -0223 dbSelected = univIdx(yDb > binThreshold); -0224 -0225 % Map back to original IDs. dbSelected is already restricted to universal -0226 % reactions via univIdx/isDraft above (an id-membership test against -0227 % model.rxns, not a name it could have been renamed away from), so no -0228 % further filtering against universalModel.rxns is needed here -- doing so -0229 % would silently drop a universal reaction that mergeModels renamed -0230 % because its id collided with one already in the draft model. -0231 addedRxns = mergedModel.rxns(dbSelected); -0232 -0233 if verbose -0234 fprintf('gapFillMILP: added %d universal reaction(s).\n', numel(addedRxns)); -0235 end -0236 -0237 % ---- Build new model ---- -0238 newModel = model; -0239 -0240 % Add universal reactions. Extracted from mergedModel by the dbSelected -0241 % indices directly, not by looking addedRxns' names back up in -0242 % universalModel: that lookup fails whenever mergeModels renamed one of -0243 % them because its id collided with one already in the draft model, in -0244 % which case setdiff would keep the (unrenamed) original in universalModel -0245 % marked for removal, silently dropping the very reaction just selected. -0246 if ~isempty(dbSelected) -0247 toRemove = true(nMergedRxns, 1); -0248 toRemove(dbSelected) = false; -0249 trimmedUniv = removeReactions(mergedModel, toRemove); -0250 newModel = mergeModels({newModel; trimmedUniv}); -0251 end -0252 -0253 exitFlag = 1; -0254 end

    +0210 +0211 % ---- Solve ---- +0212 if verbose +0213 fprintf('gapFillMILP: solving MILP (%d vars, %d binary, %d constraints)...\n', ... +0214 nVar, nUniv, size(prob.A, 1)); +0215 end +0216 sol = optimizeProb(prob, params, verbose); +0217 +0218 if ~checkSolution(sol) +0219 if verbose +0220 fprintf('gapFillMILP: MILP infeasible or timed out.\n'); +0221 end +0222 exitFlag = -1; +0223 return; +0224 end +0225 +0226 % ---- Extract solution ---- +0227 binThreshold = 1e-3; +0228 yDb = sol.full(nMergedRxns + 1 : end); +0229 dbSelected = univIdx(yDb > binThreshold); +0230 +0231 % Map back to original IDs. dbSelected is already restricted to universal +0232 % reactions via univIdx/isDraft above (an id-membership test against +0233 % model.rxns, not a name it could have been renamed away from), so no +0234 % further filtering against universalModel.rxns is needed here -- doing so +0235 % would silently drop a universal reaction that mergeModels renamed +0236 % because its id collided with one already in the draft model. +0237 addedRxns = mergedModel.rxns(dbSelected); +0238 +0239 if verbose +0240 fprintf('gapFillMILP: added %d universal reaction(s).\n', numel(addedRxns)); +0241 end +0242 +0243 % ---- Build new model ---- +0244 newModel = model; +0245 +0246 % Add universal reactions. Extracted from mergedModel by the dbSelected +0247 % indices directly, not by looking addedRxns' names back up in +0248 % universalModel: that lookup fails whenever mergeModels renamed one of +0249 % them because its id collided with one already in the draft model, in +0250 % which case setdiff would keep the (unrenamed) original in universalModel +0251 % marked for removal, silently dropping the very reaction just selected. +0252 if ~isempty(dbSelected) +0253 toRemove = true(nMergedRxns, 1); +0254 toRemove(dbSelected) = false; +0255 trimmedUniv = removeReactions(mergedModel, toRemove); +0256 newModel = mergeModels({newModel; trimmedUniv}); +0257 end +0258 +0259 exitFlag = 1; +0260 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/gapfilling/gapFillSwiftCore.html b/docs/html/gapfilling/gapFillSwiftCore.html index c429f0d8..d103b518 100644 --- a/docs/html/gapfilling/gapFillSwiftCore.html +++ b/docs/html/gapfilling/gapFillSwiftCore.html @@ -31,14 +31,13 @@

    DESCRIPTION ^DESCRIPTION ^CROSS-REFERENCE INFORMATION ^
 </ul>
 This function is called by:
 <ul style= -
  • gapFillFastLP gapFillFastLP LP-based gap-filling (fastGapFill / swiftGapFill).
  • +
  • gapFillFastLP gapFillFastLP LP-based gap-filling (fastGapFill or swiftGapFill).
  • @@ -81,33 +81,33 @@

    SOURCE CODE ^% 0004 % Single-LP alternative to gapFillFastCore. Maximises the sum of non-core 0005 % fluxes while enforcing that every core reaction carries at least epsilon -0006 % flux. Any reaction with non-zero flux in the solution is "consistent" -0007 % with the core. +0006 % flux. Any reaction with a flux of at least epsilon / 2 in the solution is +0007 % "consistent" with the core. 0008 % -0009 % Because the maximisation objective makes the LP degenerate (many optima -0010 % exist that differ only in which non-core reactions carry flux), the -0011 % returned active set can vary between LP solvers and between runs with -0012 % slightly different numerical conditions. This is the expected behaviour -0013 % of SWIFTCORE (Tefagh & Boyd 2020). -0014 % -0015 % Based on SWIFTCORE (Tefagh & Boyd 2020, BMC Bioinformatics 21:23). -0016 % -0017 % Parameters -0018 % ---------- -0019 % model : struct -0020 % a RAVEN model structure. -0021 % coreRxns : cell or logical or double -0022 % core reaction set: cell array of reaction IDs, a logical vector, or a -0023 % vector of reaction indices into model.rxns. All core reactions are -0024 % forced to carry flux >= epsilon in the LP solution. -0025 % epsilon : double -0026 % minimum flux threshold for a reaction to be considered active. -0027 % -0028 % Returns -0029 % ------- -0030 % activeRxns : logical -0031 % logical vector (length numel(model.rxns)) where true indicates the -0032 % reaction carries flux in the solution. Empty on infeasibility. +0009 % The maximisation objective makes the LP degenerate: many optima differ only +0010 % in which non-core reactions carry flux. The returned active set can +0011 % therefore vary between LP solvers and between runs with slightly different +0012 % numerical conditions. +0013 % +0014 % Based on SWIFTCORE (Tefagh & Boyd 2020, BMC Bioinformatics 21:23). +0015 % +0016 % Parameters +0017 % ---------- +0018 % model : struct +0019 % a RAVEN model structure. +0020 % coreRxns : cell or logical or double +0021 % core reaction set: cell array of reaction IDs, a logical vector, or a +0022 % vector of reaction indices into model.rxns. All core reactions are +0023 % forced to carry flux >= epsilon in the LP solution. +0024 % epsilon : double +0025 % minimum flux threshold for a reaction to be considered active. +0026 % +0027 % Returns +0028 % ------- +0029 % activeRxns : logical +0030 % logical vector (length numel(model.rxns)) where true indicates that the +0031 % reaction carries flux in the solution. All elements are false if the LP +0032 % is infeasible. 0033 % 0034 % See also 0035 % -------- diff --git a/docs/html/gapfilling/gapFillTopological.html b/docs/html/gapfilling/gapFillTopological.html index de4ecdbf..7d9f1963 100644 --- a/docs/html/gapfilling/gapFillTopological.html +++ b/docs/html/gapfilling/gapFillTopological.html @@ -31,12 +31,11 @@

    DESCRIPTION ^DESCRIPTION ^SOURCE CODE ^% 0004 % Identifies which metabolites in the draft model cannot be produced from a 0005 % given set of seed metabolites (medium), using a graph-based fixed-point -0006 % "scope" computation (no solver required). For each unreachable metabolite, +0006 % "scope" computation that needs no solver. For each unreachable metabolite, 0007 % finds candidate reactions in the universal database that produce it 0008 % directly. 0009 % -0010 % This is a computationally cheap pre-screening step that prunes the -0011 % universal database before a more expensive gap-filling solve. It is -0012 % inspired by the topological analysis used in Meneco (Prigent et al. 2017, -0013 % PLoS Comput Biol). -0014 % -0015 % Algorithm -0016 % --------- -0017 % Starting from seed metabolites (available in the medium), iteratively -0018 % mark reactions as "fireable" when all their substrates are reachable, -0019 % then mark their products as newly reachable. Continue until no new -0020 % metabolite is reached (fixed point). Reversible reactions (lb < 0) are -0021 % also considered in their reverse direction. -0022 % -0023 % Parameters -0024 % ---------- -0025 % model : struct -0026 % draft RAVEN model. -0027 % universalModel : struct -0028 % universal reaction database (RAVEN model). -0029 % -0030 % Optional name-value parameters -0031 % -------------------------------- -0032 % 'seeds' (default []) -0033 % Metabolite IDs (cell array) available from the medium. Default: -0034 % metabolites of every exchange reaction whose bounds let it supply the -0035 % metabolite, i.e. getExchangeRxns 'uptake' plus 'reverse'. -0036 % 'targets' (default []) -0037 % Metabolite IDs (cell array) that should be produced. Default: every +0010 % The step prunes the universal database before a gap-filling solve. It is +0011 % inspired by the topological analysis used in Meneco (Prigent et al. 2017, +0012 % PLoS Comput Biol). +0013 % +0014 % Algorithm +0015 % --------- +0016 % Starting from seed metabolites (available in the medium), iteratively +0017 % mark reactions as "fireable" when all their substrates are reachable, +0018 % then mark their products as newly reachable. Continue until no new +0019 % metabolite is reached (fixed point). Reversible reactions (lb < 0) are +0020 % also considered in their reverse direction. +0021 % +0022 % Parameters +0023 % ---------- +0024 % model : struct +0025 % draft RAVEN model. +0026 % universalModel : struct +0027 % universal reaction database (RAVEN model). +0028 % +0029 % Name-Value Arguments +0030 % -------------------- +0031 % seeds : cell +0032 % metabolite IDs available from the medium. If empty, the seeds are the +0033 % metabolites of every exchange reaction whose bounds let it supply the +0034 % metabolite, that is getExchangeRxns 'uptake' plus 'reverse' +0035 % (default []). +0036 % targets : cell +0037 % metabolite IDs that should be produced. If empty, the targets are every 0038 % metabolite of the objective (biomass) reaction, substrates and 0039 % products; the first one when there are several objective reactions. 0040 % Without targets and without an objective reaction the function raises -0041 % RAVEN:badInput. -0042 % 'verbose' (default true) -0043 % print a summary of the results. +0041 % RAVEN:badInput (default []). +0042 % verbose : logical +0043 % true to print a summary of the results (default true). 0044 % 0045 % Returns 0046 % ------- 0047 % result : struct -0048 % .reachableMets — logical vector (length nMets) marking producible mets -0049 % .blockedMets — cell array of metabolite IDs that cannot be reached -0050 % .candidateRxns — n-by-1 cell array of universal rxn ID lists, one -0051 % cell per blocked metabolite (same order as blockedMets) -0052 % .pruningFraction — fraction of universal reactions pruned (not needed -0053 % for any blocked metabolite) +0048 % .reachableMets: logical vector (length nMets) marking producible mets +0049 % .blockedMets: cell array of metabolite IDs that cannot be reached +0050 % .candidateRxns: n-by-1 cell array of universal rxn ID lists, one cell +0051 % per blocked metabolite (same order as blockedMets) +0052 % .pruningFraction: fraction of universal reactions pruned (not +0053 % candidates for any blocked metabolite) 0054 % 0055 % See also 0056 % -------- diff --git a/docs/html/gapfilling/gapReport.html b/docs/html/gapfilling/gapReport.html index c890a733..80d02416 100644 --- a/docs/html/gapfilling/gapReport.html +++ b/docs/html/gapfilling/gapReport.html @@ -4,7 +4,7 @@ Description of gapReport - + @@ -21,22 +21,22 @@

    gapReport

    PURPOSE ^

    -
    gapReport Perform a gap analysis and summarize the results.
    +
    gapReport Perform a gap analysis and summarise the results.

    SYNOPSIS ^

    function [noFluxRxns, noFluxRxnsRelaxed, subGraphs, notProducedMets, minToConnect,neededForProductionMat, canProduceWithoutInput, canConsumeWithoutOutput,connectedFromTemplates, addedFromTemplates]=gapReport(model, varargin)

    DESCRIPTION ^

    -
     gapReport  Perform a gap analysis and summarize the results.
    +
     gapReport  Perform a gap analysis and summarise the results.
     
    - Runs a series of connectivity checks on the model - which reactions are
    + Runs a series of connectivity checks on the model: which reactions are
      blocked, how many isolated sub-networks exist, which metabolites cannot
      be produced (and the minimal set of metabolites that would need to be
    - connected to fix that), and which metabolites can be produced/consumed
    - without any input/output, with every boundary reaction closed. If
    - templateModels is supplied, it also attempts to gap-fill using those
    - models. A formatted report is printed to the command window; the same
    - results are returned as outputs for further use.
    + connected to fix that), and which metabolites can be produced without any
    + input or consumed without any output, with every boundary reaction closed.
    + If templateModels is supplied, it also attempts to gap-fill using those
    + models. A formatted report is printed to the command window, and the same
    + results are returned as outputs.
     
      Parameters
      ----------
    @@ -46,8 +46,8 @@ 

    DESCRIPTION ^DESCRIPTION ^SOURCE CODE ^
    0001 function [noFluxRxns, noFluxRxnsRelaxed, subGraphs, notProducedMets, minToConnect,...
     0002     neededForProductionMat, canProduceWithoutInput, canConsumeWithoutOutput, ...
     0003     connectedFromTemplates, addedFromTemplates]=gapReport(model, varargin)
    -0004 % gapReport  Perform a gap analysis and summarize the results.
    +0004 % gapReport  Perform a gap analysis and summarise the results.
     0005 %
    -0006 % Runs a series of connectivity checks on the model - which reactions are
    +0006 % Runs a series of connectivity checks on the model: which reactions are
     0007 % blocked, how many isolated sub-networks exist, which metabolites cannot
     0008 % be produced (and the minimal set of metabolites that would need to be
    -0009 % connected to fix that), and which metabolites can be produced/consumed
    -0010 % without any input/output, with every boundary reaction closed. If
    -0011 % templateModels is supplied, it also attempts to gap-fill using those
    -0012 % models. A formatted report is printed to the command window; the same
    -0013 % results are returned as outputs for further use.
    +0009 % connected to fix that), and which metabolites can be produced without any
    +0010 % input or consumed without any output, with every boundary reaction closed.
    +0011 % If templateModels is supplied, it also attempts to gap-fill using those
    +0012 % models. A formatted report is printed to the command window, and the same
    +0013 % results are returned as outputs.
     0014 %
     0015 % Parameters
     0016 % ----------
    @@ -135,8 +135,8 @@ 

    SOURCE CODE ^% Name-Value Arguments 0021 % -------------------- 0022 % templateModels : cell -0023 % a cell array of template models to use for gap filling. If omitted, -0024 % the automated gap-filling section is skipped. +0023 % a cell array of template models used for gap filling. If omitted, the +0024 % automated gap-filling section is skipped. 0025 % 0026 % Returns 0027 % ------- @@ -178,8 +178,8 @@

    SOURCE CODE ^% 0064 % The model must be feasible under its own bounds and with its boundary 0065 % closed. Otherwise gapReport stops with a RAVEN:infeasibleModel error that -0066 % names the reactions with a forced flux; set the bounds of ATP maintenance -0067 % and any other forced flux to zero first. +0066 % names the reactions with a forced flux. Such reactions, for example ATP +0067 % maintenance, need bounds that allow zero flux. 0068 % 0069 % Examples 0070 % -------- diff --git a/docs/html/gapfilling/index.html b/docs/html/gapfilling/index.html index c553c80e..ac538767 100644 --- a/docs/html/gapfilling/index.html +++ b/docs/html/gapfilling/index.html @@ -19,7 +19,7 @@

    Index for gapfilling

    Matlab files in this directory:

    -
     canExchangecanExchange Check which metabolites a model can produce or consume.
     checkProductioncheckProduction Check which metabolites can be produced from a model.
     fillGapsfillGaps Use template model(s) to fill gaps in a model.
     findLeakMetabolitefindLeakMetabolite Find a metabolite that can be freely made or consumed.
     fitTasksfitTasks Fill gaps in a model so it can perform a list of tasks.
     ftINITFillGapsftINITFillGaps
     gapFillFastCoregapFillFastCore L1-norm LP subroutine for fastGapFill.
     gapFillFastLPgapFillFastLP LP-based gap-filling (fastGapFill / swiftGapFill).
     gapFillMILPgapFillMILP Objective-based gap-filling via a global growth-floor MILP.
     gapFillSwiftCoregapFillSwiftCore SWIFTCORE LP subroutine for swiftGapFill.
     gapFillTopologicalgapFillTopological BFS metabolite-producibility pre-screening.
     gapReportgapReport Perform a gap analysis and summarize the results.
    + 
    canExchangecanExchange Check which metabolites a model can produce or consume.  checkProductioncheckProduction Check which metabolites can be produced from a model.  fillGapsfillGaps Use template model(s) to fill gaps in a model.  findLeakMetabolitefindLeakMetabolite Find a metabolite that can be freely made or consumed.  fitTasksfitTasks Fill gaps in a model so it can perform a list of tasks.  ftINITFillGapsftINITFillGaps  gapFillFastCoregapFillFastCore L1-norm LP subroutine for fastGapFill.  gapFillFastLPgapFillFastLP LP-based gap-filling (fastGapFill or swiftGapFill).  gapFillMILPgapFillMILP Objective-based gap-filling via a global growth-floor MILP.  gapFillSwiftCoregapFillSwiftCore SWIFTCORE LP subroutine for swiftGapFill.  gapFillTopologicalgapFillTopological BFS metabolite-producibility pre-screening.  gapReportgapReport Perform a gap analysis and summarise the results. diff --git a/docs/html/installation/addRavenToUserPath.html b/docs/html/installation/addRavenToUserPath.html index c7d1e4e0..59175464 100644 --- a/docs/html/installation/addRavenToUserPath.html +++ b/docs/html/installation/addRavenToUserPath.html @@ -28,16 +28,17 @@

    SYNOPSIS ^DESCRIPTION ^

     This function writes a startup.m file in the userpath, adding RAVEN (and
    - all subdirectories) to the path each time Matlab is started.
    - This function is useful if the user has no rights to save paths to the
    + all subdirectories except those with .git or doc in their path) to the
    + path each time MATLAB is started.
    + The function applies when the user has no rights to save paths to the
      pathdef.m file in the matlabroot, for instance due to a lack of admin
    - rights. As the startup.m file in the userpath automatically runs with
    - each Matlab start, the paths are automatically loaded.
    + rights. The startup.m file in the userpath runs at each MATLAB start and
    + loads the paths.
     
      Name-Value Arguments
      --------------------
    -   overwrite       logical, whether startup.m in the userpath should
    -                   overwritten (otherwise the RAVEN paths are appended)
    +   overwrite       logical, true if startup.m in the userpath is
    +                   overwritten and false if the RAVEN paths are appended
                        (default true)
    @@ -55,47 +56,48 @@

    CROSS-REFERENCE INFORMATION ^
 <h2><a name=SOURCE CODE ^

    0001 function addRavenToUserPath(varargin)
     0002 % This function writes a startup.m file in the userpath, adding RAVEN (and
    -0003 % all subdirectories) to the path each time Matlab is started.
    -0004 % This function is useful if the user has no rights to save paths to the
    -0005 % pathdef.m file in the matlabroot, for instance due to a lack of admin
    -0006 % rights. As the startup.m file in the userpath automatically runs with
    -0007 % each Matlab start, the paths are automatically loaded.
    -0008 %
    -0009 % Name-Value Arguments
    -0010 % --------------------
    -0011 %   overwrite       logical, whether startup.m in the userpath should
    -0012 %                   overwritten (otherwise the RAVEN paths are appended)
    -0013 %                   (default true)
    -0014 
    -0015 p=parseRAVENargs(varargin, {'overwrite',true});
    -0016 overwrite=p.overwrite;
    -0017 
    -0018 % Get current RAVEN directory
    -0019 ravenDir=findRAVENroot();
    -0020 
    -0021 % Lists all subdirectories
    -0022 subpath=regexp(genpath(ravenDir),pathsep,'split');
    -0023 % Remove .git and doc folders
    -0024 pathsToKeep=cellfun(@(x) isempty(strfind(x,'.git')),subpath) & cellfun(@(x) isempty(strfind(x,'doc')),subpath);
    -0025 % Only keep useful paths
    -0026 subpath = subpath(pathsToKeep);
    -0027 subpath = subpath(1:end-1); % Remove last entry, is empty field
    -0028 
    -0029 % Write startup.m file
    -0030 if overwrite
    -0031     fid=fopen(fullfile(userpath,'startup.m'),'w');
    -0032     fprintf(fid,'%sn','%%% RAVEN path');
    -0033 else
    -0034     fid=fopen(fullfile(userpath,'startup.m'),'a');
    -0035     fprintf(fid,'n%sn','%%% RAVEN path');
    -0036 end
    -0037 fprintf(fid,'%sn',strcat('addpath(''',subpath{1},''',...'));
    -0038 for i=2(length(subpath)-1)
    -0039     fprintf(fid,'t%sn',strcat('''',subpath{i},''',...'));
    -0040 end
    -0041 fprintf(fid,'t%s',strcat('''',subpath{length(subpath)},''');'));
    -0042 fclose(fid);
    -0043 end
    +0003 % all subdirectories except those with .git or doc in their path) to the +0004 % path each time MATLAB is started. +0005 % The function applies when the user has no rights to save paths to the +0006 % pathdef.m file in the matlabroot, for instance due to a lack of admin +0007 % rights. The startup.m file in the userpath runs at each MATLAB start and +0008 % loads the paths. +0009 % +0010 % Name-Value Arguments +0011 % -------------------- +0012 % overwrite logical, true if startup.m in the userpath is +0013 % overwritten and false if the RAVEN paths are appended +0014 % (default true) +0015 +0016 p=parseRAVENargs(varargin, {'overwrite',true}); +0017 overwrite=p.overwrite; +0018 +0019 % Get current RAVEN directory +0020 ravenDir=findRAVENroot(); +0021 +0022 % Lists all subdirectories +0023 subpath=regexp(genpath(ravenDir),pathsep,'split'); +0024 % Remove .git and doc folders +0025 pathsToKeep=cellfun(@(x) isempty(strfind(x,'.git')),subpath) & cellfun(@(x) isempty(strfind(x,'doc')),subpath); +0026 % Only keep useful paths +0027 subpath = subpath(pathsToKeep); +0028 subpath = subpath(1:end-1); % Remove last entry, is empty field +0029 +0030 % Write startup.m file +0031 if overwrite +0032 fid=fopen(fullfile(userpath,'startup.m'),'w'); +0033 fprintf(fid,'%sn','%%% RAVEN path'); +0034 else +0035 fid=fopen(fullfile(userpath,'startup.m'),'a'); +0036 fprintf(fid,'n%sn','%%% RAVEN path'); +0037 end +0038 fprintf(fid,'%sn',strcat('addpath(''',subpath{1},''',...')); +0039 for i=2(length(subpath)-1) +0040 fprintf(fid,'t%sn',strcat('''',subpath{i},''',...')); +0041 end +0042 fprintf(fid,'t%s',strcat('''',subpath{length(subpath)},''');')); +0043 fclose(fid); +0044 end


    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/installation/checkFunctionUniqueness.html b/docs/html/installation/checkFunctionUniqueness.html index 2c7e755b..0bce9b09 100644 --- a/docs/html/installation/checkFunctionUniqueness.html +++ b/docs/html/installation/checkFunctionUniqueness.html @@ -29,16 +29,18 @@

    SYNOPSIS ^DESCRIPTION ^

     checkFunctionUniqueness
        Checks whether RAVEN toolbox functions are unique across other
    -   toolboxes or user-created functions accessible in Matlab pathlist.
    +   toolboxes and user-created functions on the MATLAB path.
     
      Name-Value Arguments
      --------------------
    -   altDirs     cell array of alternative directories with functions,
    -               should only be specified if this function is used for other
    -               purposes, NOT when looking for RAVEN toolbox functions.
    +   altDirs     char with alternative directories with functions, separated
    +               by the path separator of the platform (';' on Windows, ':'
    +               otherwise). Specified only when the function checks
    +               directories other than the RAVEN toolbox.
     
      Output:
    -   status      true if all functions are unique, otherwise false
    +   status      true if all functions are unique, otherwise false. Without
    +               an output argument the function prints Pass or Fail.
     
      Usage: checkFunctionUniqueness()
    @@ -58,109 +60,111 @@

    SOURCE CODE ^
    0001 function status = checkFunctionUniqueness(varargin)
     0002 % checkFunctionUniqueness
     0003 %   Checks whether RAVEN toolbox functions are unique across other
    -0004 %   toolboxes or user-created functions accessible in Matlab pathlist.
    +0004 %   toolboxes and user-created functions on the MATLAB path.
     0005 %
     0006 % Name-Value Arguments
     0007 % --------------------
    -0008 %   altDirs     cell array of alternative directories with functions,
    -0009 %               should only be specified if this function is used for other
    -0010 %               purposes, NOT when looking for RAVEN toolbox functions.
    -0011 %
    -0012 % Output:
    -0013 %   status      true if all functions are unique, otherwise false
    -0014 %
    -0015 % Usage: checkFunctionUniqueness()
    -0016 
    -0017 p=parseRAVENargs(varargin, {'altDirs',[]});
    -0018 altDirs=p.altDirs;
    -0019 
    -0020 if isempty(altDirs)
    -0021     %Get the RAVEN path
    -0022     ravenDir=findRAVENroot();
    -0023     
    -0024     %Now getting all RAVEN functions recursively;
    -0025     temp_res1=dir([ravenDir '/*/*.m']);
    -0026     temp_res2=dir([ravenDir '/*/*/*.m']);
    -0027     
    -0028     ravenFunctions={temp_res1.name,temp_res2.name}';
    -0029 else
    -0030     if ispc
    -0031         splitChar = ';';
    -0032     else
    -0033         splitChar = ':';
    -0034     end
    -0035     altDirs = strsplit(altDirs,splitChar)';
    -0036     ravenFunctions={};
    -0037     for i=1:numel(altDirs)
    -0038         temp_res=dir(fullfile(char(altDirs(i)),'*.m'));
    -0039         ravenFunctions = [ravenFunctions, temp_res.name];
    -0040     end
    -0041     ravenFunctions=ravenFunctions';
    -0042     ravenDir=altDirs;
    -0043 end
    -0044 
    -0045 %startup.m is not a normal function, any startup.m in the path should run
    -0046 %during startup, so duplicate use of this name is fine
    -0047 ravenFunctions=ravenFunctions(~ismember(ravenFunctions,'startup.m'));
    -0048 
    -0049 %Getting all the paths added to Matlab
    -0050 if ispc
    -0051     matlabPaths=regexp(path, ';', 'split')';
    -0052 elseif isunix
    -0053     matlabPaths=regexp(path, ':', 'split')';
    -0054 end
    -0055 
    -0056 overlapPath={};
    -0057 overlapFunctions={};
    -0058 multiRaven=false;
    -0059 multiFunction=false;
    -0060 
    -0061 for i=1:numel(matlabPaths)
    -0062     if ~startsWith(matlabPaths{i},ravenDir)
    -0063         temp_res=dir([matlabPaths{i} '/*.m']);
    -0064         if ~isempty(temp_res)
    -0065             pathFunctions={temp_res.name}';
    -0066         else
    -0067             pathFunctions='';
    -0068         end
    -0069         if ~isempty(pathFunctions) && ~any(ismember('Contents.m',pathFunctions))
    -0070             if any(ismember(ravenFunctions,pathFunctions))
    -0071                 if sum(ismember(ravenFunctions,pathFunctions))>(numel(ravenFunctions)/4)
    -0072                     multiRaven=true;
    -0073                 else
    -0074                     multiFunction=true;
    -0075                     overlapPath = [overlapPath, matlabPaths(i)];
    -0076                     overlapFunctions = [overlapFunctions, {ravenFunctions(ismember(ravenFunctions,pathFunctions))}];
    -0077                 end
    -0078             end
    -0079         end
    -0080     end
    -0081 end
    -0082 
    -0083 if multiRaven==true || multiFunction == true
    -0084         if nargout > 0
    -0085             status = false;
    -0086         else
    -0087             fprintf('Fail\n')
    -0088         end
    -0089     if multiRaven==true && isempty(altDirs)
    -0090         error(['Multiple RAVEN versions detected in MATLAB path. Remove all ',...
    -0091                'RAVEN directories from the MATLAB path with removeRavenFromPath(), ',...
    -0092                'or manually remove them. Afterwards, re-run checkRaven']);
    -0093     elseif multiFunction == true
    -0094         for i=1:numel(overlapPath)
    -0095             fprintf(['   Duplicate functions in ',regexprep(overlapPath{i},'(\\)','\\$1'),'\n']);
    -0096             fprintf(['     ' strjoin(overlapFunctions{i},'\n     ') '\n']);
    -0097         end
    -0098     fprintf('   Resolve conflicting functions to avoid unexpected behaviour\n');
    -0099     end
    -0100 else
    -0101     if nargout > 0
    -0102         status = true;
    -0103     else
    -0104         fprintf('Pass\n')
    -0105     end
    -0106 end

    +0008 % altDirs char with alternative directories with functions, separated +0009 % by the path separator of the platform (';' on Windows, ':' +0010 % otherwise). Specified only when the function checks +0011 % directories other than the RAVEN toolbox. +0012 % +0013 % Output: +0014 % status true if all functions are unique, otherwise false. Without +0015 % an output argument the function prints Pass or Fail. +0016 % +0017 % Usage: checkFunctionUniqueness() +0018 +0019 p=parseRAVENargs(varargin, {'altDirs',[]}); +0020 altDirs=p.altDirs; +0021 +0022 if isempty(altDirs) +0023 %Get the RAVEN path +0024 ravenDir=findRAVENroot(); +0025 +0026 %Now getting all RAVEN functions recursively; +0027 temp_res1=dir([ravenDir '/*/*.m']); +0028 temp_res2=dir([ravenDir '/*/*/*.m']); +0029 +0030 ravenFunctions={temp_res1.name,temp_res2.name}'; +0031 else +0032 if ispc +0033 splitChar = ';'; +0034 else +0035 splitChar = ':'; +0036 end +0037 altDirs = strsplit(altDirs,splitChar)'; +0038 ravenFunctions={}; +0039 for i=1:numel(altDirs) +0040 temp_res=dir(fullfile(char(altDirs(i)),'*.m')); +0041 ravenFunctions = [ravenFunctions, temp_res.name]; +0042 end +0043 ravenFunctions=ravenFunctions'; +0044 ravenDir=altDirs; +0045 end +0046 +0047 %startup.m is not a normal function, any startup.m in the path should run +0048 %during startup, so duplicate use of this name is fine +0049 ravenFunctions=ravenFunctions(~ismember(ravenFunctions,'startup.m')); +0050 +0051 %Getting all the paths added to Matlab +0052 if ispc +0053 matlabPaths=regexp(path, ';', 'split')'; +0054 elseif isunix +0055 matlabPaths=regexp(path, ':', 'split')'; +0056 end +0057 +0058 overlapPath={}; +0059 overlapFunctions={}; +0060 multiRaven=false; +0061 multiFunction=false; +0062 +0063 for i=1:numel(matlabPaths) +0064 if ~startsWith(matlabPaths{i},ravenDir) +0065 temp_res=dir([matlabPaths{i} '/*.m']); +0066 if ~isempty(temp_res) +0067 pathFunctions={temp_res.name}'; +0068 else +0069 pathFunctions=''; +0070 end +0071 if ~isempty(pathFunctions) && ~any(ismember('Contents.m',pathFunctions)) +0072 if any(ismember(ravenFunctions,pathFunctions)) +0073 if sum(ismember(ravenFunctions,pathFunctions))>(numel(ravenFunctions)/4) +0074 multiRaven=true; +0075 else +0076 multiFunction=true; +0077 overlapPath = [overlapPath, matlabPaths(i)]; +0078 overlapFunctions = [overlapFunctions, {ravenFunctions(ismember(ravenFunctions,pathFunctions))}]; +0079 end +0080 end +0081 end +0082 end +0083 end +0084 +0085 if multiRaven==true || multiFunction == true +0086 if nargout > 0 +0087 status = false; +0088 else +0089 fprintf('Fail\n') +0090 end +0091 if multiRaven==true && isempty(altDirs) +0092 error(['Multiple RAVEN versions detected in MATLAB path. Remove all ',... +0093 'RAVEN directories from the MATLAB path with removeRavenFromPath(), ',... +0094 'or manually remove them. Afterwards, re-run checkRaven']); +0095 elseif multiFunction == true +0096 for i=1:numel(overlapPath) +0097 fprintf([' Duplicate functions in ',regexprep(overlapPath{i},'(\\)','\\$1'),'\n']); +0098 fprintf([' ' strjoin(overlapFunctions{i},'\n ') '\n']); +0099 end +0100 fprintf(' Resolve conflicting functions to avoid unexpected behaviour\n'); +0101 end +0102 else +0103 if nargout > 0 +0104 status = true; +0105 else +0106 fprintf('Pass\n') +0107 end +0108 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/installation/downloadRavenBinaries.html b/docs/html/installation/downloadRavenBinaries.html index 64e6e4fe..4c60eb54 100644 --- a/docs/html/installation/downloadRavenBinaries.html +++ b/docs/html/installation/downloadRavenBinaries.html @@ -29,25 +29,27 @@

    SYNOPSIS ^DESCRIPTION ^

     downloadRavenBinaries
        Download RAVEN's external command-line binaries on demand from the
    -   toolbox-assets repository into RAVEN's src/external/ directory. Only the binary
    -   matching the current platform is fetched, and only when needed.
    +   toolbox-assets repository into the src/external directory of RAVEN. Only
    +   the binary matching the current platform is fetched, and only when it is
    +   missing.
     
        Input:
        tools   cell array, subset of {'blast+','diamond','hmmer'} to
                download. Defaults to all of them. Tools whose target executable is
                already present are skipped.
     
    -   This requires an internet connection. For an offline / air-gapped install,
    -   download the "*-binaries" RAVEN release (which bundles all binaries) and
    -   unzip it into the RAVEN software/ directory instead.
    +   The download requires an internet connection. An offline installation
    +   uses the offline bundle (RAVEN-<tag>-full.zip) attached to each RAVEN
    +   release, which contains the binaries of all platforms in src/external.
     
        The binaries are hosted as release assets in
        https://github.com/CompMetEng/toolbox-assets.
     
        Usage: downloadRavenBinaries({'blast+','diamond'})
     
    - NOTE: lazily invoked by getBlast/getDiamond/getKEGGModelForOrganism and
    - offered by checkRaven when a binary is missing.
    + getBlast, getDiamond and getKEGGModelForOrganism call this function + when a binary is missing. checkRaven calls it for all three tools when its + checkBinaries input is true.

    CROSS-REFERENCE INFORMATION ^

    @@ -65,112 +67,114 @@

    SOURCE CODE ^
    0001 function downloadRavenBinaries(tools)
     0002 % downloadRavenBinaries
     0003 %   Download RAVEN's external command-line binaries on demand from the
    -0004 %   toolbox-assets repository into RAVEN's src/external/ directory. Only the binary
    -0005 %   matching the current platform is fetched, and only when needed.
    -0006 %
    -0007 %   Input:
    -0008 %   tools   cell array, subset of {'blast+','diamond','hmmer'} to
    -0009 %           download. Defaults to all of them. Tools whose target executable is
    -0010 %           already present are skipped.
    -0011 %
    -0012 %   This requires an internet connection. For an offline / air-gapped install,
    -0013 %   download the "*-binaries" RAVEN release (which bundles all binaries) and
    -0014 %   unzip it into the RAVEN software/ directory instead.
    -0015 %
    -0016 %   The binaries are hosted as release assets in
    -0017 %   https://github.com/CompMetEng/toolbox-assets.
    -0018 %
    -0019 %   Usage: downloadRavenBinaries({'blast+','diamond'})
    -0020 %
    -0021 % NOTE: lazily invoked by getBlast/getDiamond/getKEGGModelForOrganism and
    -0022 % offered by checkRaven when a binary is missing.
    -0023 
    -0024 if nargin < 1 || isempty(tools)
    -0025     tools = {'blast+','diamond','hmmer'};
    -0026 elseif ischar(tools)
    -0027     tools = {tools};
    -0028 end
    -0029 
    -0030 ravenDir = findRAVENroot();
    +0004 %   toolbox-assets repository into the src/external directory of RAVEN. Only
    +0005 %   the binary matching the current platform is fetched, and only when it is
    +0006 %   missing.
    +0007 %
    +0008 %   Input:
    +0009 %   tools   cell array, subset of {'blast+','diamond','hmmer'} to
    +0010 %           download. Defaults to all of them. Tools whose target executable is
    +0011 %           already present are skipped.
    +0012 %
    +0013 %   The download requires an internet connection. An offline installation
    +0014 %   uses the offline bundle (RAVEN-<tag>-full.zip) attached to each RAVEN
    +0015 %   release, which contains the binaries of all platforms in src/external.
    +0016 %
    +0017 %   The binaries are hosted as release assets in
    +0018 %   https://github.com/CompMetEng/toolbox-assets.
    +0019 %
    +0020 %   Usage: downloadRavenBinaries({'blast+','diamond'})
    +0021 %
    +0022 % getBlast, getDiamond and getKEGGModelForOrganism call this function
    +0023 % when a binary is missing. checkRaven calls it for all three tools when its
    +0024 % checkBinaries input is true.
    +0025 
    +0026 if nargin < 1 || isempty(tools)
    +0027     tools = {'blast+','diamond','hmmer'};
    +0028 elseif ischar(tools)
    +0029     tools = {tools};
    +0030 end
     0031 
    -0032 % toolbox-assets platform key for the per-platform binary ZIPs.
    -0033 if ispc
    -0034     plat = 'windows-x86_64';
    -0035 elseif ismac
    -0036     plat = 'macos-arm64';
    -0037 else
    -0038     plat = 'linux-x86_64';
    -0039 end
    -0040 
    -0041 for i = 1:numel(tools)
    -0042     tool = tools{i};
    -0043     switch tool
    -0044         case 'blast+'
    -0045             tag = 'blast-2.17.0';
    -0046             asset = ['blast-2.17.0-' plat '.zip'];
    -0047             destDir = fullfile(ravenDir,'src','external','blast+');
    -0048             sentinel = fullfile(destDir,'blastp');           % bare name on every OS (.exe on Windows)
    -0049             execs = {'blastp','makeblastdb'};
    -0050         case 'diamond'
    -0051             tag = 'diamond-2.1.17';
    -0052             asset = ['diamond-2.1.17-' plat '.zip'];
    -0053             destDir = fullfile(ravenDir,'src','external','diamond');
    -0054             sentinel = fullfile(destDir,'diamond');
    -0055             execs = {'diamond'};
    -0056         case 'hmmer'
    -0057             if ispc
    -0058                 tag = 'hmmer-3.3.2';                          % native Windows build (Cygwin); reads HMMER3/f
    -0059                 asset = ['hmmer-3.3.2-' plat '.zip'];
    -0060             else
    -0061                 tag = 'hmmer-3.4.0';
    -0062                 asset = ['hmmer-3.4.0-' plat '.zip'];
    -0063             end
    -0064             destDir = fullfile(ravenDir,'src','external','hmmer');
    -0065             sentinel = fullfile(destDir,'hmmsearch');
    -0066             execs = {'hmmsearch'};
    -0067         otherwise
    -0068             error('downloadRavenBinaries: unknown tool ''%s''.',tool);
    -0069     end
    -0070 
    -0071     % macOS uses a .mac suffix and Windows a .exe suffix (Linux: bare name).
    -0072     if ispc
    -0073         sentinel = [sentinel '.exe']; %#ok<AGROW>
    -0074     elseif ismac
    -0075         sentinel = [sentinel '.mac']; %#ok<AGROW>
    -0076     end
    -0077     if exist(sentinel,'file')
    -0078         continue;   % already provisioned
    -0079     end
    -0080 
    -0081     zipPath = fetchToolboxAsset(fullfile(ravenDir,'src','external'),tag,asset);
    -0082     unzip(zipPath,destDir);
    -0083     delete(zipPath);
    -0084 
    -0085     % macOS uses the .mac suffix throughout RAVEN, but the toolbox-assets macOS ZIP
    -0086     % ships bare names, so rename the extracted executables to match.
    -0087     if ismac
    -0088         for k = 1:numel(execs)
    -0089             bare = fullfile(destDir,execs{k});
    -0090             if isfile(bare)
    -0091                 movefile(bare, fullfile(destDir,[execs{k} '.mac']));
    -0092             end
    -0093         end
    -0094     end
    -0095 
    -0096     % MATLAB's unzip does not preserve Unix execute permissions; restore them.
    -0097     if isunix
    -0098         if ismac; sfx = '.mac'; else; sfx = ''; end
    -0099         for k = 1:numel(execs)
    -0100             system(['chmod +x "' fullfile(destDir,[execs{k} sfx]) '"']);
    -0101         end
    -0102         % Downloaded binaries are quarantined by macOS Gatekeeper and will not
    -0103         % execute until the com.apple.quarantine attribute is cleared.
    -0104         if ismac
    -0105             system(['xattr -dr com.apple.quarantine "' destDir '"']);
    -0106         end
    -0107     end
    -0108 end
    -0109 end
    +0032 ravenDir = findRAVENroot(); +0033 +0034 % toolbox-assets platform key for the per-platform binary ZIPs. +0035 if ispc +0036 plat = 'windows-x86_64'; +0037 elseif ismac +0038 plat = 'macos-arm64'; +0039 else +0040 plat = 'linux-x86_64'; +0041 end +0042 +0043 for i = 1:numel(tools) +0044 tool = tools{i}; +0045 switch tool +0046 case 'blast+' +0047 tag = 'blast-2.17.0'; +0048 asset = ['blast-2.17.0-' plat '.zip']; +0049 destDir = fullfile(ravenDir,'src','external','blast+'); +0050 sentinel = fullfile(destDir,'blastp'); % bare name on every OS (.exe on Windows) +0051 execs = {'blastp','makeblastdb'}; +0052 case 'diamond' +0053 tag = 'diamond-2.1.17'; +0054 asset = ['diamond-2.1.17-' plat '.zip']; +0055 destDir = fullfile(ravenDir,'src','external','diamond'); +0056 sentinel = fullfile(destDir,'diamond'); +0057 execs = {'diamond'}; +0058 case 'hmmer' +0059 if ispc +0060 tag = 'hmmer-3.3.2'; % native Windows build (Cygwin); reads HMMER3/f +0061 asset = ['hmmer-3.3.2-' plat '.zip']; +0062 else +0063 tag = 'hmmer-3.4.0'; +0064 asset = ['hmmer-3.4.0-' plat '.zip']; +0065 end +0066 destDir = fullfile(ravenDir,'src','external','hmmer'); +0067 sentinel = fullfile(destDir,'hmmsearch'); +0068 execs = {'hmmsearch'}; +0069 otherwise +0070 error('downloadRavenBinaries: unknown tool ''%s''.',tool); +0071 end +0072 +0073 % macOS uses a .mac suffix and Windows a .exe suffix (Linux: bare name). +0074 if ispc +0075 sentinel = [sentinel '.exe']; %#ok<AGROW> +0076 elseif ismac +0077 sentinel = [sentinel '.mac']; %#ok<AGROW> +0078 end +0079 if exist(sentinel,'file') +0080 continue; % already provisioned +0081 end +0082 +0083 zipPath = fetchToolboxAsset(fullfile(ravenDir,'src','external'),tag,asset); +0084 unzip(zipPath,destDir); +0085 delete(zipPath); +0086 +0087 % macOS uses the .mac suffix throughout RAVEN, but the toolbox-assets macOS ZIP +0088 % ships bare names, so rename the extracted executables to match. +0089 if ismac +0090 for k = 1:numel(execs) +0091 bare = fullfile(destDir,execs{k}); +0092 if isfile(bare) +0093 movefile(bare, fullfile(destDir,[execs{k} '.mac'])); +0094 end +0095 end +0096 end +0097 +0098 % MATLAB's unzip does not preserve Unix execute permissions; restore them. +0099 if isunix +0100 if ismac; sfx = '.mac'; else; sfx = ''; end +0101 for k = 1:numel(execs) +0102 system(['chmod +x "' fullfile(destDir,[execs{k} sfx]) '"']); +0103 end +0104 % Downloaded binaries are quarantined by macOS Gatekeeper and will not +0105 % execute until the com.apple.quarantine attribute is cleared. +0106 if ismac +0107 system(['xattr -dr com.apple.quarantine "' destDir '"']); +0108 end +0109 end +0110 end +0111 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/installation/fetchToolboxAsset.html b/docs/html/installation/fetchToolboxAsset.html index 1a9306fa..ce1da222 100644 --- a/docs/html/installation/fetchToolboxAsset.html +++ b/docs/html/installation/fetchToolboxAsset.html @@ -31,11 +31,10 @@

    DESCRIPTION ^DESCRIPTION ^SOURCE CODE ^% 0004 % Downloads assetName from the given release tag of 0005 % https://github.com/CompMetEng/toolbox-assets into destDir -0006 % and returns its local path. An already-downloaded copy is reused as-is. -0007 % Generic across toolbox-assets' release families (KEGG artefacts, HMM -0008 % libraries, binaries, ...): nothing here is specific to any one of -0009 % them, so callers supply the release tag and asset name rather than -0010 % this function hardcoding either. -0011 % -0012 % A download is checked against the SHA-256 checksum that the -0013 % toolbox-assets manifest (release manifest-v3) lists for it. A file whose -0014 % checksum differs is deleted and an error is raised. An asset that the -0015 % manifest does not list, or a manifest that cannot be read, gives a -0016 % warning and the file is kept unchecked. -0017 % -0018 % Parameters -0019 % ---------- -0020 % destDir : char -0021 % directory to download into (created if it does not exist yet). -0022 % releaseTag : char -0023 % the toolbox-assets release tag, e.g. 'kegg118'. -0024 % assetName : char -0025 % the release asset file name, e.g. 'kegg118_core.tar.gz'. -0026 % -0027 % Returns -0028 % ------- -0029 % localPath : char -0030 % path to the downloaded file. -0031 -0032 if ~isfolder(destDir) -0033 mkdir(destDir); -0034 end -0035 localPath=fullfile(destDir,assetName); -0036 if isfile(localPath) -0037 return; -0038 end -0039 url=['https://github.com/CompMetEng/toolbox-assets/releases/download/' releaseTag '/' assetName]; -0040 partPath=[localPath '.part']; -0041 fprintf(['Downloading ' assetName '... ']); -0042 try -0043 websave(partPath,url); -0044 catch ME -0045 if isfile(partPath) -0046 delete(partPath); -0047 end -0048 if strcmp(ME.identifier,'MATLAB:webservices:HTTP404StatusCodeError') -0049 error('Failed to download %s, the server returned a 404 error, try again later. If the problem persists please report it on the RAVEN GitHub Issues page: https://github.com/CompMetEng/RAVEN/issues',assetName) -0050 else -0051 rethrow(ME); -0052 end -0053 end -0054 expected=manifestChecksum(url); -0055 if isempty(expected) -0056 warning('RAVEN:fetchToolboxAsset:unverified',['The toolbox-assets manifest gives no ' ... -0057 'checksum for %s; the download is not verified.'],assetName); -0058 else -0059 actual=sha256File(partPath); -0060 if ~strcmpi(actual,expected) -0061 delete(partPath); -0062 error('RAVEN:fetchToolboxAsset:checksum',['The download of %s is not the published ' ... -0063 'file (SHA-256 %s, expected %s) and was deleted. Try again; if it persists, ' ... -0064 'report it on the RAVEN GitHub Issues page: https://github.com/CompMetEng/RAVEN/issues'], ... -0065 assetName,actual,expected) -0066 end -0067 end -0068 movefile(partPath,localPath); -0069 fprintf('COMPLETE\n'); -0070 end -0071 -0072 function sha=manifestChecksum(url) -0073 % The sha256 the toolbox-assets manifest lists for an asset URL, or '' when -0074 % it lists none or cannot be read. The manifest is read once per session. -0075 persistent manifest -0076 sha=''; -0077 if isempty(manifest) -0078 try -0079 manifest=webread('https://github.com/CompMetEng/toolbox-assets/releases/download/manifest-v3/manifest.json', ... -0080 weboptions('ContentType','json','Timeout',30)); -0081 catch ME -0082 warning('RAVEN:fetchToolboxAsset:manifest','Cannot read the toolbox-assets manifest: %s',ME.message); -0083 return -0084 end -0085 end -0086 sha=findUrl(manifest,url); -0087 end -0088 -0089 function sha=findUrl(node,url) -0090 % Depth-first search for a struct with this url and a sha256 field. The -0091 % manifest's file names are not valid MATLAB field names, so the search -0092 % goes by value. -0093 sha=''; -0094 if isstruct(node) -0095 for k=1:numel(node) -0096 if isfield(node(k),'url') && isfield(node(k),'sha256') && strcmp(node(k).url,url) -0097 sha=node(k).sha256; -0098 return -0099 end -0100 f=fieldnames(node(k)); -0101 for i=1:numel(f) -0102 sha=findUrl(node(k).(f{i}),url); -0103 if ~isempty(sha) -0104 return -0105 end -0106 end -0107 end -0108 elseif iscell(node) -0109 for i=1:numel(node) -0110 sha=findUrl(node{i},url); -0111 if ~isempty(sha) -0112 return -0113 end -0114 end +0006 % and returns its local path. An already-downloaded copy is reused without a +0007 % new download or checksum test. The function applies to every toolbox-assets +0008 % release family (KEGG artefacts, HMM libraries, binaries), and the caller +0009 % supplies the release tag and the asset name. +0010 % +0011 % A download is checked against the SHA-256 checksum that the +0012 % toolbox-assets manifest (release manifest-v3) lists for it. A file whose +0013 % checksum differs is deleted and an error is raised. An asset that the +0014 % manifest does not list, or a manifest that cannot be read, gives a +0015 % warning and the file is kept unchecked. +0016 % +0017 % Parameters +0018 % ---------- +0019 % destDir : char +0020 % directory to download into (created if it does not exist yet). +0021 % releaseTag : char +0022 % the toolbox-assets release tag, for example 'kegg118'. +0023 % assetName : char +0024 % the release asset file name, for example 'kegg118_core.tar.gz'. +0025 % +0026 % Returns +0027 % ------- +0028 % localPath : char +0029 % path to the downloaded file. +0030 +0031 if ~isfolder(destDir) +0032 mkdir(destDir); +0033 end +0034 localPath=fullfile(destDir,assetName); +0035 if isfile(localPath) +0036 return; +0037 end +0038 url=['https://github.com/CompMetEng/toolbox-assets/releases/download/' releaseTag '/' assetName]; +0039 partPath=[localPath '.part']; +0040 fprintf(['Downloading ' assetName '... ']); +0041 try +0042 websave(partPath,url); +0043 catch ME +0044 if isfile(partPath) +0045 delete(partPath); +0046 end +0047 if strcmp(ME.identifier,'MATLAB:webservices:HTTP404StatusCodeError') +0048 error('Failed to download %s, the server returned a 404 error, try again later. If the problem persists please report it on the RAVEN GitHub Issues page: https://github.com/CompMetEng/RAVEN/issues',assetName) +0049 else +0050 rethrow(ME); +0051 end +0052 end +0053 expected=manifestChecksum(url); +0054 if isempty(expected) +0055 warning('RAVEN:fetchToolboxAsset:unverified',['The toolbox-assets manifest gives no ' ... +0056 'checksum for %s; the download is not verified.'],assetName); +0057 else +0058 actual=sha256File(partPath); +0059 if ~strcmpi(actual,expected) +0060 delete(partPath); +0061 error('RAVEN:fetchToolboxAsset:checksum',['The download of %s is not the published ' ... +0062 'file (SHA-256 %s, expected %s) and was deleted. Try again; if it persists, ' ... +0063 'report it on the RAVEN GitHub Issues page: https://github.com/CompMetEng/RAVEN/issues'], ... +0064 assetName,actual,expected) +0065 end +0066 end +0067 movefile(partPath,localPath); +0068 fprintf('COMPLETE\n'); +0069 end +0070 +0071 function sha=manifestChecksum(url) +0072 % The sha256 the toolbox-assets manifest lists for an asset URL, or '' when +0073 % it lists none or cannot be read. The manifest is read once per session. +0074 persistent manifest +0075 sha=''; +0076 if isempty(manifest) +0077 try +0078 manifest=webread('https://github.com/CompMetEng/toolbox-assets/releases/download/manifest-v3/manifest.json', ... +0079 weboptions('ContentType','json','Timeout',30)); +0080 catch ME +0081 warning('RAVEN:fetchToolboxAsset:manifest','Cannot read the toolbox-assets manifest: %s',ME.message); +0082 return +0083 end +0084 end +0085 sha=findUrl(manifest,url); +0086 end +0087 +0088 function sha=findUrl(node,url) +0089 % Depth-first search for a struct with this url and a sha256 field. The +0090 % manifest's file names are not valid MATLAB field names, so the search +0091 % goes by value. +0092 sha=''; +0093 if isstruct(node) +0094 for k=1:numel(node) +0095 if isfield(node(k),'url') && isfield(node(k),'sha256') && strcmp(node(k).url,url) +0096 sha=node(k).sha256; +0097 return +0098 end +0099 f=fieldnames(node(k)); +0100 for i=1:numel(f) +0101 sha=findUrl(node(k).(f{i}),url); +0102 if ~isempty(sha) +0103 return +0104 end +0105 end +0106 end +0107 elseif iscell(node) +0108 for i=1:numel(node) +0109 sha=findUrl(node{i},url); +0110 if ~isempty(sha) +0111 return +0112 end +0113 end +0114 end 0115 end -0116 end -0117 -0118 function sha=sha256File(path) -0119 % SHA-256 of a file as lower-case hex, read in chunks. -0120 md=java.security.MessageDigest.getInstance('SHA-256'); -0121 fid=fopen(path,'r'); -0122 cleanup=onCleanup(@() fclose(fid)); -0123 while true -0124 chunk=fread(fid,2^24,'*uint8'); -0125 if isempty(chunk) -0126 break -0127 end -0128 md.update(typecast(chunk,'int8')); -0129 end -0130 digest=typecast(md.digest(),'uint8'); -0131 sha=lower(reshape(dec2hex(digest,2)',1,[])); -0132 end +0116 +0117 function sha=sha256File(path) +0118 % SHA-256 of a file as lower-case hex, read in chunks. +0119 md=java.security.MessageDigest.getInstance('SHA-256'); +0120 fid=fopen(path,'r'); +0121 cleanup=onCleanup(@() fclose(fid)); +0122 while true +0123 chunk=fread(fid,2^24,'*uint8'); +0124 if isempty(chunk) +0125 break +0126 end +0127 md.update(typecast(chunk,'int8')); +0128 end +0129 digest=typecast(md.digest(),'uint8'); +0130 sha=lower(reshape(dec2hex(digest,2)',1,[])); +0131 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/installation/findRAVENroot.html b/docs/html/installation/findRAVENroot.html index f4cde4f1..554cf959 100644 --- a/docs/html/installation/findRAVENroot.html +++ b/docs/html/installation/findRAVENroot.html @@ -29,17 +29,18 @@

    SYNOPSIS ^DESCRIPTION ^

     findRAVENroot
        Finds the root of the RAVEN directory by searching for the path to
    -   version.txt. Can also record the current directory, in case a function will
    -   use the ravenPath to navigate to a precise folder, and it should return to
    -   the previous directory afterwards. See e.g. optimizeProb calling glpk.
    +   version.txt. Can also record the current directory, for functions that use
    +   the ravenPath to navigate to a precise folder and return to the previous
    +   directory afterwards. See, for example, optimizeProb calling glpk.
     
    -   The resolved path is cached for the rest of the MATLAB session: this is
    -   called on every solver invocation, and re-reading the RAVEN.ravenPath
    -   preference from disk every time is both wasteful and, under the sustained
    -   call volume of a full test run, an intermittent source of failures from
    -   MATLAB's own preference-file I/O. Run `clear findRAVENroot` after
    -   changing that preference (checkRaven does this already) to pick
    -   up the change without restarting MATLAB.
    + The RAVEN.ravenPath preference is used if it points to a directory with a + version.txt file. Otherwise the search starts at the location of this + function file and moves up the directory tree. + + The resolved path is cached for the rest of the MATLAB session, so the + preference is read once although the function is called on every solver + invocation. `clear findRAVENroot` drops the cached path after a change of + the preference (checkRaven does this) without a restart of MATLAB.

    CROSS-REFERENCE INFORMATION ^

    @@ -57,56 +58,57 @@

    SOURCE CODE ^
    0001 function [ravenPath, prevDir] = findRAVENroot()
     0002 % findRAVENroot
     0003 %   Finds the root of the RAVEN directory by searching for the path to
    -0004 %   version.txt. Can also record the current directory, in case a function will
    -0005 %   use the ravenPath to navigate to a precise folder, and it should return to
    -0006 %   the previous directory afterwards. See e.g. optimizeProb calling glpk.
    +0004 %   version.txt. Can also record the current directory, for functions that use
    +0005 %   the ravenPath to navigate to a precise folder and return to the previous
    +0006 %   directory afterwards. See, for example, optimizeProb calling glpk.
     0007 %
    -0008 %   The resolved path is cached for the rest of the MATLAB session: this is
    -0009 %   called on every solver invocation, and re-reading the RAVEN.ravenPath
    -0010 %   preference from disk every time is both wasteful and, under the sustained
    -0011 %   call volume of a full test run, an intermittent source of failures from
    -0012 %   MATLAB's own preference-file I/O. Run `clear findRAVENroot` after
    -0013 %   changing that preference (checkRaven does this already) to pick
    -0014 %   up the change without restarting MATLAB.
    -0015 
    -0016 persistent cachedPath
    -0017 prevDir = pwd();
    -0018 if ~isempty(cachedPath)
    -0019     ravenPath = cachedPath;
    -0020     return;
    -0021 end
    -0022 
    -0023 % A stored preference is only trusted if it still points at a real RAVEN
    -0024 % install; otherwise fall through to walking up from the currently
    -0025 % executing copy of this file. Without this check, a stale preference
    -0026 % left over from a different RAVEN checkout on the same machine silently
    -0027 % resolves to that other copy's data, not the one actually running.
    -0028 ravenPath = '';
    -0029 if ispref('RAVEN','ravenPath')
    -0030     prefPath = getpref('RAVEN','ravenPath');
    -0031     if isfile(fullfile(prefPath,'version.txt'))
    -0032         ravenPath = prefPath;
    -0033     end
    -0034 end
    -0035 if isempty(ravenPath)
    -0036     ST=dbstack('-completenames');
    -0037     ravenPath = ST(strcmp({ST.name},'findRAVENroot')).file;
    -0038     rootFound = 0;
    -0039     while rootFound == 0
    -0040         isRoot = isfile(fullfile(ravenPath,'version.txt'));
    -0041         if isRoot
    -0042             rootFound = 1;
    -0043         else
    -0044             ravenPathOld = ravenPath;
    -0045             ravenPath = fileparts(ravenPath);
    -0046             if strcmp(ravenPathOld,ravenPath)
    -0047                 error('Cannot find the RAVEN root directory. Make sure you have not removed the version.txt file from your RAVEN installation.')
    -0048             end
    -0049         end
    -0050     end
    -0051 end
    -0052 cachedPath = ravenPath;
    -0053 end
    +0008 % The RAVEN.ravenPath preference is used if it points to a directory with a +0009 % version.txt file. Otherwise the search starts at the location of this +0010 % function file and moves up the directory tree. +0011 % +0012 % The resolved path is cached for the rest of the MATLAB session, so the +0013 % preference is read once although the function is called on every solver +0014 % invocation. `clear findRAVENroot` drops the cached path after a change of +0015 % the preference (checkRaven does this) without a restart of MATLAB. +0016 +0017 persistent cachedPath +0018 prevDir = pwd(); +0019 if ~isempty(cachedPath) +0020 ravenPath = cachedPath; +0021 return; +0022 end +0023 +0024 % A stored preference is only trusted if it still points at a real RAVEN +0025 % install; otherwise fall through to walking up from the currently +0026 % executing copy of this file. Without this check, a stale preference +0027 % left over from a different RAVEN checkout on the same machine silently +0028 % resolves to that other copy's data, not the one actually running. +0029 ravenPath = ''; +0030 if ispref('RAVEN','ravenPath') +0031 prefPath = getpref('RAVEN','ravenPath'); +0032 if isfile(fullfile(prefPath,'version.txt')) +0033 ravenPath = prefPath; +0034 end +0035 end +0036 if isempty(ravenPath) +0037 ST=dbstack('-completenames'); +0038 ravenPath = ST(strcmp({ST.name},'findRAVENroot')).file; +0039 rootFound = 0; +0040 while rootFound == 0 +0041 isRoot = isfile(fullfile(ravenPath,'version.txt')); +0042 if isRoot +0043 rootFound = 1; +0044 else +0045 ravenPathOld = ravenPath; +0046 ravenPath = fileparts(ravenPath); +0047 if strcmp(ravenPathOld,ravenPath) +0048 error('Cannot find the RAVEN root directory. Make sure you have not removed the version.txt file from your RAVEN installation.') +0049 end +0050 end +0051 end +0052 end +0053 cachedPath = ravenPath; +0054 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/installation/removeRavenFromPath.html b/docs/html/installation/removeRavenFromPath.html index 5ba2cd21..d42642f0 100644 --- a/docs/html/installation/removeRavenFromPath.html +++ b/docs/html/installation/removeRavenFromPath.html @@ -29,10 +29,9 @@

    SYNOPSIS ^DESCRIPTION ^

     removeRavenFromPath
        This function removes all RAVEN directories and subdirectories from the
    -   MATLAB path. This function only removes RAVEN from the MATLAB path, it
    -   does not delete the RAVEN folder itself. If RAVEN was installed as
    -   MATLAB Add-On, the user is prompted to instead uninstall RAVEN via the
    -   Add-On Manager.
    +   MATLAB path and saves the path. The RAVEN folder itself is kept. If RAVEN
    +   is installed as MATLAB Add-On, the function raises an error that
    +   advises uninstalling RAVEN via the Add-On Manager.
     
      Usage: removeRavenFromPath()
    @@ -52,46 +51,45 @@

    SOURCE CODE ^
    0001 function removeRavenFromPath()
     0002 % removeRavenFromPath
     0003 %   This function removes all RAVEN directories and subdirectories from the
    -0004 %   MATLAB path. This function only removes RAVEN from the MATLAB path, it
    -0005 %   does not delete the RAVEN folder itself. If RAVEN was installed as
    -0006 %   MATLAB Add-On, the user is prompted to instead uninstall RAVEN via the
    -0007 %   Add-On Manager.
    -0008 %
    -0009 % Usage: removeRavenFromPath()
    -0010 
    -0011 % Check for installation as Add-On
    -0012 addList = matlab.addons.installedAddons;
    -0013 if any(strcmp(addList.Name,'RAVEN Toolbox'))
    -0014     error(['RAVEN is installed as MATLAB Add-On. You should uninstall RAVEN via '...
    -0015         'the Add-On Manager instead. Once uninstalled, you may attempt to run '...
    -0016         'removeRavenFromPath again to remove any potential remaining RAVEN installations.'])
    -0017 end
    -0018 
    -0019 % Get current paths
    -0020 currPath  = transpose(strsplit(path(),{';',':'}));
    -0021 ravenPath = false(numel(currPath),1);
    -0022 for i=1:numel(currPath)
    -0023     dirCont = ls(currPath{i});
    -0024     dirCont = cellstr(dirCont(3:end,:));
    -0025     if any(contains(dirCont,{'ravenCobraWrapper.m','makeFakeBlastStructure.m','setRavenSolver.m'})) % A few (hopefully) unique RAVEN functions
    -0026         ravenPath(i)=true;
    -0027     end
    -0028 end
    -0029 
    -0030 ravenPath = unique(regexprep(currPath(ravenPath),'(\\|\/)((reconstruction)|(conversion)|(solver))',''));
    -0031 addOnDir = contains(ravenPath,'MATLAB Add-Ons');
    -0032 if any(addOnDir)
    -0033     warning(['RAVEN is installed as MATLAB Add-On at the following directory, but MATLAB '...
    -0034         'somehow does not recognize this installation. You are advised to manually delete '...
    -0035         'the directory to remove any RAVEN remnants: ' ravenPath{addOnDir}]);
    -0036 end
    -0037 ravenSubDirs = currPath(startsWith(currPath,ravenPath));
    -0038 % Remove from path
    -0039 if ~isempty(ravenSubDirs)
    -0040     rmpath(ravenSubDirs{:});
    -0041     savepath;
    -0042 end
    -0043 end
    +0004 % MATLAB path and saves the path. The RAVEN folder itself is kept. If RAVEN +0005 % is installed as MATLAB Add-On, the function raises an error that +0006 % advises uninstalling RAVEN via the Add-On Manager. +0007 % +0008 % Usage: removeRavenFromPath() +0009 +0010 % Check for installation as Add-On +0011 addList = matlab.addons.installedAddons; +0012 if any(strcmp(addList.Name,'RAVEN Toolbox')) +0013 error(['RAVEN is installed as MATLAB Add-On. You should uninstall RAVEN via '... +0014 'the Add-On Manager instead. Once uninstalled, you may attempt to run '... +0015 'removeRavenFromPath again to remove any potential remaining RAVEN installations.']) +0016 end +0017 +0018 % Get current paths +0019 currPath = transpose(strsplit(path(),{';',':'})); +0020 ravenPath = false(numel(currPath),1); +0021 for i=1:numel(currPath) +0022 dirCont = ls(currPath{i}); +0023 dirCont = cellstr(dirCont(3:end,:)); +0024 if any(contains(dirCont,{'ravenCobraWrapper.m','makeFakeBlastStructure.m','setRavenSolver.m'})) % A few (hopefully) unique RAVEN functions +0025 ravenPath(i)=true; +0026 end +0027 end +0028 +0029 ravenPath = unique(regexprep(currPath(ravenPath),'(\\|\/)((reconstruction)|(conversion)|(solver))','')); +0030 addOnDir = contains(ravenPath,'MATLAB Add-Ons'); +0031 if any(addOnDir) +0032 warning(['RAVEN is installed as MATLAB Add-On at the following directory, but MATLAB '... +0033 'somehow does not recognize this installation. You are advised to manually delete '... +0034 'the directory to remove any RAVEN remnants: ' ravenPath{addOnDir}]); +0035 end +0036 ravenSubDirs = currPath(startsWith(currPath,ravenPath)); +0037 % Remove from path +0038 if ~isempty(ravenSubDirs) +0039 rmpath(ravenSubDirs{:}); +0040 savepath; +0041 end +0042 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/installation/updateDocumentation.html b/docs/html/installation/updateDocumentation.html index abbca8aa..242f22a8 100644 --- a/docs/html/installation/updateDocumentation.html +++ b/docs/html/installation/updateDocumentation.html @@ -28,13 +28,13 @@

    SYNOPSIS ^DESCRIPTION ^

     updateDocumentation
    -    Regenerates the HTML documentation in RAVEN's docs/html directory from the
    -    function help texts. The docs/html directory is deleted and rebuilt, so
    -    anything hand-edited there is lost.
    +    Regenerates the HTML documentation in the docs/html directory of RAVEN from
    +    the function help texts. The docs/html directory is deleted and rebuilt, so
    +    manual edits there are lost.
     
    -    The Documentation workflow runs this on every push to a release branch
    -    and commits the result, so docs/html stays in step with the sources without
    -    anyone running it by hand. Run it locally only to preview.
    +    The Documentation workflow runs this function on every push to the develop
    +    or main branch that changes a .m file or the workflow file, and commits the
    +    result. Running the function locally previews the documentation.
     
         Usage: updateDocumentation()
    @@ -53,13 +53,13 @@

    CROSS-REFERENCE INFORMATION ^
 <h2><a name=SOURCE CODE ^

    0001 function updateDocumentation()
     0002 % updateDocumentation
    -0003 %    Regenerates the HTML documentation in RAVEN's docs/html directory from the
    -0004 %    function help texts. The docs/html directory is deleted and rebuilt, so
    -0005 %    anything hand-edited there is lost.
    +0003 %    Regenerates the HTML documentation in the docs/html directory of RAVEN from
    +0004 %    the function help texts. The docs/html directory is deleted and rebuilt, so
    +0005 %    manual edits there are lost.
     0006 %
    -0007 %    The Documentation workflow runs this on every push to a release branch
    -0008 %    and commits the result, so docs/html stays in step with the sources without
    -0009 %    anyone running it by hand. Run it locally only to preview.
    +0007 %    The Documentation workflow runs this function on every push to the develop
    +0008 %    or main branch that changes a .m file or the workflow file, and commits the
    +0009 %    result. Running the function locally previews the documentation.
     0010 %
     0011 %    Usage: updateDocumentation()
     0012 
    diff --git a/docs/html/io/checkFileExistence.html b/docs/html/io/checkFileExistence.html
    index 7e40a45f..9e2c83fa 100644
    --- a/docs/html/io/checkFileExistence.html
    +++ b/docs/html/io/checkFileExistence.html
    @@ -29,14 +29,14 @@ 

    SYNOPSIS ^DESCRIPTION ^

     checkFileExistence  Check whether files exist.
     
    - If no full path is given a file should be located in the current folder,
    - which by default is appended to the filename.
    + A file name without a full path refers to a file in the current folder,
    + which is prepended to the file name.
     
      Parameters
      ----------
      files : char or cell
    -     string or cell array of strings with path to file(s) or path or
    -     filename(s).
    +     string or cell array of strings with the path or file name of one or
    +     more files.
     
      Name-Value Arguments
      --------------------
    @@ -48,17 +48,17 @@ 

    DESCRIPTION ^SOURCE CODE ^
    0001 function files=checkFileExistence(files,varargin)
     0002 % checkFileExistence  Check whether files exist.
     0003 %
    -0004 % If no full path is given a file should be located in the current folder,
    -0005 % which by default is appended to the filename.
    +0004 % A file name without a full path refers to a file in the current folder,
    +0005 % which is prepended to the file name.
     0006 %
     0007 % Parameters
     0008 % ----------
     0009 % files : char or cell
    -0010 %     string or cell array of strings with path to file(s) or path or
    -0011 %     filename(s).
    +0010 %     string or cell array of strings with the path or file name of one or
    +0011 %     more files.
     0012 %
     0013 % Name-Value Arguments
     0014 % --------------------
    @@ -99,17 +99,17 @@ 

    SOURCE CODE ^% - 2 : copy file(s) to system default temporary folder and return 0021 % full path 0022 % allowSpace : logical -0023 % whether the 'space' character is allowed in the path (default true). +0023 % true if the space character is allowed in the path (default true). 0024 % checkExist : logical -0025 % whether file existence should really be checked, as this function can -0026 % also be used to return the full path to a new file (default true). -0027 % Can only be set to false if fullOrTemp is set to 1. +0025 % true if file existence is checked. Setting it to false returns the full +0026 % path to a file that does not exist yet (default true). Can only be set +0027 % to false if fullOrTemp is set to 1. 0028 % 0029 % Returns 0030 % ------- 0031 % files : char or cell -0032 % string or cell array of strings with updated paths if fullOrTemp was -0033 % set as 1 or 2, otherwise original paths are returned. +0032 % string or cell array of strings with updated paths if fullOrTemp is 1 +0033 % or 2, otherwise the original paths. 0034 % 0035 % Examples 0036 % -------- diff --git a/docs/html/io/cleanSheet.html b/docs/html/io/cleanSheet.html index ca088754..2d5437ac 100644 --- a/docs/html/io/cleanSheet.html +++ b/docs/html/io/cleanSheet.html @@ -29,7 +29,7 @@

    SYNOPSIS ^DESCRIPTION ^

     cleanSheet  Clean up an Excel sheet.
     
    - Removes empty rows/columns (and performs some other checks).
    + Removes empty rows and columns, and performs further checks.
     
      Parameters
      ----------
    @@ -42,16 +42,17 @@ 

    DESCRIPTION ^SUBFUNCTIONS ^SOURCE CODE ^

    0001 % cleanSheet  Clean up an Excel sheet.
     0002 %
    -0003 % Removes empty rows/columns (and performs some other checks).
    +0003 % Removes empty rows and columns, and performs further checks.
     0004 %
     0005 % Parameters
     0006 % ----------
    @@ -92,100 +93,101 @@ 

    SOURCE CODE ^% true if commented lines (non-empty first cell in each row) should be 0014 % removed (default true). 0015 % removeOnlyCap : logical -0016 % remove columns with captions but no other values (default false). -0017 % removeNoCap : logical -0018 % remove columns without captions (default true). -0019 % removeEmptyRows : logical -0020 % remove rows with no non-empty cells (default true). -0021 % -0022 % Returns -0023 % ------- -0024 % raw : cell -0025 % cleaned version. -0026 % keptRows : double -0027 % indices of the kept rows in the original structure. -0028 % keptCols : double -0029 % indices of the kept columns in the original structure. -0030 % -0031 % Examples -0032 % -------- -0033 % [raw, keptRows, keptCols] = cleanSheet(raw, removeComments, ... -0034 % removeOnlyCap, removeNoCap, removeEmptyRows); -0035 function [raw,keptRows,keptCols]=cleanSheet(raw,varargin) -0036 p=parseRAVENargs(varargin, {'removeComments',true; 'removeOnlyCap',false; 'removeNoCap',true; 'removeEmptyRows',true}); -0037 removeComments=p.removeComments; removeOnlyCap=p.removeOnlyCap; removeNoCap=p.removeNoCap; removeEmptyRows=p.removeEmptyRows; -0038 -0039 keptRows=1:size(raw,1); -0040 keptRows=keptRows(:); -0041 keptCols=1:size(raw,2); -0042 keptCols=keptCols(:); -0043 -0044 %First check that it's a cell array. If a sheet is completely empty, then -0045 %raw=NaN -0046 if iscell(raw) -0047 %Clear cells which contain only white spaces or NaN. This could happen -0048 %if you accidentally inserted a space, or if a blank/NaN cell was read -0049 %from the source file. -0050 whites=cellfun(@wrapperWS,raw); -0051 raw(whites)={[]}; -0052 -0053 %Find the rows that are not commented. This corresponds to the first -0054 %row and the ones which are empty in the first column -0055 if removeComments==true -0056 keepers=cellfun(@isempty,raw(:,1)); -0057 keepers(1)=true; -0058 raw=raw(keepers,:); -0059 keptRows=keptRows(keepers); -0060 end -0061 -0062 %Remove columns that don't have string headers. If you cut and paste a -0063 %lot in the sheet there tends to be columns that are empty -0064 if removeNoCap==true -0065 I=cellfun(@isstr,raw(1,:)); -0066 raw=raw(:,I); -0067 keptCols=keptCols(I); -0068 end -0069 -0070 %Remove columns which are empty except for header -0071 if removeOnlyCap==true -0072 I=~all(cellfun(@isempty,raw(2:end,:)),1); -0073 raw=raw(:,I); -0074 keptCols=keptCols(I); -0075 end -0076 -0077 %Check if there are any rows that are all empty. This could happen if -0078 %it reads too far or if the user has inserted them for layout reasons. -0079 %Remove any such rows -0080 if removeEmptyRows==true -0081 I=~all(cellfun(@isempty,raw),2); -0082 raw=raw(I,:); -0083 keptRows=keptRows(I); -0084 end -0085 else -0086 raw={[]}; -0087 end -0088 -0089 %Checks if something is all white spaces or NaN -0090 function I=wrapperWS(A) -0091 if isnan(A) -0092 I=true; -0093 else -0094 %isstrprop gives an error if boolean -0095 if islogical(A) -0096 I=false; -0097 else -0098 %I have to make this check, since otherwise it will pick up -0099 %on doubles for which the ASCII representation is a white -0100 %space character -0101 if isnumeric(A) -0102 I=false; -0103 else -0104 I=all(isstrprop(A,'wspace')); -0105 end -0106 end -0107 end -0108 end -0109 end

    +0016 % true to remove columns with captions but no other values (default +0017 % false). +0018 % removeNoCap : logical +0019 % true to remove columns without captions (default true). +0020 % removeEmptyRows : logical +0021 % true to remove rows with no non-empty cells (default true). +0022 % +0023 % Returns +0024 % ------- +0025 % raw : cell +0026 % the cleaned cell array. +0027 % keptRows : double +0028 % indices of the kept rows in the original structure. +0029 % keptCols : double +0030 % indices of the kept columns in the original structure. +0031 % +0032 % Examples +0033 % -------- +0034 % [raw, keptRows, keptCols] = cleanSheet(raw, removeComments, ... +0035 % removeOnlyCap, removeNoCap, removeEmptyRows); +0036 function [raw,keptRows,keptCols]=cleanSheet(raw,varargin) +0037 p=parseRAVENargs(varargin, {'removeComments',true; 'removeOnlyCap',false; 'removeNoCap',true; 'removeEmptyRows',true}); +0038 removeComments=p.removeComments; removeOnlyCap=p.removeOnlyCap; removeNoCap=p.removeNoCap; removeEmptyRows=p.removeEmptyRows; +0039 +0040 keptRows=1:size(raw,1); +0041 keptRows=keptRows(:); +0042 keptCols=1:size(raw,2); +0043 keptCols=keptCols(:); +0044 +0045 %First check that it's a cell array. If a sheet is completely empty, then +0046 %raw=NaN +0047 if iscell(raw) +0048 %Clear cells which contain only white spaces or NaN. This could happen +0049 %if you accidentally inserted a space, or if a blank/NaN cell was read +0050 %from the source file. +0051 whites=cellfun(@wrapperWS,raw); +0052 raw(whites)={[]}; +0053 +0054 %Find the rows that are not commented. This corresponds to the first +0055 %row and the ones which are empty in the first column +0056 if removeComments==true +0057 keepers=cellfun(@isempty,raw(:,1)); +0058 keepers(1)=true; +0059 raw=raw(keepers,:); +0060 keptRows=keptRows(keepers); +0061 end +0062 +0063 %Remove columns that don't have string headers. If you cut and paste a +0064 %lot in the sheet there tends to be columns that are empty +0065 if removeNoCap==true +0066 I=cellfun(@isstr,raw(1,:)); +0067 raw=raw(:,I); +0068 keptCols=keptCols(I); +0069 end +0070 +0071 %Remove columns which are empty except for header +0072 if removeOnlyCap==true +0073 I=~all(cellfun(@isempty,raw(2:end,:)),1); +0074 raw=raw(:,I); +0075 keptCols=keptCols(I); +0076 end +0077 +0078 %Check if there are any rows that are all empty. This could happen if +0079 %it reads too far or if the user has inserted them for layout reasons. +0080 %Remove any such rows +0081 if removeEmptyRows==true +0082 I=~all(cellfun(@isempty,raw),2); +0083 raw=raw(I,:); +0084 keptRows=keptRows(I); +0085 end +0086 else +0087 raw={[]}; +0088 end +0089 +0090 %Checks if something is all white spaces or NaN +0091 function I=wrapperWS(A) +0092 if isnan(A) +0093 I=true; +0094 else +0095 %isstrprop gives an error if boolean +0096 if islogical(A) +0097 I=false; +0098 else +0099 %I have to make this check, since otherwise it will pick up +0100 %on doubles for which the ASCII representation is a white +0101 %space character +0102 if isnumeric(A) +0103 I=false; +0104 else +0105 I=all(isstrprop(A,'wspace')); +0106 end +0107 end +0108 end +0109 end +0110 end

    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/io/exportForGit.html b/docs/html/io/exportForGit.html index f864faf7..2f46a071 100644 --- a/docs/html/io/exportForGit.html +++ b/docs/html/io/exportForGit.html @@ -30,13 +30,13 @@

    DESCRIPTION ^
     exportForGit  Export a model for a Git-maintained model repository.
     
      Generates a directory structure and populates it with model files, ready
    - to be committed to a Git(Hub) maintained model repository. Writes the
    - model as SBML L3V1 FBCv2 (both XML and YAML), COBRA text, Matlab MAT-file
    - and Microsoft Excel formats.
    + to be committed to a Git or GitHub repository of a model. Writes the
    + model as SBML L3V1 FBCv2 (XML) and YAML, COBRA text, MATLAB MAT-file and
    + Microsoft Excel formats.
     
      The flags mainBranchFlag, subDirs, COBRAtext and neverPrefixIDs accept a
    - scalar logical or 0/1, or an empty value for the default. Text and other
    - types raise RAVEN:badInput.
    + scalar logical, the value 0 or 1, or an empty value for the default. Text
    + and other types raise RAVEN:badInput.
     
      Parameters
      ----------
    @@ -56,21 +56,20 @@ 

    DESCRIPTION ^DESCRIPTION ^CROSS-REFERENCE INFORMATION ^

    This function calls:
      -
    • exportModel exportModel Export a constraint-based model to an SBML file (L3V1 FBCv2).
    • exportToExcelFormat exportToExcelFormat Export a model to the Microsoft Excel model format.
    • writeYAMLmodel writeYAMLmodel Write a model to a yaml file in the standard cobra-format
    +
  • exportModel exportModel Export a constraint-based model to an SBML file (L3V1 FBCv2).
  • exportToExcelFormat exportToExcelFormat Export a model to the Microsoft Excel model format.
  • writeYAMLmodel writeYAMLmodel Write a model to a YAML file in the cobra-format !!omap structure.
  • This function is called by:
    @@ -96,13 +95,13 @@

    SOURCE CODE ^% exportForGit Export a model for a Git-maintained model repository. 0003 % 0004 % Generates a directory structure and populates it with model files, ready -0005 % to be committed to a Git(Hub) maintained model repository. Writes the -0006 % model as SBML L3V1 FBCv2 (both XML and YAML), COBRA text, Matlab MAT-file -0007 % and Microsoft Excel formats. +0005 % to be committed to a Git or GitHub repository of a model. Writes the +0006 % model as SBML L3V1 FBCv2 (XML) and YAML, COBRA text, MATLAB MAT-file and +0007 % Microsoft Excel formats. 0008 % 0009 % The flags mainBranchFlag, subDirs, COBRAtext and neverPrefixIDs accept a -0010 % scalar logical or 0/1, or an empty value for the default. Text and other -0011 % types raise RAVEN:badInput. +0010 % scalar logical, the value 0 or 1, or an empty value for the default. Text +0011 % and other types raise RAVEN:badInput. 0012 % 0013 % Parameters 0014 % ---------- @@ -122,248 +121,247 @@

    SOURCE CODE ^% should be exported (default all formats as {'mat', 'txt', 'xlsx', 0029 % 'xml', 'yml'}). 0030 % mainBranchFlag : logical -0031 % if true, function will error if RAVEN (and GECKO for enzyme- -0032 % constrained models) is/are not on the main branch (default false). +0031 % true to raise an error if RAVEN (and GECKO for enzyme-constrained +0032 % models) is not on the main or master branch (default false). 0033 % subDirs : logical -0034 % whether model files for each file format should be written in their -0035 % own subdirectory, with 'model' as parent directory, in accordance to -0036 % the standard-GEM repository format. If false, all files are stored in -0037 % the same folder (default true). +0034 % true to write the model files of each file format in their own +0035 % subdirectory, with 'model' as parent directory, following the +0036 % standard-GEM repository format. If false, all files are stored in the +0037 % same folder (default true). 0038 % COBRAtext : logical -0039 % whether the txt file should be in COBRA Toolbox format using -0040 % metabolite IDs, instead of metabolite names and compartments -0041 % (default false). +0039 % true to write the txt file in COBRA Toolbox format, which uses +0040 % metabolite IDs. If false, the file uses metabolite names and +0041 % compartments (default false). 0042 % neverPrefixIDs : logical 0043 % true if prefixes are never added to identifiers, even if they start -0044 % with e.g. digits. This might result in invalid SBML files (default -0045 % false). -0046 % -0047 % Examples -0048 % -------- -0049 % exportForGit(model, prefix, path, formats, mainBranchFlag, subDirs, ... -0050 % COBRAtext, neverPrefixIDs); -0051 p=parseRAVENargs(varargin, {'prefix',[]; 'path',[]; 'formats',[]; 'mainBranchFlag',[]; 'subDirs',[]; 'COBRAtext',[]; 'neverPrefixIDs',false}); -0052 prefix=p.prefix; path=p.path; formats=p.formats; mainBranchFlag=p.mainBranchFlag; subDirs=p.subDirs; COBRAtext=p.COBRAtext; neverPrefixIDs=p.neverPrefixIDs; -0053 COBRAtext=checkFlag(COBRAtext,'COBRAtext',false); -0054 subDirs=checkFlag(subDirs,'subDirs',true); -0055 mainBranchFlag=checkFlag(mainBranchFlag,'mainBranchFlag',false); -0056 neverPrefixIDs=checkFlag(neverPrefixIDs,'neverPrefixIDs',false); -0057 if isempty(formats) -0058 formats={'mat', 'txt', 'xlsx', 'xml', 'yml'}; -0059 else -0060 formats=convertCharArray(formats); -0061 end -0062 if any(~ismember(formats, {'mat', 'txt', 'xlsx', 'xml', 'yml'})) -0063 EM='Unknown file format defined. Only mat, txt, xlsx, xml and yml are allowed file formats.'; -0064 error(EM) -0065 end -0066 if isempty(path) -0067 path='.'; -0068 else -0069 path=char(path); -0070 end -0071 if isempty(prefix) -0072 prefix='model'; -0073 else -0074 prefix=char(prefix); -0075 end -0076 -0077 %Sort reactions, metabolites and genes alphabetically -0078 model=sortIdentifiers(model); -0079 -0080 %Get versions or commits of toolboxes. GECKO is only relevant for -0081 %enzyme-constrained models (those with an "ec" field). -0082 RAVENver = getToolboxVersion('RAVEN','ravenCobraWrapper.m',mainBranchFlag); -0083 if isfield(model,'ec') -0084 GECKOver = getToolboxVersion('GECKO','GECKOInstaller.m',mainBranchFlag); -0085 else -0086 GECKOver = []; -0087 end -0088 %Retrieve libSBML version: -0089 [ravenDir,prevDir]=findRAVENroot(); -0090 try % 5.17.0 and newer -0091 libSBMLver=OutputSBML_RAVEN; -0092 libSBMLver=libSBMLver.libSBML_version_string; -0093 catch % before 5.17.0 -0094 fid = fopen('tempModelForLibSBMLversion.xml','w+'); -0095 fclose(fid); -0096 evalc('[~,~,libSBMLver]=TranslateSBML_RAVEN(''tempModelForLibSBMLversion.xml'',0,0)'); -0097 libSBMLver=libSBMLver.libSBML_version_string; -0098 delete('tempModelForLibSBMLversion.xml'); -0099 end -0100 -0101 % Make the folders, no warnings if they already exist -0102 if subDirs -0103 path=fullfile(path,'model'); -0104 filePath=strcat(path,filesep,{'txt','yml','mat','xlsx','xml'}); -0105 [~,~,~]=mkdir(path); -0106 for i = 1:length(formats) -0107 [~,~,~]=mkdir(fullfile(path,formats{i})); -0108 end -0109 else -0110 [~,~,~]=mkdir(path); -0111 filePath=cell(1,5); filePath(:)={path}; -0112 end +0044 % with digits. This can result in invalid SBML files (default false). +0045 % +0046 % Examples +0047 % -------- +0048 % exportForGit(model, prefix, path, formats, mainBranchFlag, subDirs, ... +0049 % COBRAtext, neverPrefixIDs); +0050 p=parseRAVENargs(varargin, {'prefix',[]; 'path',[]; 'formats',[]; 'mainBranchFlag',[]; 'subDirs',[]; 'COBRAtext',[]; 'neverPrefixIDs',false}); +0051 prefix=p.prefix; path=p.path; formats=p.formats; mainBranchFlag=p.mainBranchFlag; subDirs=p.subDirs; COBRAtext=p.COBRAtext; neverPrefixIDs=p.neverPrefixIDs; +0052 COBRAtext=checkFlag(COBRAtext,'COBRAtext',false); +0053 subDirs=checkFlag(subDirs,'subDirs',true); +0054 mainBranchFlag=checkFlag(mainBranchFlag,'mainBranchFlag',false); +0055 neverPrefixIDs=checkFlag(neverPrefixIDs,'neverPrefixIDs',false); +0056 if isempty(formats) +0057 formats={'mat', 'txt', 'xlsx', 'xml', 'yml'}; +0058 else +0059 formats=convertCharArray(formats); +0060 end +0061 if any(~ismember(formats, {'mat', 'txt', 'xlsx', 'xml', 'yml'})) +0062 EM='Unknown file format defined. Only mat, txt, xlsx, xml and yml are allowed file formats.'; +0063 error(EM) +0064 end +0065 if isempty(path) +0066 path='.'; +0067 else +0068 path=char(path); +0069 end +0070 if isempty(prefix) +0071 prefix='model'; +0072 else +0073 prefix=char(prefix); +0074 end +0075 +0076 %Sort reactions, metabolites and genes alphabetically +0077 model=sortIdentifiers(model); +0078 +0079 %Get versions or commits of toolboxes. GECKO is only relevant for +0080 %enzyme-constrained models (those with an "ec" field). +0081 RAVENver = getToolboxVersion('RAVEN','ravenCobraWrapper.m',mainBranchFlag); +0082 if isfield(model,'ec') +0083 GECKOver = getToolboxVersion('GECKO','GECKOInstaller.m',mainBranchFlag); +0084 else +0085 GECKOver = []; +0086 end +0087 %Retrieve libSBML version: +0088 [ravenDir,prevDir]=findRAVENroot(); +0089 try % 5.17.0 and newer +0090 libSBMLver=OutputSBML_RAVEN; +0091 libSBMLver=libSBMLver.libSBML_version_string; +0092 catch % before 5.17.0 +0093 fid = fopen('tempModelForLibSBMLversion.xml','w+'); +0094 fclose(fid); +0095 evalc('[~,~,libSBMLver]=TranslateSBML_RAVEN(''tempModelForLibSBMLversion.xml'',0,0)'); +0096 libSBMLver=libSBMLver.libSBML_version_string; +0097 delete('tempModelForLibSBMLversion.xml'); +0098 end +0099 +0100 % Make the folders, no warnings if they already exist +0101 if subDirs +0102 path=fullfile(path,'model'); +0103 filePath=strcat(path,filesep,{'txt','yml','mat','xlsx','xml'}); +0104 [~,~,~]=mkdir(path); +0105 for i = 1:length(formats) +0106 [~,~,~]=mkdir(fullfile(path,formats{i})); +0107 end +0108 else +0109 [~,~,~]=mkdir(path); +0110 filePath=cell(1,5); filePath(:)={path}; +0111 end +0112 0113 -0114 -0115 % Write TXT format -0116 if ismember('txt', formats) -0117 fid=fopen(fullfile(filePath{1},strcat(prefix,'.txt')),'w'); -0118 if COBRAtext==true -0119 eqns=constructEquations(model,model.rxns,false,false,false); -0120 eqns=strrep(eqns,' => ',' -> '); -0121 eqns=strrep(eqns,' <=> ',' <=> '); -0122 eqns=regexprep(eqns,'> $','>'); -0123 grRules=regexprep(model.grRules,'\((?!\()','( '); -0124 grRules=regexprep(grRules,'(?<!\))\)',' )'); -0125 else -0126 eqns=constructEquations(model,model.rxns); -0127 grRules=model.grRules; -0128 end -0129 fprintf(fid, 'Rxn id\tFormula\tGene-reaction association\tLB\tUB\tObjective\n'); -0130 for i = 1:numel(model.rxns) -0131 fprintf(fid, '%s\t', model.rxns{i}); -0132 fprintf(fid, '%s\t', strtrim(eqns{i})); -0133 fprintf(fid, '%s\t', grRules{i}); -0134 fprintf(fid, '%s\t%s\t%s\n', shortestNumber(model.lb(i)), shortestNumber(model.ub(i)), shortestNumber(model.c(i))); -0135 end -0136 fclose(fid); -0137 end -0138 -0139 % Write YML format -0140 if ismember('yml', formats) -0141 writeYAMLmodel(model,fullfile(filePath{2},strcat(prefix,'.yml'))); -0142 end -0143 -0144 % Write MAT format -0145 if ismember('mat', formats) -0146 save(fullfile(filePath{3},strcat(prefix,'.mat')),'model'); -0147 end -0148 -0149 % Write XLSX format -0150 if ismember('xlsx', formats) -0151 exportToExcelFormat(model,fullfile(filePath{4},strcat(prefix,'.xlsx'))); -0152 end -0153 -0154 % Write XML format -0155 if ismember('xml', formats) -0156 exportModel(model,fullfile(filePath{5},strcat(prefix,'.xml')),neverPrefixIDs); -0157 end -0158 -0159 %Save file with versions: -0160 fid = fopen(fullfile(path,'dependencies.txt'),'wt'); -0161 fprintf(fid,['MATLAB\t' version '\n']); -0162 fprintf(fid,['libSBML\t' libSBMLver '\n']); -0163 fprintf(fid,['RAVEN_toolbox\t' RAVENver '\n']); -0164 if ~isempty(GECKOver) -0165 fprintf(fid,['GECKO_toolbox\t' GECKOver '\n']); -0166 end -0167 if isfield(model,'modelVersion') -0168 fields = fieldnames(model.modelVersion); -0169 for i = 1:length(fields) -0170 value = model.modelVersion.(fields{i}); -0171 fprintf(fid,[fields{i} '\t' num2str(value) '\n']); -0172 end -0173 end -0174 fclose(fid); -0175 end -0176 -0177 function s = shortestNumber(x) -0178 % shortestNumber The shortest text that reads back as exactly x, so a bound -0179 % is never rounded on the way out (1/3 is 0.3333333333333333, 1000 is 1000). -0180 if isinf(x) -0181 if x > 0 -0182 s = 'Inf'; -0183 else -0184 s = '-Inf'; -0185 end -0186 return -0187 elseif isnan(x) -0188 s = 'NaN'; -0189 return -0190 end -0191 if x == floor(x) && abs(x) < 1e15 -0192 s = sprintf('%d', x); -0193 return -0194 end -0195 for digits = 1:17 -0196 s = sprintf(['%.' num2str(digits) 'g'], x); -0197 if str2double(s) == x -0198 break -0199 end +0114 % Write TXT format +0115 if ismember('txt', formats) +0116 fid=fopen(fullfile(filePath{1},strcat(prefix,'.txt')),'w'); +0117 if COBRAtext==true +0118 eqns=constructEquations(model,model.rxns,false,false,false); +0119 eqns=strrep(eqns,' => ',' -> '); +0120 eqns=strrep(eqns,' <=> ',' <=> '); +0121 eqns=regexprep(eqns,'> $','>'); +0122 grRules=regexprep(model.grRules,'\((?!\()','( '); +0123 grRules=regexprep(grRules,'(?<!\))\)',' )'); +0124 else +0125 eqns=constructEquations(model,model.rxns); +0126 grRules=model.grRules; +0127 end +0128 fprintf(fid, 'Rxn id\tFormula\tGene-reaction association\tLB\tUB\tObjective\n'); +0129 for i = 1:numel(model.rxns) +0130 fprintf(fid, '%s\t', model.rxns{i}); +0131 fprintf(fid, '%s\t', strtrim(eqns{i})); +0132 fprintf(fid, '%s\t', grRules{i}); +0133 fprintf(fid, '%s\t%s\t%s\n', shortestNumber(model.lb(i)), shortestNumber(model.ub(i)), shortestNumber(model.c(i))); +0134 end +0135 fclose(fid); +0136 end +0137 +0138 % Write YML format +0139 if ismember('yml', formats) +0140 writeYAMLmodel(model,fullfile(filePath{2},strcat(prefix,'.yml'))); +0141 end +0142 +0143 % Write MAT format +0144 if ismember('mat', formats) +0145 save(fullfile(filePath{3},strcat(prefix,'.mat')),'model'); +0146 end +0147 +0148 % Write XLSX format +0149 if ismember('xlsx', formats) +0150 exportToExcelFormat(model,fullfile(filePath{4},strcat(prefix,'.xlsx'))); +0151 end +0152 +0153 % Write XML format +0154 if ismember('xml', formats) +0155 exportModel(model,fullfile(filePath{5},strcat(prefix,'.xml')),neverPrefixIDs); +0156 end +0157 +0158 %Save file with versions: +0159 fid = fopen(fullfile(path,'dependencies.txt'),'wt'); +0160 fprintf(fid,['MATLAB\t' version '\n']); +0161 fprintf(fid,['libSBML\t' libSBMLver '\n']); +0162 fprintf(fid,['RAVEN_toolbox\t' RAVENver '\n']); +0163 if ~isempty(GECKOver) +0164 fprintf(fid,['GECKO_toolbox\t' GECKOver '\n']); +0165 end +0166 if isfield(model,'modelVersion') +0167 fields = fieldnames(model.modelVersion); +0168 for i = 1:length(fields) +0169 value = model.modelVersion.(fields{i}); +0170 fprintf(fid,[fields{i} '\t' num2str(value) '\n']); +0171 end +0172 end +0173 fclose(fid); +0174 end +0175 +0176 function s = shortestNumber(x) +0177 % shortestNumber The shortest text that reads back as exactly x, so a bound +0178 % is never rounded on the way out (1/3 is 0.3333333333333333, 1000 is 1000). +0179 if isinf(x) +0180 if x > 0 +0181 s = 'Inf'; +0182 else +0183 s = '-Inf'; +0184 end +0185 return +0186 elseif isnan(x) +0187 s = 'NaN'; +0188 return +0189 end +0190 if x == floor(x) && abs(x) < 1e15 +0191 s = sprintf('%d', x); +0192 return +0193 end +0194 for digits = 1:17 +0195 s = sprintf(['%.' num2str(digits) 'g'], x); +0196 if str2double(s) == x +0197 break +0198 end +0199 end 0200 end -0201 end -0202 -0203 function version = getToolboxVersion(toolbox,fileID,mainBranchFlag) -0204 % getToolboxVersion Return the version of RAVEN or GECKO. -0205 % -0206 % Returns the version of the toolbox as stated in its version.txt, or -0207 % 'unknown' if that file cannot be found. -0208 % -0209 % toolbox name of the toolbox (e.g. 'RAVEN' or 'GECKO'). -0210 % fileID a file only found in that toolbox, used to locate its root -0211 % (e.g. 'ravenCobraWrapper.m' or 'GECKOInstaller.m'). -0212 % mainBranchFlag if true, error if the toolbox is not on the main branch. -0213 currentPath = pwd; -0214 version = ''; -0215 -0216 %Try to find root of toolbox: -0217 try -0218 toolboxPath = which(fileID); %full file path -0219 slashPos = getSlashPos(toolboxPath); -0220 toolboxPath = toolboxPath(1:slashPos(end)); %folder path -0221 %Go up until the root is found: -0222 D = dir(toolboxPath); -0223 while ~ismember({'.git'},{D.name}) -0224 slashPos = getSlashPos(toolboxPath); -0225 toolboxPath = toolboxPath(1:slashPos(end-1)); -0226 D = dir(toolboxPath); -0227 end -0228 cd(toolboxPath); -0229 catch -0230 disp([toolbox ' toolbox cannot be found']) -0231 version = 'unknown'; -0232 end -0233 %Check if in main: -0234 if mainBranchFlag -0235 [~,currentBranch] = system('git rev-parse --abbrev-ref HEAD'); -0236 currentBranch = strtrim(currentBranch); -0237 if ~any([strcmp(currentBranch, "main"), strcmp(currentBranch, "master")]) -0238 cd(currentPath); -0239 error('RAVEN:notMainBranch',['ERROR: ' toolbox ' not in main (or master) branch. Check-out this branch of ' toolbox ' before submitting model for Git.']) -0240 end -0241 end -0242 %Read the version from the toolbox version.txt; report 'unknown' otherwise -0243 if isempty(version) -0244 fid = fopen([toolboxPath 'version.txt'],'r'); -0245 if fid ~= -1 -0246 version = fscanf(fid,'%s'); -0247 fclose(fid); -0248 end -0249 if isempty(version) -0250 version = 'unknown'; -0251 end -0252 end -0253 cd(currentPath); -0254 end -0255 -0256 function slashPos = getSlashPos(path) -0257 slashPos = strfind(path,'\'); %Windows -0258 if isempty(slashPos) -0259 slashPos = strfind(path,'/'); %MAC/Linux +0201 +0202 function version = getToolboxVersion(toolbox,fileID,mainBranchFlag) +0203 % getToolboxVersion Return the version of RAVEN or GECKO. +0204 % +0205 % Returns the version of the toolbox as stated in its version.txt, or +0206 % 'unknown' if that file cannot be found. +0207 % +0208 % toolbox name of the toolbox (e.g. 'RAVEN' or 'GECKO'). +0209 % fileID a file only found in that toolbox, used to locate its root +0210 % (e.g. 'ravenCobraWrapper.m' or 'GECKOInstaller.m'). +0211 % mainBranchFlag if true, error if the toolbox is not on the main branch. +0212 currentPath = pwd; +0213 version = ''; +0214 +0215 %Try to find root of toolbox: +0216 try +0217 toolboxPath = which(fileID); %full file path +0218 slashPos = getSlashPos(toolboxPath); +0219 toolboxPath = toolboxPath(1:slashPos(end)); %folder path +0220 %Go up until the root is found: +0221 D = dir(toolboxPath); +0222 while ~ismember({'.git'},{D.name}) +0223 slashPos = getSlashPos(toolboxPath); +0224 toolboxPath = toolboxPath(1:slashPos(end-1)); +0225 D = dir(toolboxPath); +0226 end +0227 cd(toolboxPath); +0228 catch +0229 disp([toolbox ' toolbox cannot be found']) +0230 version = 'unknown'; +0231 end +0232 %Check if in main: +0233 if mainBranchFlag +0234 [~,currentBranch] = system('git rev-parse --abbrev-ref HEAD'); +0235 currentBranch = strtrim(currentBranch); +0236 if ~any([strcmp(currentBranch, "main"), strcmp(currentBranch, "master")]) +0237 cd(currentPath); +0238 error('RAVEN:notMainBranch',['ERROR: ' toolbox ' not in main (or master) branch. Check-out this branch of ' toolbox ' before submitting model for Git.']) +0239 end +0240 end +0241 %Read the version from the toolbox version.txt; report 'unknown' otherwise +0242 if isempty(version) +0243 fid = fopen([toolboxPath 'version.txt'],'r'); +0244 if fid ~= -1 +0245 version = fscanf(fid,'%s'); +0246 fclose(fid); +0247 end +0248 if isempty(version) +0249 version = 'unknown'; +0250 end +0251 end +0252 cd(currentPath); +0253 end +0254 +0255 function slashPos = getSlashPos(path) +0256 slashPos = strfind(path,'\'); %Windows +0257 if isempty(slashPos) +0258 slashPos = strfind(path,'/'); %MAC/Linux +0259 end 0260 end -0261 end -0262 -0263 function flag = checkFlag(flag, name, default) -0264 %An empty value gives the default; otherwise a scalar logical or 0/1 is required -0265 if isempty(flag) -0266 flag = default; -0267 elseif (islogical(flag) || isnumeric(flag)) && isscalar(flag) && isreal(flag) && (flag == 0 || flag == 1) -0268 flag = logical(flag); -0269 else -0270 error('RAVEN:badInput', '%s must be a scalar logical or 0/1.', name); -0271 end -0272 end

    +0261 +0262 function flag = checkFlag(flag, name, default) +0263 %An empty value gives the default; otherwise a scalar logical or 0/1 is required +0264 if isempty(flag) +0265 flag = default; +0266 elseif (islogical(flag) || isnumeric(flag)) && isscalar(flag) && isreal(flag) && (flag == 0 || flag == 1) +0267 flag = logical(flag); +0268 else +0269 error('RAVEN:badInput', '%s must be a scalar logical or 0/1.', name); +0270 end +0271 end

    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/io/exportModel.html b/docs/html/io/exportModel.html index 322fd827..79f63fd4 100644 --- a/docs/html/io/exportModel.html +++ b/docs/html/io/exportModel.html @@ -37,18 +37,18 @@

    DESCRIPTION ^SOURCE CODE ^% Name-Value Arguments 0010 % -------------------- 0011 % fileName : char -0012 % filename to export the model to. A dialog window will open if no file -0013 % name is specified. +0012 % file name to export the model to. A dialog window opens if no file name +0013 % is specified. 0014 % neverPrefix : logical 0015 % true if prefixes are never added to identifiers, even if they start -0016 % with e.g. digits. This might result in invalid SBML files (default -0017 % false). -0018 % supressWarnings : logical -0019 % true if warnings should be suppressed. This might result in invalid -0020 % SBML files, as no checks are performed (default false). +0016 % with digits. This can result in invalid SBML files (default false). +0017 % supressWarnings : logical +0018 % true if warnings should be suppressed. The check of the model +0019 % structure (checkModelStruct) is skipped, which can result in invalid +0020 % SBML files (default false). 0021 % sortIds : logical -0022 % whether metabolites, reactions and genes should be sorted -0023 % alphabetically by their identifiers (default false). +0022 % true to sort metabolites, reactions and genes alphabetically by their +0023 % identifiers (default false). 0024 % 0025 % Examples 0026 % -------- diff --git a/docs/html/io/exportToExcelFormat.html b/docs/html/io/exportToExcelFormat.html index 47f89e1a..c034f1d2 100644 --- a/docs/html/io/exportToExcelFormat.html +++ b/docs/html/io/exportToExcelFormat.html @@ -37,11 +37,11 @@

    DESCRIPTION ^SOURCE CODE ^% Name-Value Arguments 0010 % -------------------- 0011 % fileName : char -0012 % file name of the Excel file. Only xlsx format is supported. A dialog -0013 % window will open if fileName is empty. +0012 % file name of the Excel file. Only the xlsx format is supported. A +0013 % dialog window opens if fileName is empty. 0014 % sortIds : logical -0015 % whether metabolites, reactions and genes should be sorted -0016 % alphabetically by their identifiers (default false). +0015 % true to sort metabolites, reactions and genes alphabetically by their +0016 % identifiers (default false). 0017 % 0018 % Examples 0019 % -------- diff --git a/docs/html/io/importModel.html b/docs/html/io/importModel.html index 8eec851a..5c744fbd 100644 --- a/docs/html/io/importModel.html +++ b/docs/html/io/importModel.html @@ -32,19 +32,18 @@

    DESCRIPTION ^DESCRIPTION ^DESCRIPTION ^DESCRIPTION ^CROSS-REFERENCE INFORMATION ^

    @@ -128,968 +127,967 @@

    SOURCE CODE ^% Name-Value Arguments 0005 % -------------------- 0006 % fileName : char -0007 % a SBML file to import. A dialog window will open if no file name is +0007 % a SBML file to import. A dialog window opens if no file name is 0008 % specified. 0009 % removeExcMets : logical -0010 % if true, exchange metabolites are removed from the imported model -0011 % (default true). +0010 % true to remove exchange metabolites from the imported model (default +0011 % true). 0012 % removePrefix : logical -0013 % true if identifier prefixes should be removed when loading the model: -0014 % G_ for genes, R_ for reactions, M_ for metabolites, and C_ for -0015 % compartments. These are only removed if all identifiers of a certain -0016 % type contain the prefix (default true). +0013 % true to remove identifier prefixes when loading the model: G_ for +0014 % genes, R_ for reactions, M_ for metabolites, and C_ for compartments. +0015 % The prefix of a type is removed only if all identifiers of that type +0016 % contain it (default true). 0017 % supressWarnings : logical -0018 % true if warnings regarding the model structure should be supressed -0019 % (default false). -0020 % -0021 % Returns -0022 % ------- -0023 % model : struct -0024 % imported model structure with fields: -0025 % -0026 % - id : model ID -0027 % - name : name of model contents -0028 % - annotation : additional information about model -0029 % - rxns : reaction ids -0030 % - mets : metabolite ids -0031 % - S : stoichiometric matrix -0032 % - lb : lower bounds -0033 % - ub : upper bounds -0034 % - rev : reversibility vector -0035 % - c : objective coefficients -0036 % - b : equality constraints for the metabolite equations -0037 % - comps : compartment ids -0038 % - compNames : compartment names -0039 % - compOutside : the id (as in comps) for the compartment surrounding -0040 % each of the compartments -0041 % - compMiriams : structure with MIRIAM information about the -0042 % compartments -0043 % - rxnNames : reaction description -0044 % - rxnComps : compartments for reactions -0045 % - grRules : reaction to gene rules in text form -0046 % - rxnGeneMat : reaction-to-gene mapping in sparse matrix form -0047 % - subSystems : subsystem name for each reaction -0048 % - eccodes : EC-codes for the reactions -0049 % - rxnMiriams : structure with MIRIAM information about the reactions -0050 % - rxnNotes : reaction notes -0051 % - rxnReferences : reaction references -0052 % - rxnConfidenceScores : reaction confidence scores -0053 % - rxnDeltaG : reaction Gibbs free energy, from a `deltaG` notes key -0054 % (same convention exportModel writes and raven-toolbox reads/writes; -0055 % omitted entirely if no reaction has one) -0056 % - genes : list of all genes -0057 % - geneComps : compartments for genes -0058 % - geneMiriams : structure with MIRIAM information about the genes -0059 % - geneShortNames : gene alternative names (e.g. ERG10) -0060 % - proteins : protein associated to each gene -0061 % - metNames : metabolite description -0062 % - metComps : compartments for metabolites -0063 % - inchis : InChI-codes for metabolites -0064 % - metFormulas : metabolite chemical formula -0065 % - metMiriams : structure with MIRIAM information about the metabolites -0066 % - metCharges : metabolite charge -0067 % - metDeltaG : metabolite Gibbs free energy, from a `deltaG` notes key -0068 % (same convention exportModel writes and raven-toolbox reads/writes; -0069 % omitted entirely if no metabolite has one) -0070 % - unconstrained : true if the metabolite is an exchange metabolite -0071 % -0072 % Examples -0073 % -------- -0074 % model = importModel(fileName, removeExcMets, removePrefix, supressWarnings); -0075 % -0076 % Notes -0077 % ----- -0078 % A number of consistency checks are performed to ensure that the model is -0079 % valid. Take these warnings seriously and modify the model structure to -0080 % solve them. -0081 -0082 p=parseRAVENargs(varargin, {'fileName',[]; 'removeExcMets',[]; 'removePrefix',[]; 'supressWarnings',false}); -0083 fileName=p.fileName; removeExcMets=p.removeExcMets; removePrefix=p.removePrefix; supressWarnings=p.supressWarnings; -0084 if isempty(fileName) -0085 [fileName, pathName] = uigetfile({'*.xml;*.sbml'}, 'Please select the model file'); -0086 if fileName == 0 -0087 error('You should select a model file') -0088 else -0089 fileName = fullfile(pathName,fileName); -0090 end -0091 end -0092 fileName=char(fileName); -0093 if isempty(removeExcMets) -0094 removeExcMets=true; -0095 end -0096 -0097 if isempty(removePrefix) -0098 removePrefix=true; -0099 end -0100 -0101 fileName=checkFileExistence(fileName,1); -0102 % If path contains non-ASCII characters, copy file to tempdir first, as -0103 % libSBML is known to have problems with this on Windows: -0104 % https://sbml.org/software/libsbml/libsbml-docs/known-pitfalls/#matlab-on-windows-has-issues-with-unicode-filenames -0105 if ispc && any(double(fileName)>128) -0106 [~,originalFile,ext] = fileparts(fileName); -0107 tempFile = fullfile(tempdir,[originalFile ext]); -0108 copyfile(fileName,tempFile); -0109 fileName = tempFile; -0110 end -0111 -0112 %Set a canonical order of the model fields -0113 model=[]; -0114 model.id=[]; -0115 model.name=[]; -0116 model.annotation=[]; -0117 model.rxns={}; -0118 model.mets={}; -0119 model.S=[]; -0120 model.lb=[]; -0121 model.ub=[]; -0122 model.rev=[]; -0123 model.c=[]; -0124 model.b=[]; -0125 model.comps={}; -0126 model.compNames={}; -0127 model.compOutside={}; -0128 model.compMiriams={}; -0129 model.rxnNames={}; -0130 model.rxnComps=[]; -0131 model.grRules={}; -0132 model.rxnGeneMat=[]; -0133 model.subSystems={}; -0134 model.eccodes={}; -0135 model.rxnMiriams={}; -0136 model.rxnNotes={}; -0137 model.rxnReferences={}; -0138 model.rxnConfidenceScores=[]; -0139 model.genes={}; -0140 model.geneComps=[]; -0141 model.geneMiriams={}; -0142 model.geneShortNames={}; -0143 model.proteins={}; -0144 model.metNames={}; -0145 model.metComps=[]; -0146 model.inchis={}; -0147 model.metFormulas={}; -0148 model.metMiriams={}; -0149 model.metCharges=[]; -0150 model.unconstrained=[]; -0151 -0152 %Load the model using libSBML -0153 [modelSBML,errorMsg] = TranslateSBML_RAVEN(fileName,0,0,[1 1]); -0154 if exist('tempFile','var') -0155 delete(tempFile) -0156 end -0157 -0158 if isempty(modelSBML) -0159 EM=['There is a problem with the SBML file. Try using the SBML Validator at http://sbml.org/Facilities/Validator.\nlibSBML reports: ', errorMsg.message]; -0160 error('RAVEN:badInput', '%s', EM); -0161 end -0162 -0163 %RAVEN only supports SBML Level 3 Version 1 with the FBC version 2 package. -0164 if ~(isfield(modelSBML,'SBML_level') && isequal(modelSBML.SBML_level,3) && ... -0165 isfield(modelSBML,'SBML_version') && isequal(modelSBML.SBML_version,1) && ... -0166 isfield(modelSBML,'fbc_version') && isequal(modelSBML.fbc_version,2)) -0167 EM=['importModel only supports SBML Level 3 Version 1 with the FBC '... -0168 'version 2 package. Convert the model to this format first (for '... -0169 'example with another tool), or use an older RAVEN version to '... -0170 'import it.']; -0171 error('RAVEN:badInput', '%s', EM); -0172 end -0173 -0174 %Retrieve compartment names and IDs -0175 compartmentNames=cell(numel(modelSBML.compartment),1); -0176 compartmentIDs=cell(numel(modelSBML.compartment),1); -0177 compartmentOutside=cell(numel(modelSBML.compartment),1); -0178 compartmentMiriams=cell(numel(modelSBML.compartment),1); -0179 -0180 if isfield(modelSBML.compartment,'sboTerm') && isscalar(unique([modelSBML.compartment.sboTerm])) -0181 %If all the SBO terms are identical, do not add them to compMiriams -0182 modelSBML.compartment = rmfield(modelSBML.compartment,'sboTerm'); -0183 end -0184 -0185 for i=1:numel(modelSBML.compartment) -0186 compartmentNames{i}=modelSBML.compartment(i).name; -0187 compartmentIDs{i}=modelSBML.compartment(i).id; -0188 if isfield(modelSBML.compartment(i),'outside') -0189 if ~isempty(modelSBML.compartment(i).outside) -0190 compartmentOutside{i}=modelSBML.compartment(i).outside; -0191 else -0192 compartmentOutside{i}=''; -0193 end -0194 else -0195 compartmentOutside{i}=[]; -0196 end -0197 -0198 if isfield(modelSBML.compartment(i),'annotation') -0199 compartmentMiriams{i}=parseMiriam(modelSBML.compartment(i).annotation); -0200 else -0201 compartmentMiriams{i}=[]; -0202 end -0203 -0204 if isfield(modelSBML.compartment(i),'sboTerm') && ~(modelSBML.compartment(i).sboTerm==-1) -0205 compartmentMiriams{i} = addSBOtoMiriam(compartmentMiriams{i},modelSBML.compartment(i).sboTerm); -0206 end -0207 end -0208 -0209 %If there are no compartment names then use compartment id as name -0210 if all(cellfun(@isempty,compartmentNames)) -0211 compartmentNames=compartmentIDs; -0212 end -0213 -0214 %Retrieve info on metabolites -0215 metaboliteNames={}; -0216 metaboliteIDs={}; -0217 metaboliteCompartments={}; -0218 metaboliteUnconstrained=[]; -0219 metaboliteFormula={}; -0220 metaboliteInChI={}; -0221 metaboliteMiriams={}; -0222 metaboliteCharges=[]; -0223 metaboliteDeltaG=[]; -0224 -0225 %Gene information is collected from fbc_geneProduct further below -0226 geneMiriams={}; -0227 geneShortNames={}; -0228 proteins={}; -0229 -0230 %In FBC v2 all species are metabolites; genes are stored as fbc_geneProduct -0231 %and are handled separately below. -0232 metSBOs = []; -0233 %Regex of compartment names, later to be used to remove from metabolite -0234 %names if present as suffix. -0235 escapedCompNames = regexptranslate('escape',{modelSBML.compartment.name}); -0236 regexCompNames = ['\s?\[((' strjoin(escapedCompNames,')|(') '))\]$']; -0237 for i=1:numel(modelSBML.species) -0238 metaboliteNames{numel(metaboliteNames)+1,1}=modelSBML.species(i).name; -0239 metaboliteIDs{numel(metaboliteIDs)+1,1}=modelSBML.species(i).id; -0240 metaboliteCompartments{numel(metaboliteCompartments)+1,1}=modelSBML.species(i).compartment; -0241 metaboliteUnconstrained(numel(metaboliteUnconstrained)+1,1)=modelSBML.species(i).boundaryCondition; -0242 -0243 %For each metabolite retrieve the formula and the InChI code if -0244 %available First add the InChI code and the formula from the -0245 %InChI. This allows for overwriting the formula by setting the -0246 %actual formula field -0247 if ~isempty(modelSBML.species(i).annotation) -0248 %Get the formula if available -0249 startString='>InChI='; -0250 endString='</in:inchi>'; -0251 formStart=strfind(modelSBML.species(i).annotation,startString); -0252 if isempty(formStart) -0253 startString='InChI='; -0254 endString='"/>'; -0255 end -0256 formStart=strfind(modelSBML.species(i).annotation,startString); -0257 if ~isempty(formStart) -0258 formEnd=strfind(modelSBML.species(i).annotation,endString); -0259 formEndIndex=find(formEnd>formStart, 1 ); -0260 formula=modelSBML.species(i).annotation(formStart+numel(startString):formEnd(formEndIndex)-1); -0261 metaboliteInChI{numel(metaboliteInChI)+1,1}=formula; -0262 -0263 %The composition is most often present between the -0264 %first and second "/" in the model. In some simple -0265 %molecules, such as salts, there is no second "/". The -0266 %formula is then assumed to be to the end of the string -0267 compositionIndexes=strfind(formula,'/'); -0268 if numel(compositionIndexes)>1 -0269 metaboliteFormula{numel(metaboliteFormula)+1,1}=... -0270 formula(compositionIndexes(1)+1:compositionIndexes(2)-1); -0271 else -0272 if isscalar(compositionIndexes) -0273 %Probably a simple molecule which can have only -0274 %one conformation -0275 metaboliteFormula{numel(metaboliteFormula)+1,1}=... -0276 formula(compositionIndexes(1)+1:numel(formula)); -0277 else -0278 metaboliteFormula{numel(metaboliteFormula)+1,1}=''; -0279 end -0280 end -0281 elseif isfield(modelSBML.species(i),'fbc_chemicalFormula') -0282 metaboliteInChI{numel(metaboliteInChI)+1,1}=''; -0283 if ~isempty(modelSBML.species(i).fbc_chemicalFormula) -0284 %Cannot extract InChi from formula, so remains -0285 %empty -0286 metaboliteFormula{numel(metaboliteFormula)+1,1}=modelSBML.species(i).fbc_chemicalFormula; -0287 else -0288 metaboliteFormula{numel(metaboliteFormula)+1,1}=''; -0289 end -0290 else -0291 metaboliteInChI{numel(metaboliteInChI)+1,1}=''; -0292 metaboliteFormula{numel(metaboliteFormula)+1,1}=''; -0293 end -0294 -0295 %Get Miriam info -0296 metMiriam=parseMiriam(modelSBML.species(i).annotation); -0297 metaboliteMiriams{numel(metaboliteMiriams)+1,1}=metMiriam; -0298 else -0299 metaboliteInChI{numel(metaboliteInChI)+1,1}=''; -0300 metaboliteFormula{numel(metaboliteFormula)+1,1}=''; -0301 metaboliteMiriams{numel(metaboliteMiriams)+1,1}=[]; -0302 end -0303 %Get SBO term. One entry per species, always, so metSBOs stays aligned -0304 %with metaboliteNames below even when some species have no SBO term; -0305 %-1 (SBML's own "unset" value) marks those. -0306 if isfield(modelSBML.species(i),'sboTerm') && ~(modelSBML.species(i).sboTerm==-1) -0307 metSBOs(end+1,1) = modelSBML.species(i).sboTerm; -0308 else -0309 metSBOs(end+1,1) = -1; -0310 end -0311 -0312 %Remove trailing [compartment] from metabolite name if present -0313 metaboliteNames{end,1}=regexprep(metaboliteNames{end,1},regexCompNames,''); -0314 if isfield(modelSBML.species(i),'fbc_charge') -0315 if ~isempty(modelSBML.species(i).fbc_charge) && modelSBML.species(i).isSetfbc_charge -0316 metaboliteCharges(numel(metaboliteCharges)+1,1)=double(modelSBML.species(i).fbc_charge); -0317 else -0318 metaboliteCharges(numel(metaboliteCharges)+1,1)=NaN; -0319 end -0320 else -0321 metaboliteCharges(numel(metaboliteCharges)+1,1)=NaN; -0322 end -0323 %deltaG: same <p>deltaG: VALUE</p> notes convention exportModel writes -0324 %and raven-toolbox reads/writes -- see RAVEN#724. -0325 %str2double('')==NaN, matching metaboliteDeltaG's own NaN default. -0326 if isfield(modelSBML.species(i),'notes') -0327 metaboliteDeltaG(numel(metaboliteDeltaG)+1,1)=str2double(parseNote(modelSBML.species(i).notes,'deltaG')); -0328 else -0329 metaboliteDeltaG(numel(metaboliteDeltaG)+1,1)=NaN; -0330 end -0331 %Additional information from FBC format Chemical formula -0332 if isfield(modelSBML.species(i),'fbc_chemicalFormula') -0333 if ~isempty(modelSBML.species(i).fbc_chemicalFormula) -0334 metaboliteFormula{numel(metaboliteFormula),1}=modelSBML.species(i).fbc_chemicalFormula; -0335 end -0336 end -0337 end -0338 -0339 %Add SBO terms to metabolite miriam fields -0340 if numel(unique(metSBOs)) > 1 -0341 for i = 1:numel(metaboliteNames) -0342 if metSBOs(i) ~= -1 -0343 metaboliteMiriams{i} = addSBOtoMiriam(metaboliteMiriams{i},metSBOs(i)); -0344 end -0345 end -0346 end -0347 -0348 %Retrieve info on reactions -0349 reactionNames=cell(numel(modelSBML.reaction),1); -0350 reactionIDs=cell(numel(modelSBML.reaction),1); -0351 subsystems=cell(numel(modelSBML.reaction),1); -0352 eccodes=cell(numel(modelSBML.reaction),1); -0353 eccodes(:,:)=cellstr(''); -0354 rxnconfidencescores=NaN(numel(modelSBML.reaction),1); -0355 rxndeltag=NaN(numel(modelSBML.reaction),1); -0356 rxnreferences=cell(numel(modelSBML.reaction),1); -0357 rxnreferences(:,:)=cellstr(''); -0358 rxnnotes=cell(numel(modelSBML.reaction),1); -0359 rxnnotes(:,:)=cellstr(''); -0360 grRules=cell(numel(modelSBML.reaction),1); -0361 grRules(:,:)=cellstr(''); -0362 rxnComps=zeros(numel(modelSBML.reaction),1); -0363 rxnMiriams=cell(numel(modelSBML.reaction),1); -0364 reactionReversibility=zeros(numel(modelSBML.reaction),1); -0365 reactionUB=zeros(numel(modelSBML.reaction),1); -0366 reactionLB=zeros(numel(modelSBML.reaction),1); -0367 reactionObjective=zeros(numel(modelSBML.reaction),1); -0368 -0369 %Construct the stoichiometric matrix while the reaction info is read. -0370 %Accumulated as (row,col,value) triples and assembled into a sparse -0371 %matrix in one pass at the end (sparse() sums duplicate (row,col) -0372 %triples, matching the +'d accumulation below), rather than allocating a -0373 %dense (mets x rxns) matrix, which for a genome-scale model can be -0374 %hundreds of MB even though S is typically >95% sparse. -0375 nStoichEntries=sum(arrayfun(@(r) numel(r.reactant)+numel(r.product),modelSBML.reaction)); -0376 sRow=zeros(nStoichEntries,1); -0377 sCol=zeros(nStoichEntries,1); -0378 sVal=zeros(nStoichEntries,1); -0379 sN=0; -0380 -0381 counter=0; -0382 %If FBC, then bounds have parameter ids defined for the whole model -0383 if isfield(modelSBML,'parameter') -0384 parameter.name=cell(numel(modelSBML.parameter),1); -0385 parameter.name={modelSBML.parameter(:).id}'; -0386 parameter.value={modelSBML.parameter(:).value}'; -0387 end -0388 -0389 if isfield(modelSBML.reaction,'sboTerm') && isscalar(unique([modelSBML.reaction.sboTerm])) -0390 %If all the SBO terms are identical, do not add them to rxnMiriams -0391 modelSBML.reaction = rmfield(modelSBML.reaction,'sboTerm'); -0392 end -0393 -0394 for i=1:numel(modelSBML.reaction) -0395 -0396 counter=counter+1; -0397 -0398 reactionNames{counter}=modelSBML.reaction(i).name; -0399 -0400 reactionIDs{counter}=modelSBML.reaction(i).id; -0401 reactionReversibility(counter)=modelSBML.reaction(i).reversible; -0402 -0403 %If model is FBC, first get parameter of bound and then replace it with -0404 %the correct value. Probably faster with replace(), but this was only -0405 %introduced in Matlab R2016b -0406 if isfield(modelSBML.reaction(i),'fbc_lowerFluxBound') -0407 lb=modelSBML.reaction(i).fbc_lowerFluxBound; -0408 ub=modelSBML.reaction(i).fbc_upperFluxBound; -0409 [~,fluxBoundIdx] = ismember({lb,ub},parameter.name); -0410 reactionLB(counter) = parameter.value{fluxBoundIdx(1)}; -0411 reactionUB(counter) = parameter.value{fluxBoundIdx(2)}; -0412 else -0413 if reactionReversibility(counter)==true -0414 reactionLB(counter)=-inf; -0415 else -0416 reactionLB(counter)=0; -0417 end -0418 reactionUB(counter)=inf; -0419 reactionObjective(counter)=0; -0420 end -0421 -0422 %Find the associated gene if available. In FBC v2 gene association data -0423 %is stored in the fbc_geneProductAssociation field. -0424 if isfield(modelSBML.reaction(i),'fbc_geneProductAssociation') -0425 if ~isempty(modelSBML.reaction(i).fbc_geneProductAssociation) && ~isempty(modelSBML.reaction(i).fbc_geneProductAssociation.fbc_association) -0426 grRules{counter}=modelSBML.reaction(i).fbc_geneProductAssociation.fbc_association.fbc_association; -0427 end -0428 end -0429 -0430 %Add reaction compartment -0431 rxnComp=''; -0432 if isfield(modelSBML.reaction(i),'compartment') -0433 if ~isempty(modelSBML.reaction(i).compartment) -0434 rxnComp=modelSBML.reaction(i).compartment; -0435 end -0436 end -0437 if ~isempty(rxnComp) -0438 %Find it in the compartment list -0439 [~, J]=ismember(rxnComp,compartmentIDs); -0440 rxnComps(counter)=J; -0441 end +0018 % true to suppress warnings about the model structure (default false). +0019 % +0020 % Returns +0021 % ------- +0022 % model : struct +0023 % imported model structure with fields: +0024 % +0025 % - id : model ID +0026 % - name : name of model contents +0027 % - annotation : additional information about model +0028 % - rxns : reaction ids +0029 % - mets : metabolite ids +0030 % - S : stoichiometric matrix +0031 % - lb : lower bounds +0032 % - ub : upper bounds +0033 % - rev : reversibility vector +0034 % - c : objective coefficients +0035 % - b : equality constraints for the metabolite equations +0036 % - comps : compartment ids +0037 % - compNames : compartment names +0038 % - compOutside : the id (as in comps) for the compartment surrounding +0039 % each of the compartments +0040 % - compMiriams : structure with MIRIAM information about the +0041 % compartments +0042 % - rxnNames : reaction description +0043 % - rxnComps : compartments for reactions +0044 % - grRules : reaction to gene rules in text form +0045 % - rxnGeneMat : reaction-to-gene mapping in sparse matrix form +0046 % - subSystems : subsystem name for each reaction +0047 % - eccodes : EC-codes for the reactions +0048 % - rxnMiriams : structure with MIRIAM information about the reactions +0049 % - rxnNotes : reaction notes +0050 % - rxnReferences : reaction references +0051 % - rxnConfidenceScores : reaction confidence scores +0052 % - rxnDeltaG : reaction Gibbs free energy, from the `deltaG` notes +0053 % field (the convention that exportModel writes and raven-toolbox +0054 % reads and writes; omitted if no reaction has one) +0055 % - genes : list of all genes +0056 % - geneComps : compartments for genes +0057 % - geneMiriams : structure with MIRIAM information about the genes +0058 % - geneShortNames : gene alternative names (for example ERG10) +0059 % - proteins : protein associated to each gene +0060 % - metNames : metabolite description +0061 % - metComps : compartments for metabolites +0062 % - inchis : InChI-codes for metabolites +0063 % - metFormulas : metabolite chemical formula +0064 % - metMiriams : structure with MIRIAM information about the metabolites +0065 % - metCharges : metabolite charge +0066 % - metDeltaG : metabolite Gibbs free energy, from the `deltaG` notes +0067 % field (the convention that exportModel writes and raven-toolbox +0068 % reads and writes; omitted if no metabolite has one) +0069 % - unconstrained : true if the metabolite is an exchange metabolite +0070 % +0071 % Examples +0072 % -------- +0073 % model = importModel(fileName, removeExcMets, removePrefix, supressWarnings); +0074 % +0075 % Notes +0076 % ----- +0077 % checkModelStruct checks the consistency of the imported model and raises +0078 % warnings about problems in the model structure. supressWarnings skips this +0079 % check. +0080 +0081 p=parseRAVENargs(varargin, {'fileName',[]; 'removeExcMets',[]; 'removePrefix',[]; 'supressWarnings',false}); +0082 fileName=p.fileName; removeExcMets=p.removeExcMets; removePrefix=p.removePrefix; supressWarnings=p.supressWarnings; +0083 if isempty(fileName) +0084 [fileName, pathName] = uigetfile({'*.xml;*.sbml'}, 'Please select the model file'); +0085 if fileName == 0 +0086 error('You should select a model file') +0087 else +0088 fileName = fullfile(pathName,fileName); +0089 end +0090 end +0091 fileName=char(fileName); +0092 if isempty(removeExcMets) +0093 removeExcMets=true; +0094 end +0095 +0096 if isempty(removePrefix) +0097 removePrefix=true; +0098 end +0099 +0100 fileName=checkFileExistence(fileName,1); +0101 % If path contains non-ASCII characters, copy file to tempdir first, as +0102 % libSBML is known to have problems with this on Windows: +0103 % https://sbml.org/software/libsbml/libsbml-docs/known-pitfalls/#matlab-on-windows-has-issues-with-unicode-filenames +0104 if ispc && any(double(fileName)>128) +0105 [~,originalFile,ext] = fileparts(fileName); +0106 tempFile = fullfile(tempdir,[originalFile ext]); +0107 copyfile(fileName,tempFile); +0108 fileName = tempFile; +0109 end +0110 +0111 %Set a canonical order of the model fields +0112 model=[]; +0113 model.id=[]; +0114 model.name=[]; +0115 model.annotation=[]; +0116 model.rxns={}; +0117 model.mets={}; +0118 model.S=[]; +0119 model.lb=[]; +0120 model.ub=[]; +0121 model.rev=[]; +0122 model.c=[]; +0123 model.b=[]; +0124 model.comps={}; +0125 model.compNames={}; +0126 model.compOutside={}; +0127 model.compMiriams={}; +0128 model.rxnNames={}; +0129 model.rxnComps=[]; +0130 model.grRules={}; +0131 model.rxnGeneMat=[]; +0132 model.subSystems={}; +0133 model.eccodes={}; +0134 model.rxnMiriams={}; +0135 model.rxnNotes={}; +0136 model.rxnReferences={}; +0137 model.rxnConfidenceScores=[]; +0138 model.genes={}; +0139 model.geneComps=[]; +0140 model.geneMiriams={}; +0141 model.geneShortNames={}; +0142 model.proteins={}; +0143 model.metNames={}; +0144 model.metComps=[]; +0145 model.inchis={}; +0146 model.metFormulas={}; +0147 model.metMiriams={}; +0148 model.metCharges=[]; +0149 model.unconstrained=[]; +0150 +0151 %Load the model using libSBML +0152 [modelSBML,errorMsg] = TranslateSBML_RAVEN(fileName,0,0,[1 1]); +0153 if exist('tempFile','var') +0154 delete(tempFile) +0155 end +0156 +0157 if isempty(modelSBML) +0158 EM=['There is a problem with the SBML file. Try using the SBML Validator at http://sbml.org/Facilities/Validator.\nlibSBML reports: ', errorMsg.message]; +0159 error('RAVEN:badInput', '%s', EM); +0160 end +0161 +0162 %RAVEN only supports SBML Level 3 Version 1 with the FBC version 2 package. +0163 if ~(isfield(modelSBML,'SBML_level') && isequal(modelSBML.SBML_level,3) && ... +0164 isfield(modelSBML,'SBML_version') && isequal(modelSBML.SBML_version,1) && ... +0165 isfield(modelSBML,'fbc_version') && isequal(modelSBML.fbc_version,2)) +0166 EM=['importModel only supports SBML Level 3 Version 1 with the FBC '... +0167 'version 2 package. Convert the model to this format first (for '... +0168 'example with another tool), or use an older RAVEN version to '... +0169 'import it.']; +0170 error('RAVEN:badInput', '%s', EM); +0171 end +0172 +0173 %Retrieve compartment names and IDs +0174 compartmentNames=cell(numel(modelSBML.compartment),1); +0175 compartmentIDs=cell(numel(modelSBML.compartment),1); +0176 compartmentOutside=cell(numel(modelSBML.compartment),1); +0177 compartmentMiriams=cell(numel(modelSBML.compartment),1); +0178 +0179 if isfield(modelSBML.compartment,'sboTerm') && isscalar(unique([modelSBML.compartment.sboTerm])) +0180 %If all the SBO terms are identical, do not add them to compMiriams +0181 modelSBML.compartment = rmfield(modelSBML.compartment,'sboTerm'); +0182 end +0183 +0184 for i=1:numel(modelSBML.compartment) +0185 compartmentNames{i}=modelSBML.compartment(i).name; +0186 compartmentIDs{i}=modelSBML.compartment(i).id; +0187 if isfield(modelSBML.compartment(i),'outside') +0188 if ~isempty(modelSBML.compartment(i).outside) +0189 compartmentOutside{i}=modelSBML.compartment(i).outside; +0190 else +0191 compartmentOutside{i}=''; +0192 end +0193 else +0194 compartmentOutside{i}=[]; +0195 end +0196 +0197 if isfield(modelSBML.compartment(i),'annotation') +0198 compartmentMiriams{i}=parseMiriam(modelSBML.compartment(i).annotation); +0199 else +0200 compartmentMiriams{i}=[]; +0201 end +0202 +0203 if isfield(modelSBML.compartment(i),'sboTerm') && ~(modelSBML.compartment(i).sboTerm==-1) +0204 compartmentMiriams{i} = addSBOtoMiriam(compartmentMiriams{i},modelSBML.compartment(i).sboTerm); +0205 end +0206 end +0207 +0208 %If there are no compartment names then use compartment id as name +0209 if all(cellfun(@isempty,compartmentNames)) +0210 compartmentNames=compartmentIDs; +0211 end +0212 +0213 %Retrieve info on metabolites +0214 metaboliteNames={}; +0215 metaboliteIDs={}; +0216 metaboliteCompartments={}; +0217 metaboliteUnconstrained=[]; +0218 metaboliteFormula={}; +0219 metaboliteInChI={}; +0220 metaboliteMiriams={}; +0221 metaboliteCharges=[]; +0222 metaboliteDeltaG=[]; +0223 +0224 %Gene information is collected from fbc_geneProduct further below +0225 geneMiriams={}; +0226 geneShortNames={}; +0227 proteins={}; +0228 +0229 %In FBC v2 all species are metabolites; genes are stored as fbc_geneProduct +0230 %and are handled separately below. +0231 metSBOs = []; +0232 %Regex of compartment names, later to be used to remove from metabolite +0233 %names if present as suffix. +0234 escapedCompNames = regexptranslate('escape',{modelSBML.compartment.name}); +0235 regexCompNames = ['\s?\[((' strjoin(escapedCompNames,')|(') '))\]$']; +0236 for i=1:numel(modelSBML.species) +0237 metaboliteNames{numel(metaboliteNames)+1,1}=modelSBML.species(i).name; +0238 metaboliteIDs{numel(metaboliteIDs)+1,1}=modelSBML.species(i).id; +0239 metaboliteCompartments{numel(metaboliteCompartments)+1,1}=modelSBML.species(i).compartment; +0240 metaboliteUnconstrained(numel(metaboliteUnconstrained)+1,1)=modelSBML.species(i).boundaryCondition; +0241 +0242 %For each metabolite retrieve the formula and the InChI code if +0243 %available First add the InChI code and the formula from the +0244 %InChI. This allows for overwriting the formula by setting the +0245 %actual formula field +0246 if ~isempty(modelSBML.species(i).annotation) +0247 %Get the formula if available +0248 startString='>InChI='; +0249 endString='</in:inchi>'; +0250 formStart=strfind(modelSBML.species(i).annotation,startString); +0251 if isempty(formStart) +0252 startString='InChI='; +0253 endString='"/>'; +0254 end +0255 formStart=strfind(modelSBML.species(i).annotation,startString); +0256 if ~isempty(formStart) +0257 formEnd=strfind(modelSBML.species(i).annotation,endString); +0258 formEndIndex=find(formEnd>formStart, 1 ); +0259 formula=modelSBML.species(i).annotation(formStart+numel(startString):formEnd(formEndIndex)-1); +0260 metaboliteInChI{numel(metaboliteInChI)+1,1}=formula; +0261 +0262 %The composition is most often present between the +0263 %first and second "/" in the model. In some simple +0264 %molecules, such as salts, there is no second "/". The +0265 %formula is then assumed to be to the end of the string +0266 compositionIndexes=strfind(formula,'/'); +0267 if numel(compositionIndexes)>1 +0268 metaboliteFormula{numel(metaboliteFormula)+1,1}=... +0269 formula(compositionIndexes(1)+1:compositionIndexes(2)-1); +0270 else +0271 if isscalar(compositionIndexes) +0272 %Probably a simple molecule which can have only +0273 %one conformation +0274 metaboliteFormula{numel(metaboliteFormula)+1,1}=... +0275 formula(compositionIndexes(1)+1:numel(formula)); +0276 else +0277 metaboliteFormula{numel(metaboliteFormula)+1,1}=''; +0278 end +0279 end +0280 elseif isfield(modelSBML.species(i),'fbc_chemicalFormula') +0281 metaboliteInChI{numel(metaboliteInChI)+1,1}=''; +0282 if ~isempty(modelSBML.species(i).fbc_chemicalFormula) +0283 %Cannot extract InChi from formula, so remains +0284 %empty +0285 metaboliteFormula{numel(metaboliteFormula)+1,1}=modelSBML.species(i).fbc_chemicalFormula; +0286 else +0287 metaboliteFormula{numel(metaboliteFormula)+1,1}=''; +0288 end +0289 else +0290 metaboliteInChI{numel(metaboliteInChI)+1,1}=''; +0291 metaboliteFormula{numel(metaboliteFormula)+1,1}=''; +0292 end +0293 +0294 %Get Miriam info +0295 metMiriam=parseMiriam(modelSBML.species(i).annotation); +0296 metaboliteMiriams{numel(metaboliteMiriams)+1,1}=metMiriam; +0297 else +0298 metaboliteInChI{numel(metaboliteInChI)+1,1}=''; +0299 metaboliteFormula{numel(metaboliteFormula)+1,1}=''; +0300 metaboliteMiriams{numel(metaboliteMiriams)+1,1}=[]; +0301 end +0302 %Get SBO term. One entry per species, always, so metSBOs stays aligned +0303 %with metaboliteNames below even when some species have no SBO term; +0304 %-1 (SBML's own "unset" value) marks those. +0305 if isfield(modelSBML.species(i),'sboTerm') && ~(modelSBML.species(i).sboTerm==-1) +0306 metSBOs(end+1,1) = modelSBML.species(i).sboTerm; +0307 else +0308 metSBOs(end+1,1) = -1; +0309 end +0310 +0311 %Remove trailing [compartment] from metabolite name if present +0312 metaboliteNames{end,1}=regexprep(metaboliteNames{end,1},regexCompNames,''); +0313 if isfield(modelSBML.species(i),'fbc_charge') +0314 if ~isempty(modelSBML.species(i).fbc_charge) && modelSBML.species(i).isSetfbc_charge +0315 metaboliteCharges(numel(metaboliteCharges)+1,1)=double(modelSBML.species(i).fbc_charge); +0316 else +0317 metaboliteCharges(numel(metaboliteCharges)+1,1)=NaN; +0318 end +0319 else +0320 metaboliteCharges(numel(metaboliteCharges)+1,1)=NaN; +0321 end +0322 %deltaG: same <p>deltaG: VALUE</p> notes convention exportModel writes +0323 %and raven-toolbox reads/writes -- see RAVEN#724. +0324 %str2double('')==NaN, matching metaboliteDeltaG's own NaN default. +0325 if isfield(modelSBML.species(i),'notes') +0326 metaboliteDeltaG(numel(metaboliteDeltaG)+1,1)=str2double(parseNote(modelSBML.species(i).notes,'deltaG')); +0327 else +0328 metaboliteDeltaG(numel(metaboliteDeltaG)+1,1)=NaN; +0329 end +0330 %Additional information from FBC format Chemical formula +0331 if isfield(modelSBML.species(i),'fbc_chemicalFormula') +0332 if ~isempty(modelSBML.species(i).fbc_chemicalFormula) +0333 metaboliteFormula{numel(metaboliteFormula),1}=modelSBML.species(i).fbc_chemicalFormula; +0334 end +0335 end +0336 end +0337 +0338 %Add SBO terms to metabolite miriam fields +0339 if numel(unique(metSBOs)) > 1 +0340 for i = 1:numel(metaboliteNames) +0341 if metSBOs(i) ~= -1 +0342 metaboliteMiriams{i} = addSBOtoMiriam(metaboliteMiriams{i},metSBOs(i)); +0343 end +0344 end +0345 end +0346 +0347 %Retrieve info on reactions +0348 reactionNames=cell(numel(modelSBML.reaction),1); +0349 reactionIDs=cell(numel(modelSBML.reaction),1); +0350 subsystems=cell(numel(modelSBML.reaction),1); +0351 eccodes=cell(numel(modelSBML.reaction),1); +0352 eccodes(:,:)=cellstr(''); +0353 rxnconfidencescores=NaN(numel(modelSBML.reaction),1); +0354 rxndeltag=NaN(numel(modelSBML.reaction),1); +0355 rxnreferences=cell(numel(modelSBML.reaction),1); +0356 rxnreferences(:,:)=cellstr(''); +0357 rxnnotes=cell(numel(modelSBML.reaction),1); +0358 rxnnotes(:,:)=cellstr(''); +0359 grRules=cell(numel(modelSBML.reaction),1); +0360 grRules(:,:)=cellstr(''); +0361 rxnComps=zeros(numel(modelSBML.reaction),1); +0362 rxnMiriams=cell(numel(modelSBML.reaction),1); +0363 reactionReversibility=zeros(numel(modelSBML.reaction),1); +0364 reactionUB=zeros(numel(modelSBML.reaction),1); +0365 reactionLB=zeros(numel(modelSBML.reaction),1); +0366 reactionObjective=zeros(numel(modelSBML.reaction),1); +0367 +0368 %Construct the stoichiometric matrix while the reaction info is read. +0369 %Accumulated as (row,col,value) triples and assembled into a sparse +0370 %matrix in one pass at the end (sparse() sums duplicate (row,col) +0371 %triples, matching the +'d accumulation below), rather than allocating a +0372 %dense (mets x rxns) matrix, which for a genome-scale model can be +0373 %hundreds of MB even though S is typically >95% sparse. +0374 nStoichEntries=sum(arrayfun(@(r) numel(r.reactant)+numel(r.product),modelSBML.reaction)); +0375 sRow=zeros(nStoichEntries,1); +0376 sCol=zeros(nStoichEntries,1); +0377 sVal=zeros(nStoichEntries,1); +0378 sN=0; +0379 +0380 counter=0; +0381 %If FBC, then bounds have parameter ids defined for the whole model +0382 if isfield(modelSBML,'parameter') +0383 parameter.name=cell(numel(modelSBML.parameter),1); +0384 parameter.name={modelSBML.parameter(:).id}'; +0385 parameter.value={modelSBML.parameter(:).value}'; +0386 end +0387 +0388 if isfield(modelSBML.reaction,'sboTerm') && isscalar(unique([modelSBML.reaction.sboTerm])) +0389 %If all the SBO terms are identical, do not add them to rxnMiriams +0390 modelSBML.reaction = rmfield(modelSBML.reaction,'sboTerm'); +0391 end +0392 +0393 for i=1:numel(modelSBML.reaction) +0394 +0395 counter=counter+1; +0396 +0397 reactionNames{counter}=modelSBML.reaction(i).name; +0398 +0399 reactionIDs{counter}=modelSBML.reaction(i).id; +0400 reactionReversibility(counter)=modelSBML.reaction(i).reversible; +0401 +0402 %If model is FBC, first get parameter of bound and then replace it with +0403 %the correct value. Probably faster with replace(), but this was only +0404 %introduced in Matlab R2016b +0405 if isfield(modelSBML.reaction(i),'fbc_lowerFluxBound') +0406 lb=modelSBML.reaction(i).fbc_lowerFluxBound; +0407 ub=modelSBML.reaction(i).fbc_upperFluxBound; +0408 [~,fluxBoundIdx] = ismember({lb,ub},parameter.name); +0409 reactionLB(counter) = parameter.value{fluxBoundIdx(1)}; +0410 reactionUB(counter) = parameter.value{fluxBoundIdx(2)}; +0411 else +0412 if reactionReversibility(counter)==true +0413 reactionLB(counter)=-inf; +0414 else +0415 reactionLB(counter)=0; +0416 end +0417 reactionUB(counter)=inf; +0418 reactionObjective(counter)=0; +0419 end +0420 +0421 %Find the associated gene if available. In FBC v2 gene association data +0422 %is stored in the fbc_geneProductAssociation field. +0423 if isfield(modelSBML.reaction(i),'fbc_geneProductAssociation') +0424 if ~isempty(modelSBML.reaction(i).fbc_geneProductAssociation) && ~isempty(modelSBML.reaction(i).fbc_geneProductAssociation.fbc_association) +0425 grRules{counter}=modelSBML.reaction(i).fbc_geneProductAssociation.fbc_association.fbc_association; +0426 end +0427 end +0428 +0429 %Add reaction compartment +0430 rxnComp=''; +0431 if isfield(modelSBML.reaction(i),'compartment') +0432 if ~isempty(modelSBML.reaction(i).compartment) +0433 rxnComp=modelSBML.reaction(i).compartment; +0434 end +0435 end +0436 if ~isempty(rxnComp) +0437 %Find it in the compartment list +0438 [~, J]=ismember(rxnComp,compartmentIDs); +0439 rxnComps(counter)=J; +0440 end +0441 0442 -0443 -0444 miriamStruct=parseMiriam(modelSBML.reaction(i).annotation); -0445 rxnMiriams{counter}=miriamStruct; -0446 if isfield(modelSBML.reaction(i),'notes') -0447 %parseNote joins several SUBSYSTEM notes on a reaction with ';', -0448 %so split on it, or a reaction with more than one subsystem ends -0449 %up with a single subsystem literally named "a;b" -0450 subsystems{counter,1}=strtrim(strsplit(parseNote(modelSBML.reaction(i).notes,'SUBSYSTEM'),';')); -0451 subsystems{counter,1}(cellfun('isempty',subsystems{counter,1})) = []; -0452 if strfind(modelSBML.reaction(i).notes,'Confidence Level') -0453 confScore = parseNote(modelSBML.reaction(i).notes,'Confidence Level'); -0454 if isempty(confScore) -0455 confScore = 0; -0456 end -0457 rxnconfidencescores(counter)=str2double(confScore); -0458 end -0459 rxnreferences{counter,1}=parseNote(modelSBML.reaction(i).notes,'AUTHORS'); -0460 rxnnotes{counter,1}=parseNote(modelSBML.reaction(i).notes,'NOTES'); -0461 %deltaG: same <p>deltaG: VALUE</p> notes convention exportModel -0462 %writes and raven-toolbox reads/writes -- see RAVEN#724. -0463 %str2double('')==NaN, matching rxndeltag's own NaN default. -0464 rxndeltag(counter)=str2double(parseNote(modelSBML.reaction(i).notes,'deltaG')); -0465 end -0466 -0467 %Get SBO terms -0468 if isfield(modelSBML.reaction(i),'sboTerm') && ~(modelSBML.reaction(i).sboTerm==-1) -0469 rxnMiriams{counter} = addSBOtoMiriam(rxnMiriams{counter}, modelSBML.reaction(i).sboTerm); -0470 end -0471 -0472 %Get ec-codes -0473 eccode=''; -0474 if ~isempty(modelSBML.reaction(i).annotation) -0475 if strfind(modelSBML.reaction(i).annotation,'http://identifiers.org/ec-code') -0476 eccode=parseAnnotation(modelSBML.reaction(i).annotation,'http://identifiers.org/','/','ec-code'); -0477 elseif strfind(modelSBML.reaction(i).annotation,'https://identifiers.org/ec-code') -0478 eccode=parseAnnotation(modelSBML.reaction(i).annotation,'https://identifiers.org/','/','ec-code'); -0479 end -0480 end -0481 eccodes{counter}=eccode; -0482 -0483 %Add all reactants -0484 for j=1:numel(modelSBML.reaction(i).reactant) -0485 %Get the index of the metabolite in metaboliteIDs. External -0486 %metabolites will be removed at a later stage -0487 metIndex=find(strcmp(modelSBML.reaction(i).reactant(j).species,metaboliteIDs),1); -0488 if isempty(metIndex) -0489 EM=['Could not find metabolite ' modelSBML.reaction(i).reactant(j).species ' in reaction ' reactionIDs{counter}]; -0490 error('RAVEN:badInput', '%s', EM); -0491 end -0492 sN=sN+1; -0493 sRow(sN)=metIndex; -0494 sCol(sN)=counter; -0495 sVal(sN)=modelSBML.reaction(i).reactant(j).stoichiometry*-1; -0496 end -0497 -0498 %Add all products -0499 for j=1:numel(modelSBML.reaction(i).product) -0500 %Get the index of the metabolite in metaboliteIDs. -0501 metIndex=find(strcmp(modelSBML.reaction(i).product(j).species,metaboliteIDs),1); -0502 if isempty(metIndex) -0503 EM=['Could not find metabolite ' modelSBML.reaction(i).product(j).species ' in reaction ' reactionIDs{counter}]; -0504 error('RAVEN:badInput', '%s', EM); -0505 end -0506 sN=sN+1; -0507 sRow(sN)=metIndex; -0508 sCol(sN)=counter; -0509 sVal(sN)=modelSBML.reaction(i).product(j).stoichiometry; -0510 end -0511 end -0512 -0513 %if FBC, objective function is separately defined. Multiple objective -0514 %functions can be defined, one is set as active -0515 if isfield(modelSBML, 'fbc_activeObjective') -0516 obj=modelSBML.fbc_activeObjective; -0517 for i=1:numel(modelSBML.fbc_objective) -0518 if strcmp(obj,modelSBML.fbc_objective(i).fbc_id) -0519 if ~isempty(modelSBML.fbc_objective(i).fbc_fluxObjective) -0520 %fbc_fluxObjective is a struct array when the objective is -0521 %a combination of more than one reaction; indexing it -0522 %directly would silently keep only the first -0523 fluxObjective=modelSBML.fbc_objective(i).fbc_fluxObjective; -0524 for k=1:numel(fluxObjective) -0525 idx=ismember(reactionIDs,fluxObjective(k).fbc_reaction); -0526 reactionObjective(idx)=fluxObjective(k).fbc_coefficient; -0527 end -0528 end -0529 end -0530 end -0531 end -0532 -0533 %subSystems can be stored as groups instead of in annotations -0534 if isfield(modelSBML,'groups_group') -0535 for i=1:numel(modelSBML.groups_group) -0536 groupreactions={modelSBML.groups_group(i).groups_member(:).groups_idRef}; -0537 [~, idx] = ismember(groupreactions, reactionIDs); -0538 if any(idx) -0539 for j=1:numel(idx) -0540 if isempty(subsystems{idx(j)}) % First subsystem -0541 subsystems{idx(j)} = {modelSBML.groups_group(i).groups_name}; -0542 else % Consecutive subsystems: concatenate -0543 subsystems{idx(j)} = horzcat(subsystems{idx(j)}, modelSBML.groups_group(i).groups_name); -0544 end -0545 end -0546 end -0547 end -0548 end -0549 -0550 %Shrink the structures if complex-forming reactions had to be skipped -0551 reactionNames=reactionNames(1:counter); -0552 reactionIDs=reactionIDs(1:counter); -0553 subsystems=subsystems(1:counter); -0554 eccodes=eccodes(1:counter); -0555 rxnconfidencescores=rxnconfidencescores(1:counter); -0556 rxndeltag=rxndeltag(1:counter); -0557 rxnreferences=rxnreferences(1:counter); -0558 rxnnotes=rxnnotes(1:counter); -0559 grRules=grRules(1:counter); -0560 rxnMiriams=rxnMiriams(1:counter); -0561 reactionReversibility=reactionReversibility(1:counter); -0562 reactionUB=reactionUB(1:counter); -0563 reactionLB=reactionLB(1:counter); -0564 reactionObjective=reactionObjective(1:counter); -0565 -0566 model.name=modelSBML.name; -0567 model.id=modelSBML.id; -0568 model.rxns=reactionIDs; -0569 model.mets=metaboliteIDs; -0570 model.S=sparse(sRow(1:sN),sCol(1:sN),sVal(1:sN),numel(metaboliteIDs),counter); -0571 model.lb=reactionLB; -0572 model.ub=reactionUB; -0573 model.rev=reactionReversibility; -0574 model.c=reactionObjective; -0575 model.b=zeros(numel(metaboliteIDs),1); -0576 model.comps=compartmentIDs; -0577 model.compNames=compartmentNames; -0578 model.rxnConfidenceScores=rxnconfidencescores; -0579 model.rxnReferences=rxnreferences; -0580 model.rxnNotes=rxnnotes; -0581 model.rxnDeltaG=rxndeltag; -0582 -0583 %Load annotation if available. If there are several authors, only the first -0584 %author credentials are imported -0585 if isfield(modelSBML,'annotation') -0586 endString='</'; -0587 I=strfind(modelSBML.annotation,endString); -0588 J=strfind(modelSBML.annotation,'<vCard:Family>'); -0589 if any(J) -0590 model.annotation.familyName=modelSBML.annotation(J(1)+14:I(find(I>J(1),1))-1); -0591 end -0592 J=strfind(modelSBML.annotation,'<vCard:Given>'); -0593 if any(J) -0594 model.annotation.givenName=modelSBML.annotation(J(1)+13:I(find(I>J(1),1))-1); -0595 end -0596 J=strfind(modelSBML.annotation,'<vCard:EMAIL>'); -0597 if any(J) -0598 model.annotation.email=modelSBML.annotation(J(1)+13:I(find(I>J(1),1))-1); -0599 end -0600 J=strfind(modelSBML.annotation,'<vCard:Orgname>'); -0601 if any(J) -0602 model.annotation.organization=modelSBML.annotation(J(1)+15:I(find(I>J(1),1))-1); -0603 end -0604 endString='"/>'; -0605 I=strfind(modelSBML.annotation,endString); -0606 %Finding whether the taxonomy is written in the old or the new way -0607 J=strfind(modelSBML.annotation,'"http://identifiers.org/'); -0608 if any(J) -0609 I = I(find(I>J,1))-1; -0610 J = J(find(J<I,1))+24; -0611 model.annotation.taxonomy=modelSBML.annotation(J:I); -0612 else -0613 J=strfind(modelSBML.annotation,'"https://identifiers.org/'); -0614 if any(J) -0615 I = I(find(I>J,1))-1; -0616 J = J(find(J<I,1))+25; -0617 model.annotation.taxonomy=modelSBML.annotation(J:I); -0618 end -0619 end -0620 end -0621 if isfield(modelSBML,'notes') -0622 startString=strfind(modelSBML.notes,'xhtml">'); -0623 endString=strfind(modelSBML.notes,'</body>'); -0624 if any(startString) && any(endString) -0625 model.annotation.note=modelSBML.notes(startString(1)+7:endString-1); -0626 model.annotation.note=regexprep(model.annotation.note,'<p.*?>|</p.*?>',''); -0627 model.annotation.note=strtrim(model.annotation.note); -0628 if regexp(model.annotation.note,'This file was generated using the exportModel function in RAVEN Toolbox \d\.\d and OutputSBML in libSBML') -0629 model.annotation=rmfield(model.annotation,'note'); % Default note added when running exportModel -0630 end -0631 end -0632 end -0633 -0634 if any(~cellfun(@isempty,compartmentOutside)) -0635 model.compOutside=compartmentOutside; -0636 end -0637 -0638 model.rxnNames=reactionNames; -0639 model.metNames=metaboliteNames; -0640 -0641 %Match the compartments for metabolites -0642 [~, J]=ismember(metaboliteCompartments,model.comps); -0643 model.metComps=J; -0644 -0645 %If any gene associations have been loaded, build the gene list -0646 if ~all(cellfun(@isempty,grRules)) -0647 %If fbc_geneProduct exists, follow the specified gene order, such -0648 %that matching geneShortNames in function below will work -0649 if isfield(modelSBML,'fbc_geneProduct') -0650 genes={modelSBML.fbc_geneProduct.fbc_id}; -0651 -0652 %Get gene Miriams from fbc_geneProduct -0653 if isempty(geneMiriams) -0654 geneMiriams = cell(numel(genes),1); -0655 if isfield(modelSBML.fbc_geneProduct,'sboTerm') && isscalar(unique([modelSBML.fbc_geneProduct.sboTerm])) -0656 %If all the SBO terms are identical, do not add them to geneMiriams -0657 modelSBML.fbc_geneProduct = rmfield(modelSBML.fbc_geneProduct,'sboTerm'); -0658 end -0659 for i = 1:numel(genes) -0660 geneMiriams{i}=parseMiriam(modelSBML.fbc_geneProduct(i).annotation); -0661 if isfield(modelSBML.fbc_geneProduct(i),'sboTerm') && ~(modelSBML.fbc_geneProduct(i).sboTerm==-1) -0662 geneMiriams{i} = addSBOtoMiriam(geneMiriams{i},modelSBML.fbc_geneProduct(i).sboTerm); -0663 end -0664 end -0665 end -0666 proteins={modelSBML.fbc_geneProduct.fbc_name}; -0667 else -0668 genes=getGenesFromGrRules(grRules); -0669 end -0670 model.genes=genes; -0671 model.grRules=grRules; -0672 [grRules,rxnGeneMat] = standardizeGrRules(model,true); -0673 model.grRules = grRules; -0674 model.rxnGeneMat = rxnGeneMat; -0675 end -0676 -0677 if all(cellfun(@isempty,geneShortNames)) -0678 if isfield(modelSBML,'fbc_geneProduct') -0679 %Iterate fbc_geneProduct directly: genes is only assigned above -0680 %when grRules is non-empty, but fbc_geneProduct can be present -0681 %(even as an empty list) regardless of that. -0682 for i=1:numel(modelSBML.fbc_geneProduct) -0683 if ~isempty(modelSBML.fbc_geneProduct(i).fbc_label) -0684 geneShortNames{i,1}=modelSBML.fbc_geneProduct(i).fbc_label; -0685 elseif ~isempty(modelSBML.fbc_geneProduct(i).fbc_name) -0686 geneShortNames{i,1}=modelSBML.fbc_geneProduct(i).fbc_name; -0687 else -0688 geneShortNames{i,1}=''; -0689 end -0690 end -0691 end -0692 end -0693 -0694 %If any InChIs have been loaded -0695 if any(~cellfun(@isempty,metaboliteInChI)) -0696 model.inchis=metaboliteInChI; -0697 end -0698 -0699 %If any formulas have been loaded -0700 if any(~cellfun(@isempty,metaboliteFormula)) -0701 model.metFormulas=metaboliteFormula; -0702 end -0703 -0704 %If any charges have been loaded -0705 if ~isempty(metaboliteCharges) -0706 model.metCharges=metaboliteCharges; -0707 end -0708 -0709 %If any deltaG values have been loaded -0710 if ~isempty(metaboliteDeltaG) && ~all(isnan(metaboliteDeltaG)) -0711 model.metDeltaG=metaboliteDeltaG; -0712 end -0713 -0714 %If any gene short names have been loaded -0715 if any(~cellfun(@isempty,geneShortNames)) -0716 model.geneShortNames=geneShortNames; -0717 end -0718 -0719 %If any Miriam strings for compartments have been loaded -0720 if any(~cellfun(@isempty,compartmentMiriams)) -0721 model.compMiriams=compartmentMiriams; -0722 end -0723 -0724 %If any Miriam strings for metabolites have been loaded -0725 if any(~cellfun(@isempty,metaboliteMiriams)) -0726 model.metMiriams=metaboliteMiriams; -0727 end -0728 -0729 %If any subsystems have been loaded -0730 if any(~cellfun(@isempty,subsystems)) -0731 model.subSystems=subsystems; -0732 end -0733 if any(rxnComps) -0734 if all(rxnComps) -0735 model.rxnComps=rxnComps; -0736 else -0737 if supressWarnings==false -0738 EM='The compartments for the following reactions could not be matched. Ignoring reaction compartment information'; -0739 warning('RAVEN:warning', '%s', ravenList(EM,model.rxns(rxnComps==0))); -0740 end -0741 end -0742 end -0743 -0744 %If any ec-codes have been loaded -0745 if any(~cellfun(@isempty,eccodes)) -0746 model.eccodes=eccodes; -0747 end -0748 -0749 %If any Miriam strings for reactions have been loaded -0750 if any(~cellfun(@isempty,rxnMiriams)) -0751 model.rxnMiriams=rxnMiriams; -0752 end -0753 -0754 %If any Miriam strings for genes have been loaded -0755 if any(~cellfun(@isempty,geneMiriams)) -0756 model.geneMiriams=geneMiriams; -0757 end -0758 -0759 %If any protein strings have been loaded -0760 if any(~cellfun(@isempty,proteins)) -0761 proteins = reshape(proteins,[],1); -0762 model.proteins=proteins; -0763 end -0764 -0765 model.unconstrained=metaboliteUnconstrained; -0766 -0767 %Convert SBML IDs back into their original strings. Here we are using part -0768 %from convertSBMLID, originating from the COBRA Toolbox -0769 model.rxns=regexprep(model.rxns,'__([0-9]+)__','${char(str2num($1))}'); -0770 model.mets=regexprep(model.mets,'__([0-9]+)__','${char(str2num($1))}'); -0771 model.comps=regexprep(model.comps,'__([0-9]+)__','${char(str2num($1))}'); -0772 model.grRules=regexprep(model.grRules,'__([0-9]+)__','${char(str2num($1))}'); -0773 model.genes=regexprep(model.genes,'__([0-9]+)__','${char(str2num($1))}'); -0774 model.id=regexprep(model.id,'__([0-9]+)__','${char(str2num($1))}'); -0775 -0776 if removePrefix -0777 [model, hasChanged]=removeIdentifierPrefix(model); -0778 warning('RAVEN:warning', '%s', ravenList(['The following fields have prefixes removed from all entries. '... -0779 'If this is undesired, run importModel with removePrefix as false. Example: '... -0780 'importModel(''filename.xml'',[],false);'],hasChanged)) -0781 end -0782 -0783 %Remove unused fields -0784 if isempty(model.annotation) -0785 model=rmfield(model,'annotation'); -0786 end -0787 if isempty(model.compOutside) -0788 model=rmfield(model,'compOutside'); -0789 end -0790 if isempty(model.compMiriams) -0791 model=rmfield(model,'compMiriams'); -0792 end -0793 if isempty(model.rxnComps) -0794 model=rmfield(model,'rxnComps'); -0795 end -0796 if isempty(model.grRules) -0797 model=rmfield(model,'grRules'); -0798 end -0799 if isempty(model.rxnGeneMat) -0800 model=rmfield(model,'rxnGeneMat'); -0801 end -0802 if isempty(model.subSystems) -0803 model=rmfield(model,'subSystems'); -0804 else -0805 model.subSystems(cellfun(@isempty,subsystems))={{''}}; -0806 % If all subSystems have single entries, then unnest them -0807 if all(cellfun(@(x) iscell(x) && isscalar(x), model.subSystems)) -0808 model.subSystems = transpose([model.subSystems{:}]); -0809 end -0810 end -0811 if isempty(model.eccodes) -0812 model=rmfield(model,'eccodes'); -0813 end -0814 if isempty(model.rxnMiriams) -0815 model=rmfield(model,'rxnMiriams'); -0816 end -0817 if cellfun(@isempty,model.rxnNotes) -0818 model=rmfield(model,'rxnNotes'); -0819 end -0820 if cellfun(@isempty,model.rxnReferences) -0821 model=rmfield(model,'rxnReferences'); -0822 end -0823 if isempty(model.rxnConfidenceScores) || all(isnan(model.rxnConfidenceScores)) -0824 model=rmfield(model,'rxnConfidenceScores'); -0825 end -0826 if isempty(model.rxnDeltaG) || all(isnan(model.rxnDeltaG)) -0827 model=rmfield(model,'rxnDeltaG'); -0828 end -0829 if isempty(model.genes) -0830 model=rmfield(model,'genes'); -0831 elseif isrow(model.genes) -0832 model.genes=transpose(model.genes); -0833 end -0834 if isempty(model.geneComps) -0835 model=rmfield(model,'geneComps'); -0836 end -0837 if isempty(model.geneMiriams) -0838 model=rmfield(model,'geneMiriams'); -0839 end -0840 if isempty(model.geneShortNames) -0841 model=rmfield(model,'geneShortNames'); -0842 end -0843 if isempty(model.proteins) -0844 model=rmfield(model,'proteins'); -0845 end -0846 if isempty(model.inchis) -0847 model=rmfield(model,'inchis'); -0848 end -0849 if isempty(model.metFormulas) -0850 model=rmfield(model,'metFormulas'); -0851 end -0852 if isempty(model.metMiriams) -0853 model=rmfield(model,'metMiriams'); -0854 end -0855 %Unlike inchis/metFormulas/metMiriams above, metCharges always gets one -0856 %entry per metabolite (NaN where SBML had no fbc_charge), so it is never -0857 %literally empty; drop it only when none of those entries are real, not -0858 %when they are all genuinely zero (any(zeros)==false would remove a -0859 %field of real, all-neutral charges; any(NaN)==true would keep an -0860 %all-unset field) -0861 if all(isnan(model.metCharges)) -0862 model=rmfield(model,'metCharges'); -0863 end -0864 -0865 %This just removes the grRules if no genes have been loaded -0866 if ~isfield(model,'genes') && isfield(model,'grRules') -0867 model=rmfield(model,'grRules'); -0868 end -0869 -0870 %Print warnings about bad structure -0871 if supressWarnings==false -0872 checkModelStruct(model,false); -0873 end -0874 -0875 if removeExcMets==true -0876 model=simplifyModel(model); +0443 miriamStruct=parseMiriam(modelSBML.reaction(i).annotation); +0444 rxnMiriams{counter}=miriamStruct; +0445 if isfield(modelSBML.reaction(i),'notes') +0446 %parseNote joins several SUBSYSTEM notes on a reaction with ';', +0447 %so split on it, or a reaction with more than one subsystem ends +0448 %up with a single subsystem literally named "a;b" +0449 subsystems{counter,1}=strtrim(strsplit(parseNote(modelSBML.reaction(i).notes,'SUBSYSTEM'),';')); +0450 subsystems{counter,1}(cellfun('isempty',subsystems{counter,1})) = []; +0451 if strfind(modelSBML.reaction(i).notes,'Confidence Level') +0452 confScore = parseNote(modelSBML.reaction(i).notes,'Confidence Level'); +0453 if isempty(confScore) +0454 confScore = 0; +0455 end +0456 rxnconfidencescores(counter)=str2double(confScore); +0457 end +0458 rxnreferences{counter,1}=parseNote(modelSBML.reaction(i).notes,'AUTHORS'); +0459 rxnnotes{counter,1}=parseNote(modelSBML.reaction(i).notes,'NOTES'); +0460 %deltaG: same <p>deltaG: VALUE</p> notes convention exportModel +0461 %writes and raven-toolbox reads/writes -- see RAVEN#724. +0462 %str2double('')==NaN, matching rxndeltag's own NaN default. +0463 rxndeltag(counter)=str2double(parseNote(modelSBML.reaction(i).notes,'deltaG')); +0464 end +0465 +0466 %Get SBO terms +0467 if isfield(modelSBML.reaction(i),'sboTerm') && ~(modelSBML.reaction(i).sboTerm==-1) +0468 rxnMiriams{counter} = addSBOtoMiriam(rxnMiriams{counter}, modelSBML.reaction(i).sboTerm); +0469 end +0470 +0471 %Get ec-codes +0472 eccode=''; +0473 if ~isempty(modelSBML.reaction(i).annotation) +0474 if strfind(modelSBML.reaction(i).annotation,'http://identifiers.org/ec-code') +0475 eccode=parseAnnotation(modelSBML.reaction(i).annotation,'http://identifiers.org/','/','ec-code'); +0476 elseif strfind(modelSBML.reaction(i).annotation,'https://identifiers.org/ec-code') +0477 eccode=parseAnnotation(modelSBML.reaction(i).annotation,'https://identifiers.org/','/','ec-code'); +0478 end +0479 end +0480 eccodes{counter}=eccode; +0481 +0482 %Add all reactants +0483 for j=1:numel(modelSBML.reaction(i).reactant) +0484 %Get the index of the metabolite in metaboliteIDs. External +0485 %metabolites will be removed at a later stage +0486 metIndex=find(strcmp(modelSBML.reaction(i).reactant(j).species,metaboliteIDs),1); +0487 if isempty(metIndex) +0488 EM=['Could not find metabolite ' modelSBML.reaction(i).reactant(j).species ' in reaction ' reactionIDs{counter}]; +0489 error('RAVEN:badInput', '%s', EM); +0490 end +0491 sN=sN+1; +0492 sRow(sN)=metIndex; +0493 sCol(sN)=counter; +0494 sVal(sN)=modelSBML.reaction(i).reactant(j).stoichiometry*-1; +0495 end +0496 +0497 %Add all products +0498 for j=1:numel(modelSBML.reaction(i).product) +0499 %Get the index of the metabolite in metaboliteIDs. +0500 metIndex=find(strcmp(modelSBML.reaction(i).product(j).species,metaboliteIDs),1); +0501 if isempty(metIndex) +0502 EM=['Could not find metabolite ' modelSBML.reaction(i).product(j).species ' in reaction ' reactionIDs{counter}]; +0503 error('RAVEN:badInput', '%s', EM); +0504 end +0505 sN=sN+1; +0506 sRow(sN)=metIndex; +0507 sCol(sN)=counter; +0508 sVal(sN)=modelSBML.reaction(i).product(j).stoichiometry; +0509 end +0510 end +0511 +0512 %if FBC, objective function is separately defined. Multiple objective +0513 %functions can be defined, one is set as active +0514 if isfield(modelSBML, 'fbc_activeObjective') +0515 obj=modelSBML.fbc_activeObjective; +0516 for i=1:numel(modelSBML.fbc_objective) +0517 if strcmp(obj,modelSBML.fbc_objective(i).fbc_id) +0518 if ~isempty(modelSBML.fbc_objective(i).fbc_fluxObjective) +0519 %fbc_fluxObjective is a struct array when the objective is +0520 %a combination of more than one reaction; indexing it +0521 %directly would silently keep only the first +0522 fluxObjective=modelSBML.fbc_objective(i).fbc_fluxObjective; +0523 for k=1:numel(fluxObjective) +0524 idx=ismember(reactionIDs,fluxObjective(k).fbc_reaction); +0525 reactionObjective(idx)=fluxObjective(k).fbc_coefficient; +0526 end +0527 end +0528 end +0529 end +0530 end +0531 +0532 %subSystems can be stored as groups instead of in annotations +0533 if isfield(modelSBML,'groups_group') +0534 for i=1:numel(modelSBML.groups_group) +0535 groupreactions={modelSBML.groups_group(i).groups_member(:).groups_idRef}; +0536 [~, idx] = ismember(groupreactions, reactionIDs); +0537 if any(idx) +0538 for j=1:numel(idx) +0539 if isempty(subsystems{idx(j)}) % First subsystem +0540 subsystems{idx(j)} = {modelSBML.groups_group(i).groups_name}; +0541 else % Consecutive subsystems: concatenate +0542 subsystems{idx(j)} = horzcat(subsystems{idx(j)}, modelSBML.groups_group(i).groups_name); +0543 end +0544 end +0545 end +0546 end +0547 end +0548 +0549 %Shrink the structures if complex-forming reactions had to be skipped +0550 reactionNames=reactionNames(1:counter); +0551 reactionIDs=reactionIDs(1:counter); +0552 subsystems=subsystems(1:counter); +0553 eccodes=eccodes(1:counter); +0554 rxnconfidencescores=rxnconfidencescores(1:counter); +0555 rxndeltag=rxndeltag(1:counter); +0556 rxnreferences=rxnreferences(1:counter); +0557 rxnnotes=rxnnotes(1:counter); +0558 grRules=grRules(1:counter); +0559 rxnMiriams=rxnMiriams(1:counter); +0560 reactionReversibility=reactionReversibility(1:counter); +0561 reactionUB=reactionUB(1:counter); +0562 reactionLB=reactionLB(1:counter); +0563 reactionObjective=reactionObjective(1:counter); +0564 +0565 model.name=modelSBML.name; +0566 model.id=modelSBML.id; +0567 model.rxns=reactionIDs; +0568 model.mets=metaboliteIDs; +0569 model.S=sparse(sRow(1:sN),sCol(1:sN),sVal(1:sN),numel(metaboliteIDs),counter); +0570 model.lb=reactionLB; +0571 model.ub=reactionUB; +0572 model.rev=reactionReversibility; +0573 model.c=reactionObjective; +0574 model.b=zeros(numel(metaboliteIDs),1); +0575 model.comps=compartmentIDs; +0576 model.compNames=compartmentNames; +0577 model.rxnConfidenceScores=rxnconfidencescores; +0578 model.rxnReferences=rxnreferences; +0579 model.rxnNotes=rxnnotes; +0580 model.rxnDeltaG=rxndeltag; +0581 +0582 %Load annotation if available. If there are several authors, only the first +0583 %author credentials are imported +0584 if isfield(modelSBML,'annotation') +0585 endString='</'; +0586 I=strfind(modelSBML.annotation,endString); +0587 J=strfind(modelSBML.annotation,'<vCard:Family>'); +0588 if any(J) +0589 model.annotation.familyName=modelSBML.annotation(J(1)+14:I(find(I>J(1),1))-1); +0590 end +0591 J=strfind(modelSBML.annotation,'<vCard:Given>'); +0592 if any(J) +0593 model.annotation.givenName=modelSBML.annotation(J(1)+13:I(find(I>J(1),1))-1); +0594 end +0595 J=strfind(modelSBML.annotation,'<vCard:EMAIL>'); +0596 if any(J) +0597 model.annotation.email=modelSBML.annotation(J(1)+13:I(find(I>J(1),1))-1); +0598 end +0599 J=strfind(modelSBML.annotation,'<vCard:Orgname>'); +0600 if any(J) +0601 model.annotation.organization=modelSBML.annotation(J(1)+15:I(find(I>J(1),1))-1); +0602 end +0603 endString='"/>'; +0604 I=strfind(modelSBML.annotation,endString); +0605 %Finding whether the taxonomy is written in the old or the new way +0606 J=strfind(modelSBML.annotation,'"http://identifiers.org/'); +0607 if any(J) +0608 I = I(find(I>J,1))-1; +0609 J = J(find(J<I,1))+24; +0610 model.annotation.taxonomy=modelSBML.annotation(J:I); +0611 else +0612 J=strfind(modelSBML.annotation,'"https://identifiers.org/'); +0613 if any(J) +0614 I = I(find(I>J,1))-1; +0615 J = J(find(J<I,1))+25; +0616 model.annotation.taxonomy=modelSBML.annotation(J:I); +0617 end +0618 end +0619 end +0620 if isfield(modelSBML,'notes') +0621 startString=strfind(modelSBML.notes,'xhtml">'); +0622 endString=strfind(modelSBML.notes,'</body>'); +0623 if any(startString) && any(endString) +0624 model.annotation.note=modelSBML.notes(startString(1)+7:endString-1); +0625 model.annotation.note=regexprep(model.annotation.note,'<p.*?>|</p.*?>',''); +0626 model.annotation.note=strtrim(model.annotation.note); +0627 if regexp(model.annotation.note,'This file was generated using the exportModel function in RAVEN Toolbox \d\.\d and OutputSBML in libSBML') +0628 model.annotation=rmfield(model.annotation,'note'); % Default note added when running exportModel +0629 end +0630 end +0631 end +0632 +0633 if any(~cellfun(@isempty,compartmentOutside)) +0634 model.compOutside=compartmentOutside; +0635 end +0636 +0637 model.rxnNames=reactionNames; +0638 model.metNames=metaboliteNames; +0639 +0640 %Match the compartments for metabolites +0641 [~, J]=ismember(metaboliteCompartments,model.comps); +0642 model.metComps=J; +0643 +0644 %If any gene associations have been loaded, build the gene list +0645 if ~all(cellfun(@isempty,grRules)) +0646 %If fbc_geneProduct exists, follow the specified gene order, such +0647 %that matching geneShortNames in function below will work +0648 if isfield(modelSBML,'fbc_geneProduct') +0649 genes={modelSBML.fbc_geneProduct.fbc_id}; +0650 +0651 %Get gene Miriams from fbc_geneProduct +0652 if isempty(geneMiriams) +0653 geneMiriams = cell(numel(genes),1); +0654 if isfield(modelSBML.fbc_geneProduct,'sboTerm') && isscalar(unique([modelSBML.fbc_geneProduct.sboTerm])) +0655 %If all the SBO terms are identical, do not add them to geneMiriams +0656 modelSBML.fbc_geneProduct = rmfield(modelSBML.fbc_geneProduct,'sboTerm'); +0657 end +0658 for i = 1:numel(genes) +0659 geneMiriams{i}=parseMiriam(modelSBML.fbc_geneProduct(i).annotation); +0660 if isfield(modelSBML.fbc_geneProduct(i),'sboTerm') && ~(modelSBML.fbc_geneProduct(i).sboTerm==-1) +0661 geneMiriams{i} = addSBOtoMiriam(geneMiriams{i},modelSBML.fbc_geneProduct(i).sboTerm); +0662 end +0663 end +0664 end +0665 proteins={modelSBML.fbc_geneProduct.fbc_name}; +0666 else +0667 genes=getGenesFromGrRules(grRules); +0668 end +0669 model.genes=genes; +0670 model.grRules=grRules; +0671 [grRules,rxnGeneMat] = standardizeGrRules(model,true); +0672 model.grRules = grRules; +0673 model.rxnGeneMat = rxnGeneMat; +0674 end +0675 +0676 if all(cellfun(@isempty,geneShortNames)) +0677 if isfield(modelSBML,'fbc_geneProduct') +0678 %Iterate fbc_geneProduct directly: genes is only assigned above +0679 %when grRules is non-empty, but fbc_geneProduct can be present +0680 %(even as an empty list) regardless of that. +0681 for i=1:numel(modelSBML.fbc_geneProduct) +0682 if ~isempty(modelSBML.fbc_geneProduct(i).fbc_label) +0683 geneShortNames{i,1}=modelSBML.fbc_geneProduct(i).fbc_label; +0684 elseif ~isempty(modelSBML.fbc_geneProduct(i).fbc_name) +0685 geneShortNames{i,1}=modelSBML.fbc_geneProduct(i).fbc_name; +0686 else +0687 geneShortNames{i,1}=''; +0688 end +0689 end +0690 end +0691 end +0692 +0693 %If any InChIs have been loaded +0694 if any(~cellfun(@isempty,metaboliteInChI)) +0695 model.inchis=metaboliteInChI; +0696 end +0697 +0698 %If any formulas have been loaded +0699 if any(~cellfun(@isempty,metaboliteFormula)) +0700 model.metFormulas=metaboliteFormula; +0701 end +0702 +0703 %If any charges have been loaded +0704 if ~isempty(metaboliteCharges) +0705 model.metCharges=metaboliteCharges; +0706 end +0707 +0708 %If any deltaG values have been loaded +0709 if ~isempty(metaboliteDeltaG) && ~all(isnan(metaboliteDeltaG)) +0710 model.metDeltaG=metaboliteDeltaG; +0711 end +0712 +0713 %If any gene short names have been loaded +0714 if any(~cellfun(@isempty,geneShortNames)) +0715 model.geneShortNames=geneShortNames; +0716 end +0717 +0718 %If any Miriam strings for compartments have been loaded +0719 if any(~cellfun(@isempty,compartmentMiriams)) +0720 model.compMiriams=compartmentMiriams; +0721 end +0722 +0723 %If any Miriam strings for metabolites have been loaded +0724 if any(~cellfun(@isempty,metaboliteMiriams)) +0725 model.metMiriams=metaboliteMiriams; +0726 end +0727 +0728 %If any subsystems have been loaded +0729 if any(~cellfun(@isempty,subsystems)) +0730 model.subSystems=subsystems; +0731 end +0732 if any(rxnComps) +0733 if all(rxnComps) +0734 model.rxnComps=rxnComps; +0735 else +0736 if supressWarnings==false +0737 EM='The compartments for the following reactions could not be matched. Ignoring reaction compartment information'; +0738 warning('RAVEN:warning', '%s', ravenList(EM,model.rxns(rxnComps==0))); +0739 end +0740 end +0741 end +0742 +0743 %If any ec-codes have been loaded +0744 if any(~cellfun(@isempty,eccodes)) +0745 model.eccodes=eccodes; +0746 end +0747 +0748 %If any Miriam strings for reactions have been loaded +0749 if any(~cellfun(@isempty,rxnMiriams)) +0750 model.rxnMiriams=rxnMiriams; +0751 end +0752 +0753 %If any Miriam strings for genes have been loaded +0754 if any(~cellfun(@isempty,geneMiriams)) +0755 model.geneMiriams=geneMiriams; +0756 end +0757 +0758 %If any protein strings have been loaded +0759 if any(~cellfun(@isempty,proteins)) +0760 proteins = reshape(proteins,[],1); +0761 model.proteins=proteins; +0762 end +0763 +0764 model.unconstrained=metaboliteUnconstrained; +0765 +0766 %Convert SBML IDs back into their original strings. Here we are using part +0767 %from convertSBMLID, originating from the COBRA Toolbox +0768 model.rxns=regexprep(model.rxns,'__([0-9]+)__','${char(str2num($1))}'); +0769 model.mets=regexprep(model.mets,'__([0-9]+)__','${char(str2num($1))}'); +0770 model.comps=regexprep(model.comps,'__([0-9]+)__','${char(str2num($1))}'); +0771 model.grRules=regexprep(model.grRules,'__([0-9]+)__','${char(str2num($1))}'); +0772 model.genes=regexprep(model.genes,'__([0-9]+)__','${char(str2num($1))}'); +0773 model.id=regexprep(model.id,'__([0-9]+)__','${char(str2num($1))}'); +0774 +0775 if removePrefix +0776 [model, hasChanged]=removeIdentifierPrefix(model); +0777 warning('RAVEN:warning', '%s', ravenList(['The following fields have prefixes removed from all entries. '... +0778 'If this is undesired, run importModel with removePrefix as false. Example: '... +0779 'importModel(''filename.xml'',[],false);'],hasChanged)) +0780 end +0781 +0782 %Remove unused fields +0783 if isempty(model.annotation) +0784 model=rmfield(model,'annotation'); +0785 end +0786 if isempty(model.compOutside) +0787 model=rmfield(model,'compOutside'); +0788 end +0789 if isempty(model.compMiriams) +0790 model=rmfield(model,'compMiriams'); +0791 end +0792 if isempty(model.rxnComps) +0793 model=rmfield(model,'rxnComps'); +0794 end +0795 if isempty(model.grRules) +0796 model=rmfield(model,'grRules'); +0797 end +0798 if isempty(model.rxnGeneMat) +0799 model=rmfield(model,'rxnGeneMat'); +0800 end +0801 if isempty(model.subSystems) +0802 model=rmfield(model,'subSystems'); +0803 else +0804 model.subSystems(cellfun(@isempty,subsystems))={{''}}; +0805 % If all subSystems have single entries, then unnest them +0806 if all(cellfun(@(x) iscell(x) && isscalar(x), model.subSystems)) +0807 model.subSystems = transpose([model.subSystems{:}]); +0808 end +0809 end +0810 if isempty(model.eccodes) +0811 model=rmfield(model,'eccodes'); +0812 end +0813 if isempty(model.rxnMiriams) +0814 model=rmfield(model,'rxnMiriams'); +0815 end +0816 if cellfun(@isempty,model.rxnNotes) +0817 model=rmfield(model,'rxnNotes'); +0818 end +0819 if cellfun(@isempty,model.rxnReferences) +0820 model=rmfield(model,'rxnReferences'); +0821 end +0822 if isempty(model.rxnConfidenceScores) || all(isnan(model.rxnConfidenceScores)) +0823 model=rmfield(model,'rxnConfidenceScores'); +0824 end +0825 if isempty(model.rxnDeltaG) || all(isnan(model.rxnDeltaG)) +0826 model=rmfield(model,'rxnDeltaG'); +0827 end +0828 if isempty(model.genes) +0829 model=rmfield(model,'genes'); +0830 elseif isrow(model.genes) +0831 model.genes=transpose(model.genes); +0832 end +0833 if isempty(model.geneComps) +0834 model=rmfield(model,'geneComps'); +0835 end +0836 if isempty(model.geneMiriams) +0837 model=rmfield(model,'geneMiriams'); +0838 end +0839 if isempty(model.geneShortNames) +0840 model=rmfield(model,'geneShortNames'); +0841 end +0842 if isempty(model.proteins) +0843 model=rmfield(model,'proteins'); +0844 end +0845 if isempty(model.inchis) +0846 model=rmfield(model,'inchis'); +0847 end +0848 if isempty(model.metFormulas) +0849 model=rmfield(model,'metFormulas'); +0850 end +0851 if isempty(model.metMiriams) +0852 model=rmfield(model,'metMiriams'); +0853 end +0854 %Unlike inchis/metFormulas/metMiriams above, metCharges always gets one +0855 %entry per metabolite (NaN where SBML had no fbc_charge), so it is never +0856 %literally empty; drop it only when none of those entries are real, not +0857 %when they are all genuinely zero (any(zeros)==false would remove a +0858 %field of real, all-neutral charges; any(NaN)==true would keep an +0859 %all-unset field) +0860 if all(isnan(model.metCharges)) +0861 model=rmfield(model,'metCharges'); +0862 end +0863 +0864 %This just removes the grRules if no genes have been loaded +0865 if ~isfield(model,'genes') && isfield(model,'grRules') +0866 model=rmfield(model,'grRules'); +0867 end +0868 +0869 %Print warnings about bad structure +0870 if supressWarnings==false +0871 checkModelStruct(model,false); +0872 end +0873 +0874 if removeExcMets==true +0875 model=simplifyModel(model); +0876 end 0877 end -0878 end -0879 -0880 function fieldContent=parseNote(searchString,fieldName) -0881 %The function obtains the particular information from "notes" field, using -0882 %fieldName as the dummy string -0883 -0884 fieldContent=''; -0885 -0886 if strfind(searchString,fieldName) -0887 [~,targetString] = regexp(searchString,['<p>' fieldName '.*?</p>'],'tokens','match'); -0888 targetString=regexprep(targetString,'<p>|</p>',''); -0889 targetString=regexprep(targetString,[fieldName, ':'],''); -0890 for i=1:numel(targetString) -0891 fieldContent=[fieldContent ';' strtrim(targetString{1,i})]; -0892 end -0893 fieldContent=regexprep(fieldContent,'^;|;$',''); -0894 else -0895 fieldContent=''; +0878 +0879 function fieldContent=parseNote(searchString,fieldName) +0880 %The function obtains the particular information from "notes" field, using +0881 %fieldName as the dummy string +0882 +0883 fieldContent=''; +0884 +0885 if strfind(searchString,fieldName) +0886 [~,targetString] = regexp(searchString,['<p>' fieldName '.*?</p>'],'tokens','match'); +0887 targetString=regexprep(targetString,'<p>|</p>',''); +0888 targetString=regexprep(targetString,[fieldName, ':'],''); +0889 for i=1:numel(targetString) +0890 fieldContent=[fieldContent ';' strtrim(targetString{1,i})]; +0891 end +0892 fieldContent=regexprep(fieldContent,'^;|;$',''); +0893 else +0894 fieldContent=''; +0895 end 0896 end -0897 end -0898 -0899 function fieldContent=parseAnnotation(searchString,startString,midString,fieldName) -0900 -0901 fieldContent=''; -0902 -0903 %Removing whitespace characters from the ending strings, which may occur in -0904 %several cases -0905 searchString=regexprep(searchString,'" />','"/>'); -0906 [~,targetString] = regexp(searchString,['<rdf:li rdf:resource="' startString fieldName midString '.*?"/>'],'tokens','match'); -0907 targetString=regexprep(targetString,'<rdf:li rdf:resource="|"/>',''); -0908 targetString=regexprep(targetString,startString,''); -0909 targetString=regexprep(targetString,[fieldName midString],''); -0910 -0911 for i=1:numel(targetString) -0912 fieldContent=[fieldContent ';' strtrim(targetString{1,i})]; -0913 end -0914 -0915 fieldContent=regexprep(fieldContent,'^;|;$',''); -0916 end -0917 -0918 function miriamStruct=parseMiriam(searchString) -0919 %Generates miriam structure from annotation field -0920 -0921 %Finding whether miriams are written in the old or the new way -0922 if strfind(searchString,'http://identifiers.org/') -0923 startString='http://identifiers.org/'; -0924 elseif strfind(searchString,'https://identifiers.org/') -0925 startString='https://identifiers.org/'; -0926 else -0927 miriamStruct=[]; -0928 return; -0929 end -0930 -0931 miriamStruct=[]; -0932 -0933 searchString=regexprep(searchString,'" />','"/>'); -0934 [~,targetString] = regexp(searchString,'<rdf:li rdf:resource=".*?"/>','tokens','match'); -0935 targetString=regexprep(targetString,'<rdf:li rdf:resource="|"/>',''); -0936 targetString=regexprep(targetString,startString,''); -0937 -0938 fwdslash = contains(targetString,'/'); -0939 midString = cell(numel(targetString),1); -0940 midString(fwdslash) = {'/'}; -0941 midString(~fwdslash) = {':'}; -0942 -0943 counter=0; -0944 for i=1:numel(targetString) -0945 if isempty(regexp(targetString{1,i},'inchi|ec-code', 'once')) -0946 counter=counter+1; -0947 miriamStruct.name{counter,1} = regexprep(targetString{1,i},[midString{i} '.+'],'','once'); -0948 miriamStruct.value{counter,1} = regexprep(targetString{1,i},[miriamStruct.name{counter,1} midString{i}],'','once'); -0949 miriamStruct.name{counter,1} = regexprep(miriamStruct.name{counter,1},'^obo\.',''); -0950 end +0897 +0898 function fieldContent=parseAnnotation(searchString,startString,midString,fieldName) +0899 +0900 fieldContent=''; +0901 +0902 %Removing whitespace characters from the ending strings, which may occur in +0903 %several cases +0904 searchString=regexprep(searchString,'" />','"/>'); +0905 [~,targetString] = regexp(searchString,['<rdf:li rdf:resource="' startString fieldName midString '.*?"/>'],'tokens','match'); +0906 targetString=regexprep(targetString,'<rdf:li rdf:resource="|"/>',''); +0907 targetString=regexprep(targetString,startString,''); +0908 targetString=regexprep(targetString,[fieldName midString],''); +0909 +0910 for i=1:numel(targetString) +0911 fieldContent=[fieldContent ';' strtrim(targetString{1,i})]; +0912 end +0913 +0914 fieldContent=regexprep(fieldContent,'^;|;$',''); +0915 end +0916 +0917 function miriamStruct=parseMiriam(searchString) +0918 %Generates miriam structure from annotation field +0919 +0920 %Finding whether miriams are written in the old or the new way +0921 if strfind(searchString,'http://identifiers.org/') +0922 startString='http://identifiers.org/'; +0923 elseif strfind(searchString,'https://identifiers.org/') +0924 startString='https://identifiers.org/'; +0925 else +0926 miriamStruct=[]; +0927 return; +0928 end +0929 +0930 miriamStruct=[]; +0931 +0932 searchString=regexprep(searchString,'" />','"/>'); +0933 [~,targetString] = regexp(searchString,'<rdf:li rdf:resource=".*?"/>','tokens','match'); +0934 targetString=regexprep(targetString,'<rdf:li rdf:resource="|"/>',''); +0935 targetString=regexprep(targetString,startString,''); +0936 +0937 fwdslash = contains(targetString,'/'); +0938 midString = cell(numel(targetString),1); +0939 midString(fwdslash) = {'/'}; +0940 midString(~fwdslash) = {':'}; +0941 +0942 counter=0; +0943 for i=1:numel(targetString) +0944 if isempty(regexp(targetString{1,i},'inchi|ec-code', 'once')) +0945 counter=counter+1; +0946 miriamStruct.name{counter,1} = regexprep(targetString{1,i},[midString{i} '.+'],'','once'); +0947 miriamStruct.value{counter,1} = regexprep(targetString{1,i},[miriamStruct.name{counter,1} midString{i}],'','once'); +0948 miriamStruct.name{counter,1} = regexprep(miriamStruct.name{counter,1},'^obo\.',''); +0949 end +0950 end 0951 end -0952 end -0953 -0954 function miriam = addSBOtoMiriam(miriam,sboTerm) -0955 %Appends SBO term to miriam structure -0956 -0957 sboTerm = {['SBO:' sprintf('%07u',sboTerm)]}; % convert to proper format -0958 if isempty(miriam) -0959 miriam.name = {'sbo'}; -0960 miriam.value = sboTerm; -0961 elseif any(strcmp('sbo',miriam.name)) -0962 currSbo = strcmp('sbo',miriam.name); -0963 miriam.value(currSbo) = sboTerm; -0964 else -0965 miriam.name(end+1) = {'sbo'}; -0966 miriam.value(end+1) = sboTerm; -0967 end -0968 end +0952 +0953 function miriam = addSBOtoMiriam(miriam,sboTerm) +0954 %Appends SBO term to miriam structure +0955 +0956 sboTerm = {['SBO:' sprintf('%07u',sboTerm)]}; % convert to proper format +0957 if isempty(miriam) +0958 miriam.name = {'sbo'}; +0959 miriam.value = sboTerm; +0960 elseif any(strcmp('sbo',miriam.name)) +0961 currSbo = strcmp('sbo',miriam.name); +0962 miriam.value(currSbo) = sboTerm; +0963 else +0964 miriam.name(end+1) = {'sbo'}; +0965 miriam.value(end+1) = sboTerm; +0966 end +0967 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/io/index.html b/docs/html/io/index.html index 0f7726ed..480d02b3 100644 --- a/docs/html/io/index.html +++ b/docs/html/io/index.html @@ -19,7 +19,7 @@

    Index for io

    Matlab files in this directory:

    -
     checkFileExistencecheckFileExistence Check whether files exist.
     cleanSheetcleanSheet Clean up an Excel sheet.
     exportForGitexportForGit Export a model for a Git-maintained model repository.
     exportModelexportModel Export a constraint-based model to an SBML file (L3V1 FBCv2).
     exportToExcelFormatexportToExcelFormat Export a model to the Microsoft Excel model format.
     importModelimportModel Import a constraint-based model from an SBML file.
     parseYAMLparseYAML Read a YAML file into a MATLAB struct/cell tree.
     readFastareadFasta Read sequences from a FASTA file.
     readYAMLmodelreadYAMLmodel Read a model structure from a YAML file.
     writeExcelwriteExcel Write one or more sheets to an .xlsx (Office Open XML) file.
     writeFastawriteFasta Write sequences to a FASTA file.
     writeYAMLmodelwriteYAMLmodel Write a model to a yaml file in the standard cobra-format
    + checkFileExistencecheckFileExistence Check whether files exist.  cleanSheetcleanSheet Clean up an Excel sheet.  exportForGitexportForGit Export a model for a Git-maintained model repository.  exportModelexportModel Export a constraint-based model to an SBML file (L3V1 FBCv2).  exportToExcelFormatexportToExcelFormat Export a model to the Microsoft Excel model format.  importModelimportModel Import a constraint-based model from an SBML file.  parseYAMLparseYAML Read a YAML file into a tree of MATLAB structs and cells.  readFastareadFasta Read sequences from a FASTA file.  readYAMLmodelreadYAMLmodel Read a model structure from a YAML file.  writeExcelwriteExcel Write one or more sheets to an .xlsx (Office Open XML) file.  writeFastawriteFasta Write sequences to a FASTA file.  writeYAMLmodelwriteYAMLmodel Write a model to a YAML file in the cobra-format !!omap structure. diff --git a/docs/html/io/parseYAML.html b/docs/html/io/parseYAML.html index 99c89e87..caa23842 100644 --- a/docs/html/io/parseYAML.html +++ b/docs/html/io/parseYAML.html @@ -4,7 +4,7 @@ Description of parseYAML - + @@ -21,29 +21,28 @@

    parseYAML

    PURPOSE ^

    -
    parseYAML Read a YAML file into a MATLAB struct/cell tree.
    +
    parseYAML Read a YAML file into a tree of MATLAB structs and cells.

    SYNOPSIS ^

    function out = parseYAML(filename)

    DESCRIPTION ^

    -
     parseYAML  Read a YAML file into a MATLAB struct/cell tree.
    +
     parseYAML  Read a YAML file into a tree of MATLAB structs and cells.
     
      Supports block-style mappings and sequences (indentation-based),
      flow-style mappings and sequences (`{ key: value }`, `[a, b]`), quoted
      and unquoted scalars, and comments. Does not support multi-document
    - files, anchors/aliases, tags, or block scalars (`|`, `>`).
    + files, anchors, aliases, tags, or block scalars (`|`, `>`).
     
    - Use this for parsing YAML configuration / data files with a nested
    - mapping/sequence/scalar shape (e.g. condition files consumed by
    - applyCondition). For loading a cobra-format model YAML, use
    - readYAMLmodel instead — that function knows the model schema and
    - returns a populated RAVEN model struct.
    + The function parses YAML configuration and data files with nested
    + mappings, sequences and scalars (for example condition files consumed by
    + applyCondition). readYAMLmodel loads a cobra-format model YAML: it applies
    + the model schema and returns a populated RAVEN model struct.
     
      Parameters
      ----------
      filename : char
    -     Path to the YAML file.
    +     path to the YAML file.
     
      Returns
      -------
    @@ -54,7 +53,7 @@ 

    DESCRIPTION ^SUBFUNCTIONS ^SOURCE CODE ^

    0001 function out = parseYAML(filename)
    -0002 % parseYAML  Read a YAML file into a MATLAB struct/cell tree.
    +0002 % parseYAML  Read a YAML file into a tree of MATLAB structs and cells.
     0003 %
     0004 % Supports block-style mappings and sequences (indentation-based),
     0005 % flow-style mappings and sequences (`{ key: value }`, `[a, b]`), quoted
     0006 % and unquoted scalars, and comments. Does not support multi-document
    -0007 % files, anchors/aliases, tags, or block scalars (`|`, `>`).
    +0007 % files, anchors, aliases, tags, or block scalars (`|`, `>`).
     0008 %
    -0009 % Use this for parsing YAML configuration / data files with a nested
    -0010 % mapping/sequence/scalar shape (e.g. condition files consumed by
    -0011 % applyCondition). For loading a cobra-format model YAML, use
    -0012 % readYAMLmodel instead — that function knows the model schema and
    -0013 % returns a populated RAVEN model struct.
    -0014 %
    -0015 % Parameters
    -0016 % ----------
    -0017 % filename : char
    -0018 %     Path to the YAML file.
    -0019 %
    -0020 % Returns
    -0021 % -------
    -0022 % out : struct or cell or char or double or logical or []
    -0023 %     MATLAB representation of the document:
    -0024 %
    -0025 %     - mapping  -> struct (keys sanitised to valid MATLAB field names)
    -0026 %     - sequence -> cell column vector
    -0027 %     - quoted or unquoted string -> char
    -0028 %     - integer or float -> double
    -0029 %     - true/false -> logical
    -0030 %     - null / ~ / empty value -> []
    -0031 %
    -0032 % Examples
    -0033 % --------
    -0034 %     cfg = parseYAML('data/conditions/anaerobic.yml');
    -0035 %
    -0036 % See also
    -0037 % --------
    -0038 % readYAMLmodel
    -0039 
    -0040 if ~isfile(filename)
    -0041     error('parseYAML:fileNotFound', 'File not found: %s', filename);
    -0042 end
    -0043 
    -0044 [lines, indents] = readYAMLLines(filename);
    -0045 if isempty(lines)
    -0046     out = struct();
    -0047     return
    -0048 end
    -0049 [out, ~] = parseNode(lines, 1, indents);
    -0050 end
    +0009 % The function parses YAML configuration and data files with nested
    +0010 % mappings, sequences and scalars (for example condition files consumed by
    +0011 % applyCondition). readYAMLmodel loads a cobra-format model YAML: it applies
    +0012 % the model schema and returns a populated RAVEN model struct.
    +0013 %
    +0014 % Parameters
    +0015 % ----------
    +0016 % filename : char
    +0017 %     path to the YAML file.
    +0018 %
    +0019 % Returns
    +0020 % -------
    +0021 % out : struct or cell or char or double or logical or []
    +0022 %     MATLAB representation of the document:
    +0023 %
    +0024 %     - mapping  -> struct (keys sanitised to valid MATLAB field names)
    +0025 %     - sequence -> cell column vector
    +0026 %     - quoted or unquoted string -> char
    +0027 %     - integer or float -> double
    +0028 %     - true, false -> logical
    +0029 %     - null / ~ / empty value -> []
    +0030 %
    +0031 % Examples
    +0032 % --------
    +0033 %     cfg = parseYAML('data/conditions/anaerobic.yml');
    +0034 %
    +0035 % See also
    +0036 % --------
    +0037 % readYAMLmodel
    +0038 
    +0039 if ~isfile(filename)
    +0040     error('parseYAML:fileNotFound', 'File not found: %s', filename);
    +0041 end
    +0042 
    +0043 [lines, indents] = readYAMLLines(filename);
    +0044 if isempty(lines)
    +0045     out = struct();
    +0046     return
    +0047 end
    +0048 [out, ~] = parseNode(lines, 1, indents);
    +0049 end
    +0050 
     0051 
    -0052 
    -0053 function [lines, indents] = readYAMLLines(filename)
    -0054 % Read filename into non-blank, comment-stripped content lines, paired
    -0055 % with each line's leading-space indentation count. Document markers
    -0056 % (`---`, `...`) are dropped.
    -0057 fid = fopen(filename, 'r');
    -0058 cleanup = onCleanup(@() fclose(fid));
    -0059 
    -0060 lines = {};
    -0061 indents = [];
    -0062 tline = fgetl(fid);
    -0063 while ischar(tline)
    -0064     content = stripComment(tline);
    -0065     trimmed = strtrim(content);
    -0066     if ~isempty(trimmed) && ~strcmp(trimmed, '---') && ~strcmp(trimmed, '...')
    -0067         indents(end+1,1) = numel(content) - numel(regexprep(content, '^ +', '')); %#ok<AGROW>
    -0068         lines{end+1,1} = trimmed; %#ok<AGROW>
    -0069     end
    -0070     tline = fgetl(fid);
    +0052 function [lines, indents] = readYAMLLines(filename)
    +0053 % Read filename into non-blank, comment-stripped content lines, paired
    +0054 % with each line's leading-space indentation count. Document markers
    +0055 % (`---`, `...`) are dropped.
    +0056 fid = fopen(filename, 'r');
    +0057 cleanup = onCleanup(@() fclose(fid));
    +0058 
    +0059 lines = {};
    +0060 indents = [];
    +0061 tline = fgetl(fid);
    +0062 while ischar(tline)
    +0063     content = stripComment(tline);
    +0064     trimmed = strtrim(content);
    +0065     if ~isempty(trimmed) && ~strcmp(trimmed, '---') && ~strcmp(trimmed, '...')
    +0066         indents(end+1,1) = numel(content) - numel(regexprep(content, '^ +', '')); %#ok<AGROW>
    +0067         lines{end+1,1} = trimmed; %#ok<AGROW>
    +0068     end
    +0069     tline = fgetl(fid);
    +0070 end
     0071 end
    -0072 end
    +0072 
     0073 
    -0074 
    -0075 function line = stripComment(line)
    -0076 % Strip a trailing `# comment`, ignoring `#` inside a quoted string.
    -0077 inSingle = false;
    -0078 inDouble = false;
    -0079 for i = 1:numel(line)
    -0080     c = line(i);
    -0081     if c == '''' && ~inDouble
    -0082         inSingle = ~inSingle;
    -0083     elseif c == '"' && ~inSingle
    -0084         inDouble = ~inDouble;
    -0085     elseif c == '#' && ~inSingle && ~inDouble
    -0086         line = line(1:i-1);
    -0087         break
    -0088     end
    +0074 function line = stripComment(line)
    +0075 % Strip a trailing `# comment`, ignoring `#` inside a quoted string.
    +0076 inSingle = false;
    +0077 inDouble = false;
    +0078 for i = 1:numel(line)
    +0079     c = line(i);
    +0080     if c == '''' && ~inDouble
    +0081         inSingle = ~inSingle;
    +0082     elseif c == '"' && ~inSingle
    +0083         inDouble = ~inDouble;
    +0084     elseif c == '#' && ~inSingle && ~inDouble
    +0085         line = line(1:i-1);
    +0086         break
    +0087     end
    +0088 end
     0089 end
    -0090 end
    +0090 
     0091 
    -0092 
    -0093 function [value, nextIdx] = parseNode(lines, idx, indents)
    -0094 % Dispatch to a block sequence or block mapping, based on whether the
    -0095 % line at idx starts a sequence item (`-`) or a mapping key.
    -0096 if startsWithDash(lines{idx})
    -0097     [value, nextIdx] = parseSequence(lines, idx, indents);
    -0098 else
    -0099     [value, nextIdx] = parseMapping(lines, idx, indents);
    +0092 function [value, nextIdx] = parseNode(lines, idx, indents)
    +0093 % Dispatch to a block sequence or block mapping, based on whether the
    +0094 % line at idx starts a sequence item (`-`) or a mapping key.
    +0095 if startsWithDash(lines{idx})
    +0096     [value, nextIdx] = parseSequence(lines, idx, indents);
    +0097 else
    +0098     [value, nextIdx] = parseMapping(lines, idx, indents);
    +0099 end
     0100 end
    -0101 end
    +0101 
     0102 
    -0103 
    -0104 function tf = startsWithDash(s)
    -0105 tf = ~isempty(s) && (strcmp(s, '-') || (numel(s) >= 2 && s(1) == '-' && s(2) == ' '));
    -0106 end
    +0103 function tf = startsWithDash(s)
    +0104 tf = ~isempty(s) && (strcmp(s, '-') || (numel(s) >= 2 && s(1) == '-' && s(2) == ' '));
    +0105 end
    +0106 
     0107 
    -0108 
    -0109 function [value, nextIdx] = parseMapping(lines, idx, indents)
    -0110 baseIndent = indents(idx);
    -0111 value = struct();
    -0112 while idx <= numel(lines) && indents(idx) == baseIndent && ~startsWithDash(lines{idx})
    -0113     [key, rest] = splitKeyValue(lines{idx});
    -0114     key = matlabFieldName(key);
    -0115     if isempty(rest)
    -0116         if idx+1 <= numel(lines) && indents(idx+1) > baseIndent
    -0117             [childVal, idx] = parseNode(lines, idx+1, indents);
    -0118         else
    -0119             childVal = [];
    -0120             idx = idx + 1;
    -0121         end
    -0122     else
    -0123         childVal = parseScalarOrFlow(rest);
    -0124         idx = idx + 1;
    -0125     end
    -0126     value.(key) = childVal;
    -0127 end
    -0128 nextIdx = idx;
    -0129 end
    +0108 function [value, nextIdx] = parseMapping(lines, idx, indents)
    +0109 baseIndent = indents(idx);
    +0110 value = struct();
    +0111 while idx <= numel(lines) && indents(idx) == baseIndent && ~startsWithDash(lines{idx})
    +0112     [key, rest] = splitKeyValue(lines{idx});
    +0113     key = matlabFieldName(key);
    +0114     if isempty(rest)
    +0115         if idx+1 <= numel(lines) && indents(idx+1) > baseIndent
    +0116             [childVal, idx] = parseNode(lines, idx+1, indents);
    +0117         else
    +0118             childVal = [];
    +0119             idx = idx + 1;
    +0120         end
    +0121     else
    +0122         childVal = parseScalarOrFlow(rest);
    +0123         idx = idx + 1;
    +0124     end
    +0125     value.(key) = childVal;
    +0126 end
    +0127 nextIdx = idx;
    +0128 end
    +0129 
     0130 
    -0131 
    -0132 function [value, nextIdx] = parseSequence(lines, idx, indents)
    -0133 baseIndent = indents(idx);
    -0134 items = {};
    -0135 while idx <= numel(lines) && indents(idx) == baseIndent && startsWithDash(lines{idx})
    -0136     rest = strtrim(lines{idx}(2:end));
    -0137     if isempty(rest)
    -0138         if idx+1 <= numel(lines) && indents(idx+1) > baseIndent
    -0139             [itemVal, idx] = parseNode(lines, idx+1, indents);
    -0140         else
    -0141             itemVal = [];
    -0142             idx = idx + 1;
    -0143         end
    -0144     else
    -0145         itemVal = parseScalarOrFlow(rest);
    -0146         idx = idx + 1;
    -0147     end
    -0148     items{end+1,1} = itemVal; %#ok<AGROW>
    -0149 end
    -0150 value = items;
    -0151 nextIdx = idx;
    -0152 end
    +0131 function [value, nextIdx] = parseSequence(lines, idx, indents)
    +0132 baseIndent = indents(idx);
    +0133 items = {};
    +0134 while idx <= numel(lines) && indents(idx) == baseIndent && startsWithDash(lines{idx})
    +0135     rest = strtrim(lines{idx}(2:end));
    +0136     if isempty(rest)
    +0137         if idx+1 <= numel(lines) && indents(idx+1) > baseIndent
    +0138             [itemVal, idx] = parseNode(lines, idx+1, indents);
    +0139         else
    +0140             itemVal = [];
    +0141             idx = idx + 1;
    +0142         end
    +0143     else
    +0144         itemVal = parseScalarOrFlow(rest);
    +0145         idx = idx + 1;
    +0146     end
    +0147     items{end+1,1} = itemVal; %#ok<AGROW>
    +0148 end
    +0149 value = items;
    +0150 nextIdx = idx;
    +0151 end
    +0152 
     0153 
    -0154 
    -0155 function [key, rest] = splitKeyValue(line)
    -0156 % Split "key: value" (or "key:") on the first top-level colon.
    -0157 inSingle = false;
    -0158 inDouble = false;
    -0159 colonPos = 0;
    -0160 for i = 1:numel(line)
    -0161     c = line(i);
    -0162     if c == '''' && ~inDouble
    -0163         inSingle = ~inSingle;
    -0164     elseif c == '"' && ~inSingle
    -0165         inDouble = ~inDouble;
    -0166     elseif c == ':' && ~inSingle && ~inDouble
    -0167         colonPos = i;
    -0168         break
    -0169     end
    -0170 end
    -0171 if colonPos == 0
    -0172     error('parseYAML:invalidLine', 'Expected "key: value" or "key:", got: %s', line);
    -0173 end
    -0174 key = strtrim(line(1:colonPos-1));
    -0175 key = strrep(strrep(key, '"', ''), '''', '');
    -0176 rest = strtrim(line(colonPos+1:end));
    -0177 end
    +0154 function [key, rest] = splitKeyValue(line)
    +0155 % Split "key: value" (or "key:") on the first top-level colon.
    +0156 inSingle = false;
    +0157 inDouble = false;
    +0158 colonPos = 0;
    +0159 for i = 1:numel(line)
    +0160     c = line(i);
    +0161     if c == '''' && ~inDouble
    +0162         inSingle = ~inSingle;
    +0163     elseif c == '"' && ~inSingle
    +0164         inDouble = ~inDouble;
    +0165     elseif c == ':' && ~inSingle && ~inDouble
    +0166         colonPos = i;
    +0167         break
    +0168     end
    +0169 end
    +0170 if colonPos == 0
    +0171     error('parseYAML:invalidLine', 'Expected "key: value" or "key:", got: %s', line);
    +0172 end
    +0173 key = strtrim(line(1:colonPos-1));
    +0174 key = strrep(strrep(key, '"', ''), '''', '');
    +0175 rest = strtrim(line(colonPos+1:end));
    +0176 end
    +0177 
     0178 
    -0179 
    -0180 function value = parseScalarOrFlow(s)
    -0181 s = strtrim(s);
    -0182 if ~isempty(s) && s(1) == '{'
    -0183     value = parseFlowMap(s);
    -0184 elseif ~isempty(s) && s(1) == '['
    -0185     value = parseFlowSeq(s);
    -0186 else
    -0187     value = parseScalar(s);
    +0179 function value = parseScalarOrFlow(s)
    +0180 s = strtrim(s);
    +0181 if ~isempty(s) && s(1) == '{'
    +0182     value = parseFlowMap(s);
    +0183 elseif ~isempty(s) && s(1) == '['
    +0184     value = parseFlowSeq(s);
    +0185 else
    +0186     value = parseScalar(s);
    +0187 end
     0188 end
    -0189 end
    +0189 
     0190 
    -0191 
    -0192 function value = parseFlowMap(s)
    -0193 inner = strtrim(s(2:end-1));
    -0194 value = struct();
    -0195 if isempty(inner)
    -0196     return
    -0197 end
    -0198 parts = splitTopLevel(inner, ',');
    -0199 for i = 1:numel(parts)
    -0200     [key, rest] = splitKeyValue(parts{i});
    -0201     value.(matlabFieldName(key)) = parseScalarOrFlow(rest);
    +0191 function value = parseFlowMap(s)
    +0192 inner = strtrim(s(2:end-1));
    +0193 value = struct();
    +0194 if isempty(inner)
    +0195     return
    +0196 end
    +0197 parts = splitTopLevel(inner, ',');
    +0198 for i = 1:numel(parts)
    +0199     [key, rest] = splitKeyValue(parts{i});
    +0200     value.(matlabFieldName(key)) = parseScalarOrFlow(rest);
    +0201 end
     0202 end
    -0203 end
    +0203 
     0204 
    -0205 
    -0206 function value = parseFlowSeq(s)
    -0207 inner = strtrim(s(2:end-1));
    -0208 if isempty(inner)
    -0209     value = cell(0,1);
    -0210     return
    -0211 end
    -0212 parts = splitTopLevel(inner, ',');
    -0213 value = cell(numel(parts), 1);
    -0214 for i = 1:numel(parts)
    -0215     value{i} = parseScalarOrFlow(parts{i});
    +0205 function value = parseFlowSeq(s)
    +0206 inner = strtrim(s(2:end-1));
    +0207 if isempty(inner)
    +0208     value = cell(0,1);
    +0209     return
    +0210 end
    +0211 parts = splitTopLevel(inner, ',');
    +0212 value = cell(numel(parts), 1);
    +0213 for i = 1:numel(parts)
    +0214     value{i} = parseScalarOrFlow(parts{i});
    +0215 end
     0216 end
    -0217 end
    +0217 
     0218 
    -0219 
    -0220 function parts = splitTopLevel(s, delim)
    -0221 % Split s on delim, ignoring delim inside quotes or nested {}/[].
    -0222 parts = {};
    -0223 depth = 0;
    -0224 inSingle = false;
    -0225 inDouble = false;
    -0226 last = 1;
    -0227 for i = 1:numel(s)
    -0228     c = s(i);
    -0229     if c == '''' && ~inDouble
    -0230         inSingle = ~inSingle;
    -0231     elseif c == '"' && ~inSingle
    -0232         inDouble = ~inDouble;
    -0233     elseif ~inSingle && ~inDouble
    -0234         if c == '{' || c == '['
    -0235             depth = depth + 1;
    -0236         elseif c == '}' || c == ']'
    -0237             depth = depth - 1;
    -0238         elseif c == delim && depth == 0
    -0239             parts{end+1} = s(last:i-1); %#ok<AGROW>
    -0240             last = i + 1;
    -0241         end
    -0242     end
    -0243 end
    -0244 parts{end+1} = s(last:end); %#ok<AGROW>
    -0245 parts = strtrim(parts);
    -0246 end
    +0219 function parts = splitTopLevel(s, delim)
    +0220 % Split s on delim, ignoring delim inside quotes or nested {}/[].
    +0221 parts = {};
    +0222 depth = 0;
    +0223 inSingle = false;
    +0224 inDouble = false;
    +0225 last = 1;
    +0226 for i = 1:numel(s)
    +0227     c = s(i);
    +0228     if c == '''' && ~inDouble
    +0229         inSingle = ~inSingle;
    +0230     elseif c == '"' && ~inSingle
    +0231         inDouble = ~inDouble;
    +0232     elseif ~inSingle && ~inDouble
    +0233         if c == '{' || c == '['
    +0234             depth = depth + 1;
    +0235         elseif c == '}' || c == ']'
    +0236             depth = depth - 1;
    +0237         elseif c == delim && depth == 0
    +0238             parts{end+1} = s(last:i-1); %#ok<AGROW>
    +0239             last = i + 1;
    +0240         end
    +0241     end
    +0242 end
    +0243 parts{end+1} = s(last:end); %#ok<AGROW>
    +0244 parts = strtrim(parts);
    +0245 end
    +0246 
     0247 
    -0248 
    -0249 function value = parseScalar(s)
    -0250 if isempty(s) || strcmp(s, '~') || strcmpi(s, 'null')
    -0251     value = [];
    -0252     return
    -0253 end
    -0254 if numel(s) >= 2 && ((s(1) == '"' && s(end) == '"') || (s(1) == '''' && s(end) == ''''))
    -0255     value = s(2:end-1);
    -0256     return
    -0257 end
    -0258 if strcmpi(s, 'true')
    -0259     value = true;
    -0260     return
    -0261 end
    -0262 if strcmpi(s, 'false')
    -0263     value = false;
    -0264     return
    -0265 end
    -0266 if ~isempty(regexp(s, '^[+-]?(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?$', 'once'))
    -0267     value = str2double(s);
    -0268     return
    -0269 end
    -0270 value = s;
    -0271 end
    +0248 function value = parseScalar(s)
    +0249 if isempty(s) || strcmp(s, '~') || strcmpi(s, 'null')
    +0250     value = [];
    +0251     return
    +0252 end
    +0253 if numel(s) >= 2 && ((s(1) == '"' && s(end) == '"') || (s(1) == '''' && s(end) == ''''))
    +0254     value = s(2:end-1);
    +0255     return
    +0256 end
    +0257 if strcmpi(s, 'true')
    +0258     value = true;
    +0259     return
    +0260 end
    +0261 if strcmpi(s, 'false')
    +0262     value = false;
    +0263     return
    +0264 end
    +0265 if ~isempty(regexp(s, '^[+-]?(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?$', 'once'))
    +0266     value = str2double(s);
    +0267     return
    +0268 end
    +0269 value = s;
    +0270 end
    +0271 
     0272 
    -0273 
    -0274 function name = matlabFieldName(key)
    -0275 % Sanitise a YAML key into a valid MATLAB field name. Replaces non-
    -0276 % alphanumeric characters with underscores; prefixes a digit-starting
    -0277 % key with 'f_'.
    -0278 name = regexprep(key, '[^A-Za-z0-9_]', '_');
    -0279 if isempty(name) || ~isstrprop(name(1), 'alpha')
    -0280     name = ['f_' name];
    -0281 end
    -0282 end
    +0273 function name = matlabFieldName(key) +0274 % Sanitise a YAML key into a valid MATLAB field name. Replaces non- +0275 % alphanumeric characters with underscores; prefixes a digit-starting +0276 % key with 'f_'. +0277 name = regexprep(key, '[^A-Za-z0-9_]', '_'); +0278 if isempty(name) || ~isstrprop(name(1), 'alpha') +0279 name = ['f_' name]; +0280 end +0281 end

    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/io/readYAMLmodel.html b/docs/html/io/readYAMLmodel.html index 3f5bb2dd..1ecf7ac2 100644 --- a/docs/html/io/readYAMLmodel.html +++ b/docs/html/io/readYAMLmodel.html @@ -29,16 +29,16 @@

    SYNOPSIS ^DESCRIPTION ^

     readYAMLmodel  Read a model structure from a YAML file.
     
    - Reads a yaml file matching (roughly) the standard cobra-format model
    - yaml structure.
    + Reads a yaml file in the cobra-format model yaml structure that
    + writeYAMLmodel writes.
     
      Name-Value Arguments
      --------------------
      fileName : char
    -     a model file in yaml file format. A dialog window will open if no
    -     file name is specified.
    +     a model file in yaml file format. A dialog window opens if no file
    +     name is specified.
      verbose : logical
    -     set as true to monitor progress (default false).
    +     true to print the progress (default false).
     
      Returns
      -------
    @@ -67,16 +67,16 @@ 

    SOURCE CODE ^
    0001 function model=readYAMLmodel(varargin)
     0002 % readYAMLmodel  Read a model structure from a YAML file.
     0003 %
    -0004 % Reads a yaml file matching (roughly) the standard cobra-format model
    -0005 % yaml structure.
    +0004 % Reads a yaml file in the cobra-format model yaml structure that
    +0005 % writeYAMLmodel writes.
     0006 %
     0007 % Name-Value Arguments
     0008 % --------------------
     0009 % fileName : char
    -0010 %     a model file in yaml file format. A dialog window will open if no
    -0011 %     file name is specified.
    +0010 %     a model file in yaml file format. A dialog window opens if no file
    +0011 %     name is specified.
     0012 % verbose : logical
    -0013 %     set as true to monitor progress (default false).
    +0013 %     true to print the progress (default false).
     0014 %
     0015 % Returns
     0016 % -------
    diff --git a/docs/html/io/writeYAMLmodel.html b/docs/html/io/writeYAMLmodel.html
    index 9db69ac0..b27db4ec 100644
    --- a/docs/html/io/writeYAMLmodel.html
    +++ b/docs/html/io/writeYAMLmodel.html
    @@ -4,7 +4,7 @@
     
       Description of writeYAMLmodel
       
    -  
    +  
       
       
       
    @@ -21,18 +21,18 @@ 

    writeYAMLmodel

    PURPOSE ^

    -
    writeYAMLmodel Write a model to a yaml file in the standard cobra-format
    +
    writeYAMLmodel Write a model to a YAML file in the cobra-format !!omap structure.

    SYNOPSIS ^

    function writeYAMLmodel(model,varargin)

    DESCRIPTION ^

    -
     writeYAMLmodel  Write a model to a yaml file in the standard cobra-format
    - !!omap structure.
    +
     writeYAMLmodel  Write a model to a YAML file in the cobra-format !!omap structure.
     
      The format uses the standard !!omap layout, extended with RAVEN-only
      top-level per-entry keys (inchis, deltaG, metFrom, rxnFrom, references,
    - confidence_score, protein) and the GECKO ec-rxns / ec-enzymes sections.
    + confidence_score, protein, notes) and the GECKO ec-rxns and ec-enzymes
    + sections.
      Reaction EC numbers are written inside the `annotation` block as
      `ec-code`, not as a top-level reaction key.
     
    @@ -44,12 +44,11 @@ 

    DESCRIPTION ^SUBFUNCTIONS ^SOURCE CODE ^

    0001 function writeYAMLmodel(model,varargin)
    -0002 % writeYAMLmodel  Write a model to a yaml file in the standard cobra-format
    -0003 % !!omap structure.
    -0004 %
    -0005 % The format uses the standard !!omap layout, extended with RAVEN-only
    -0006 % top-level per-entry keys (inchis, deltaG, metFrom, rxnFrom, references,
    -0007 % confidence_score, protein) and the GECKO ec-rxns / ec-enzymes sections.
    +0002 % writeYAMLmodel  Write a model to a YAML file in the cobra-format !!omap structure.
    +0003 %
    +0004 % The format uses the standard !!omap layout, extended with RAVEN-only
    +0005 % top-level per-entry keys (inchis, deltaG, metFrom, rxnFrom, references,
    +0006 % confidence_score, protein, notes) and the GECKO ec-rxns and ec-enzymes
    +0007 % sections.
     0008 % Reaction EC numbers are written inside the `annotation` block as
     0009 % `ec-code`, not as a top-level reaction key.
     0010 %
    @@ -88,607 +87,606 @@ 

    SOURCE CODE ^% Name-Value Arguments 0017 % -------------------- 0018 % fileName : char -0019 % name that the file will have. A dialog window will open if no file -0020 % name is specified. -0021 % sortIds : logical -0022 % if metabolites, reactions, genes and compartments should be sorted -0023 % alphabetically by their identifier, otherwise they are kept in their -0024 % original order (default false). -0025 % -0026 % Examples -0027 % -------- -0028 % writeYAMLmodel(model,fileName,sortIds); -0029 p=parseRAVENargs(varargin, {'fileName',[]; 'sortIds',false}); -0030 fileName=p.fileName; sortIds=p.sortIds; -0031 if isempty(fileName) -0032 [fileName, pathName] = uiputfile({'*.yml;*.yaml'}, 'Select file for model export',[model.id '.yml']); -0033 if fileName == 0 -0034 error('You should provide a file location') -0035 else -0036 fileName = fullfile(pathName,fileName); -0037 end -0038 end -0039 fileName=char(fileName); -0040 if ~endsWith(fileName,{'.yml','.yaml'}) -0041 fileName = strcat(fileName,'.yml'); -0042 end -0043 -0044 %Check that model is in RAVEN format: -0045 if isfield(model,'rules') -0046 model = ravenCobraWrapper(model); -0047 end -0048 -0049 %Check that the model structure has no problems -0050 checkModelStruct(model); -0051 -0052 %Sort identifiers alphabetically -0053 if sortIds == true -0054 model = sortIdentifiers(model); -0055 end -0056 -0057 %Simplify Miriam fields: -0058 if isfield(model,'metMiriams') -0059 [model.newMetMiriams,model.newMetMiriamNames] = extractMiriam(model.metMiriams); -0060 end -0061 if isfield(model,'rxnMiriams') -0062 [model.newRxnMiriams,model.newRxnMiriamNames] = extractMiriam(model.rxnMiriams); -0063 end -0064 if isfield(model,'geneMiriams') -0065 [model.newGeneMiriams,model.newGeneMiriamNames] = extractMiriam(model.geneMiriams); -0066 end -0067 if isfield(model,'compMiriams') -0068 [model.newCompMiriams,model.newCompMiriamNames] = extractMiriam(model.compMiriams); -0069 end -0070 -0071 %Open file: -0072 fid = fopen(fileName,'wt'); -0073 if fid == -1 -0074 error(['Cannot write to ' fileName ', does the directory exist?']) -0075 end -0076 % Emit a bare `!!omap` root, with no document-start marker. -0077 fprintf(fid,'!!omap\n'); -0078 -0079 %Insert file header (metadata) -0080 writeMetadata(model,fid); -0081 -0082 %Metabolites: -0083 % Field order: -0084 % id, name, compartment, charge, formula, annotation, then RAVEN-only -0085 % extras (inchis, deltaG, metFrom, notes). -0086 % SMILES goes inside the annotation block, not at metabolite top level — -0087 % the reader still accepts top-level `smiles:` for backward compatibility -0088 % with older yeast-GEM files. -0089 fprintf(fid,'- metabolites:\n'); -0090 for i = 1:length(model.mets) -0091 fprintf(fid,' - !!omap\n'); -0092 writeField(model, fid, 'mets', 'txt', i, ' - id', 4) -0093 writeField(model, fid, 'metNames', 'txt', i, ' - name', 4) -0094 writeField(model, fid, 'metComps', 'txt', i, ' - compartment', 4) -0095 writeField(model, fid, 'metCharges', 'flt', i, ' - charge', 4) -0096 writeField(model, fid, 'metFormulas', 'txt', i, ' - formula', 4) -0097 writeAnnotation(model, fid, 'met', i) -0098 writeField(model, fid, 'inchis', 'txt', i, ' - inchis', 4) -0099 writeField(model, fid, 'metDeltaG', 'flt', i, ' - deltaG', 4) -0100 writeField(model, fid, 'metFrom', 'txt', i, ' - metFrom', 4) -0101 writeField(model, fid, 'metNotes', 'txt', i, ' - notes', 4) -0102 end -0103 -0104 %Reactions: -0105 % Field order: -0106 % id, name, metabolites, lower_bound, upper_bound, gene_reaction_rule, -0107 % objective_coefficient, subsystem, annotation (which carries the EC -0108 % numbers under `ec-code`), then RAVEN-only extras (references, rxnFrom, -0109 % deltaG, confidence_score, notes). The notes key is the canonical -0110 % `notes`, not `rxnNotes`; the reader still accepts the legacy key. -0111 fprintf(fid,'- reactions:\n'); -0112 for i = 1:length(model.rxns) -0113 fprintf(fid,' - !!omap\n'); -0114 writeField(model, fid, 'rxns', 'txt', i, ' - id', 4) -0115 writeField(model, fid, 'rxnNames', 'txt', i, ' - name', 4) -0116 writeField(model, fid, 'S', 'txt', i, ' - metabolites', 4) -0117 writeField(model, fid, 'lb', 'flt', i, ' - lower_bound', 4) -0118 writeField(model, fid, 'ub', 'flt', i, ' - upper_bound', 4) -0119 % An absent gene_reaction_rule reads back as '' regardless of whether -0120 % it was written, so dropping an empty one when writing is safe, and -0121 % keeps the file free of the resulting churn. -0122 writeField(model, fid, 'grRules', 'txt', i, ' - gene_reaction_rule', 4) -0123 if model.c(i)~=0 -0124 writeField(model, fid, 'c', 'flt', i, ' - objective_coefficient', 4) -0125 end -0126 writeField(model, fid, 'subSystems', 'txt', i, ' - subsystem', 4) -0127 writeAnnotation(model, fid, 'rxn', i) -0128 writeField(model, fid, 'rxnReferences', 'txt', i, ' - references', 4) -0129 writeField(model, fid, 'rxnFrom', 'txt', i, ' - rxnFrom', 4) -0130 writeField(model, fid, 'rxnDeltaG', 'flt', i, ' - deltaG', 4) -0131 writeField(model, fid, 'rxnConfidenceScores', 'flt', i, ' - confidence_score', 4) -0132 writeField(model, fid, 'rxnNotes', 'txt', i, ' - notes', 4) -0133 end -0134 -0135 %Genes: -0136 % For a gene-less model, write the flow-style empty list rather than a -0137 % bare `- genes:` with nothing following, which parses as `genes: null` -0138 % and would crash a reader that assumes a present key means a list. -0139 if isfield(model,'genes') && ~isempty(model.genes) -0140 fprintf(fid,'- genes:\n'); -0141 for i = 1:length(model.genes) -0142 fprintf(fid,' - !!omap\n'); -0143 writeField(model, fid, 'genes', 'txt', i, ' - id', 4) -0144 writeField(model, fid, 'geneShortNames', 'txt', i, ' - name', 4) -0145 writeAnnotationSimple(model, fid, 'geneMiriams', 'newGeneMiriams', 'newGeneMiriamNames', i) -0146 writeField(model, fid, 'proteins', 'txt', i, ' - protein', 4) -0147 end -0148 else -0149 fprintf(fid,'- genes: []\n'); -0150 end -0151 -0152 %Compartments: -0153 fprintf(fid,'- compartments: !!omap\n'); -0154 for i = 1:length(model.comps) -0155 writeField(model, fid, 'compNames', 'txt', i, [' - ' model.comps{i}], 2) -0156 writeAnnotationSimple(model, fid, 'compMiriams', 'newCompMiriams', 'newCompMiriamNames', i, ' ', 6) -0157 end +0019 % name of the file. A dialog window opens if no file name is specified. +0020 % sortIds : logical +0021 % true to sort metabolites, reactions, genes and compartments +0022 % alphabetically by their identifier. If false, they keep their original +0023 % order (default false). +0024 % +0025 % Examples +0026 % -------- +0027 % writeYAMLmodel(model,fileName,sortIds); +0028 p=parseRAVENargs(varargin, {'fileName',[]; 'sortIds',false}); +0029 fileName=p.fileName; sortIds=p.sortIds; +0030 if isempty(fileName) +0031 [fileName, pathName] = uiputfile({'*.yml;*.yaml'}, 'Select file for model export',[model.id '.yml']); +0032 if fileName == 0 +0033 error('You should provide a file location') +0034 else +0035 fileName = fullfile(pathName,fileName); +0036 end +0037 end +0038 fileName=char(fileName); +0039 if ~endsWith(fileName,{'.yml','.yaml'}) +0040 fileName = strcat(fileName,'.yml'); +0041 end +0042 +0043 %Check that model is in RAVEN format: +0044 if isfield(model,'rules') +0045 model = ravenCobraWrapper(model); +0046 end +0047 +0048 %Check that the model structure has no problems +0049 checkModelStruct(model); +0050 +0051 %Sort identifiers alphabetically +0052 if sortIds == true +0053 model = sortIdentifiers(model); +0054 end +0055 +0056 %Simplify Miriam fields: +0057 if isfield(model,'metMiriams') +0058 [model.newMetMiriams,model.newMetMiriamNames] = extractMiriam(model.metMiriams); +0059 end +0060 if isfield(model,'rxnMiriams') +0061 [model.newRxnMiriams,model.newRxnMiriamNames] = extractMiriam(model.rxnMiriams); +0062 end +0063 if isfield(model,'geneMiriams') +0064 [model.newGeneMiriams,model.newGeneMiriamNames] = extractMiriam(model.geneMiriams); +0065 end +0066 if isfield(model,'compMiriams') +0067 [model.newCompMiriams,model.newCompMiriamNames] = extractMiriam(model.compMiriams); +0068 end +0069 +0070 %Open file: +0071 fid = fopen(fileName,'wt'); +0072 if fid == -1 +0073 error(['Cannot write to ' fileName ', does the directory exist?']) +0074 end +0075 % Emit a bare `!!omap` root, with no document-start marker. +0076 fprintf(fid,'!!omap\n'); +0077 +0078 %Insert file header (metadata) +0079 writeMetadata(model,fid); +0080 +0081 %Metabolites: +0082 % Field order: +0083 % id, name, compartment, charge, formula, annotation, then RAVEN-only +0084 % extras (inchis, deltaG, metFrom, notes). +0085 % SMILES goes inside the annotation block, not at metabolite top level — +0086 % the reader still accepts top-level `smiles:` for backward compatibility +0087 % with older yeast-GEM files. +0088 fprintf(fid,'- metabolites:\n'); +0089 for i = 1:length(model.mets) +0090 fprintf(fid,' - !!omap\n'); +0091 writeField(model, fid, 'mets', 'txt', i, ' - id', 4) +0092 writeField(model, fid, 'metNames', 'txt', i, ' - name', 4) +0093 writeField(model, fid, 'metComps', 'txt', i, ' - compartment', 4) +0094 writeField(model, fid, 'metCharges', 'flt', i, ' - charge', 4) +0095 writeField(model, fid, 'metFormulas', 'txt', i, ' - formula', 4) +0096 writeAnnotation(model, fid, 'met', i) +0097 writeField(model, fid, 'inchis', 'txt', i, ' - inchis', 4) +0098 writeField(model, fid, 'metDeltaG', 'flt', i, ' - deltaG', 4) +0099 writeField(model, fid, 'metFrom', 'txt', i, ' - metFrom', 4) +0100 writeField(model, fid, 'metNotes', 'txt', i, ' - notes', 4) +0101 end +0102 +0103 %Reactions: +0104 % Field order: +0105 % id, name, metabolites, lower_bound, upper_bound, gene_reaction_rule, +0106 % objective_coefficient, subsystem, annotation (which carries the EC +0107 % numbers under `ec-code`), then RAVEN-only extras (references, rxnFrom, +0108 % deltaG, confidence_score, notes). The notes key is the canonical +0109 % `notes`, not `rxnNotes`; the reader still accepts the legacy key. +0110 fprintf(fid,'- reactions:\n'); +0111 for i = 1:length(model.rxns) +0112 fprintf(fid,' - !!omap\n'); +0113 writeField(model, fid, 'rxns', 'txt', i, ' - id', 4) +0114 writeField(model, fid, 'rxnNames', 'txt', i, ' - name', 4) +0115 writeField(model, fid, 'S', 'txt', i, ' - metabolites', 4) +0116 writeField(model, fid, 'lb', 'flt', i, ' - lower_bound', 4) +0117 writeField(model, fid, 'ub', 'flt', i, ' - upper_bound', 4) +0118 % An absent gene_reaction_rule reads back as '' regardless of whether +0119 % it was written, so dropping an empty one when writing is safe, and +0120 % keeps the file free of the resulting churn. +0121 writeField(model, fid, 'grRules', 'txt', i, ' - gene_reaction_rule', 4) +0122 if model.c(i)~=0 +0123 writeField(model, fid, 'c', 'flt', i, ' - objective_coefficient', 4) +0124 end +0125 writeField(model, fid, 'subSystems', 'txt', i, ' - subsystem', 4) +0126 writeAnnotation(model, fid, 'rxn', i) +0127 writeField(model, fid, 'rxnReferences', 'txt', i, ' - references', 4) +0128 writeField(model, fid, 'rxnFrom', 'txt', i, ' - rxnFrom', 4) +0129 writeField(model, fid, 'rxnDeltaG', 'flt', i, ' - deltaG', 4) +0130 writeField(model, fid, 'rxnConfidenceScores', 'flt', i, ' - confidence_score', 4) +0131 writeField(model, fid, 'rxnNotes', 'txt', i, ' - notes', 4) +0132 end +0133 +0134 %Genes: +0135 % For a gene-less model, write the flow-style empty list rather than a +0136 % bare `- genes:` with nothing following, which parses as `genes: null` +0137 % and would crash a reader that assumes a present key means a list. +0138 if isfield(model,'genes') && ~isempty(model.genes) +0139 fprintf(fid,'- genes:\n'); +0140 for i = 1:length(model.genes) +0141 fprintf(fid,' - !!omap\n'); +0142 writeField(model, fid, 'genes', 'txt', i, ' - id', 4) +0143 writeField(model, fid, 'geneShortNames', 'txt', i, ' - name', 4) +0144 writeAnnotationSimple(model, fid, 'geneMiriams', 'newGeneMiriams', 'newGeneMiriamNames', i) +0145 writeField(model, fid, 'proteins', 'txt', i, ' - protein', 4) +0146 end +0147 else +0148 fprintf(fid,'- genes: []\n'); +0149 end +0150 +0151 %Compartments: +0152 fprintf(fid,'- compartments: !!omap\n'); +0153 for i = 1:length(model.comps) +0154 writeField(model, fid, 'compNames', 'txt', i, [' - ' model.comps{i}], 2) +0155 writeAnnotationSimple(model, fid, 'compMiriams', 'newCompMiriams', 'newCompMiriamNames', i, ' ', 6) +0156 end +0157 0158 -0159 -0160 %EC-model: -0161 if isfield(model,'ec') -0162 % gecko_light flag at the top level (keeps the metaData block a pure -0163 % provenance container). The reader accepts both this key and the -0164 % legacy geckoLight key inside metaData. -0165 if model.ec.geckoLight; geckoLightStr = 'true'; else; geckoLightStr = 'false'; end -0166 fprintf(fid,'- gecko_light: %s\n', geckoLightStr); -0167 fprintf(fid,'- ec-rxns:\n'); -0168 for i = 1:length(model.ec.rxns) -0169 fprintf(fid,' - !!omap\n'); -0170 writeField(model.ec, fid, 'rxns', 'txt', i, ' - id', 4) -0171 writeField(model.ec, fid, 'kcat', 'flt', i, ' - kcat', 4) -0172 writeField(model.ec, fid, 'source', 'txt', i, ' - source', 4) -0173 writeField(model.ec, fid, 'notes', 'txt', i, ' - notes', 4) -0174 writeField(model.ec, fid, 'eccodes', 'txt', i, ' - eccodes', 4) -0175 writeField(model.ec, fid, 'rxnEnzMat', 'txt', i, ' - enzymes', 4) -0176 end -0177 -0178 fprintf(fid,'- ec-enzymes:\n'); -0179 for i = 1:length(model.ec.genes) -0180 fprintf(fid,' - !!omap\n'); -0181 writeField(model.ec, fid, 'genes', 'txt', i, ' - genes', 4) -0182 writeField(model.ec, fid, 'enzymes', 'txt', i, ' - enzymes', 4) -0183 writeField(model.ec, fid, 'mw', 'flt', i, ' - mw', 4) -0184 writeField(model.ec, fid, 'sequence', 'txt', i, ' - sequence', 4) -0185 writeField(model.ec, fid, 'concs', 'flt', i, ' - concs', 4) -0186 end -0187 end -0188 -0189 %Close file: -0190 fclose(fid); -0191 -0192 end -0193 -0194 function writeField(model,fid,fieldName,type,pos,name,keyIndent) -0195 %Writes a new line in the yaml file if the field exists and the field is -0196 %not empty at the corresponding position. It's recursive for the S -0197 %field (reaction stoichiometry). -0198 -0199 if isfield(model,fieldName) -0200 if strcmp(fieldName,'metComps') -0201 %metComps: write full name -0202 fieldName = 'comps'; -0203 pos = model.metComps(pos); -0204 end -0205 -0206 field = model.(fieldName); -0207 -0208 if strcmp(fieldName,'S') -0209 %S: create header & write each metabolite in a new line. Reactions -0210 %with no metabolites emit `metabolites: !!omap []` (the flow-style -0211 %empty omap representation) so the file remains a valid YAML 1.2 -0212 %document. -0213 if sum(field(:,pos) ~= 0) > 0 -0214 fprintf(fid,'%s: !!omap\n',name); -0215 model.mets = model.mets(field(:,pos) ~= 0); -0216 model.coeffs = field(field(:,pos) ~= 0,pos); -0217 %Sort metabolites: -0218 [model.mets,order] = sort(model.mets); -0219 model.coeffs = model.coeffs(order); -0220 for i = 1:length(model.mets) -0221 writeField(model, fid, 'coeffs', 'flt', i, [' - ' model.mets{i}], 6) -0222 end -0223 else -0224 fprintf(fid,'%s: !!omap []\n',name); -0225 end -0226 -0227 elseif strcmp(fieldName,'rxnEnzMat') -0228 %S: create header & write each enzyme in a new line -0229 fprintf(fid,'%s: !!omap\n',name); -0230 if sum(field(pos,:) ~= 0) > 0 -0231 model.enzymes = model.enzymes(field(pos,:) ~= 0); -0232 model.coeffs = field(pos,field(pos,:) ~= 0); -0233 %Sort metabolites: -0234 [model.enzymes,order] = sort(model.enzymes); -0235 model.coeffs = model.coeffs(order); -0236 for i = 1:length(model.enzymes) -0237 writeField(model, fid, 'coeffs', 'flt', i, [' - ' model.enzymes{i}], 6) -0238 end -0239 end -0240 -0241 elseif strcmp(fieldName,'subSystems') -0242 %subsystem: omitted entirely when the reaction carries none -0243 %(matching every other optional field), otherwise a block list — -0244 %always a list, even for a single subsystem, since a reaction can -0245 %have more than one and the reader already expects the list form. -0246 if iscell(field) -0247 if pos > numel(field); return; end -0248 list = field{pos}; -0249 else -0250 if pos > size(field, 1); return; end -0251 list = field(pos, :); -0252 end -0253 if ischar(list); list = {list}; end -0254 list = list(~cellfun('isempty', list)); -0255 if isempty(list) -0256 return -0257 end -0258 fprintf(fid,'%s:\n',name); -0259 indentStr = regexprep(name,'(^\s*).*','$1'); -0260 for i = 1:length(list) -0261 emitListItem(fid, [indentStr ' -'], list{i}, keyIndent+2) -0262 end -0263 -0264 elseif strcmp(fieldName,'eccodes') -0265 %eccodes (GECKO ec-rxns section only): ;-joined string -> a -0266 %block list if more than one code, else a bare scalar. -0267 if isempty(field{pos}) -0268 return -0269 end -0270 list = strip(strsplit(strrep(field{pos},' ',''),';')); -0271 list = list(~cellfun('isempty',list)); -0272 if isempty(list) -0273 return -0274 elseif isscalar(list) -0275 emitScalarLine(fid, name, list{1}, keyIndent) -0276 else -0277 fprintf(fid,'%s:\n',name); -0278 indentStr = regexprep(name,'(^\s*).*','$1'); -0279 for i = 1:length(list) -0280 emitListItem(fid, [indentStr ' -'], list{i}, keyIndent+2) -0281 end -0282 end -0283 -0284 elseif sum(pos) > 0 -0285 %All other fields: -0286 if strcmp(type,'txt') -0287 value = field{pos}; -0288 if ~isempty(value) -0289 emitScalarLine(fid, name, value, keyIndent) -0290 end -0291 elseif strcmp(type,'flt') -0292 if ~isnan(field(pos)) -0293 emitScalarLine(fid, name, formatFloat(full(field(pos))), keyIndent, true) -0294 end -0295 end -0296 end +0159 %EC-model: +0160 if isfield(model,'ec') +0161 % gecko_light flag at the top level (keeps the metaData block a pure +0162 % provenance container). The reader accepts both this key and the +0163 % legacy geckoLight key inside metaData. +0164 if model.ec.geckoLight; geckoLightStr = 'true'; else; geckoLightStr = 'false'; end +0165 fprintf(fid,'- gecko_light: %s\n', geckoLightStr); +0166 fprintf(fid,'- ec-rxns:\n'); +0167 for i = 1:length(model.ec.rxns) +0168 fprintf(fid,' - !!omap\n'); +0169 writeField(model.ec, fid, 'rxns', 'txt', i, ' - id', 4) +0170 writeField(model.ec, fid, 'kcat', 'flt', i, ' - kcat', 4) +0171 writeField(model.ec, fid, 'source', 'txt', i, ' - source', 4) +0172 writeField(model.ec, fid, 'notes', 'txt', i, ' - notes', 4) +0173 writeField(model.ec, fid, 'eccodes', 'txt', i, ' - eccodes', 4) +0174 writeField(model.ec, fid, 'rxnEnzMat', 'txt', i, ' - enzymes', 4) +0175 end +0176 +0177 fprintf(fid,'- ec-enzymes:\n'); +0178 for i = 1:length(model.ec.genes) +0179 fprintf(fid,' - !!omap\n'); +0180 writeField(model.ec, fid, 'genes', 'txt', i, ' - genes', 4) +0181 writeField(model.ec, fid, 'enzymes', 'txt', i, ' - enzymes', 4) +0182 writeField(model.ec, fid, 'mw', 'flt', i, ' - mw', 4) +0183 writeField(model.ec, fid, 'sequence', 'txt', i, ' - sequence', 4) +0184 writeField(model.ec, fid, 'concs', 'flt', i, ' - concs', 4) +0185 end +0186 end +0187 +0188 %Close file: +0189 fclose(fid); +0190 +0191 end +0192 +0193 function writeField(model,fid,fieldName,type,pos,name,keyIndent) +0194 %Writes a new line in the yaml file if the field exists and the field is +0195 %not empty at the corresponding position. It's recursive for the S +0196 %field (reaction stoichiometry). +0197 +0198 if isfield(model,fieldName) +0199 if strcmp(fieldName,'metComps') +0200 %metComps: write full name +0201 fieldName = 'comps'; +0202 pos = model.metComps(pos); +0203 end +0204 +0205 field = model.(fieldName); +0206 +0207 if strcmp(fieldName,'S') +0208 %S: create header & write each metabolite in a new line. Reactions +0209 %with no metabolites emit `metabolites: !!omap []` (the flow-style +0210 %empty omap representation) so the file remains a valid YAML 1.2 +0211 %document. +0212 if sum(field(:,pos) ~= 0) > 0 +0213 fprintf(fid,'%s: !!omap\n',name); +0214 model.mets = model.mets(field(:,pos) ~= 0); +0215 model.coeffs = field(field(:,pos) ~= 0,pos); +0216 %Sort metabolites: +0217 [model.mets,order] = sort(model.mets); +0218 model.coeffs = model.coeffs(order); +0219 for i = 1:length(model.mets) +0220 writeField(model, fid, 'coeffs', 'flt', i, [' - ' model.mets{i}], 6) +0221 end +0222 else +0223 fprintf(fid,'%s: !!omap []\n',name); +0224 end +0225 +0226 elseif strcmp(fieldName,'rxnEnzMat') +0227 %S: create header & write each enzyme in a new line +0228 fprintf(fid,'%s: !!omap\n',name); +0229 if sum(field(pos,:) ~= 0) > 0 +0230 model.enzymes = model.enzymes(field(pos,:) ~= 0); +0231 model.coeffs = field(pos,field(pos,:) ~= 0); +0232 %Sort metabolites: +0233 [model.enzymes,order] = sort(model.enzymes); +0234 model.coeffs = model.coeffs(order); +0235 for i = 1:length(model.enzymes) +0236 writeField(model, fid, 'coeffs', 'flt', i, [' - ' model.enzymes{i}], 6) +0237 end +0238 end +0239 +0240 elseif strcmp(fieldName,'subSystems') +0241 %subsystem: omitted entirely when the reaction carries none +0242 %(matching every other optional field), otherwise a block list — +0243 %always a list, even for a single subsystem, since a reaction can +0244 %have more than one and the reader already expects the list form. +0245 if iscell(field) +0246 if pos > numel(field); return; end +0247 list = field{pos}; +0248 else +0249 if pos > size(field, 1); return; end +0250 list = field(pos, :); +0251 end +0252 if ischar(list); list = {list}; end +0253 list = list(~cellfun('isempty', list)); +0254 if isempty(list) +0255 return +0256 end +0257 fprintf(fid,'%s:\n',name); +0258 indentStr = regexprep(name,'(^\s*).*','$1'); +0259 for i = 1:length(list) +0260 emitListItem(fid, [indentStr ' -'], list{i}, keyIndent+2) +0261 end +0262 +0263 elseif strcmp(fieldName,'eccodes') +0264 %eccodes (GECKO ec-rxns section only): ;-joined string -> a +0265 %block list if more than one code, else a bare scalar. +0266 if isempty(field{pos}) +0267 return +0268 end +0269 list = strip(strsplit(strrep(field{pos},' ',''),';')); +0270 list = list(~cellfun('isempty',list)); +0271 if isempty(list) +0272 return +0273 elseif isscalar(list) +0274 emitScalarLine(fid, name, list{1}, keyIndent) +0275 else +0276 fprintf(fid,'%s:\n',name); +0277 indentStr = regexprep(name,'(^\s*).*','$1'); +0278 for i = 1:length(list) +0279 emitListItem(fid, [indentStr ' -'], list{i}, keyIndent+2) +0280 end +0281 end +0282 +0283 elseif sum(pos) > 0 +0284 %All other fields: +0285 if strcmp(type,'txt') +0286 value = field{pos}; +0287 if ~isempty(value) +0288 emitScalarLine(fid, name, value, keyIndent) +0289 end +0290 elseif strcmp(type,'flt') +0291 if ~isnan(field(pos)) +0292 emitScalarLine(fid, name, formatFloat(full(field(pos))), keyIndent, true) +0293 end +0294 end +0295 end +0296 end 0297 end -0298 end -0299 -0300 function writeMetadata(model, fid) -0301 % Writes the metaData block. The `date` field is preserved when the model -0302 % carries one (model.date), so round-trips don't churn on every write; if -0303 % absent it's filled with the current date. -0304 -0305 fprintf(fid, '- metaData: !!omap\n'); -0306 emitMetaField(fid, 'id', valueOrDefault(model,'id','blankID')); -0307 emitMetaField(fid, 'name', valueOrDefault(model,'name','blankName')); -0308 if isfield(model,'version') && ~isempty(model.version) -0309 emitMetaField(fid, 'version', model.version); -0310 end -0311 if isfield(model,'date') && ~isempty(model.date) -0312 dateValue = model.date; -0313 else -0314 dateValue = datestr(now, 29); %#ok<DATST> % 29 = yyyy-mm-dd -0315 end -0316 emitMetaField(fid, 'date', dateValue); -0317 if isfield(model,'annotation') -0318 annoFields = {'defaultLB','defaultUB','givenName','familyName', ... -0319 'authors','email','organization','taxonomy','note','sourceUrl'}; -0320 for k = 1:numel(annoFields) -0321 f = annoFields{k}; -0322 if isfield(model.annotation, f) && ~isempty(model.annotation.(f)) -0323 emitMetaField(fid, f, model.annotation.(f)); -0324 end -0325 end -0326 % Any other annotation field is a caller-specific provenance key with -0327 % no RAVEN-defined slot (e.g. a tool-specific `_version` key). Emit it -0328 % too, sorted alphabetically for deterministic output, instead of -0329 % silently dropping it — readYAMLmodel.m's matching `otherwise` case -0330 % restores it on read, so a write/read round trip is lossless. -0331 extraFields = sort(setdiff(fieldnames(model.annotation), annoFields)); -0332 for k = 1:numel(extraFields) -0333 f = extraFields{k}; -0334 if ~isempty(model.annotation.(f)) -0335 emitMetaField(fid, f, model.annotation.(f)); -0336 end -0337 end -0338 end -0339 % gecko_light is emitted at the top level (see geckoLight emission near -0340 % the GECKO ec-* sections) so that metaData stays a pure provenance -0341 % block. -0342 end -0343 -0344 function v = valueOrDefault(model, field, defaultVal) -0345 if isfield(model, field) && ~isempty(model.(field)) -0346 v = model.(field); -0347 else -0348 v = defaultVal; +0298 +0299 function writeMetadata(model, fid) +0300 % Writes the metaData block. The `date` field is preserved when the model +0301 % carries one (model.date), so round-trips don't churn on every write; if +0302 % absent it's filled with the current date. +0303 +0304 fprintf(fid, '- metaData: !!omap\n'); +0305 emitMetaField(fid, 'id', valueOrDefault(model,'id','blankID')); +0306 emitMetaField(fid, 'name', valueOrDefault(model,'name','blankName')); +0307 if isfield(model,'version') && ~isempty(model.version) +0308 emitMetaField(fid, 'version', model.version); +0309 end +0310 if isfield(model,'date') && ~isempty(model.date) +0311 dateValue = model.date; +0312 else +0313 dateValue = datestr(now, 29); %#ok<DATST> % 29 = yyyy-mm-dd +0314 end +0315 emitMetaField(fid, 'date', dateValue); +0316 if isfield(model,'annotation') +0317 annoFields = {'defaultLB','defaultUB','givenName','familyName', ... +0318 'authors','email','organization','taxonomy','note','sourceUrl'}; +0319 for k = 1:numel(annoFields) +0320 f = annoFields{k}; +0321 if isfield(model.annotation, f) && ~isempty(model.annotation.(f)) +0322 emitMetaField(fid, f, model.annotation.(f)); +0323 end +0324 end +0325 % Any other annotation field is a caller-specific provenance key with +0326 % no RAVEN-defined slot (e.g. a tool-specific `_version` key). Emit it +0327 % too, sorted alphabetically for deterministic output, instead of +0328 % silently dropping it — readYAMLmodel.m's matching `otherwise` case +0329 % restores it on read, so a write/read round trip is lossless. +0330 extraFields = sort(setdiff(fieldnames(model.annotation), annoFields)); +0331 for k = 1:numel(extraFields) +0332 f = extraFields{k}; +0333 if ~isempty(model.annotation.(f)) +0334 emitMetaField(fid, f, model.annotation.(f)); +0335 end +0336 end +0337 end +0338 % gecko_light is emitted at the top level (see geckoLight emission near +0339 % the GECKO ec-* sections) so that metaData stays a pure provenance +0340 % block. +0341 end +0342 +0343 function v = valueOrDefault(model, field, defaultVal) +0344 if isfield(model, field) && ~isempty(model.(field)) +0345 v = model.(field); +0346 else +0347 v = defaultVal; +0348 end 0349 end -0350 end -0351 -0352 function emitMetaField(fid, key, value) -0353 % Emit one ` - key: value` line inside the metaData omap block. -0354 if islogical(value) -0355 if value; value = 'true'; else; value = 'false'; end -0356 emitScalarLine(fid, [' - ' key], value, 2, true); -0357 elseif isnumeric(value) -0358 % defaultLB/defaultUB are genuinely floats (reaction bounds); -0359 % every other metaData annotation value that reaches here numeric -0360 % (e.g. a stray numeric taxonomy id) is formatted the same way, since -0361 % none of RAVEN's own metaData fields are conceptually integers. -0362 emitScalarLine(fid, [' - ' key], formatFloat(double(value)), 2, true); -0363 else -0364 if ~ischar(value) && ~isstring(value) -0365 value = char(value); -0366 end -0367 emitScalarLine(fid, [' - ' key], char(value), 2); +0350 +0351 function emitMetaField(fid, key, value) +0352 % Emit one ` - key: value` line inside the metaData omap block. +0353 if islogical(value) +0354 if value; value = 'true'; else; value = 'false'; end +0355 emitScalarLine(fid, [' - ' key], value, 2, true); +0356 elseif isnumeric(value) +0357 % defaultLB/defaultUB are genuinely floats (reaction bounds); +0358 % every other metaData annotation value that reaches here numeric +0359 % (e.g. a stray numeric taxonomy id) is formatted the same way, since +0360 % none of RAVEN's own metaData fields are conceptually integers. +0361 emitScalarLine(fid, [' - ' key], formatFloat(double(value)), 2, true); +0362 else +0363 if ~ischar(value) && ~isstring(value) +0364 value = char(value); +0365 end +0366 emitScalarLine(fid, [' - ' key], char(value), 2); +0367 end 0368 end -0369 end -0370 -0371 function s = formatFloat(x) -0372 % Format every numeric field the same way: a whole number still gets an -0373 % explicit ".0" (e.g. "1000.0", not "1000"; "2.0" for a charge, not "2"). -0374 if isinf(x) -0375 if x > 0 -0376 s = '.inf'; -0377 else -0378 s = '-.inf'; -0379 end -0380 return; -0381 end -0382 if x == floor(x) && abs(x) < 1e16 -0383 s = sprintf('%.1f', x); % e.g. 1000 -> "1000.0" -0384 else -0385 % Shortest text that reads back as exactly x (15 to 17 significant -0386 % digits), as Python's repr does, so 1/3 is written 0.3333333333333333 -0387 % and a value is never rounded on the way out. -0388 for digits = 15:17 -0389 s = sprintf(['%.' num2str(digits) 'g'], x); -0390 if str2double(s) == x -0391 break -0392 end -0393 end +0369 +0370 function s = formatFloat(x) +0371 % Format every numeric field the same way: a whole number still gets an +0372 % explicit ".0" (e.g. "1000.0", not "1000"; "2.0" for a charge, not "2"). +0373 if isinf(x) +0374 if x > 0 +0375 s = '.inf'; +0376 else +0377 s = '-.inf'; +0378 end +0379 return; +0380 end +0381 if x == floor(x) && abs(x) < 1e16 +0382 s = sprintf('%.1f', x); % e.g. 1000 -> "1000.0" +0383 else +0384 % Shortest text that reads back as exactly x (15 to 17 significant +0385 % digits), as Python's repr does, so 1/3 is written 0.3333333333333333 +0386 % and a value is never rounded on the way out. +0387 for digits = 15:17 +0388 s = sprintf(['%.' num2str(digits) 'g'], x); +0389 if str2double(s) == x +0390 break +0391 end +0392 end +0393 end 0394 end -0395 end -0396 -0397 function writeAnnotationSimple(model, fid, miriamsField, miriamValsField, miriamNamesField, pos, indent, subIndent) -0398 % Emit a bare `annotation: !!omap` block from MIRIAM cross-references only -0399 % (used for genes and compartments, which carry no extra non-MIRIAM -0400 % annotation key and have no established reference format to follow — -0401 % compartments in particular are just a flat {id: name} map with no -0402 % per-compartment annotation at all — so the layout here follows only -0403 % readYAMLmodel's own expectation that a compartment's annotation -0404 % sub-entries are indented >=6 spaces). Keys are emitted in alphabetical -0405 % order, matching the sort order used elsewhere in this file. -0406 if nargin < 7 -0407 indent = ' '; -0408 end -0409 if nargin < 8 -0410 subIndent = length(indent)+2; -0411 end -0412 if ~isfield(model, miriamsField) || isempty(model.(miriamsField){pos}) -0413 return -0414 end -0415 names = model.(miriamNamesField); -0416 values = model.(miriamValsField); -0417 entries = collectMiriamEntries(names, values, pos); -0418 if isempty(entries) -0419 return -0420 end -0421 [~,order] = sort({entries.name}); -0422 entries = entries(order); -0423 fprintf(fid, '%s- annotation: !!omap\n', indent); -0424 emitAnnotationEntries(fid, entries, subIndent); -0425 end -0426 -0427 function entries = collectMiriamEntries(names, values, pos) -0428 entries = struct('name', {}, 'list', {}, 'forceList', {}); -0429 for j = 1:size(values, 2) -0430 v = values{pos, j}; -0431 if isempty(v); continue; end -0432 list = strip(strsplit(strrep(v, ' ', ''), ';')); -0433 entries(end+1) = struct('name', names{j}, 'list', {list}, 'forceList', false); %#ok<AGROW> +0395 +0396 function writeAnnotationSimple(model, fid, miriamsField, miriamValsField, miriamNamesField, pos, indent, subIndent) +0397 % Emit a bare `annotation: !!omap` block from MIRIAM cross-references only +0398 % (used for genes and compartments, which carry no extra non-MIRIAM +0399 % annotation key and have no established reference format to follow — +0400 % compartments in particular are just a flat {id: name} map with no +0401 % per-compartment annotation at all — so the layout here follows only +0402 % readYAMLmodel's own expectation that a compartment's annotation +0403 % sub-entries are indented >=6 spaces). Keys are emitted in alphabetical +0404 % order, matching the sort order used elsewhere in this file. +0405 if nargin < 7 +0406 indent = ' '; +0407 end +0408 if nargin < 8 +0409 subIndent = length(indent)+2; +0410 end +0411 if ~isfield(model, miriamsField) || isempty(model.(miriamsField){pos}) +0412 return +0413 end +0414 names = model.(miriamNamesField); +0415 values = model.(miriamValsField); +0416 entries = collectMiriamEntries(names, values, pos); +0417 if isempty(entries) +0418 return +0419 end +0420 [~,order] = sort({entries.name}); +0421 entries = entries(order); +0422 fprintf(fid, '%s- annotation: !!omap\n', indent); +0423 emitAnnotationEntries(fid, entries, subIndent); +0424 end +0425 +0426 function entries = collectMiriamEntries(names, values, pos) +0427 entries = struct('name', {}, 'list', {}, 'forceList', {}); +0428 for j = 1:size(values, 2) +0429 v = values{pos, j}; +0430 if isempty(v); continue; end +0431 list = strip(strsplit(strrep(v, ' ', ''), ';')); +0432 entries(end+1) = struct('name', names{j}, 'list', {list}, 'forceList', false); %#ok<AGROW> +0433 end 0434 end -0435 end -0436 -0437 function writeAnnotation(model, fid, kind, pos) -0438 % Emit the per-entry `annotation` block, fusing MIRIAM cross-references -0439 % with the non-MIRIAM annotation keys: `smiles` for metabolites and -0440 % `ec-code` (EC numbers) for reactions — both are written inside -0441 % `annotation` rather than as top-level entry keys. `ec-code` is always -0442 % emitted as a list, even when there is a single code. All keys (MIRIAMs -0443 % and the extra key together) are emitted in alphabetical order. -0444 switch kind -0445 case 'met' -0446 miriamsField = 'metMiriams'; -0447 miriamNamesField = 'newMetMiriamNames'; -0448 miriamValsField = 'newMetMiriams'; -0449 extraName = 'smiles'; -0450 extraField = 'metSmiles'; -0451 case 'rxn' -0452 miriamsField = 'rxnMiriams'; -0453 miriamNamesField = 'newRxnMiriamNames'; -0454 miriamValsField = 'newRxnMiriams'; -0455 extraName = 'ec-code'; -0456 extraField = 'eccodes'; -0457 otherwise -0458 error('writeAnnotation:unsupportedKind', 'Unsupported kind: %s', kind); -0459 end -0460 -0461 hasMiriams = isfield(model, miriamsField) && ~isempty(model.(miriamsField){pos}); -0462 hasExtra = isfield(model, extraField) && pos <= numel(model.(extraField)) ... -0463 && ~isempty(model.(extraField){pos}); -0464 -0465 if ~hasMiriams && ~hasExtra -0466 return; -0467 end -0468 -0469 entries = struct('name', {}, 'list', {}, 'forceList', {}); -0470 if hasMiriams -0471 entries = collectMiriamEntries(model.(miriamNamesField), model.(miriamValsField), pos); -0472 end -0473 if hasExtra -0474 % EC numbers / SMILES are always emitted as a list, even when there is -0475 % a single value — unlike a MIRIAM entry, which collapses to a bare -0476 % scalar when there is only one. -0477 codes = strip(strsplit(strrep(model.(extraField){pos}, ' ', ''), ';')); -0478 codes = codes(~cellfun('isempty', codes)); -0479 if ~isempty(codes) -0480 entries(end+1) = struct('name', extraName, 'list', {codes}, 'forceList', true); %#ok<AGROW> -0481 end -0482 end -0483 if isempty(entries) -0484 return -0485 end -0486 [~,order] = sort({entries.name}); -0487 entries = entries(order); -0488 -0489 fprintf(fid, ' - annotation: !!omap\n'); -0490 emitAnnotationEntries(fid, entries, 6); -0491 end -0492 -0493 function emitAnnotationEntries(fid, entries, keyIndent) -0494 % Shared tail for writeAnnotation / writeAnnotationSimple: entries are -0495 % already sorted by name; emit each as a bare scalar (single value) or a -0496 % block list (multiple values), at the given key indent. -0497 pad = repmat(' ', 1, keyIndent); -0498 for e = entries -0499 if numel(e.list) == 1 && ~e.forceList -0500 emitScalarLine(fid, [pad '- ' e.name], e.list{1}, keyIndent) -0501 else -0502 fprintf(fid, '%s- %s:\n', pad, e.name); -0503 for k = 1:numel(e.list) -0504 emitListItem(fid, [pad ' -'], e.list{k}, keyIndent+2) -0505 end -0506 end +0435 +0436 function writeAnnotation(model, fid, kind, pos) +0437 % Emit the per-entry `annotation` block, fusing MIRIAM cross-references +0438 % with the non-MIRIAM annotation keys: `smiles` for metabolites and +0439 % `ec-code` (EC numbers) for reactions — both are written inside +0440 % `annotation` rather than as top-level entry keys. `ec-code` is always +0441 % emitted as a list, even when there is a single code. All keys (MIRIAMs +0442 % and the extra key together) are emitted in alphabetical order. +0443 switch kind +0444 case 'met' +0445 miriamsField = 'metMiriams'; +0446 miriamNamesField = 'newMetMiriamNames'; +0447 miriamValsField = 'newMetMiriams'; +0448 extraName = 'smiles'; +0449 extraField = 'metSmiles'; +0450 case 'rxn' +0451 miriamsField = 'rxnMiriams'; +0452 miriamNamesField = 'newRxnMiriamNames'; +0453 miriamValsField = 'newRxnMiriams'; +0454 extraName = 'ec-code'; +0455 extraField = 'eccodes'; +0456 otherwise +0457 error('writeAnnotation:unsupportedKind', 'Unsupported kind: %s', kind); +0458 end +0459 +0460 hasMiriams = isfield(model, miriamsField) && ~isempty(model.(miriamsField){pos}); +0461 hasExtra = isfield(model, extraField) && pos <= numel(model.(extraField)) ... +0462 && ~isempty(model.(extraField){pos}); +0463 +0464 if ~hasMiriams && ~hasExtra +0465 return; +0466 end +0467 +0468 entries = struct('name', {}, 'list', {}, 'forceList', {}); +0469 if hasMiriams +0470 entries = collectMiriamEntries(model.(miriamNamesField), model.(miriamValsField), pos); +0471 end +0472 if hasExtra +0473 % EC numbers / SMILES are always emitted as a list, even when there is +0474 % a single value — unlike a MIRIAM entry, which collapses to a bare +0475 % scalar when there is only one. +0476 codes = strip(strsplit(strrep(model.(extraField){pos}, ' ', ''), ';')); +0477 codes = codes(~cellfun('isempty', codes)); +0478 if ~isempty(codes) +0479 entries(end+1) = struct('name', extraName, 'list', {codes}, 'forceList', true); %#ok<AGROW> +0480 end +0481 end +0482 if isempty(entries) +0483 return +0484 end +0485 [~,order] = sort({entries.name}); +0486 entries = entries(order); +0487 +0488 fprintf(fid, ' - annotation: !!omap\n'); +0489 emitAnnotationEntries(fid, entries, 6); +0490 end +0491 +0492 function emitAnnotationEntries(fid, entries, keyIndent) +0493 % Shared tail for writeAnnotation / writeAnnotationSimple: entries are +0494 % already sorted by name; emit each as a bare scalar (single value) or a +0495 % block list (multiple values), at the given key indent. +0496 pad = repmat(' ', 1, keyIndent); +0497 for e = entries +0498 if numel(e.list) == 1 && ~e.forceList +0499 emitScalarLine(fid, [pad '- ' e.name], e.list{1}, keyIndent) +0500 else +0501 fprintf(fid, '%s- %s:\n', pad, e.name); +0502 for k = 1:numel(e.list) +0503 emitListItem(fid, [pad ' -'], e.list{k}, keyIndent+2) +0504 end +0505 end +0506 end 0507 end -0508 end -0509 -0510 function emitScalarLine(fid, prefix, value, keyIndent, bareNumeric) -0511 % Emit "PREFIX: VALUE\n" (double-quoted when needed), where PREFIX is the -0512 % " - key" part with no trailing colon and value is the raw, -0513 % unescaped scalar content. -0514 % -0515 % bareNumeric (default false): value is already a pre-formatted number -0516 % (from formatFloat) that must never be quoted — numbers are never -0517 % ambiguous once formatted. -0518 if nargin < 5 -0519 bareNumeric = false; -0520 end -0521 fprintf(fid, '%s:', prefix); -0522 emitScalarCore(fid, length(prefix)+1, value, keyIndent, bareNumeric) -0523 end -0524 -0525 function emitListItem(fid, dashPrefix, value, keyIndent) -0526 % Emit "DASHPREFIX VALUE\n" for a plain sequence entry (no key, just the -0527 % "- " marker) — e.g. a subsystem, EC code, SMILES or MIRIAM list item. -0528 % Unlike emitScalarLine, no colon is written. -0529 fprintf(fid, '%s', dashPrefix); -0530 emitScalarCore(fid, length(dashPrefix), value, keyIndent, false) -0531 end -0532 -0533 function emitScalarCore(fid, ~, value, ~, bareNumeric) -0534 % Shared tail for emitScalarLine / emitListItem: writes a single space -0535 % after ':' or '-', then the value, double-quoted when needed (matching -0536 % Prettier's YAML default). No folding — this format never wraps a -0537 % scalar across lines, however long, so the two unused positional -0538 % arguments (column and key indent) only exist to keep every call site's -0539 % signature stable. -0540 if bareNumeric -0541 fprintf(fid, ' %s\n', value); -0542 return -0543 end -0544 if needsQuote(value) -0545 escaped = strrep(value, '\', '\\'); -0546 escaped = strrep(escaped, '"', '\"'); -0547 fprintf(fid, ' "%s"\n', escaped); -0548 else -0549 fprintf(fid, ' %s\n', value); +0508 +0509 function emitScalarLine(fid, prefix, value, keyIndent, bareNumeric) +0510 % Emit "PREFIX: VALUE\n" (double-quoted when needed), where PREFIX is the +0511 % " - key" part with no trailing colon and value is the raw, +0512 % unescaped scalar content. +0513 % +0514 % bareNumeric (default false): value is already a pre-formatted number +0515 % (from formatFloat) that must never be quoted — numbers are never +0516 % ambiguous once formatted. +0517 if nargin < 5 +0518 bareNumeric = false; +0519 end +0520 fprintf(fid, '%s:', prefix); +0521 emitScalarCore(fid, length(prefix)+1, value, keyIndent, bareNumeric) +0522 end +0523 +0524 function emitListItem(fid, dashPrefix, value, keyIndent) +0525 % Emit "DASHPREFIX VALUE\n" for a plain sequence entry (no key, just the +0526 % "- " marker) — e.g. a subsystem, EC code, SMILES or MIRIAM list item. +0527 % Unlike emitScalarLine, no colon is written. +0528 fprintf(fid, '%s', dashPrefix); +0529 emitScalarCore(fid, length(dashPrefix), value, keyIndent, false) +0530 end +0531 +0532 function emitScalarCore(fid, ~, value, ~, bareNumeric) +0533 % Shared tail for emitScalarLine / emitListItem: writes a single space +0534 % after ':' or '-', then the value, double-quoted when needed (matching +0535 % Prettier's YAML default). No folding — this format never wraps a +0536 % scalar across lines, however long, so the two unused positional +0537 % arguments (column and key indent) only exist to keep every call site's +0538 % signature stable. +0539 if bareNumeric +0540 fprintf(fid, ' %s\n', value); +0541 return +0542 end +0543 if needsQuote(value) +0544 escaped = strrep(value, '\', '\\'); +0545 escaped = strrep(escaped, '"', '\"'); +0546 fprintf(fid, ' "%s"\n', escaped); +0547 else +0548 fprintf(fid, ' %s\n', value); +0549 end 0550 end -0551 end -0552 -0553 function tf = needsQuote(s) -0554 % Whether this scalar needs quoting when written: either because a plain -0555 % (bare) reading would resolve to a non-string YAML type (bool/int/float/ -0556 % null/timestamp — this writer never emits explicit type tags), or -0557 % because the text itself is not valid as a plain block scalar (leading -0558 % indicator character, a mid-string ": "/" #", leading/trailing -0559 % whitespace, or embedded control characters). Implements the YAML 1.2 -0560 % rules for resolving implicit scalar types and analyzing scalar style, -0561 % and also quotes the YAML 1.1 booleans y, n, yes, no, on and off in any -0562 % case, which YAML 1.1 loaders such as PyYAML read as booleans. -0563 if isempty(s) -0564 tf = true; -0565 return -0566 end -0567 -0568 resolverPattern = [ ... -0569 '^(true|True|TRUE|false|False|FALSE)$|' ... -0570 '^([Yy]|[Nn]|[Yy][Ee][Ss]|[Nn][Oo]|[Oo][Nn]|[Oo][Ff][Ff])$|' ... -0571 '^[-+]?[0-9][0-9_]*\.[0-9_]*([eE][-+]?[0-9]+)?$|' ... -0572 '^[-+]?[0-9][0-9_]*[eE][-+]?[0-9]+$|' ... -0573 '^[-+]?\.[0-9_]+([eE][-+][0-9]+)?$|' ... -0574 '^[-+]?\.(inf|Inf|INF)$|' ... -0575 '^\.(nan|NaN|NAN)$|' ... -0576 '^[-+]?0b[01_]+$|' ... -0577 '^[-+]?0o?[0-7_]+$|' ... -0578 '^[-+]?[0-9_]+$|' ... -0579 '^[-+]?0x[0-9a-fA-F_]+$|' ... -0580 '^(~|[Nn]ull|NULL)$|' ... -0581 '^[0-9]{4}-[0-9]{2}-[0-9]{2}$' ... -0582 ]; -0583 if ~isempty(regexp(s, resolverPattern, 'once')) -0584 tf = true; -0585 return -0586 end -0587 -0588 first = s(1); -0589 if any(first == '#,[]{}&*!|>''"%@`') -0590 tf = true; return -0591 end -0592 followedByWs = (numel(s) == 1) || isWs(s(2)); -0593 if (first == '?' || first == ':') && followedByWs -0594 tf = true; return -0595 end -0596 if first == '-' && followedByWs -0597 tf = true; return -0598 end -0599 if isWs(first) || isWs(s(end)) -0600 tf = true; return -0601 end -0602 if any(s == char(10)) || any(s == char(13)) -0603 tf = true; return -0604 end -0605 for i = 2:numel(s) -0606 ch = s(i); -0607 if ch == ':' && ((i == numel(s)) || isWs(s(i+1))) -0608 tf = true; return -0609 end -0610 if ch == '#' && isWs(s(i-1)) -0611 tf = true; return -0612 end -0613 end -0614 tf = false; -0615 end -0616 -0617 function tf = isWs(ch) -0618 tf = ch == ' ' || ch == char(9) || ch == char(10) || ch == char(13); -0619 end

    +0551 +0552 function tf = needsQuote(s) +0553 % Whether this scalar needs quoting when written: either because a plain +0554 % (bare) reading would resolve to a non-string YAML type (bool/int/float/ +0555 % null/timestamp — this writer never emits explicit type tags), or +0556 % because the text itself is not valid as a plain block scalar (leading +0557 % indicator character, a mid-string ": "/" #", leading/trailing +0558 % whitespace, or embedded control characters). Implements the YAML 1.2 +0559 % rules for resolving implicit scalar types and analyzing scalar style, +0560 % and also quotes the YAML 1.1 booleans y, n, yes, no, on and off in any +0561 % case, which YAML 1.1 loaders such as PyYAML read as booleans. +0562 if isempty(s) +0563 tf = true; +0564 return +0565 end +0566 +0567 resolverPattern = [ ... +0568 '^(true|True|TRUE|false|False|FALSE)$|' ... +0569 '^([Yy]|[Nn]|[Yy][Ee][Ss]|[Nn][Oo]|[Oo][Nn]|[Oo][Ff][Ff])$|' ... +0570 '^[-+]?[0-9][0-9_]*\.[0-9_]*([eE][-+]?[0-9]+)?$|' ... +0571 '^[-+]?[0-9][0-9_]*[eE][-+]?[0-9]+$|' ... +0572 '^[-+]?\.[0-9_]+([eE][-+][0-9]+)?$|' ... +0573 '^[-+]?\.(inf|Inf|INF)$|' ... +0574 '^\.(nan|NaN|NAN)$|' ... +0575 '^[-+]?0b[01_]+$|' ... +0576 '^[-+]?0o?[0-7_]+$|' ... +0577 '^[-+]?[0-9_]+$|' ... +0578 '^[-+]?0x[0-9a-fA-F_]+$|' ... +0579 '^(~|[Nn]ull|NULL)$|' ... +0580 '^[0-9]{4}-[0-9]{2}-[0-9]{2}$' ... +0581 ]; +0582 if ~isempty(regexp(s, resolverPattern, 'once')) +0583 tf = true; +0584 return +0585 end +0586 +0587 first = s(1); +0588 if any(first == '#,[]{}&*!|>''"%@`') +0589 tf = true; return +0590 end +0591 followedByWs = (numel(s) == 1) || isWs(s(2)); +0592 if (first == '?' || first == ':') && followedByWs +0593 tf = true; return +0594 end +0595 if first == '-' && followedByWs +0596 tf = true; return +0597 end +0598 if isWs(first) || isWs(s(end)) +0599 tf = true; return +0600 end +0601 if any(s == char(10)) || any(s == char(13)) +0602 tf = true; return +0603 end +0604 for i = 2:numel(s) +0605 ch = s(i); +0606 if ch == ':' && ((i == numel(s)) || isWs(s(i+1))) +0607 tf = true; return +0608 end +0609 if ch == '#' && isWs(s(i-1)) +0610 tf = true; return +0611 end +0612 end +0613 tf = false; +0614 end +0615 +0616 function tf = isWs(ch) +0617 tf = ch == ' ' || ch == char(9) || ch == char(10) || ch == char(13); +0618 end

    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/localization/defaultCompartmentMap.html b/docs/html/localization/defaultCompartmentMap.html index 13faeeed..6cd07188 100644 --- a/docs/html/localization/defaultCompartmentMap.html +++ b/docs/html/localization/defaultCompartmentMap.html @@ -4,7 +4,7 @@ Description of defaultCompartmentMap - + @@ -21,33 +21,33 @@

    defaultCompartmentMap

    PURPOSE ^

    -
    defaultCompartmentMap Default predictor/database compartment label -> model compartment id.
    +
    defaultCompartmentMap Default map from compartment labels to model compartment ids.

    SYNOPSIS ^

    function map = defaultCompartmentMap()

    DESCRIPTION ^

    -
     defaultCompartmentMap  Default predictor/database compartment label -> model compartment id.
    +
     defaultCompartmentMap  Default map from compartment labels to model compartment ids.
     
    - Returns a containers.Map from lower-case predictor / database compartment labels (as used by
    - DeepLoc, MULocDeep, COMPARTMENTS and UniProt) to compartment ids, tuned for yeast/fungal
    - models (e.g. yeast-GEM codes). Pass it (or your own map) to parseScores / getUniProtScores as
    + Returns a containers.Map from lower-case compartment labels of predictors and databases (as
    + used by DeepLoc, MULocDeep, COMPARTMENTS and UniProt) to compartment ids, for yeast and fungal
    + models (for example yeast-GEM codes). The map is passed to parseScores or getUniProtScores as
      'compartmentMap'.
     
    - The table below is also the worked example of how such a map is built, so read it as three
    - deliberate decisions rather than a lookup list:
    + The map holds three decisions:
     
    - - **Which compartments exist.** A label absent from the map is dropped along with its score
    -   column, so listing only the ids your model has is how unwanted compartments are excluded.
    -   'plastid' is absent because fungi lack one; a plant model would add it.
    - - **Which labels are the same thing.** Predictors disagree on wording, so 'cytoplasm' and
    -   'cytosol' both map to 'c', and 'mitochondrion' / 'mitochondria' / 'mitochondrial' all map
    -   to 'm'. Merged columns are combined by maximum, not sum.
    - - **Which distinct compartments to collapse.** 'lysosome' maps to 'v' not because it is a
    -   vacuole but because a yeast model has no lysosome and the vacuole is its functional
    -   equivalent. Change this if your model separates them.
    + - Compartments present. A label absent from the map is dropped along with its score column, so
    +   the map lists only the compartment ids of the model. 'plastid' is absent because fungi have
    +   no plastid; a plant model adds it.
    + - Labels that name the same compartment. Predictors differ in wording, so 'cytoplasm' and
    +   'cytosol' both map to 'c', and 'mitochondrion', 'mitochondria' and 'mitochondrial' all map
    +   to 'm'. Merged columns are combined by their maximum.
    + - Distinct compartments that are collapsed. 'lysosome' maps to 'v' because a yeast model has
    +   no lysosome and the vacuole is its functional equivalent. A model that separates the two
    +   needs a different mapping.
     
    - Extend a copy rather than editing this function, e.g.
    + Changes to the mapping go into a copy of the map, which leaves this function unedited, for
    + example:
     
          map = defaultCompartmentMap;
          map('cell wall') = 'ce';
    @@ -69,34 +69,34 @@ 

    CROSS-REFERENCE INFORMATION ^
 </ul>
 This function is called by:
 <ul style= -
  • getUniProtScores getUniProtScores Fetch curated subcellular locations from UniProt -> gene scoring structure.
  • parseScores parseScores Parse predictor / database output into a gene scoring structure (GSS).
  • +
  • getUniProtScores getUniProtScores Fetch curated subcellular locations from UniProt as a gene scoring structure.
  • parseScores parseScores Parse predictor or database output into a gene scoring structure (GSS).
  • SOURCE CODE ^

    0001 function map = defaultCompartmentMap()
    -0002 % defaultCompartmentMap  Default predictor/database compartment label -> model compartment id.
    +0002 % defaultCompartmentMap  Default map from compartment labels to model compartment ids.
     0003 %
    -0004 % Returns a containers.Map from lower-case predictor / database compartment labels (as used by
    -0005 % DeepLoc, MULocDeep, COMPARTMENTS and UniProt) to compartment ids, tuned for yeast/fungal
    -0006 % models (e.g. yeast-GEM codes). Pass it (or your own map) to parseScores / getUniProtScores as
    +0004 % Returns a containers.Map from lower-case compartment labels of predictors and databases (as
    +0005 % used by DeepLoc, MULocDeep, COMPARTMENTS and UniProt) to compartment ids, for yeast and fungal
    +0006 % models (for example yeast-GEM codes). The map is passed to parseScores or getUniProtScores as
     0007 % 'compartmentMap'.
     0008 %
    -0009 % The table below is also the worked example of how such a map is built, so read it as three
    -0010 % deliberate decisions rather than a lookup list:
    -0011 %
    -0012 % - **Which compartments exist.** A label absent from the map is dropped along with its score
    -0013 %   column, so listing only the ids your model has is how unwanted compartments are excluded.
    -0014 %   'plastid' is absent because fungi lack one; a plant model would add it.
    -0015 % - **Which labels are the same thing.** Predictors disagree on wording, so 'cytoplasm' and
    -0016 %   'cytosol' both map to 'c', and 'mitochondrion' / 'mitochondria' / 'mitochondrial' all map
    -0017 %   to 'm'. Merged columns are combined by maximum, not sum.
    -0018 % - **Which distinct compartments to collapse.** 'lysosome' maps to 'v' not because it is a
    -0019 %   vacuole but because a yeast model has no lysosome and the vacuole is its functional
    -0020 %   equivalent. Change this if your model separates them.
    -0021 %
    -0022 % Extend a copy rather than editing this function, e.g.
    +0009 % The map holds three decisions:
    +0010 %
    +0011 % - Compartments present. A label absent from the map is dropped along with its score column, so
    +0012 %   the map lists only the compartment ids of the model. 'plastid' is absent because fungi have
    +0013 %   no plastid; a plant model adds it.
    +0014 % - Labels that name the same compartment. Predictors differ in wording, so 'cytoplasm' and
    +0015 %   'cytosol' both map to 'c', and 'mitochondrion', 'mitochondria' and 'mitochondrial' all map
    +0016 %   to 'm'. Merged columns are combined by their maximum.
    +0017 % - Distinct compartments that are collapsed. 'lysosome' maps to 'v' because a yeast model has
    +0018 %   no lysosome and the vacuole is its functional equivalent. A model that separates the two
    +0019 %   needs a different mapping.
    +0020 %
    +0021 % Changes to the mapping go into a copy of the map, which leaves this function unedited, for
    +0022 % example:
     0023 %
     0024 %     map = defaultCompartmentMap;
     0025 %     map('cell wall') = 'ce';
    diff --git a/docs/html/localization/getUniProtScores.html b/docs/html/localization/getUniProtScores.html
    index f63ec7b0..e3b7b277 100644
    --- a/docs/html/localization/getUniProtScores.html
    +++ b/docs/html/localization/getUniProtScores.html
    @@ -4,7 +4,7 @@
     
       Description of getUniProtScores
       
    -  
    +  
       
       
       
    @@ -21,44 +21,44 @@ 

    getUniProtScores

    PURPOSE ^

    -
    getUniProtScores Fetch curated subcellular locations from UniProt -> gene scoring structure.
    +
    getUniProtScores Fetch curated subcellular locations from UniProt as a gene scoring structure.

    SYNOPSIS ^

    function GSS = getUniProtScores(organism, varargin)

    DESCRIPTION ^

    -
     getUniProtScores  Fetch curated subcellular locations from UniProt -> gene scoring structure.
    +
     getUniProtScores  Fetch curated subcellular locations from UniProt as a gene scoring structure.
     
    - Queries the UniProtKB REST API for an organism's "Subcellular location" annotations and parses
    - them into the GSS consumed by predictLocalization (no manual export needed). UniProt's curated
    - location is qualitative, so each annotated compartment gets score 1.0 (a multi-located protein
    - lands in several). Requires an internet connection.
    + Queries the UniProtKB REST API for the "Subcellular location" annotations of an organism and
    + parses them into the GSS consumed by predictLocalization. No manual export is needed. UniProt
    + locations are qualitative, so each annotated compartment gets score 1.0 and a protein with
    + several annotated locations scores 1.0 in each of them. The function requires an internet
    + connection.
     
      Parameters
      ----------
      organism : double or char
    -     a UniProt organism / taxonomy id (e.g. 559292 for S. cerevisiae S288C).
    +     a UniProt organism (taxonomy) id (for example 559292 for S. cerevisiae S288C).
     
      Name-Value Arguments
      --------------------
      idField : char
    -     which identifier becomes the gene id: 'gene_oln' (ordered locus / ORF, the default, which
    -     matches yeast-GEM), 'accession', or 'gene_primary'.
    +     which identifier becomes the gene id: 'gene_oln' (ordered locus or ORF name, the default,
    +     which matches yeast-GEM), 'accession', or 'gene_primary'.
      reviewed : logical
    -     restrict to curated Swiss-Prot entries (default true).
    +     true to restrict the query to curated Swiss-Prot entries (default true).
      compartmentMap : containers.Map
    -     UniProt location term -> your model's compartment id (default
    -     defaultCompartmentMap). Here the map is not only a renaming: its keys are
    -     also the vocabulary searched for in UniProt's "Subcellular location [CC]"
    -     free text, matched whole-word and case insensitively, so they must be
    -     UniProt's own noun forms ('endoplasmic reticulum', not 'ER') and a term
    -     that is not a key is never looked for. Several keys may share one
    -     compartment id, which merges them; since every hit scores 1.0, merging
    -     here means "annotated to any of these counts as this compartment". One
    -     key cannot be split across two compartment ids - see parseScores for why,
    -     and for the recipe to duplicate a column on the returned GSS instead.
    +     maps UniProt location terms to the compartment ids of the model (default
    +     defaultCompartmentMap). The keys are also the vocabulary searched for in the
    +     "Subcellular location [CC]" free text of UniProt, matched as whole words and
    +     case insensitively. The keys must be the noun forms used by UniProt
    +     ('endoplasmic reticulum', not 'ER'), and a term that is not a key is not searched.
    +     Several keys may share one compartment id, which merges them; since every hit
    +     scores 1.0, a protein annotated to any of the merged terms scores 1.0 in that
    +     compartment. One key cannot map to two compartment ids; parseScores describes
    +     how to duplicate a column of the returned GSS instead.
      extraQuery : char
    -     an additional UniProt query clause, ANDed into the query (e.g. 'gene:CIT1').
    +     an additional UniProt query clause, combined with the query by AND (for example 'gene:CIT1').
     
      Returns
      -------
    @@ -80,7 +80,7 @@ 

    DESCRIPTION ^CROSS-REFERENCE INFORMATION ^

    This function calls:
      -
    • defaultCompartmentMap defaultCompartmentMap Default predictor/database compartment label -> model compartment id.
    • parseScores parseScores Parse predictor / database output into a gene scoring structure (GSS).
    +
  • defaultCompartmentMap defaultCompartmentMap Default map from compartment labels to model compartment ids.
  • parseScores parseScores Parse predictor or database output into a gene scoring structure (GSS).
  • This function is called by:
    @@ -92,38 +92,38 @@

    SUBFUNCTIONS ^SOURCE CODE ^

    0001 function GSS = getUniProtScores(organism, varargin)
    -0002 % getUniProtScores  Fetch curated subcellular locations from UniProt -> gene scoring structure.
    +0002 % getUniProtScores  Fetch curated subcellular locations from UniProt as a gene scoring structure.
     0003 %
    -0004 % Queries the UniProtKB REST API for an organism's "Subcellular location" annotations and parses
    -0005 % them into the GSS consumed by predictLocalization (no manual export needed). UniProt's curated
    -0006 % location is qualitative, so each annotated compartment gets score 1.0 (a multi-located protein
    -0007 % lands in several). Requires an internet connection.
    -0008 %
    -0009 % Parameters
    -0010 % ----------
    -0011 % organism : double or char
    -0012 %     a UniProt organism / taxonomy id (e.g. 559292 for S. cerevisiae S288C).
    -0013 %
    -0014 % Name-Value Arguments
    -0015 % --------------------
    -0016 % idField : char
    -0017 %     which identifier becomes the gene id: 'gene_oln' (ordered locus / ORF, the default, which
    -0018 %     matches yeast-GEM), 'accession', or 'gene_primary'.
    -0019 % reviewed : logical
    -0020 %     restrict to curated Swiss-Prot entries (default true).
    -0021 % compartmentMap : containers.Map
    -0022 %     UniProt location term -> your model's compartment id (default
    -0023 %     defaultCompartmentMap). Here the map is not only a renaming: its keys are
    -0024 %     also the vocabulary searched for in UniProt's "Subcellular location [CC]"
    -0025 %     free text, matched whole-word and case insensitively, so they must be
    -0026 %     UniProt's own noun forms ('endoplasmic reticulum', not 'ER') and a term
    -0027 %     that is not a key is never looked for. Several keys may share one
    -0028 %     compartment id, which merges them; since every hit scores 1.0, merging
    -0029 %     here means "annotated to any of these counts as this compartment". One
    -0030 %     key cannot be split across two compartment ids - see parseScores for why,
    -0031 %     and for the recipe to duplicate a column on the returned GSS instead.
    +0004 % Queries the UniProtKB REST API for the "Subcellular location" annotations of an organism and
    +0005 % parses them into the GSS consumed by predictLocalization. No manual export is needed. UniProt
    +0006 % locations are qualitative, so each annotated compartment gets score 1.0 and a protein with
    +0007 % several annotated locations scores 1.0 in each of them. The function requires an internet
    +0008 % connection.
    +0009 %
    +0010 % Parameters
    +0011 % ----------
    +0012 % organism : double or char
    +0013 %     a UniProt organism (taxonomy) id (for example 559292 for S. cerevisiae S288C).
    +0014 %
    +0015 % Name-Value Arguments
    +0016 % --------------------
    +0017 % idField : char
    +0018 %     which identifier becomes the gene id: 'gene_oln' (ordered locus or ORF name, the default,
    +0019 %     which matches yeast-GEM), 'accession', or 'gene_primary'.
    +0020 % reviewed : logical
    +0021 %     true to restrict the query to curated Swiss-Prot entries (default true).
    +0022 % compartmentMap : containers.Map
    +0023 %     maps UniProt location terms to the compartment ids of the model (default
    +0024 %     defaultCompartmentMap). The keys are also the vocabulary searched for in the
    +0025 %     "Subcellular location [CC]" free text of UniProt, matched as whole words and
    +0026 %     case insensitively. The keys must be the noun forms used by UniProt
    +0027 %     ('endoplasmic reticulum', not 'ER'), and a term that is not a key is not searched.
    +0028 %     Several keys may share one compartment id, which merges them; since every hit
    +0029 %     scores 1.0, a protein annotated to any of the merged terms scores 1.0 in that
    +0030 %     compartment. One key cannot map to two compartment ids; parseScores describes
    +0031 %     how to duplicate a column of the returned GSS instead.
     0032 % extraQuery : char
    -0033 %     an additional UniProt query clause, ANDed into the query (e.g. 'gene:CIT1').
    +0033 %     an additional UniProt query clause, combined with the query by AND (for example 'gene:CIT1').
     0034 %
     0035 % Returns
     0036 % -------
    diff --git a/docs/html/localization/index.html b/docs/html/localization/index.html
    index 3011d3d9..75c636ec 100644
    --- a/docs/html/localization/index.html
    +++ b/docs/html/localization/index.html
    @@ -19,7 +19,7 @@ 

    Index for localization

    Matlab files in this directory:

    -
     assignCompartmentsassignCompartments Assign reactions to compartments by a functionality-constrained MILP.
     defaultCompartmentMapdefaultCompartmentMap Default predictor/database compartment label -> model compartment id.
     getUniProtScoresgetUniProtScores Fetch curated subcellular locations from UniProt -> gene scoring structure.
     parseScoresparseScores Parse predictor / database output into a gene scoring structure (GSS).
     predictLocalizationpredictLocalization Assign reactions to compartments using localization predictors.
    + assignCompartmentsassignCompartments Assign reactions to compartments by a functionality-constrained MILP.  defaultCompartmentMapdefaultCompartmentMap Default map from compartment labels to model compartment ids.  getUniProtScoresgetUniProtScores Fetch curated subcellular locations from UniProt as a gene scoring structure.  parseScoresparseScores Parse predictor or database output into a gene scoring structure (GSS).  predictLocalizationpredictLocalization Assign reactions to compartments using localization predictors. diff --git a/docs/html/localization/parseScores.html b/docs/html/localization/parseScores.html index 410e8462..573d3c31 100644 --- a/docs/html/localization/parseScores.html +++ b/docs/html/localization/parseScores.html @@ -4,7 +4,7 @@ Description of parseScores - + @@ -21,22 +21,22 @@

    parseScores

    PURPOSE ^

    -
    parseScores Parse predictor / database output into a gene scoring structure (GSS).
    +
    parseScores Parse predictor or database output into a gene scoring structure (GSS).

    SYNOPSIS ^

    function GSS = parseScores(inputFile, varargin)

    DESCRIPTION ^

    -
     parseScores  Parse predictor / database output into a gene scoring structure (GSS).
    +
     parseScores  Parse predictor or database output into a gene scoring structure (GSS).
     
    - Reads the output of a subcellular-localization predictor or evidence database and
    + Reads the output of a subcellular localisation predictor or evidence database and
      builds the gene scoring structure consumed by predictLocalization. Scores are
    - normalized so the best score for each gene is 1.0.
    + normalised so that the highest score of each gene is 1.0.
     
      Parameters
      ----------
      inputFile : char
    -     the file with the predictor / database output.
    +     the file with the predictor or database output.
     
      Name-Value Arguments
      --------------------
    @@ -46,15 +46,15 @@ 

    DESCRIPTION ^DESCRIPTION ^DESCRIPTION ^DESCRIPTION ^CROSS-REFERENCE INFORMATION ^

    This function calls:
      -
    • defaultCompartmentMap defaultCompartmentMap Default predictor/database compartment label -> model compartment id.
    +
  • defaultCompartmentMap defaultCompartmentMap Default map from compartment labels to model compartment ids.
  • This function is called by:
      -
    • getUniProtScores getUniProtScores Fetch curated subcellular locations from UniProt -> gene scoring structure.
    +
  • getUniProtScores getUniProtScores Fetch curated subcellular locations from UniProt as a gene scoring structure.
  • SUBFUNCTIONS ^

    @@ -135,16 +132,16 @@

    SUBFUNCTIONS ^SOURCE CODE ^

    0001 function GSS = parseScores(inputFile, varargin)
    -0002 % parseScores  Parse predictor / database output into a gene scoring structure (GSS).
    +0002 % parseScores  Parse predictor or database output into a gene scoring structure (GSS).
     0003 %
    -0004 % Reads the output of a subcellular-localization predictor or evidence database and
    +0004 % Reads the output of a subcellular localisation predictor or evidence database and
     0005 % builds the gene scoring structure consumed by predictLocalization. Scores are
    -0006 % normalized so the best score for each gene is 1.0.
    +0006 % normalised so that the highest score of each gene is 1.0.
     0007 %
     0008 % Parameters
     0009 % ----------
     0010 % inputFile : char
    -0011 %     the file with the predictor / database output.
    +0011 %     the file with the predictor or database output.
     0012 %
     0013 % Name-Value Arguments
     0014 % --------------------
    @@ -154,15 +151,15 @@ 

    SOURCE CODE ^% compartment), 'compartments' (a COMPARTMENTS jensenlab.org channel TSV), or 0019 % 'uniprot' (a UniProtKB TSV export with a "Subcellular location [CC]" column). 0020 % compartmentMap : containers.Map -0021 % renames the predictor's / database's compartment labels to your model's -0022 % compartment ids. Keys are the labels as the predictor writes them, lower -0023 % case; values are the compartment ids you want in the GSS. See -0024 % Compartment mapping below. Optional for every predictor except 'uniprot', -0025 % where it also supplies the location vocabulary and defaults to -0026 % defaultCompartmentMap. -0027 % idColumn : char or double -0028 % column (name or 1-based index) holding the gene id for 'mulocdeep' / 'uniprot'. -0029 % Default: the first column. For yeast-GEM, use the UniProt ordered-locus column. +0021 % maps the compartment labels of the predictor or database to the compartment ids of +0022 % the model. Keys are the labels as the predictor writes them, in lower case; values +0023 % are the compartment ids in the GSS. Optional for every predictor except 'uniprot', +0024 % where the map also supplies the location vocabulary and defaults to +0025 % defaultCompartmentMap. See Compartment mapping below. +0026 % idColumn : char or double +0027 % column (name or 1-based index) holding the gene id for 'mulocdeep' and 'uniprot' +0028 % (default: the first column). For yeast-GEM, the UniProt ordered-locus column holds +0029 % the matching ids. 0030 % minConfidence : double 0031 % for 'compartments', drop annotations with confidence below this (default 0). 0032 % @@ -173,282 +170,279 @@

    SOURCE CODE ^% 0038 % - genes : cell, one entry per gene. 0039 % - compartments : cell, one entry per compartment column. -0040 % - scores : double, numel(genes)-by-numel(compartments), normalized so -0041 % the best score per gene is 1.0. +0040 % - scores : double, numel(genes)-by-numel(compartments), normalised so +0041 % that the highest score per gene is 1.0. 0042 % 0043 % Compartment mapping 0044 % ------------------- -0045 % Predictors emit their own compartment vocabulary ("Endoplasmic reticulum", -0046 % "Golgi apparatus", ...), which rarely matches a model's compartment ids. -0047 % compartmentMap is a containers.Map that translates them, and it is the only -0048 % place that translation happens - the returned GSS already uses your ids. -0049 % defaultCompartmentMap returns a worked example, tuned for yeast/fungal -0050 % models; use it as-is, or as the template for your own. Three rules: +0045 % Predictors use their own compartment vocabulary ("Endoplasmic reticulum", +0046 % "Golgi apparatus", ...), which need not match the compartment ids of a model. +0047 % compartmentMap is a containers.Map that translates the labels; the translation +0048 % happens only there, so the returned GSS contains the ids of the map values. +0049 % defaultCompartmentMap returns an example map for yeast and fungal models, which can +0050 % be used as it is or as a template. Three rules apply: 0051 % -0052 % - **A label not in the map is dropped**, together with its whole score -0053 % column. This is how you exclude compartments your model does not have -0054 % (defaultCompartmentMap has no 'plastid' key, so plastid scores never reach -0055 % a fungal model). Passing no map at all keeps every label untouched. -0056 % - **Several labels may share one compartment id**, which merges their -0057 % columns. The merged score for a gene is the *maximum* over the merged -0058 % labels, not their sum, so a gene predicted 0.7 cytoplasm and 0.4 cytosol -0059 % scores 0.7 for 'c'. Use this for synonyms and for deliberately collapsing -0060 % distinct compartments your model does not separate (defaultCompartmentMap -0061 % sends both 'vacuole' and 'lysosome' to 'v'). Column order follows the first -0062 % appearance of each id. -0063 % - **One label cannot be split across two compartment ids**: a containers.Map -0064 % holds one value per key, and the GSS is what predictLocalization scores -0065 % against, so a split has to be an explicit statement about the returned -0066 % scores rather than a mapping rule. Duplicate the column afterwards, e.g. to -0067 % let a gene predicted "golgi" compete for both 'g' and 'v': -0068 % -0069 % GSS = parseScores(file, 'compartmentMap', map); -0070 % g = strcmp(GSS.compartments, 'g'); -0071 % GSS.compartments{end+1} = 'v'; -0072 % GSS.scores(:,end+1) = GSS.scores(:,g); -0073 % -0074 % The two columns are then independent: predictLocalization assigns each gene -0075 % to exactly one compartment, so it picks whichever of 'g'/'v' keeps the -0076 % network better connected. Halve the copied column instead of duplicating it -0077 % if the evidence should count for less in the second compartment. -0078 % -0079 % For 'uniprot' the keys do double duty: they are also the terms searched for -0080 % in the "Subcellular location [CC]" free text, matched whole-word and case -0081 % insensitively, so they must be UniProt's own noun forms. A term that is not a -0082 % key is never looked for. -0083 % -0084 % Examples -0085 % -------- -0086 % GSS = parseScores(file, 'predictor', 'deeploc'); -0087 % GSS = parseScores(file, 'predictor', 'deeploc', 'compartmentMap', defaultCompartmentMap); -0088 % GSS = parseScores(file, 'predictor', 'uniprot', 'idColumn', 'Gene Names (ordered locus)'); -0089 % -0090 % See also -0091 % -------- -0092 % predictLocalization, getUniProtScores, defaultCompartmentMap -0093 -0094 p = parseRAVENargs(varargin, {'predictor',[]; 'compartmentMap',[]; ... -0095 'idColumn',[]; 'minConfidence',0}); -0096 predictor = p.predictor; -0097 if isempty(predictor); predictor = 'deeploc'; else; predictor = char(predictor); end -0098 compartmentMap = p.compartmentMap; -0099 -0100 if ~isfile(inputFile) -0101 error('RAVEN:badInput', 'Could not open file %s', char(string(inputFile))); -0102 end -0103 -0104 switch lower(predictor) -0105 case 'cello' -0106 GSS = i_parseCello(inputFile); -0107 case 'deeploc' -0108 GSS = i_parseDeepLoc(inputFile); -0109 case 'mulocdeep' -0110 GSS = i_parseWide(inputFile, p.idColumn); -0111 case 'compartments' -0112 GSS = i_parseCompartments(inputFile, p.minConfidence); -0113 case 'uniprot' -0114 if isempty(compartmentMap); compartmentMap = defaultCompartmentMap(); end -0115 GSS = i_parseUniProt(inputFile, p.idColumn, compartmentMap); -0116 compartmentMap = []; % already applied during parsing -0117 case 'wolf' -0118 error('RAVEN:badInput', ['WoLF PSORT support has been removed. Use a modern ' ... -0119 'predictor (''deeploc'', ''mulocdeep''), the ''compartments'' database, or ' ... -0120 '''uniprot'' (see getUniProtScores).']); -0121 otherwise -0122 error('RAVEN:badInput', 'Unknown predictor "%s"', predictor); -0123 end -0124 -0125 % Remove duplicate genes -0126 [~, J, K] = unique(GSS.genes); -0127 if numel(J) ~= numel(K) -0128 warning('RAVEN:warning', 'There are duplicate genes in the input file'); -0129 GSS.genes = GSS.genes(J); -0130 GSS.scores = GSS.scores(J,:); -0131 end -0132 -0133 % Optional renaming of compartments to model ids (already done for 'uniprot') -0134 if ~isempty(compartmentMap) -0135 GSS = i_applyCompartmentMap(GSS, compartmentMap); -0136 end -0137 -0138 if isempty(GSS.genes) || isempty(GSS.compartments) -0139 warning('RAVEN:warning', ['parseScores produced an empty gene scoring structure ' ... -0140 '(no genes or no compartments matched). Check the input file and, if used, that ' ... -0141 'compartmentMap labels match the predictor/database compartment names.']); -0142 end -0143 -0144 % Normalize so the best score per gene is 1.0 (genes with no positive evidence untouched) -0145 m = max(GSS.scores, [], 2); -0146 m(m <= 0) = 1; -0147 GSS.scores = bsxfun(@times, GSS.scores, 1./m); -0148 end -0149 -0150 % ------------------------------------------------------------------------- parsers -0151 -0152 function GSS = i_parseCello(inputFile) -0153 fid = fopen(inputFile, 'r'); -0154 tline = fgetl(fid); -0155 tline = regexprep(tline, '^.+#Combined:\t', ''); -0156 tline = regexprep(tline, '\t#Most-likely-Location.+', ''); -0157 GSS.compartments = transpose(regexp(tline, '\t', 'split')); -0158 GSS.genes = {}; GSS.scores = []; -0159 row = 0; -0160 while true -0161 tline = fgetl(fid); -0162 if ~ischar(tline); break; end -0163 tline = regexprep(tline, '^.+:\t', ''); -0164 tline = regexprep(tline, ' .+', ''); -0165 tline = regexp(tline, '\t', 'split'); -0166 if numel(tline) < numel(GSS.compartments) -0167 error('RAVEN:badInput', 'Malformed CELLO row %d (fewer fields than compartments)', row+1); -0168 end -0169 row = row + 1; -0170 GSS.scores(row,:) = str2double(tline(1:numel(GSS.compartments))); -0171 GSS.genes{row,1} = tline{1,end}; -0172 end -0173 fclose(fid); -0174 end -0175 -0176 function GSS = i_parseDeepLoc(inputFile) -0177 fid = fopen(inputFile, 'r'); -0178 tline = fgetl(fid); -0179 comps = regexp(tline, ',', 'split'); -0180 GSS.compartments = transpose(comps(4:end)); % cols 1-3 are Protein_ID/Localizations/Signals -0181 GSS.genes = {}; GSS.scores = []; -0182 row = 0; -0183 while true -0184 tline = fgetl(fid); -0185 if ~ischar(tline); break; end -0186 parts = regexp(tline, ',', 'split'); -0187 row = row + 1; -0188 GSS.scores(row,:) = str2double(parts(4:end)); -0189 GSS.genes{row,1} = parts{1,1}; -0190 end -0191 fclose(fid); -0192 end -0193 -0194 function GSS = i_parseWide(inputFile, idColumn) -0195 % MULocDeep / generic wide table: gene id column + one numeric column per compartment. -0196 T = readtable(inputFile, 'FileType', 'text', 'VariableNamingRule', 'preserve'); -0197 idx = i_idColumnIndex(T, idColumn); -0198 GSS.genes = cellstr(string(T{:,idx})); -0199 rest = setdiff(1:width(T), idx, 'stable'); -0200 GSS.compartments = transpose(T.Properties.VariableNames(rest)); -0201 GSS.scores = i_numericMatrix(T, rest); -0202 end -0203 -0204 function GSS = i_parseCompartments(inputFile, minConfidence) -0205 % COMPARTMENTS channel TSV (no header): col1 gene, col4 GO term name, last col confidence. -0206 T = readtable(inputFile, 'FileType', 'text', 'Delimiter', '\t', 'ReadVariableNames', false); -0207 if width(T) < 5 -0208 error('RAVEN:badInput', 'Expected a COMPARTMENTS full TSV (>=5 columns)'); -0209 end -0210 genes = string(T{:,1}); -0211 comps = string(T{:,4}); -0212 score = T{:,end}; -0213 if ~isnumeric(score); score = str2double(string(score)); end -0214 keep = ~isnan(score) & score >= minConfidence; -0215 genes = genes(keep); comps = comps(keep); score = score(keep); -0216 -0217 ug = unique(genes, 'stable'); -0218 uc = unique(comps, 'stable'); -0219 M = zeros(numel(ug), numel(uc)); -0220 [~, gi] = ismember(genes, ug); -0221 [~, ci] = ismember(comps, uc); -0222 for k = 1:numel(genes) -0223 if M(gi(k), ci(k)) < score(k); M(gi(k), ci(k)) = score(k); end -0224 end -0225 GSS.genes = cellstr(ug); -0226 GSS.compartments = cellstr(uc); -0227 GSS.scores = M; -0228 end -0229 -0230 function GSS = i_parseUniProt(inputFile, idColumn, map) -0231 % UniProtKB TSV export: scan the "Subcellular location [CC]" text for known terms. -0232 T = readtable(inputFile, 'FileType', 'text', 'Delimiter', '\t', 'VariableNamingRule', 'preserve'); -0233 vn = string(T.Properties.VariableNames); -0234 locIdx = find(contains(lower(vn), 'subcellular'), 1); -0235 if isempty(locIdx) -0236 error('RAVEN:badInput', 'No "Subcellular location" column in the UniProt file'); -0237 end -0238 idIdx = i_idColumnIndex(T, idColumn); -0239 ids = string(T{:,idIdx}); -0240 loc = string(T{:,locIdx}); -0241 -0242 labels = lower(string(keys(map))); -0243 codesForLabels = string(values(map)); -0244 codeList = unique(codesForLabels, 'stable'); -0245 % Whole-word patterns so a short label (e.g. 'cytoplasm') does not match inside a longer, -0246 % distinct location term (e.g. 'Cytoplasmic vesicle'). Labels must be the UniProt noun forms. -0247 patterns = "\<" + arrayfun(@(s) string(regexptranslate('escape', char(s))), labels) + "\>"; -0248 -0249 M = zeros(numel(ids), numel(codeList)); -0250 hasHit = false(numel(ids), 1); -0251 for i = 1:numel(ids) -0252 txt = lower(loc(i)); -0253 txt = regexprep(txt, '\{[^}]*\}', ' '); % drop {ECO:...} evidence -0254 % Split into per-isoform blocks and drop the Note=... free text WITHIN each block, so a -0255 % later isoform's locations survive and Note prose is never read as a location. -0256 blocks = regexp(char(txt), '\[isoform[^\]]*\]:', 'split'); -0257 for b = 1:numel(blocks) -0258 seg = regexprep(blocks{b}, 'note=.*', ' '); -0259 for j = 1:numel(labels) -0260 if ~isempty(regexp(seg, patterns(j), 'once')) -0261 c = find(codeList == codesForLabels(j), 1); -0262 M(i, c) = 1; hasHit(i) = true; -0263 end -0264 end -0265 end -0266 end -0267 keep = hasHit & strlength(strtrim(ids)) > 0; -0268 GSS.genes = cellstr(ids(keep)); -0269 GSS.compartments = cellstr(codeList); -0270 GSS.scores = M(keep, :); -0271 end -0272 -0273 % ------------------------------------------------------------------------- helpers -0274 -0275 function idx = i_idColumnIndex(T, idColumn) -0276 if isempty(idColumn) -0277 idx = 1; -0278 elseif isnumeric(idColumn) -0279 idx = idColumn; -0280 else -0281 idx = find(strcmp(T.Properties.VariableNames, char(idColumn)), 1); -0282 if isempty(idx) -0283 error('RAVEN:badInput', 'idColumn "%s" not found', char(idColumn)); -0284 end -0285 end -0286 end -0287 -0288 function M = i_numericMatrix(T, cols) -0289 M = zeros(height(T), numel(cols)); -0290 for k = 1:numel(cols) -0291 col = T{:, cols(k)}; -0292 if ~isnumeric(col); col = str2double(string(col)); end -0293 M(:, k) = col; -0294 end -0295 end -0296 -0297 function GSS = i_applyCompartmentMap(GSS, map) -0298 labels = lower(string(keys(map))); -0299 codes = string(values(map)); -0300 old = lower(string(GSS.compartments)); -0301 mapped = strings(numel(old), 1); -0302 keepCol = false(numel(old), 1); -0303 for i = 1:numel(old) -0304 j = find(labels == old(i), 1); -0305 if ~isempty(j); mapped(i) = codes(j); keepCol(i) = true; end -0306 end -0307 usedCodes = unique(mapped(keepCol), 'stable'); -0308 M = zeros(numel(GSS.genes), numel(usedCodes)); -0309 for c = 1:numel(usedCodes) -0310 cols = keepCol & mapped == usedCodes(c); -0311 M(:, c) = max(GSS.scores(:, cols), [], 2); -0312 end -0313 GSS.compartments = cellstr(usedCodes); -0314 GSS.scores = M; -0315 end

    +0052 % - A label that is not in the map is dropped, together with its whole score +0053 % column. This excludes compartments that the model does not have +0054 % (defaultCompartmentMap has no 'plastid' key, so plastid scores are dropped for a +0055 % fungal model). Without a map, every label is kept unchanged. +0056 % - Several labels may share one compartment id, which merges their +0057 % columns. The merged score of a gene is the maximum over the merged labels, not +0058 % their sum: a gene predicted 0.7 cytoplasm and 0.4 cytosol scores 0.7 for 'c'. +0059 % Sharing an id merges synonyms and collapses compartments that the model does not +0060 % separate (defaultCompartmentMap sends both 'vacuole' and 'lysosome' to 'v'). +0061 % Column order follows the first appearance of each id. +0062 % - One label cannot map to two compartment ids: a containers.Map holds one +0063 % value per key. A split is made on the returned scores. Duplicating the +0064 % column afterwards lets a gene predicted "golgi" compete for both 'g' and 'v': +0065 % +0066 % GSS = parseScores(file, 'compartmentMap', map); +0067 % g = strcmp(GSS.compartments, 'g'); +0068 % GSS.compartments{end+1} = 'v'; +0069 % GSS.scores(:,end+1) = GSS.scores(:,g); +0070 % +0071 % The two columns are then independent: predictLocalization assigns each gene +0072 % to one compartment, so it picks whichever of 'g' and 'v' keeps the network +0073 % better connected. Halving the copied column gives the evidence half the +0074 % weight in the second compartment. +0075 % +0076 % For 'uniprot', the keys are also the terms searched for in the +0077 % "Subcellular location [CC]" free text, matched as whole words and case +0078 % insensitively. The keys must be the noun forms used by UniProt. A term that is +0079 % not a key is not searched. +0080 % +0081 % Examples +0082 % -------- +0083 % GSS = parseScores(file, 'predictor', 'deeploc'); +0084 % GSS = parseScores(file, 'predictor', 'deeploc', 'compartmentMap', defaultCompartmentMap); +0085 % GSS = parseScores(file, 'predictor', 'uniprot', 'idColumn', 'Gene Names (ordered locus)'); +0086 % +0087 % See also +0088 % -------- +0089 % predictLocalization, getUniProtScores, defaultCompartmentMap +0090 +0091 p = parseRAVENargs(varargin, {'predictor',[]; 'compartmentMap',[]; ... +0092 'idColumn',[]; 'minConfidence',0}); +0093 predictor = p.predictor; +0094 if isempty(predictor); predictor = 'deeploc'; else; predictor = char(predictor); end +0095 compartmentMap = p.compartmentMap; +0096 +0097 if ~isfile(inputFile) +0098 error('RAVEN:badInput', 'Could not open file %s', char(string(inputFile))); +0099 end +0100 +0101 switch lower(predictor) +0102 case 'cello' +0103 GSS = i_parseCello(inputFile); +0104 case 'deeploc' +0105 GSS = i_parseDeepLoc(inputFile); +0106 case 'mulocdeep' +0107 GSS = i_parseWide(inputFile, p.idColumn); +0108 case 'compartments' +0109 GSS = i_parseCompartments(inputFile, p.minConfidence); +0110 case 'uniprot' +0111 if isempty(compartmentMap); compartmentMap = defaultCompartmentMap(); end +0112 GSS = i_parseUniProt(inputFile, p.idColumn, compartmentMap); +0113 compartmentMap = []; % already applied during parsing +0114 case 'wolf' +0115 error('RAVEN:badInput', ['WoLF PSORT support has been removed. Use a modern ' ... +0116 'predictor (''deeploc'', ''mulocdeep''), the ''compartments'' database, or ' ... +0117 '''uniprot'' (see getUniProtScores).']); +0118 otherwise +0119 error('RAVEN:badInput', 'Unknown predictor "%s"', predictor); +0120 end +0121 +0122 % Remove duplicate genes +0123 [~, J, K] = unique(GSS.genes); +0124 if numel(J) ~= numel(K) +0125 warning('RAVEN:warning', 'There are duplicate genes in the input file'); +0126 GSS.genes = GSS.genes(J); +0127 GSS.scores = GSS.scores(J,:); +0128 end +0129 +0130 % Optional renaming of compartments to model ids (already done for 'uniprot') +0131 if ~isempty(compartmentMap) +0132 GSS = i_applyCompartmentMap(GSS, compartmentMap); +0133 end +0134 +0135 if isempty(GSS.genes) || isempty(GSS.compartments) +0136 warning('RAVEN:warning', ['parseScores produced an empty gene scoring structure ' ... +0137 '(no genes or no compartments matched). Check the input file and, if used, that ' ... +0138 'compartmentMap labels match the predictor/database compartment names.']); +0139 end +0140 +0141 % Normalize so the best score per gene is 1.0 (genes with no positive evidence untouched) +0142 m = max(GSS.scores, [], 2); +0143 m(m <= 0) = 1; +0144 GSS.scores = bsxfun(@times, GSS.scores, 1./m); +0145 end +0146 +0147 % ------------------------------------------------------------------------- parsers +0148 +0149 function GSS = i_parseCello(inputFile) +0150 fid = fopen(inputFile, 'r'); +0151 tline = fgetl(fid); +0152 tline = regexprep(tline, '^.+#Combined:\t', ''); +0153 tline = regexprep(tline, '\t#Most-likely-Location.+', ''); +0154 GSS.compartments = transpose(regexp(tline, '\t', 'split')); +0155 GSS.genes = {}; GSS.scores = []; +0156 row = 0; +0157 while true +0158 tline = fgetl(fid); +0159 if ~ischar(tline); break; end +0160 tline = regexprep(tline, '^.+:\t', ''); +0161 tline = regexprep(tline, ' .+', ''); +0162 tline = regexp(tline, '\t', 'split'); +0163 if numel(tline) < numel(GSS.compartments) +0164 error('RAVEN:badInput', 'Malformed CELLO row %d (fewer fields than compartments)', row+1); +0165 end +0166 row = row + 1; +0167 GSS.scores(row,:) = str2double(tline(1:numel(GSS.compartments))); +0168 GSS.genes{row,1} = tline{1,end}; +0169 end +0170 fclose(fid); +0171 end +0172 +0173 function GSS = i_parseDeepLoc(inputFile) +0174 fid = fopen(inputFile, 'r'); +0175 tline = fgetl(fid); +0176 comps = regexp(tline, ',', 'split'); +0177 GSS.compartments = transpose(comps(4:end)); % cols 1-3 are Protein_ID/Localizations/Signals +0178 GSS.genes = {}; GSS.scores = []; +0179 row = 0; +0180 while true +0181 tline = fgetl(fid); +0182 if ~ischar(tline); break; end +0183 parts = regexp(tline, ',', 'split'); +0184 row = row + 1; +0185 GSS.scores(row,:) = str2double(parts(4:end)); +0186 GSS.genes{row,1} = parts{1,1}; +0187 end +0188 fclose(fid); +0189 end +0190 +0191 function GSS = i_parseWide(inputFile, idColumn) +0192 % MULocDeep / generic wide table: gene id column + one numeric column per compartment. +0193 T = readtable(inputFile, 'FileType', 'text', 'VariableNamingRule', 'preserve'); +0194 idx = i_idColumnIndex(T, idColumn); +0195 GSS.genes = cellstr(string(T{:,idx})); +0196 rest = setdiff(1:width(T), idx, 'stable'); +0197 GSS.compartments = transpose(T.Properties.VariableNames(rest)); +0198 GSS.scores = i_numericMatrix(T, rest); +0199 end +0200 +0201 function GSS = i_parseCompartments(inputFile, minConfidence) +0202 % COMPARTMENTS channel TSV (no header): col1 gene, col4 GO term name, last col confidence. +0203 T = readtable(inputFile, 'FileType', 'text', 'Delimiter', '\t', 'ReadVariableNames', false); +0204 if width(T) < 5 +0205 error('RAVEN:badInput', 'Expected a COMPARTMENTS full TSV (>=5 columns)'); +0206 end +0207 genes = string(T{:,1}); +0208 comps = string(T{:,4}); +0209 score = T{:,end}; +0210 if ~isnumeric(score); score = str2double(string(score)); end +0211 keep = ~isnan(score) & score >= minConfidence; +0212 genes = genes(keep); comps = comps(keep); score = score(keep); +0213 +0214 ug = unique(genes, 'stable'); +0215 uc = unique(comps, 'stable'); +0216 M = zeros(numel(ug), numel(uc)); +0217 [~, gi] = ismember(genes, ug); +0218 [~, ci] = ismember(comps, uc); +0219 for k = 1:numel(genes) +0220 if M(gi(k), ci(k)) < score(k); M(gi(k), ci(k)) = score(k); end +0221 end +0222 GSS.genes = cellstr(ug); +0223 GSS.compartments = cellstr(uc); +0224 GSS.scores = M; +0225 end +0226 +0227 function GSS = i_parseUniProt(inputFile, idColumn, map) +0228 % UniProtKB TSV export: scan the "Subcellular location [CC]" text for known terms. +0229 T = readtable(inputFile, 'FileType', 'text', 'Delimiter', '\t', 'VariableNamingRule', 'preserve'); +0230 vn = string(T.Properties.VariableNames); +0231 locIdx = find(contains(lower(vn), 'subcellular'), 1); +0232 if isempty(locIdx) +0233 error('RAVEN:badInput', 'No "Subcellular location" column in the UniProt file'); +0234 end +0235 idIdx = i_idColumnIndex(T, idColumn); +0236 ids = string(T{:,idIdx}); +0237 loc = string(T{:,locIdx}); +0238 +0239 labels = lower(string(keys(map))); +0240 codesForLabels = string(values(map)); +0241 codeList = unique(codesForLabels, 'stable'); +0242 % Whole-word patterns so a short label (e.g. 'cytoplasm') does not match inside a longer, +0243 % distinct location term (e.g. 'Cytoplasmic vesicle'). Labels must be the UniProt noun forms. +0244 patterns = "\<" + arrayfun(@(s) string(regexptranslate('escape', char(s))), labels) + "\>"; +0245 +0246 M = zeros(numel(ids), numel(codeList)); +0247 hasHit = false(numel(ids), 1); +0248 for i = 1:numel(ids) +0249 txt = lower(loc(i)); +0250 txt = regexprep(txt, '\{[^}]*\}', ' '); % drop {ECO:...} evidence +0251 % Split into per-isoform blocks and drop the Note=... free text WITHIN each block, so a +0252 % later isoform's locations survive and Note prose is never read as a location. +0253 blocks = regexp(char(txt), '\[isoform[^\]]*\]:', 'split'); +0254 for b = 1:numel(blocks) +0255 seg = regexprep(blocks{b}, 'note=.*', ' '); +0256 for j = 1:numel(labels) +0257 if ~isempty(regexp(seg, patterns(j), 'once')) +0258 c = find(codeList == codesForLabels(j), 1); +0259 M(i, c) = 1; hasHit(i) = true; +0260 end +0261 end +0262 end +0263 end +0264 keep = hasHit & strlength(strtrim(ids)) > 0; +0265 GSS.genes = cellstr(ids(keep)); +0266 GSS.compartments = cellstr(codeList); +0267 GSS.scores = M(keep, :); +0268 end +0269 +0270 % ------------------------------------------------------------------------- helpers +0271 +0272 function idx = i_idColumnIndex(T, idColumn) +0273 if isempty(idColumn) +0274 idx = 1; +0275 elseif isnumeric(idColumn) +0276 idx = idColumn; +0277 else +0278 idx = find(strcmp(T.Properties.VariableNames, char(idColumn)), 1); +0279 if isempty(idx) +0280 error('RAVEN:badInput', 'idColumn "%s" not found', char(idColumn)); +0281 end +0282 end +0283 end +0284 +0285 function M = i_numericMatrix(T, cols) +0286 M = zeros(height(T), numel(cols)); +0287 for k = 1:numel(cols) +0288 col = T{:, cols(k)}; +0289 if ~isnumeric(col); col = str2double(string(col)); end +0290 M(:, k) = col; +0291 end +0292 end +0293 +0294 function GSS = i_applyCompartmentMap(GSS, map) +0295 labels = lower(string(keys(map))); +0296 codes = string(values(map)); +0297 old = lower(string(GSS.compartments)); +0298 mapped = strings(numel(old), 1); +0299 keepCol = false(numel(old), 1); +0300 for i = 1:numel(old) +0301 j = find(labels == old(i), 1); +0302 if ~isempty(j); mapped(i) = codes(j); keepCol(i) = true; end +0303 end +0304 usedCodes = unique(mapped(keepCol), 'stable'); +0305 M = zeros(numel(GSS.genes), numel(usedCodes)); +0306 for c = 1:numel(usedCodes) +0307 cols = keepCol & mapped == usedCodes(c); +0308 M(:, c) = max(GSS.scores(:, cols), [], 2); +0309 end +0310 GSS.compartments = cellstr(usedCodes); +0311 GSS.scores = M; +0312 end

    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/localization/predictLocalization.html b/docs/html/localization/predictLocalization.html index b617d046..200fc1f3 100644 --- a/docs/html/localization/predictLocalization.html +++ b/docs/html/localization/predictLocalization.html @@ -29,71 +29,62 @@

    SYNOPSIS ^DESCRIPTION ^

     predictLocalization  Assign reactions to compartments using localization predictors.
     
    - Tries to assign reactions to compartments in a manner that is in
    - agreement with localization predictors while at the same time maintaining
    - connectivity.
    + Assigns reactions to compartments so that the assignment agrees with the localization
    + predictors and the network stays connected.
     
      Parameters
      ----------
      model : struct
    -     a model structure. If the model contains several compartments they
    -     will be merged.
    +     a model structure. If the model contains several compartments, they
    +     are merged.
      GSS : struct
          a gene scoring structure as from parseScores.
      defaultCompartment : char
          transport reactions are expressed as diffusion between the
    -     defaultCompartment and the others. This is usually the cytosol. The
    -     default compartment must have a match in GSS.
    +     defaultCompartment and the others. The compartment is usually the cytosol. It must
    +     be one of GSS.compartments.
     
      Name-Value Arguments
      --------------------
      transportCost : double
    -     the cost for including a transport reaction. If this is a scalar then
    -     the same cost is used for all metabolites. It can also be a vector of
    -     costs with the same dimension as model.mets. Negative costs encourage
    -     transport of the metabolite (default 0.5).
    +     the cost for including a transport reaction. A scalar applies the same cost to all
    +     metabolites; a vector must have the same dimension as model.mets. A negative cost
    +     favours transport of the metabolite (default 0.5).
      maxTime : double
    -     maximum optimization time in minutes (default 15).
    +     maximum optimisation time in minutes (default 15).
      plotResults : logical
    -     true if the results should be plotted during the optimization
    -     (default false).
    +     true to plot the results during the optimisation (default false).
     
      Returns
      -------
      outModel : struct
          the resulting model structure.
      geneLocalization : struct
    -     structure with the genes and their resulting localization.
    +     structure with the genes and their resulting localisation.
      transportStruct : struct
    -     structure with the transport reactions that had to be inferred and
    -     between which compartments.
    +     structure with the inferred transport reactions and the compartments between which
    +     they transport.
      scores : struct
    -     structure that contains the total score history together with the
    -     score based on gene localization and the score based on included
    -     transport reactions.
    +     structure with the total score history, the score based on gene localisation and
    +     the score based on the included transport reactions.
      removedRxns : cell
    -     cell array with the reaction ids that had to be removed to have a
    -     connected input model.
    +     cell array with the ids of the reactions removed to obtain a connected input model.
     
      Notes
      -----
    - This function requires that the starting network is connected when it is
    - in one compartment. Reactions that are unconnected are removed and saved
    - in removedRxns. Try running fillGaps to have a more connected input model
    - if there are many such reactions. The input model should also not include
    - any exchange, demand or sink reactions, otherwise this function would not
    - provide any results.
    + The starting network must be connected when it is in one compartment. Unconnected
    + reactions are removed and returned in removedRxns. If many reactions are removed,
    + fillGaps can produce a more connected input model. The input model must not include
    + exchange, demand or sink reactions; otherwise the function provides no results.
     
    - In the final model all metabolites are produced in at least one reaction.
    - This does not guarantee a fully functional model since there can be
    - internal loops. Transport reactions are only included as passive diffusion
    - (A <=> B).
    + In the final model, every metabolite is produced in at least one reaction. Internal
    + loops can still make the model non-functional. Transport reactions are included only
    + as passive diffusion (A <=> B).
     
    - The score of a model is the sum of scores for all genes in their assigned
    - compartment minus the cost of all transport reactions that had to be
    - included. A gene can only be assigned to one compartment. This is a
    - simplification to keep the problem size down. The problem is solved using
    - simulated annealing.
    + The score of a model is the sum of the scores of all genes in their assigned
    + compartment minus the cost of all included transport reactions. A gene is assigned
    + to one compartment, which limits the problem size. The problem is solved by simulated
    + annealing.
     
      Examples
      --------
    @@ -121,1159 +112,1150 @@ 

    SOURCE CODE ^% predictLocalization Assign reactions to compartments using localization predictors. 0005 % -0006 % Tries to assign reactions to compartments in a manner that is in -0007 % agreement with localization predictors while at the same time maintaining -0008 % connectivity. -0009 % -0010 % Parameters -0011 % ---------- -0012 % model : struct -0013 % a model structure. If the model contains several compartments they -0014 % will be merged. -0015 % GSS : struct -0016 % a gene scoring structure as from parseScores. -0017 % defaultCompartment : char -0018 % transport reactions are expressed as diffusion between the -0019 % defaultCompartment and the others. This is usually the cytosol. The -0020 % default compartment must have a match in GSS. -0021 % -0022 % Name-Value Arguments -0023 % -------------------- -0024 % transportCost : double -0025 % the cost for including a transport reaction. If this is a scalar then -0026 % the same cost is used for all metabolites. It can also be a vector of -0027 % costs with the same dimension as model.mets. Negative costs encourage -0028 % transport of the metabolite (default 0.5). -0029 % maxTime : double -0030 % maximum optimization time in minutes (default 15). -0031 % plotResults : logical -0032 % true if the results should be plotted during the optimization -0033 % (default false). -0034 % -0035 % Returns -0036 % ------- -0037 % outModel : struct -0038 % the resulting model structure. -0039 % geneLocalization : struct -0040 % structure with the genes and their resulting localization. -0041 % transportStruct : struct -0042 % structure with the transport reactions that had to be inferred and -0043 % between which compartments. -0044 % scores : struct -0045 % structure that contains the total score history together with the -0046 % score based on gene localization and the score based on included -0047 % transport reactions. -0048 % removedRxns : cell -0049 % cell array with the reaction ids that had to be removed to have a -0050 % connected input model. -0051 % -0052 % Notes -0053 % ----- -0054 % This function requires that the starting network is connected when it is -0055 % in one compartment. Reactions that are unconnected are removed and saved -0056 % in removedRxns. Try running fillGaps to have a more connected input model -0057 % if there are many such reactions. The input model should also not include -0058 % any exchange, demand or sink reactions, otherwise this function would not -0059 % provide any results. -0060 % -0061 % In the final model all metabolites are produced in at least one reaction. -0062 % This does not guarantee a fully functional model since there can be -0063 % internal loops. Transport reactions are only included as passive diffusion -0064 % (A <=> B). -0065 % -0066 % The score of a model is the sum of scores for all genes in their assigned -0067 % compartment minus the cost of all transport reactions that had to be -0068 % included. A gene can only be assigned to one compartment. This is a -0069 % simplification to keep the problem size down. The problem is solved using -0070 % simulated annealing. -0071 % -0072 % Examples -0073 % -------- -0074 % [outModel, geneLocalization, transportStruct, scores, removedRxns] = ... -0075 % predictLocalization(model, GSS, defaultCompartment, ... -0076 % transportCost, maxTime, plotResults); -0077 -0078 p=parseRAVENargs(varargin, {'transportCost',[]; 'maxTime',[]; 'plotResults',[]}); -0079 transportCost=p.transportCost; -0080 maxTime=p.maxTime; -0081 plotResults=p.plotResults; -0082 -0083 if isempty(transportCost) -0084 transportCost=ones(numel(model.mets),1)*0.5; +0006 % Assigns reactions to compartments so that the assignment agrees with the localization +0007 % predictors and the network stays connected. +0008 % +0009 % Parameters +0010 % ---------- +0011 % model : struct +0012 % a model structure. If the model contains several compartments, they +0013 % are merged. +0014 % GSS : struct +0015 % a gene scoring structure as from parseScores. +0016 % defaultCompartment : char +0017 % transport reactions are expressed as diffusion between the +0018 % defaultCompartment and the others. The compartment is usually the cytosol. It must +0019 % be one of GSS.compartments. +0020 % +0021 % Name-Value Arguments +0022 % -------------------- +0023 % transportCost : double +0024 % the cost for including a transport reaction. A scalar applies the same cost to all +0025 % metabolites; a vector must have the same dimension as model.mets. A negative cost +0026 % favours transport of the metabolite (default 0.5). +0027 % maxTime : double +0028 % maximum optimisation time in minutes (default 15). +0029 % plotResults : logical +0030 % true to plot the results during the optimisation (default false). +0031 % +0032 % Returns +0033 % ------- +0034 % outModel : struct +0035 % the resulting model structure. +0036 % geneLocalization : struct +0037 % structure with the genes and their resulting localisation. +0038 % transportStruct : struct +0039 % structure with the inferred transport reactions and the compartments between which +0040 % they transport. +0041 % scores : struct +0042 % structure with the total score history, the score based on gene localisation and +0043 % the score based on the included transport reactions. +0044 % removedRxns : cell +0045 % cell array with the ids of the reactions removed to obtain a connected input model. +0046 % +0047 % Notes +0048 % ----- +0049 % The starting network must be connected when it is in one compartment. Unconnected +0050 % reactions are removed and returned in removedRxns. If many reactions are removed, +0051 % fillGaps can produce a more connected input model. The input model must not include +0052 % exchange, demand or sink reactions; otherwise the function provides no results. +0053 % +0054 % In the final model, every metabolite is produced in at least one reaction. Internal +0055 % loops can still make the model non-functional. Transport reactions are included only +0056 % as passive diffusion (A <=> B). +0057 % +0058 % The score of a model is the sum of the scores of all genes in their assigned +0059 % compartment minus the cost of all included transport reactions. A gene is assigned +0060 % to one compartment, which limits the problem size. The problem is solved by simulated +0061 % annealing. +0062 % +0063 % Examples +0064 % -------- +0065 % [outModel, geneLocalization, transportStruct, scores, removedRxns] = ... +0066 % predictLocalization(model, GSS, defaultCompartment, ... +0067 % transportCost, maxTime, plotResults); +0068 +0069 p=parseRAVENargs(varargin, {'transportCost',[]; 'maxTime',[]; 'plotResults',[]}); +0070 transportCost=p.transportCost; +0071 maxTime=p.maxTime; +0072 plotResults=p.plotResults; +0073 +0074 if isempty(transportCost) +0075 transportCost=ones(numel(model.mets),1)*0.5; +0076 end +0077 if numel(transportCost)==1 +0078 transportCost=ones(numel(model.mets),1)*transportCost; +0079 end +0080 transportCost=transportCost(:); +0081 +0082 if numel(transportCost)~=numel(model.mets) +0083 EM='The vector of transport costs must have the same dimension as model.mets'; +0084 error('RAVEN:badInput', '%s', EM); 0085 end -0086 if numel(transportCost)==1 -0087 transportCost=ones(numel(model.mets),1)*transportCost; +0086 if isempty(maxTime) +0087 maxTime=15; 0088 end -0089 transportCost=transportCost(:); -0090 -0091 if numel(transportCost)~=numel(model.mets) -0092 EM='The vector of transport costs must have the same dimension as model.mets'; -0093 error('RAVEN:badInput', '%s', EM); -0094 end -0095 if isempty(maxTime) -0096 maxTime=15; +0089 if isempty(plotResults) +0090 plotResults=false; +0091 end +0092 +0093 if isfield(model,'rxnComps') +0094 model=rmfield(model,'rxnComps'); +0095 EM='The model structure contains information about reaction compartmentalization. This is not supported by this function. The rxnComps field has been deleted'; +0096 warning('RAVEN:warning', '%s', EM); 0097 end -0098 if isempty(plotResults) -0099 plotResults=false; -0100 end -0101 -0102 if isfield(model,'rxnComps') -0103 model=rmfield(model,'rxnComps'); -0104 EM='The model structure contains information about reaction compartmentalization. This is not supported by this function. The rxnComps field has been deleted'; -0105 warning('RAVEN:warning', '%s', EM); -0106 end -0107 if isfield(model,'geneComps') -0108 model=rmfield(model,'geneComps'); -0109 EM='The model structure contains information about gene compartmentalization. This is not supported by this function. The geneComps field has been deleted'; -0110 warning('RAVEN:warning', '%s', EM); -0111 end -0112 -0113 defaultCompartment=char(defaultCompartment); -0114 I=ismember(defaultCompartment,GSS.compartments); -0115 if I==false -0116 error('RAVEN:badInput', ... -0117 ['defaultCompartment ''%s'' not found in GSS. Available compartments: %s. ' ... -0118 '(If you mapped scores to model ids via compartmentMap, pass an id such as ''c''; ' ... -0119 'otherwise pass the raw predictor label.)'], ... -0120 defaultCompartment, strjoin(GSS.compartments(:)', ', ')); -0121 end -0122 -0123 if numel(model.comps)>1 -0124 EM='The model has several compartments. All compartments will be merged'; -0125 warning('RAVEN:warning', '%s', EM); -0126 model=mergeCompartments(model,true,true); -0127 end -0128 -0129 noGenes = ismember(model.genes,GSS.genes); -0130 if ~all(noGenes) -0131 EM=['For ' num2str(numel(find(~noGenes))) ' of ' num2str(numel(model.genes)) ' model genes no data was found in GSS']; -0132 warning('RAVEN:warning', '%s', EM); -0133 end -0134 -0135 %***Begin formating the data structures -0136 -0137 %Expand the model so that iso-enzymes have different reactions -0138 model=expandModel(model); -0139 -0140 %Identify reactions that have to be deleted because the involved mets are -0141 %never produced. This is done in an iterative manner -0142 removedRxns={}; -0143 %This is to keep track of which metabolites are removed in this step. It is -0144 %needed to adjust the transport costs -0145 originalModelMets=model.mets; -0146 while 1 -0147 irrevModel=convertToIrrev(model); -0148 -0149 I=sum(irrevModel.S>0,2); -0150 -0151 %Pretend that the unconstrained metabolites are made enough -0152 if isfield(irrevModel,'unconstrained') -0153 I(irrevModel.unconstrained~=0)=2; -0154 end -0155 metsToDelete=false(numel(model.mets),1); -0156 -0157 %This is not very neat but iterate through each metabolite and check -0158 %whether it can be produced (without using only one isolated reversible -0159 %reaction) -0160 for i=1:numel(irrevModel.mets) -0161 %If something can be made in two reactions then everything is fine. -0162 %If it can be made in one reaction it is fine unless it is through -0163 %an isolated reversible reaction (which can act as a mini loop) -0164 if I(i)<2 -0165 if I(i)==1 -0166 %Find the reaction where this metabolite is produced -0167 [~, J]=find(irrevModel.S(i,:)>0); -0168 -0169 %Check the metabolites that are consumed in this reaction. -0170 %The problem is if any of them is only produced in the -0171 %opposite reversible reaction -0172 K=irrevModel.S(:,J)<0; -0173 check=find(K & I<=1); -0174 -0175 for j=1:numel(check) -0176 %Find the reactions where it participates -0177 [~, L]=find(irrevModel.S(check(j),:)>0); -0178 -0179 if ~isempty(L) -0180 rxn=irrevModel.rxns(J); -0181 rxnRev=irrevModel.rxns(L); -0182 if strcmp(strrep(rxn,'_REV',''),strrep(rxnRev,'_REV','')) -0183 metsToDelete(i)=true; -0184 end -0185 else -0186 %If the metabolite was never produced then do -0187 %nothing and deal with it when the loop gets there -0188 continue; -0189 end -0190 end -0191 else -0192 %Not made anywhere -0193 metsToDelete(i)=true; -0194 end -0195 end -0196 end -0197 -0198 if any(metsToDelete) -0199 %Delete any reactions involving any of the metsToDelete -0200 [~, I]=find(model.S(metsToDelete,:)); -0201 removedRxns=[removedRxns;model.rxns(I)]; -0202 model=removeReactions(model,I,true,true); -0203 else -0204 %All bad reactions deleted -0205 break; -0206 end -0207 end -0208 -0209 %Adjust the transport costs -0210 transportCost=transportCost(ismember(originalModelMets,model.mets)); -0211 -0212 %Assign fake genes to reactions without genes -0213 I=find(sum(model.rxnGeneMat,2)==0); -0214 for i=1:numel(I) -0215 model.genes=[model.genes;['&&FAKE&&' num2str(i)]]; -0216 if isfield(model,'geneShortNames') -0217 model.geneShortNames=[model.geneShortNames;{''}]; +0098 if isfield(model,'geneComps') +0099 model=rmfield(model,'geneComps'); +0100 EM='The model structure contains information about gene compartmentalization. This is not supported by this function. The geneComps field has been deleted'; +0101 warning('RAVEN:warning', '%s', EM); +0102 end +0103 +0104 defaultCompartment=char(defaultCompartment); +0105 I=ismember(defaultCompartment,GSS.compartments); +0106 if I==false +0107 error('RAVEN:badInput', ... +0108 ['defaultCompartment ''%s'' not found in GSS. Available compartments: %s. ' ... +0109 '(If you mapped scores to model ids via compartmentMap, pass an id such as ''c''; ' ... +0110 'otherwise pass the raw predictor label.)'], ... +0111 defaultCompartment, strjoin(GSS.compartments(:)', ', ')); +0112 end +0113 +0114 if numel(model.comps)>1 +0115 EM='The model has several compartments. All compartments will be merged'; +0116 warning('RAVEN:warning', '%s', EM); +0117 model=mergeCompartments(model,true,true); +0118 end +0119 +0120 noGenes = ismember(model.genes,GSS.genes); +0121 if ~all(noGenes) +0122 EM=['For ' num2str(numel(find(~noGenes))) ' of ' num2str(numel(model.genes)) ' model genes no data was found in GSS']; +0123 warning('RAVEN:warning', '%s', EM); +0124 end +0125 +0126 %***Begin formating the data structures +0127 +0128 %Expand the model so that iso-enzymes have different reactions +0129 model=expandModel(model); +0130 +0131 %Identify reactions that have to be deleted because the involved mets are +0132 %never produced. This is done in an iterative manner +0133 removedRxns={}; +0134 %This is to keep track of which metabolites are removed in this step. It is +0135 %needed to adjust the transport costs +0136 originalModelMets=model.mets; +0137 while 1 +0138 irrevModel=convertToIrrev(model); +0139 +0140 I=sum(irrevModel.S>0,2); +0141 +0142 %Pretend that the unconstrained metabolites are made enough +0143 if isfield(irrevModel,'unconstrained') +0144 I(irrevModel.unconstrained~=0)=2; +0145 end +0146 metsToDelete=false(numel(model.mets),1); +0147 +0148 %This is not very neat but iterate through each metabolite and check +0149 %whether it can be produced (without using only one isolated reversible +0150 %reaction) +0151 for i=1:numel(irrevModel.mets) +0152 %If something can be made in two reactions then everything is fine. +0153 %If it can be made in one reaction it is fine unless it is through +0154 %an isolated reversible reaction (which can act as a mini loop) +0155 if I(i)<2 +0156 if I(i)==1 +0157 %Find the reaction where this metabolite is produced +0158 [~, J]=find(irrevModel.S(i,:)>0); +0159 +0160 %Check the metabolites that are consumed in this reaction. +0161 %The problem is if any of them is only produced in the +0162 %opposite reversible reaction +0163 K=irrevModel.S(:,J)<0; +0164 check=find(K & I<=1); +0165 +0166 for j=1:numel(check) +0167 %Find the reactions where it participates +0168 [~, L]=find(irrevModel.S(check(j),:)>0); +0169 +0170 if ~isempty(L) +0171 rxn=irrevModel.rxns(J); +0172 rxnRev=irrevModel.rxns(L); +0173 if strcmp(strrep(rxn,'_REV',''),strrep(rxnRev,'_REV','')) +0174 metsToDelete(i)=true; +0175 end +0176 else +0177 %If the metabolite was never produced then do +0178 %nothing and deal with it when the loop gets there +0179 continue; +0180 end +0181 end +0182 else +0183 %Not made anywhere +0184 metsToDelete(i)=true; +0185 end +0186 end +0187 end +0188 +0189 if any(metsToDelete) +0190 %Delete any reactions involving any of the metsToDelete +0191 [~, I]=find(model.S(metsToDelete,:)); +0192 removedRxns=[removedRxns;model.rxns(I)]; +0193 model=removeReactions(model,I,true,true); +0194 else +0195 %All bad reactions deleted +0196 break; +0197 end +0198 end +0199 +0200 %Adjust the transport costs +0201 transportCost=transportCost(ismember(originalModelMets,model.mets)); +0202 +0203 %Assign fake genes to reactions without genes +0204 I=find(sum(model.rxnGeneMat,2)==0); +0205 for i=1:numel(I) +0206 model.genes=[model.genes;['&&FAKE&&' num2str(i)]]; +0207 if isfield(model,'geneShortNames') +0208 model.geneShortNames=[model.geneShortNames;{''}]; +0209 end +0210 if isfield(model,'geneMiriams') +0211 model.geneMiriams=[model.geneMiriams;{[]}]; +0212 end +0213 if isfield(model,'proteins') +0214 model.proteins=[model.proteins;{[]}]; +0215 end +0216 if isfield(model,'geneFrom') +0217 model.geneFrom=[model.geneFrom;{{'FAKE'}}]; 0218 end -0219 if isfield(model,'geneMiriams') -0220 model.geneMiriams=[model.geneMiriams;{[]}]; -0221 end -0222 if isfield(model,'proteins') -0223 model.proteins=[model.proteins;{[]}]; -0224 end -0225 if isfield(model,'geneFrom') -0226 model.geneFrom=[model.geneFrom;{{'FAKE'}}]; -0227 end -0228 model.rxnGeneMat(I(i),numel(model.genes))=1; -0229 model.grRules{I(i)}=''; -0230 end -0231 -0232 %Update the GSS. All genes, fake or real, for which there is no evidence -0233 %gets a score 0.5 in all compartments -0234 I=setdiff(model.genes,GSS.genes); -0235 GSS.genes=[GSS.genes;I]; -0236 GSS.scores=[GSS.scores;ones(numel(I),numel(GSS.compartments))*0.5]; -0237 -0238 %Gene complexes should be moved together to be biologically -0239 %relevant. The average score for the genes is used for each compartment. -0240 %This is done by changing the model so that gene complexes are used as a -0241 %single gene name and then a score is calculated for that "gene". -0242 -0243 %Only "and"-relationships exist after expandModel -0244 genes=unique(model.grRules); -0245 nGenes=strrep(genes,'(',''); -0246 nGenes=strrep(nGenes,')',''); -0247 %nGenes=strrep(nGenes," and ","_and_"); -0248 complexes=setdiff(nGenes,model.genes); -0249 if ~isempty(complexes) -0250 if isempty(complexes{1}) %Empty grRules also come up here -0251 complexes(1)=[]; -0252 end -0253 end -0254 cScores=zeros(numel(complexes),numel(GSS.compartments)); -0255 for i=1:numel(complexes) -0256 genesInComplex=regexp(complexes{i},' and ','split'); -0257 -0258 %Find these genes in GSS -0259 [I, J]=ismember(genesInComplex,GSS.genes); -0260 -0261 if any(I) -0262 %Get the average of the genes that were found -0263 mScores=mean(GSS.scores(J(I),:)); -0264 -0265 %And add 0.5 for the genes that were not found, to be -0266 %consistent with non-complexes -0267 mScores=(mScores.*sum(I)+(numel(genesInComplex)-sum(I))*0.5)/numel(genesInComplex); -0268 else -0269 EM=['Could not parse grRule "' complexes{i} '". Assigning score 0.0 in all compartments']; -0270 warning('RAVEN:warning', '%s', EM); -0271 mScores=ones(1,numel(genesInComplex))*0.5; -0272 end -0273 cScores(i,:)=mScores; -0274 -0275 %Add this complex as a new gene -0276 model.genes=[model.genes;complexes{i}]; -0277 if isfield(model,'geneMiriams') -0278 model.geneMiriams=[model.geneMiriams;{[]}]; +0219 model.rxnGeneMat(I(i),numel(model.genes))=1; +0220 model.grRules{I(i)}=''; +0221 end +0222 +0223 %Update the GSS. All genes, fake or real, for which there is no evidence +0224 %gets a score 0.5 in all compartments +0225 I=setdiff(model.genes,GSS.genes); +0226 GSS.genes=[GSS.genes;I]; +0227 GSS.scores=[GSS.scores;ones(numel(I),numel(GSS.compartments))*0.5]; +0228 +0229 %Gene complexes should be moved together to be biologically +0230 %relevant. The average score for the genes is used for each compartment. +0231 %This is done by changing the model so that gene complexes are used as a +0232 %single gene name and then a score is calculated for that "gene". +0233 +0234 %Only "and"-relationships exist after expandModel +0235 genes=unique(model.grRules); +0236 nGenes=strrep(genes,'(',''); +0237 nGenes=strrep(nGenes,')',''); +0238 %nGenes=strrep(nGenes," and ","_and_"); +0239 complexes=setdiff(nGenes,model.genes); +0240 if ~isempty(complexes) +0241 if isempty(complexes{1}) %Empty grRules also come up here +0242 complexes(1)=[]; +0243 end +0244 end +0245 cScores=zeros(numel(complexes),numel(GSS.compartments)); +0246 for i=1:numel(complexes) +0247 genesInComplex=regexp(complexes{i},' and ','split'); +0248 +0249 %Find these genes in GSS +0250 [I, J]=ismember(genesInComplex,GSS.genes); +0251 +0252 if any(I) +0253 %Get the average of the genes that were found +0254 mScores=mean(GSS.scores(J(I),:)); +0255 +0256 %And add 0.5 for the genes that were not found, to be +0257 %consistent with non-complexes +0258 mScores=(mScores.*sum(I)+(numel(genesInComplex)-sum(I))*0.5)/numel(genesInComplex); +0259 else +0260 EM=['Could not parse grRule "' complexes{i} '". Assigning score 0.0 in all compartments']; +0261 warning('RAVEN:warning', '%s', EM); +0262 mScores=ones(1,numel(genesInComplex))*0.5; +0263 end +0264 cScores(i,:)=mScores; +0265 +0266 %Add this complex as a new gene +0267 model.genes=[model.genes;complexes{i}]; +0268 if isfield(model,'geneMiriams') +0269 model.geneMiriams=[model.geneMiriams;{[]}]; +0270 end +0271 if isfield(model,'geneShortNames') +0272 model.geneShortNames=[model.geneShortNames;{''}]; +0273 end +0274 if isfield(model,'proteins') +0275 model.proteins=[model.proteins;{''}]; +0276 end +0277 if isfield(model,'geneFrom') +0278 model.geneFrom=[model.geneFrom;{'COMPLEX'}]; 0279 end -0280 if isfield(model,'geneShortNames') -0281 model.geneShortNames=[model.geneShortNames;{''}]; -0282 end -0283 if isfield(model,'proteins') -0284 model.proteins=[model.proteins;{''}]; -0285 end -0286 if isfield(model,'geneFrom') -0287 model.geneFrom=[model.geneFrom;{'COMPLEX'}]; +0280 %Find the reactions which had the original complex and change them to +0281 %use the new "gene" +0282 I=ismember(model.grRules,['(' complexes{i} ')']); +0283 +0284 %Should check more carefully if there can be an error here +0285 if ~isempty(I) +0286 model.rxnGeneMat(I,:)=0; %Ok since the split on "or" was applied +0287 model.rxnGeneMat(I,numel(model.genes))=1; 0288 end -0289 %Find the reactions which had the original complex and change them to -0290 %use the new "gene" -0291 I=ismember(model.grRules,['(' complexes{i} ')']); -0292 -0293 %Should check more carefully if there can be an error here -0294 if ~isempty(I) -0295 model.rxnGeneMat(I,:)=0; %Ok since the split on "or" was applied -0296 model.rxnGeneMat(I,numel(model.genes))=1; -0297 end -0298 end -0299 -0300 %Add the new "genes" -0301 GSS.genes=[GSS.genes;complexes]; -0302 GSS.scores=[GSS.scores;cScores]; -0303 -0304 %After merging the complexes it could happen that there are genes that are -0305 %no longer in use. Delete such genes -0306 model=removeReactions(model,{},false,true); -0307 -0308 %Exchange reactions, defined as involving an unconstrained metabolite, are -0309 %special in that they have to stay in the defaultCompartment. This means -0310 %that uptake/excretion of metabolites is always via the default -0311 %compartment. This is a small simplification, but should be valid in most -0312 %cases -0313 [~, I]=getExchangeRxns(model); -0314 -0315 %It will be easier later on if the same place. Put them in the beginning -0316 J=1:numel(model.rxns); -0317 J(I)=[]; -0318 model=permuteModel(model,[I;J'],'rxns'); -0319 -0320 %Number of exchange reactions -0321 nER=numel(I); -0322 -0323 %Also put the exchange metabolites in the beginning -0324 if isfield(model,'unconstrained') -0325 I=find(model.unconstrained); -0326 J=1:numel(model.mets); -0327 J(I)=[]; -0328 model=permuteModel(model,[I;J'],'mets'); -0329 %Also reorder the transport costs -0330 transportCost=transportCost([I;J']); -0331 %Number of exchange metabolites -0332 nEM=numel(I); -0333 else -0334 nEM=0; -0335 end -0336 -0337 %There is no point of having genes for exchange reactions, so delete them. -0338 %Also to make computations easier -0339 model.rxnGeneMat(1:nER,:)=0; -0340 model.grRules(1:nER)={''}; -0341 -0342 %Remove unused genes -0343 model=removeReactions(model,{},false,true); -0344 -0345 %Remove genes with no match to the model and reorder so that the genes are -0346 %in the same order as model.genes. Since the fake genes are already added -0347 %so that all genes in model exist in GSS it is fine to do like this -0348 [~, J]=ismember(model.genes,GSS.genes); -0349 GSS.genes=model.genes; -0350 GSS.scores=GSS.scores(J,:); +0289 end +0290 +0291 %Add the new "genes" +0292 GSS.genes=[GSS.genes;complexes]; +0293 GSS.scores=[GSS.scores;cScores]; +0294 +0295 %After merging the complexes it could happen that there are genes that are +0296 %no longer in use. Delete such genes +0297 model=removeReactions(model,{},false,true); +0298 +0299 %Exchange reactions, defined as involving an unconstrained metabolite, are +0300 %special in that they have to stay in the defaultCompartment. This means +0301 %that uptake/excretion of metabolites is always via the default +0302 %compartment. This is a small simplification, but should be valid in most +0303 %cases +0304 [~, I]=getExchangeRxns(model); +0305 +0306 %It will be easier later on if the same place. Put them in the beginning +0307 J=1:numel(model.rxns); +0308 J(I)=[]; +0309 model=permuteModel(model,[I;J'],'rxns'); +0310 +0311 %Number of exchange reactions +0312 nER=numel(I); +0313 +0314 %Also put the exchange metabolites in the beginning +0315 if isfield(model,'unconstrained') +0316 I=find(model.unconstrained); +0317 J=1:numel(model.mets); +0318 J(I)=[]; +0319 model=permuteModel(model,[I;J'],'mets'); +0320 %Also reorder the transport costs +0321 transportCost=transportCost([I;J']); +0322 %Number of exchange metabolites +0323 nEM=numel(I); +0324 else +0325 nEM=0; +0326 end +0327 +0328 %There is no point of having genes for exchange reactions, so delete them. +0329 %Also to make computations easier +0330 model.rxnGeneMat(1:nER,:)=0; +0331 model.grRules(1:nER)={''}; +0332 +0333 %Remove unused genes +0334 model=removeReactions(model,{},false,true); +0335 +0336 %Remove genes with no match to the model and reorder so that the genes are +0337 %in the same order as model.genes. Since the fake genes are already added +0338 %so that all genes in model exist in GSS it is fine to do like this +0339 [~, J]=ismember(model.genes,GSS.genes); +0340 GSS.genes=model.genes; +0341 GSS.scores=GSS.scores(J,:); +0342 +0343 %Reorder the GSS so that the first index corresponds to the default +0344 %compartment +0345 [~, J]=ismember(defaultCompartment,GSS.compartments); +0346 reorder=1:numel(GSS.compartments); +0347 reorder(J)=[]; +0348 reorder=[J reorder]; +0349 GSS.scores=GSS.scores(:,reorder); +0350 GSS.compartments=GSS.compartments(reorder); 0351 -0352 %Reorder the GSS so that the first index corresponds to the default -0353 %compartment -0354 [~, J]=ismember(defaultCompartment,GSS.compartments); -0355 reorder=1:numel(GSS.compartments); -0356 reorder(J)=[]; -0357 reorder=[J reorder]; -0358 GSS.scores=GSS.scores(:,reorder); -0359 GSS.compartments=GSS.compartments(reorder); -0360 -0361 %Since it is only checked whether the metabolites can be synthesized, there -0362 %is no need to care about the stoichiometry. Change to -1/1 to simplify -0363 %later. Keep the S matrix for later though -0364 oldS=model.S; -0365 model.S(model.S>0)=1; -0366 model.S(model.S<0)=-1; -0367 -0368 %Here is a bit of a trick. To avoid the recurring calculation which -0369 %reactions are reversible, the reversible reactions have the coefficients -0370 %-10/10 instead of -1/1 -0371 model.S(:,model.rev==1)=model.S(:,model.rev==1).*10; -0372 -0373 %***Begin problem formulation -0374 -0375 %Some numbers that are good to have -0376 nRxns=numel(model.rxns)-nER; %Excluding exchange rxns -0377 nMets=numel(model.mets)-nEM; %Excluding exchange mets -0378 nGenes=numel(model.genes); -0379 nComps=numel(GSS.compartments); -0380 -0381 %Create a big stoichiometric matrix that will be the current model. In -0382 %order to have faster simulations the maximal model size is declared and -0383 %reactions are then moved within it. -0384 -0385 %First the original model (with the first nE being exchange rxns), then -0386 %reserve space for number of rxns minus exchange rxns for each non-default -0387 %compartment, then transport reactions for all non-exchange mets between -0388 %the default compartment and all others. -0389 %NOTE: Kept eye()*0 since eye() can be used to include all transport from -0390 %the beginning -0391 s=repmat(eye(nMets)*0,1,nComps-1); -0392 s=[zeros(numel(model.mets)-nMets,size(s,2));s]; -0393 S=[model.S sparse(numel(model.mets),nRxns*(nComps-1)) s]; -0394 s=[sparse(nMets*(nComps-1),numel(model.rxns)+nRxns*(nComps-1)) eye(nMets*(nComps-1))*0]; -0395 S=[S;s]; +0352 %Since it is only checked whether the metabolites can be synthesized, there +0353 %is no need to care about the stoichiometry. Change to -1/1 to simplify +0354 %later. Keep the S matrix for later though +0355 oldS=model.S; +0356 model.S(model.S>0)=1; +0357 model.S(model.S<0)=-1; +0358 +0359 %Here is a bit of a trick. To avoid the recurring calculation which +0360 %reactions are reversible, the reversible reactions have the coefficients +0361 %-10/10 instead of -1/1 +0362 model.S(:,model.rev==1)=model.S(:,model.rev==1).*10; +0363 +0364 %***Begin problem formulation +0365 +0366 %Some numbers that are good to have +0367 nRxns=numel(model.rxns)-nER; %Excluding exchange rxns +0368 nMets=numel(model.mets)-nEM; %Excluding exchange mets +0369 nGenes=numel(model.genes); +0370 nComps=numel(GSS.compartments); +0371 +0372 %Create a big stoichiometric matrix that will be the current model. In +0373 %order to have faster simulations the maximal model size is declared and +0374 %reactions are then moved within it. +0375 +0376 %First the original model (with the first nE being exchange rxns), then +0377 %reserve space for number of rxns minus exchange rxns for each non-default +0378 %compartment, then transport reactions for all non-exchange mets between +0379 %the default compartment and all others. +0380 %NOTE: Kept eye()*0 since eye() can be used to include all transport from +0381 %the beginning +0382 s=repmat(eye(nMets)*0,1,nComps-1); +0383 s=[zeros(numel(model.mets)-nMets,size(s,2));s]; +0384 S=[model.S sparse(numel(model.mets),nRxns*(nComps-1)) s]; +0385 s=[sparse(nMets*(nComps-1),numel(model.rxns)+nRxns*(nComps-1)) eye(nMets*(nComps-1))*0]; +0386 S=[S;s]; +0387 +0388 %Also replicate the transport costs +0389 transportCost=[transportCost(1:nEM);repmat(transportCost(nEM+1:end),nComps,1)]; +0390 +0391 %Create a binary matrix that says where the genes are in the current +0392 %solution +0393 g2c=false(nGenes,nComps); +0394 %All genes start in the default compartment +0395 g2c(:,1)=true; 0396 -0397 %Also replicate the transport costs -0398 transportCost=[transportCost(1:nEM);repmat(transportCost(nEM+1:end),nComps,1)]; -0399 -0400 %Create a binary matrix that says where the genes are in the current -0401 %solution -0402 g2c=false(nGenes,nComps); -0403 %All genes start in the default compartment -0404 g2c(:,1)=true; -0405 -0406 %Start of main optimization loop -0407 tic; -0408 bestScore=-inf; -0409 bestS=[]; -0410 bestg2c=[]; -0411 -0412 %Temp for testing -0413 plotScore=[]; -0414 nTrans=[]; -0415 totScore=[]; -0416 minScore=sum(min(GSS.scores,[],2)); -0417 maxScore=sum(max(GSS.scores,[],2)); -0418 -0419 while toc<maxTime*60 -0420 %Pick a random gene, weighted by it is current score minus the best -0421 %score for that gene (often 1.0, but can be 0.5 for no genes or average -0422 %for complexes). Genes with bad fits are more likely to be moved. This -0423 %formulation never moves a gene from its best compartment. Therefore a -0424 %small uniform weight is added -0425 [I, J]=find(g2c); -0426 geneToMove=randsample(nGenes,1,true,max(GSS.scores(I,:),[],2)-GSS.scores(sub2ind(size(g2c),I,J))+0.1); -0427 -0428 %Sample among possible compartments to move to. Add a larger weight to -0429 %even out the odds a little. Also a way of getting rid of loops where -0430 %the same set of genes are moved back and forth several times -0431 toComp=randsample(nComps,1,true,GSS.scores(geneToMove,:)+0.2); -0432 -0433 %Check that it moves to a new compartment -0434 if toComp==find(g2c(geneToMove,:)) -0435 continue; -0436 end -0437 -0438 %Moves the gene -0439 [newS, newg2c]=moveGene(S,model,g2c,geneToMove,toComp,nRxns,nMets); -0440 -0441 %Tries to connect the network. If this was not possible in 10 -0442 %iterations, then abort. If more than 20 modifications were needed then -0443 %it is unlikely that it will be a lower score -0444 wasConnected=false; -0445 for j=1:10 -0446 %Find the metabolites that are now unconnected -0447 unconnected=findUnconnected(newS,nEM); -0448 -0449 %Continue if there are still unconnected -0450 if any(unconnected) -0451 %For each gene find out how many of these could be connected if -0452 %the gene was moved and how many would be disconnected by -0453 %moving that gene -0454 [geneIndex, moveTo, deltaConnected, deltaScore]=selectGenes(newS,nEM,nMets,nER,nRxns,model,unconnected,g2c,GSS); -0455 -0456 %Score which gene would be the best to move. The highest -0457 %deltaScore is 1.0. It should be possible to move a gene from -0458 %worst to best compartment even if it disconnects, say, 1.5 -0459 %more metabolites -0460 [score, I]=max(1.5*deltaScore+deltaConnected); +0397 %Start of main optimization loop +0398 tic; +0399 bestScore=-inf; +0400 bestS=[]; +0401 bestg2c=[]; +0402 +0403 %Temp for testing +0404 plotScore=[]; +0405 nTrans=[]; +0406 totScore=[]; +0407 minScore=sum(min(GSS.scores,[],2)); +0408 maxScore=sum(max(GSS.scores,[],2)); +0409 +0410 while toc<maxTime*60 +0411 %Pick a random gene, weighted by it is current score minus the best +0412 %score for that gene (often 1.0, but can be 0.5 for no genes or average +0413 %for complexes). Genes with bad fits are more likely to be moved. This +0414 %formulation never moves a gene from its best compartment. Therefore a +0415 %small uniform weight is added +0416 [I, J]=find(g2c); +0417 geneToMove=randsample(nGenes,1,true,max(GSS.scores(I,:),[],2)-GSS.scores(sub2ind(size(g2c),I,J))+0.1); +0418 +0419 %Sample among possible compartments to move to. Add a larger weight to +0420 %even out the odds a little. Also a way of getting rid of loops where +0421 %the same set of genes are moved back and forth several times +0422 toComp=randsample(nComps,1,true,GSS.scores(geneToMove,:)+0.2); +0423 +0424 %Check that it moves to a new compartment +0425 if toComp==find(g2c(geneToMove,:)) +0426 continue; +0427 end +0428 +0429 %Moves the gene +0430 [newS, newg2c]=moveGene(S,model,g2c,geneToMove,toComp,nRxns,nMets); +0431 +0432 %Tries to connect the network. If this was not possible in 10 +0433 %iterations, then abort. If more than 20 modifications were needed then +0434 %it is unlikely that it will be a lower score +0435 wasConnected=false; +0436 for j=1:10 +0437 %Find the metabolites that are now unconnected +0438 unconnected=findUnconnected(newS,nEM); +0439 +0440 %Continue if there are still unconnected +0441 if any(unconnected) +0442 %For each gene find out how many of these could be connected if +0443 %the gene was moved and how many would be disconnected by +0444 %moving that gene +0445 [geneIndex, moveTo, deltaConnected, deltaScore]=selectGenes(newS,nEM,nMets,nER,nRxns,model,unconnected,g2c,GSS); +0446 +0447 %Score which gene would be the best to move. The highest +0448 %deltaScore is 1.0. It should be possible to move a gene from +0449 %worst to best compartment even if it disconnects, say, 1.5 +0450 %more metabolites +0451 [score, I]=max(1.5*deltaScore+deltaConnected); +0452 +0453 %Check if it has to add a transport or if there is a gene that +0454 %could be moved order to have a more connected network +0455 hasToAddTransport=true; +0456 if ~isempty(deltaConnected) +0457 if score>0 +0458 hasToAddTransport=false; +0459 end +0460 end 0461 -0462 %Check if it has to add a transport or if there is a gene that -0463 %could be moved order to have a more connected network -0464 hasToAddTransport=true; -0465 if ~isempty(deltaConnected) -0466 if score>0 -0467 hasToAddTransport=false; -0468 end -0469 end -0470 -0471 %If it is possible to move any gene to have a more -0472 %connected network, then move the best one -0473 if hasToAddTransport==false -0474 [newS, newg2c]=moveGene(newS,model,g2c,geneIndex(I),moveTo(I),nRxns,nMets); -0475 else -0476 %Choose a random unconnected metabolite that should be -0477 %connected -0478 transMet=unconnected(randsample(numel(unconnected),1)); -0479 -0480 %First get where the metabolite is now -0481 comps=ceil((transMet-nEM)/((size(S,1)-nEM)/nComps)); -0482 -0483 %Find the corresponding metabolite index if it were in the -0484 %default compartment -0485 dcIndex=transMet-(comps-1)*nMets; -0486 -0487 %Then get the indexes of that metabolite in all -0488 %compartments -0489 allIndexes=dcIndex; -0490 for k=1:nComps-1 -0491 allIndexes=[allIndexes;dcIndex+nMets*k]; -0492 end +0462 %If it is possible to move any gene to have a more +0463 %connected network, then move the best one +0464 if hasToAddTransport==false +0465 [newS, newg2c]=moveGene(newS,model,g2c,geneIndex(I),moveTo(I),nRxns,nMets); +0466 else +0467 %Choose a random unconnected metabolite that should be +0468 %connected +0469 transMet=unconnected(randsample(numel(unconnected),1)); +0470 +0471 %First get where the metabolite is now +0472 comps=ceil((transMet-nEM)/((size(S,1)-nEM)/nComps)); +0473 +0474 %Find the corresponding metabolite index if it were in the +0475 %default compartment +0476 dcIndex=transMet-(comps-1)*nMets; +0477 +0478 %Then get the indexes of that metabolite in all +0479 %compartments +0480 allIndexes=dcIndex; +0481 for k=1:nComps-1 +0482 allIndexes=[allIndexes;dcIndex+nMets*k]; +0483 end +0484 +0485 %It could be that some of these are not used in any +0486 %reaction. Get only the ones which are +0487 I=sum(newS(allIndexes,:)~=0,2)>0; +0488 +0489 %Then get the ones that are used but not in unconnected. +0490 %These are metabolites that could potentially be +0491 %transported to connect transMet +0492 connectedUsed=setdiff(allIndexes(I),unconnected); 0493 -0494 %It could be that some of these are not used in any -0495 %reaction. Get only the ones which are -0496 I=sum(newS(allIndexes,:)~=0,2)>0; -0497 -0498 %Then get the ones that are used but not in unconnected. -0499 %These are metabolites that could potentially be -0500 %transported to connect transMet -0501 connectedUsed=setdiff(allIndexes(I),unconnected); -0502 -0503 %This may be an error but leave it for now. It seems to -0504 %happen if nothing can be connected in one step -0505 if isempty(connectedUsed) -0506 break; -0507 end -0508 -0509 %If transMet is in the default compartment then everything -0510 %is fine, just connect it to a random one -0511 if transMet==dcIndex -0512 newS=addTransportToS(newS,nRxns,nER,nMets,nEM,nComps,transMet,connectedUsed(randsample(numel(connectedUsed),1))); -0513 else -0514 %If one of the connectedUsed is in the default -0515 %compartment then connect to that one -0516 I=connectedUsed(connectedUsed<(nMets+nEM)); -0517 if any(I) -0518 newS=addTransportToS(newS,nRxns,nER,nMets,nEM,nComps,transMet,I(randsample(numel(I),1))); -0519 else -0520 %This is if the only way to connect it is by adding -0521 %two transport reactions, going via the default -0522 %compartment -0523 break; -0524 end -0525 end -0526 end -0527 else -0528 wasConnected=true; -0529 break; -0530 end -0531 end -0532 -0533 %If the network was connected in a new way, it is possible that some -0534 %transport reactions are no longer needed. They should be removed -0535 if wasConnected==true -0536 %These are the metabolites that are being transported -0537 activeTransport=find(sum(newS(:,nER+nRxns*nComps+1:end),2)); -0538 -0539 %Get the metabolites that are unconnected if transport was not used -0540 unconnected=findUnconnected(newS(:,1:nER+nRxns*nComps),nEM); -0541 -0542 %Find the transport reactions that are not needed and delete them -0543 I=setdiff(activeTransport,unconnected); -0544 -0545 %Since both metabolites in a transport rxns must be connected for -0546 %the reaction to be deleted, the sum over the colums should be 4 -0547 newS(:,find(sum(newS(I,nER+nRxns*nComps+1:end))==4)+nER+nRxns*nComps)=0; -0548 -0549 %Score the solution and determine whether to keep it as a new -0550 %solution -0551 [score, geneScore, trCost]=scoreLocalization(newS,newg2c,GSS,transportCost); -0552 -0553 %If it was the best solution so far, keep it -0554 if score>bestScore -0555 bestScore=score; -0556 bestS=newS; -0557 bestg2c=newg2c; -0558 end -0559 -0560 %This should not be steepest descent later -0561 if score>=bestScore% || exp((score-bestScore)*7)>rand() -0562 plotScore=[plotScore;geneScore]; -0563 nTrans=[nTrans;trCost]; -0564 totScore=[totScore;score]; -0565 S=newS; -0566 g2c=newg2c; -0567 -0568 if plotResults==true -0569 subplot(3,2,1); -0570 spy(S); -0571 subplot(3,2,2); -0572 plot(plotScore,'r'); -0573 xlabel('Gene score'); -0574 subplot(3,2,3); -0575 plot((plotScore-minScore)/(maxScore-minScore),'r'); -0576 xlabel('Gene score relative to predictions'); -0577 subplot(3,2,4); -0578 plot(nTrans,'g'); -0579 xlabel('Transport cost'); -0580 subplot(3,2,5); -0581 plot(totScore,'b'); -0582 xlabel('Total score'); -0583 subplot(3,2,6); -0584 pause(0.2); -0585 end -0586 end -0587 end -0588 end -0589 scores.totScore=score; -0590 scores.geneScore=geneScore; -0591 scores.transCost=trCost; -0592 -0593 %Find which metabolites are transported and to where -0594 [I, J]=find(bestS(nEM+1:nEM+nMets,end-nMets*(nComps-1)+1:end)); -0595 J=ceil(J/nMets+1); -0596 transportStruct.mets=model.metNames(I+nEM); -0597 transportStruct.toComp=GSS.compartments(J); +0494 %This may be an error but leave it for now. It seems to +0495 %happen if nothing can be connected in one step +0496 if isempty(connectedUsed) +0497 break; +0498 end +0499 +0500 %If transMet is in the default compartment then everything +0501 %is fine, just connect it to a random one +0502 if transMet==dcIndex +0503 newS=addTransportToS(newS,nRxns,nER,nMets,nEM,nComps,transMet,connectedUsed(randsample(numel(connectedUsed),1))); +0504 else +0505 %If one of the connectedUsed is in the default +0506 %compartment then connect to that one +0507 I=connectedUsed(connectedUsed<(nMets+nEM)); +0508 if any(I) +0509 newS=addTransportToS(newS,nRxns,nER,nMets,nEM,nComps,transMet,I(randsample(numel(I),1))); +0510 else +0511 %This is if the only way to connect it is by adding +0512 %two transport reactions, going via the default +0513 %compartment +0514 break; +0515 end +0516 end +0517 end +0518 else +0519 wasConnected=true; +0520 break; +0521 end +0522 end +0523 +0524 %If the network was connected in a new way, it is possible that some +0525 %transport reactions are no longer needed. They should be removed +0526 if wasConnected==true +0527 %These are the metabolites that are being transported +0528 activeTransport=find(sum(newS(:,nER+nRxns*nComps+1:end),2)); +0529 +0530 %Get the metabolites that are unconnected if transport was not used +0531 unconnected=findUnconnected(newS(:,1:nER+nRxns*nComps),nEM); +0532 +0533 %Find the transport reactions that are not needed and delete them +0534 I=setdiff(activeTransport,unconnected); +0535 +0536 %Since both metabolites in a transport rxns must be connected for +0537 %the reaction to be deleted, the sum over the colums should be 4 +0538 newS(:,find(sum(newS(I,nER+nRxns*nComps+1:end))==4)+nER+nRxns*nComps)=0; +0539 +0540 %Score the solution and determine whether to keep it as a new +0541 %solution +0542 [score, geneScore, trCost]=scoreLocalization(newS,newg2c,GSS,transportCost); +0543 +0544 %If it was the best solution so far, keep it +0545 if score>bestScore +0546 bestScore=score; +0547 bestS=newS; +0548 bestg2c=newg2c; +0549 end +0550 +0551 %This should not be steepest descent later +0552 if score>=bestScore% || exp((score-bestScore)*7)>rand() +0553 plotScore=[plotScore;geneScore]; +0554 nTrans=[nTrans;trCost]; +0555 totScore=[totScore;score]; +0556 S=newS; +0557 g2c=newg2c; +0558 +0559 if plotResults==true +0560 subplot(3,2,1); +0561 spy(S); +0562 subplot(3,2,2); +0563 plot(plotScore,'r'); +0564 xlabel('Gene score'); +0565 subplot(3,2,3); +0566 plot((plotScore-minScore)/(maxScore-minScore),'r'); +0567 xlabel('Gene score relative to predictions'); +0568 subplot(3,2,4); +0569 plot(nTrans,'g'); +0570 xlabel('Transport cost'); +0571 subplot(3,2,5); +0572 plot(totScore,'b'); +0573 xlabel('Total score'); +0574 subplot(3,2,6); +0575 pause(0.2); +0576 end +0577 end +0578 end +0579 end +0580 scores.totScore=score; +0581 scores.geneScore=geneScore; +0582 scores.transCost=trCost; +0583 +0584 %Find which metabolites are transported and to where +0585 [I, J]=find(bestS(nEM+1:nEM+nMets,end-nMets*(nComps-1)+1:end)); +0586 J=ceil(J/nMets+1); +0587 transportStruct.mets=model.metNames(I+nEM); +0588 transportStruct.toComp=GSS.compartments(J); +0589 +0590 [I, J]=find(bestg2c); +0591 geneLocalization.genes=GSS.genes(I); +0592 geneLocalization.comps=GSS.compartments(J); +0593 +0594 %Resort the gene names +0595 [~, I]=sort(geneLocalization.genes); +0596 geneLocalization.genes=geneLocalization.genes(I); +0597 geneLocalization.comps=geneLocalization.comps(I); 0598 -0599 [I, J]=find(bestg2c); -0600 geneLocalization.genes=GSS.genes(I); -0601 geneLocalization.comps=GSS.compartments(J); -0602 -0603 %Resort the gene names -0604 [~, I]=sort(geneLocalization.genes); -0605 geneLocalization.genes=geneLocalization.genes(I); -0606 geneLocalization.comps=geneLocalization.comps(I); -0607 -0608 %Remove the fake genes -0609 I=strncmp('&&FAKE&&',geneLocalization.genes,8); -0610 geneLocalization.genes(I)=[]; -0611 geneLocalization.comps(I)=[]; +0599 %Remove the fake genes +0600 I=strncmp('&&FAKE&&',geneLocalization.genes,8); +0601 geneLocalization.genes(I)=[]; +0602 geneLocalization.comps(I)=[]; +0603 +0604 %Put together the model. This is done by first duplicating the S matrix +0605 %into the different compartments. Then the transport reactions are added +0606 %based on transportStruct. By now model.S should have the same size as the +0607 %S matrix used in the optimization, but with conserved stoichiometry. In +0608 %the final step all reactions and metabolites that are not used in the S +0609 %matrix from the optimization are deleted from the model +0610 outModel=model; +0611 outModel.S=oldS; 0612 -0613 %Put together the model. This is done by first duplicating the S matrix -0614 %into the different compartments. Then the transport reactions are added -0615 %based on transportStruct. By now model.S should have the same size as the -0616 %S matrix used in the optimization, but with conserved stoichiometry. In -0617 %the final step all reactions and metabolites that are not used in the S -0618 %matrix from the optimization are deleted from the model -0619 outModel=model; -0620 outModel.S=oldS; -0621 -0622 %This is the S matrix without exchange rxns or metabolites -0623 copyPart=outModel.S(nEM+1:end,nER+1:end); -0624 -0625 %Replicate to give the rxnGeneMat for the full system -0626 copyRxnGeneMat=outModel.rxnGeneMat(nER+1:end,:); -0627 outModel.rxnGeneMat=[outModel.rxnGeneMat;repmat(copyRxnGeneMat,nComps-1,1)]; -0628 -0629 %First fix the compartments. The model is already ordered with the exchange -0630 %metabolites first. The original model may contain one or two compartments, -0631 %depending on whether any exchange metabolites are defined -0632 nStartComps=numel(outModel.comps); -0633 if nStartComps==1 -0634 outModel.comps={'1'}; -0635 outModel.compNames=GSS.compartments(1); -0636 else -0637 if model.metComps(1)==1 -0638 outModel.compNames(1)=GSS.compartments(1); -0639 else -0640 outModel.compNames(2)=GSS.compartments(1); -0641 end -0642 end -0643 outModel.compNames=[outModel.compNames;GSS.compartments(2:end)]; -0644 -0645 %Ugly little loop -0646 for i=1:numel(GSS.compartments)-1 -0647 outModel.comps=[outModel.comps;num2str(numel(outModel.comps)+1)]; -0648 end -0649 %This information is not known from the data, so empty fields are added -0650 outModel.compOutside=cell(numel(outModel.comps),1); -0651 outModel.compOutside(:)={''}; -0652 -0653 for i=1:nComps-1 -0654 outModel.S=[outModel.S sparse(size(outModel.S,1),nRxns)]; -0655 outModel.S=[outModel.S;[sparse(nMets,nRxns*i+nER) copyPart]]; -0656 outModel.rxns=[outModel.rxns;strcat(outModel.rxns(nER+1:nER+nRxns),'_',GSS.compartments{i+1})]; -0657 outModel.rxnNames=[outModel.rxnNames;strcat(outModel.rxnNames(nER+1:nER+nRxns),' (',GSS.compartments{i+1},')')]; -0658 outModel.lb=[outModel.lb;outModel.lb(nER+1:nER+nRxns)]; -0659 outModel.ub=[outModel.ub;outModel.ub(nER+1:nER+nRxns)]; -0660 outModel.rev=[outModel.rev;outModel.rev(nER+1:nER+nRxns)]; -0661 outModel.c=[outModel.c;outModel.c(nER+1:nER+nRxns)]; -0662 if isfield(outModel,'grRules') -0663 outModel.grRules=[outModel.grRules;outModel.grRules(nER+1:nER+nRxns)]; +0613 %This is the S matrix without exchange rxns or metabolites +0614 copyPart=outModel.S(nEM+1:end,nER+1:end); +0615 +0616 %Replicate to give the rxnGeneMat for the full system +0617 copyRxnGeneMat=outModel.rxnGeneMat(nER+1:end,:); +0618 outModel.rxnGeneMat=[outModel.rxnGeneMat;repmat(copyRxnGeneMat,nComps-1,1)]; +0619 +0620 %First fix the compartments. The model is already ordered with the exchange +0621 %metabolites first. The original model may contain one or two compartments, +0622 %depending on whether any exchange metabolites are defined +0623 nStartComps=numel(outModel.comps); +0624 if nStartComps==1 +0625 outModel.comps={'1'}; +0626 outModel.compNames=GSS.compartments(1); +0627 else +0628 if model.metComps(1)==1 +0629 outModel.compNames(1)=GSS.compartments(1); +0630 else +0631 outModel.compNames(2)=GSS.compartments(1); +0632 end +0633 end +0634 outModel.compNames=[outModel.compNames;GSS.compartments(2:end)]; +0635 +0636 %Ugly little loop +0637 for i=1:numel(GSS.compartments)-1 +0638 outModel.comps=[outModel.comps;num2str(numel(outModel.comps)+1)]; +0639 end +0640 %This information is not known from the data, so empty fields are added +0641 outModel.compOutside=cell(numel(outModel.comps),1); +0642 outModel.compOutside(:)={''}; +0643 +0644 for i=1:nComps-1 +0645 outModel.S=[outModel.S sparse(size(outModel.S,1),nRxns)]; +0646 outModel.S=[outModel.S;[sparse(nMets,nRxns*i+nER) copyPart]]; +0647 outModel.rxns=[outModel.rxns;strcat(outModel.rxns(nER+1:nER+nRxns),'_',GSS.compartments{i+1})]; +0648 outModel.rxnNames=[outModel.rxnNames;strcat(outModel.rxnNames(nER+1:nER+nRxns),' (',GSS.compartments{i+1},')')]; +0649 outModel.lb=[outModel.lb;outModel.lb(nER+1:nER+nRxns)]; +0650 outModel.ub=[outModel.ub;outModel.ub(nER+1:nER+nRxns)]; +0651 outModel.rev=[outModel.rev;outModel.rev(nER+1:nER+nRxns)]; +0652 outModel.c=[outModel.c;outModel.c(nER+1:nER+nRxns)]; +0653 if isfield(outModel,'grRules') +0654 outModel.grRules=[outModel.grRules;outModel.grRules(nER+1:nER+nRxns)]; +0655 end +0656 if isfield(outModel,'subSystems') +0657 outModel.subSystems=[outModel.subSystems;outModel.subSystems(nER+1:nER+nRxns)]; +0658 end +0659 if isfield(outModel,'eccodes') +0660 outModel.eccodes=[outModel.eccodes;outModel.eccodes(nER+1:nER+nRxns)]; +0661 end +0662 if isfield(outModel,'rxnFrom') +0663 outModel.rxnFrom=[outModel.rxnFrom;outModel.rxnFrom(nER+1:nER+nRxns)]; 0664 end -0665 if isfield(outModel,'subSystems') -0666 outModel.subSystems=[outModel.subSystems;outModel.subSystems(nER+1:nER+nRxns)]; +0665 if isfield(outModel,'rxnMiriams') +0666 outModel.rxnMiriams=[outModel.rxnMiriams;outModel.rxnMiriams(nER+1:nER+nRxns)]; 0667 end -0668 if isfield(outModel,'eccodes') -0669 outModel.eccodes=[outModel.eccodes;outModel.eccodes(nER+1:nER+nRxns)]; +0668 if isfield(outModel,'rxnNotes') +0669 outModel.rxnNotes=[outModel.rxnNotes;outModel.rxnNotes(nER+1:nER+nRxns)]; 0670 end -0671 if isfield(outModel,'rxnFrom') -0672 outModel.rxnFrom=[outModel.rxnFrom;outModel.rxnFrom(nER+1:nER+nRxns)]; +0671 if isfield(outModel,'rxnReferences') +0672 outModel.rxnReferences=[outModel.rxnReferences;outModel.rxnReferences(nER+1:nER+nRxns)]; 0673 end -0674 if isfield(outModel,'rxnMiriams') -0675 outModel.rxnMiriams=[outModel.rxnMiriams;outModel.rxnMiriams(nER+1:nER+nRxns)]; +0674 if isfield(outModel,'rxnConfidenceScores') +0675 outModel.rxnConfidenceScores=[outModel.rxnConfidenceScores;outModel.rxnConfidenceScores(nER+1:nER+nRxns)]; 0676 end -0677 if isfield(outModel,'rxnNotes') -0678 outModel.rxnNotes=[outModel.rxnNotes;outModel.rxnNotes(nER+1:nER+nRxns)]; +0677 if isfield(outModel,'rxnDeltaG') +0678 outModel.rxnDeltaG=[outModel.rxnDeltaG;outModel.rxnDeltaG(nER+1:nER+nRxns)]; 0679 end -0680 if isfield(outModel,'rxnReferences') -0681 outModel.rxnReferences=[outModel.rxnReferences;outModel.rxnReferences(nER+1:nER+nRxns)]; -0682 end -0683 if isfield(outModel,'rxnConfidenceScores') -0684 outModel.rxnConfidenceScores=[outModel.rxnConfidenceScores;outModel.rxnConfidenceScores(nER+1:nER+nRxns)]; -0685 end -0686 if isfield(outModel,'rxnDeltaG') -0687 outModel.rxnDeltaG=[outModel.rxnDeltaG;outModel.rxnDeltaG(nER+1:nER+nRxns)]; -0688 end -0689 outModel.mets=[outModel.mets;strcat(outModel.mets(nEM+1:nEM+nMets),'_',GSS.compartments{i+1})]; -0690 outModel.metNames=[outModel.metNames;outModel.metNames(nEM+1:nEM+nMets)]; -0691 outModel.b=[outModel.b;outModel.b(nEM+1:nEM+nMets,:)]; -0692 I=ones(nMets,1)*nStartComps+i; -0693 outModel.metComps=[outModel.metComps;I]; -0694 if isfield(outModel,'inchis') -0695 outModel.inchis=[outModel.inchis;outModel.inchis(nEM+1:nEM+nMets)]; +0680 outModel.mets=[outModel.mets;strcat(outModel.mets(nEM+1:nEM+nMets),'_',GSS.compartments{i+1})]; +0681 outModel.metNames=[outModel.metNames;outModel.metNames(nEM+1:nEM+nMets)]; +0682 outModel.b=[outModel.b;outModel.b(nEM+1:nEM+nMets,:)]; +0683 I=ones(nMets,1)*nStartComps+i; +0684 outModel.metComps=[outModel.metComps;I]; +0685 if isfield(outModel,'inchis') +0686 outModel.inchis=[outModel.inchis;outModel.inchis(nEM+1:nEM+nMets)]; +0687 end +0688 if isfield(outModel,'metSmiles') +0689 outModel.metSmiles=[outModel.metSmiles;outModel.metSmiles(nEM+1:nEM+nMets)]; +0690 end +0691 if isfield(outModel,'unconstrained') +0692 outModel.unconstrained=[outModel.unconstrained;outModel.unconstrained(nEM+1:nEM+nMets)]; +0693 end +0694 if isfield(outModel,'metMiriams') +0695 outModel.metMiriams=[outModel.metMiriams;outModel.metMiriams(nEM+1:nEM+nMets)]; 0696 end -0697 if isfield(outModel,'metSmiles') -0698 outModel.metSmiles=[outModel.metSmiles;outModel.metSmiles(nEM+1:nEM+nMets)]; +0697 if isfield(outModel,'metFormulas') +0698 outModel.metFormulas=[outModel.metFormulas;outModel.metFormulas(nEM+1:nEM+nMets)]; 0699 end -0700 if isfield(outModel,'unconstrained') -0701 outModel.unconstrained=[outModel.unconstrained;outModel.unconstrained(nEM+1:nEM+nMets)]; +0700 if isfield(outModel,'metFrom') +0701 outModel.metFrom=[outModel.metFrom;outModel.metFrom(nEM+1:nEM+nMets)]; 0702 end -0703 if isfield(outModel,'metMiriams') -0704 outModel.metMiriams=[outModel.metMiriams;outModel.metMiriams(nEM+1:nEM+nMets)]; +0703 if isfield(outModel,'metCharges') +0704 outModel.metCharges=[outModel.metCharges;outModel.metCharges(nEM+1:nEM+nMets)]; 0705 end -0706 if isfield(outModel,'metFormulas') -0707 outModel.metFormulas=[outModel.metFormulas;outModel.metFormulas(nEM+1:nEM+nMets)]; +0706 if isfield(outModel,'metDeltaG') +0707 outModel.metDeltaG=[outModel.metDeltaG;outModel.metDeltaG(nEM+1:nEM+nMets)]; 0708 end -0709 if isfield(outModel,'metFrom') -0710 outModel.metFrom=[outModel.metFrom;outModel.metFrom(nEM+1:nEM+nMets)]; -0711 end -0712 if isfield(outModel,'metCharges') -0713 outModel.metCharges=[outModel.metCharges;outModel.metCharges(nEM+1:nEM+nMets)]; -0714 end -0715 if isfield(outModel,'metDeltaG') -0716 outModel.metDeltaG=[outModel.metDeltaG;outModel.metDeltaG(nEM+1:nEM+nMets)]; -0717 end -0718 end -0719 -0720 %Add the transport reactions -0721 transS=bestS(:,numel(outModel.rxns)+1:end); -0722 J=sum(transS)>0; %Active rxns -0723 -0724 %Transport reactions are written in a different way compared to a "real" -0725 %stoichimetric matrix. This is to fix that -0726 transS(transS~=0)=1; -0727 transS(1:nEM+nMets,:)=transS(1:nEM+nMets,:)*-1; -0728 I=find(sum(transS>0,2)); -0729 nTransRxns=numel(I); -0730 outModel.S=[outModel.S transS(:,J)]; -0731 filler=ones(nTransRxns,1); -0732 outModel.lb=[outModel.lb;filler*-1000]; -0733 outModel.ub=[outModel.ub;filler*1000]; -0734 outModel.rev=[outModel.rev;filler]; -0735 outModel.c=[outModel.c;filler*0]; -0736 outModel.rxnGeneMat=[outModel.rxnGeneMat;sparse(nTransRxns,numel(outModel.genes))]; -0737 -0738 for i=1:numel(I) -0739 outModel.rxns=[outModel.rxns;strcat('transport',num2str(i))]; -0740 outModel.rxnNames=[outModel.rxnNames;['Transport of ',outModel.metNames{I(i)}]]; -0741 if isfield(outModel,'grRules') -0742 outModel.grRules=[outModel.grRules;{''}]; -0743 end -0744 if isfield(outModel,'rxnMiriams') -0745 outModel.rxnMiriams=[outModel.rxnMiriams;{[]}]; -0746 end -0747 if isfield(outModel,'subSystems') -0748 if any(cellfun(@(x) iscell(x), outModel.subSystems)) -0749 outModel.subSystems=[outModel.subSystems;{{'Inferred transport reactions'}}]; -0750 else -0751 outModel.subSystems=[outModel.subSystems;{'Inferred transport reactions'}]; -0752 end +0709 end +0710 +0711 %Add the transport reactions +0712 transS=bestS(:,numel(outModel.rxns)+1:end); +0713 J=sum(transS)>0; %Active rxns +0714 +0715 %Transport reactions are written in a different way compared to a "real" +0716 %stoichimetric matrix. This is to fix that +0717 transS(transS~=0)=1; +0718 transS(1:nEM+nMets,:)=transS(1:nEM+nMets,:)*-1; +0719 I=find(sum(transS>0,2)); +0720 nTransRxns=numel(I); +0721 outModel.S=[outModel.S transS(:,J)]; +0722 filler=ones(nTransRxns,1); +0723 outModel.lb=[outModel.lb;filler*-1000]; +0724 outModel.ub=[outModel.ub;filler*1000]; +0725 outModel.rev=[outModel.rev;filler]; +0726 outModel.c=[outModel.c;filler*0]; +0727 outModel.rxnGeneMat=[outModel.rxnGeneMat;sparse(nTransRxns,numel(outModel.genes))]; +0728 +0729 for i=1:numel(I) +0730 outModel.rxns=[outModel.rxns;strcat('transport',num2str(i))]; +0731 outModel.rxnNames=[outModel.rxnNames;['Transport of ',outModel.metNames{I(i)}]]; +0732 if isfield(outModel,'grRules') +0733 outModel.grRules=[outModel.grRules;{''}]; +0734 end +0735 if isfield(outModel,'rxnMiriams') +0736 outModel.rxnMiriams=[outModel.rxnMiriams;{[]}]; +0737 end +0738 if isfield(outModel,'subSystems') +0739 if any(cellfun(@(x) iscell(x), outModel.subSystems)) +0740 outModel.subSystems=[outModel.subSystems;{{'Inferred transport reactions'}}]; +0741 else +0742 outModel.subSystems=[outModel.subSystems;{'Inferred transport reactions'}]; +0743 end +0744 end +0745 if isfield(outModel,'eccodes') +0746 outModel.eccodes=[outModel.eccodes;{''}]; +0747 end +0748 if isfield(outModel,'rxnFrom') +0749 outModel.rxnFrom=[outModel.rxnFrom;{''}]; +0750 end +0751 if isfield(outModel,'rxnNotes') +0752 outModel.rxnNotes=[outModel.rxnNotes;{''}]; 0753 end -0754 if isfield(outModel,'eccodes') -0755 outModel.eccodes=[outModel.eccodes;{''}]; +0754 if isfield(outModel,'rxnReferences') +0755 outModel.rxnReferences=[outModel.rxnReferences;{''}]; 0756 end -0757 if isfield(outModel,'rxnFrom') -0758 outModel.rxnFrom=[outModel.rxnFrom;{''}]; +0757 if isfield(outModel,'rxnConfidenceScores') +0758 outModel.rxnConfidenceScores=[outModel.rxnConfidenceScores;NaN]; 0759 end -0760 if isfield(outModel,'rxnNotes') -0761 outModel.rxnNotes=[outModel.rxnNotes;{''}]; +0760 if isfield(outModel,'rxnDeltaG') +0761 outModel.rxnDeltaG=[outModel.rxnDeltaG;NaN]; 0762 end -0763 if isfield(outModel,'rxnReferences') -0764 outModel.rxnReferences=[outModel.rxnReferences;{''}]; -0765 end -0766 if isfield(outModel,'rxnConfidenceScores') -0767 outModel.rxnConfidenceScores=[outModel.rxnConfidenceScores;NaN]; -0768 end -0769 if isfield(outModel,'rxnDeltaG') -0770 outModel.rxnDeltaG=[outModel.rxnDeltaG;NaN]; -0771 end -0772 end -0773 -0774 %Then remove all reactions and metabolites that are not used in the final -0775 %solution from the optimization -0776 [~, J]=find(bestS(:,1:nER+nComps*nRxns)); -0777 K=true(numel(outModel.rxns),1); -0778 K(J)=false; -0779 K(end-nTransRxns+1:end)=false; -0780 outModel=removeReactions(outModel,K,true); -0781 -0782 %Remove all fake genes -0783 I=strncmp('&&FAKE&&',outModel.genes,8); -0784 outModel.genes(I)=[]; -0785 if isfield(outModel,'geneMiriams') -0786 outModel.geneMiriams(I)=[]; -0787 end -0788 if isfield(outModel,'geneShortNames') -0789 outModel.geneShortNames(I)=[]; -0790 end -0791 if isfield(outModel,'proteins') -0792 outModel.proteins(I)=[]; -0793 end -0794 outModel.rxnGeneMat(:,I)=[]; -0795 -0796 %Fix grRules and reconstruct rxnGeneMat -0797 [grRules,rxnGeneMat] = standardizeGrRules(outModel,true); -0798 outModel.grRules = grRules; -0799 outModel.rxnGeneMat = rxnGeneMat; -0800 end -0801 -0802 %Moves a gene and all associated reactions from one compartment to another -0803 function [S, g2c]=moveGene(S,model,g2c,geneToMove,toComp,nRxns,nMets) -0804 %Find the current compartment and update to the new one -0805 currentComp=find(g2c(geneToMove,:)); -0806 g2c(geneToMove,:)=false; -0807 g2c(geneToMove,toComp)=true; +0763 end +0764 +0765 %Then remove all reactions and metabolites that are not used in the final +0766 %solution from the optimization +0767 [~, J]=find(bestS(:,1:nER+nComps*nRxns)); +0768 K=true(numel(outModel.rxns),1); +0769 K(J)=false; +0770 K(end-nTransRxns+1:end)=false; +0771 outModel=removeReactions(outModel,K,true); +0772 +0773 %Remove all fake genes +0774 I=strncmp('&&FAKE&&',outModel.genes,8); +0775 outModel.genes(I)=[]; +0776 if isfield(outModel,'geneMiriams') +0777 outModel.geneMiriams(I)=[]; +0778 end +0779 if isfield(outModel,'geneShortNames') +0780 outModel.geneShortNames(I)=[]; +0781 end +0782 if isfield(outModel,'proteins') +0783 outModel.proteins(I)=[]; +0784 end +0785 outModel.rxnGeneMat(:,I)=[]; +0786 +0787 %Fix grRules and reconstruct rxnGeneMat +0788 [grRules,rxnGeneMat] = standardizeGrRules(outModel,true); +0789 outModel.grRules = grRules; +0790 outModel.rxnGeneMat = rxnGeneMat; +0791 end +0792 +0793 %Moves a gene and all associated reactions from one compartment to another +0794 function [S, g2c]=moveGene(S,model,g2c,geneToMove,toComp,nRxns,nMets) +0795 %Find the current compartment and update to the new one +0796 currentComp=find(g2c(geneToMove,:)); +0797 g2c(geneToMove,:)=false; +0798 g2c(geneToMove,toComp)=true; +0799 +0800 %Find the reactions in the original model that the gene controls +0801 [I, ~]=find(model.rxnGeneMat(:,geneToMove)); +0802 +0803 %Calculate their current positions in the S matrix +0804 oldRxns=I+(currentComp-1)*nRxns; +0805 +0806 %And their new positions +0807 newRxns=I+(toComp-1)*nRxns; 0808 -0809 %Find the reactions in the original model that the gene controls -0810 [I, ~]=find(model.rxnGeneMat(:,geneToMove)); -0811 -0812 %Calculate their current positions in the S matrix -0813 oldRxns=I+(currentComp-1)*nRxns; -0814 -0815 %And their new positions -0816 newRxns=I+(toComp-1)*nRxns; -0817 -0818 %The metabolite ids also have to be changed to match the new -0819 %compartment -0820 metChange=nMets*(toComp-currentComp); +0809 %The metabolite ids also have to be changed to match the new +0810 %compartment +0811 metChange=nMets*(toComp-currentComp); +0812 +0813 %Update the reactions +0814 [I, J, K]=find(S(:,oldRxns)); +0815 I=I+metChange; +0816 +0817 %Move the reactions +0818 S(:,oldRxns)=0; +0819 S(sub2ind(size(S),I,newRxns(J)))=K; +0820 end 0821 -0822 %Update the reactions -0823 [I, J, K]=find(S(:,oldRxns)); -0824 I=I+metChange; -0825 -0826 %Move the reactions -0827 S(:,oldRxns)=0; -0828 S(sub2ind(size(S),I,newRxns(J)))=K; -0829 end -0830 -0831 %Finds which metabolites are unconnected, in the sense that they are never -0832 %a product or only a product in a reversible reaction where one reactant is -0833 %only a product in the opposite direction of that reaction. This function -0834 %ignores exchange metabolites. Returns a vector of metabolite indexes. -0835 %metsToCheck is an array of metabolite indexes to check for connectivity. -0836 %If not supplied then all metabolites are checked -0837 function unconnected=findUnconnected(S,nEM,metsToCheck) -0838 if nargin>2 -0839 %Do this by deleting everything from the network that is not in -0840 %metsToCheck and that is not exchange metabolites -0841 I=false(size(S,1),1); -0842 I(1:nEM)=true; -0843 I(metsToCheck)=true; -0844 S=S(I,:); -0845 end -0846 -0847 em=false(size(S,1),1); -0848 em(1:nEM)=true; -0849 -0850 %Construct a matrix in which the reversible reactions are inverted -0851 I=sum(S>2,1) | sum(S>2,1); -0852 revS=S; -0853 revS(:,I)=revS(:,I)*-1; -0854 -0855 %First calculate the ones that are ok -0856 %Produced in 2 rxns, is exchange, is not used at all, is produced in -0857 %non-reversible, involved in more than 1 reversible reactions -0858 connected=sum(S>0,2)>1 | em | sum(S~=0,2)==0 | sum(S(:,~I)>0,2)>0 | sum(S(:,I)~=0,2)>1; -0859 -0860 %Then get the ones that are unconnected because they are never produced -0861 unconnected=sum(S>0 | revS>0,2)==0 & connected==false; -0862 -0863 %Then get the ones that are potentially unconnected -0864 maybeUnconnected=~connected & ~unconnected; -0865 %maybeUnconnected=find(maybeUnconnectedS); -0866 -0867 %The metabolites in maybeUnconnected are involved in one reversible -0868 %reaction and not produced in any other reaction. This means that the -0869 %reactions which have at least one met in maybeUnconnected as reactant and -0870 %one as product are unconnected. The metabolites in maybeUnconnected that -0871 %are present in those reactions are then dead ends -0872 deadRxns=any(S(maybeUnconnected,:)>0) & any(S(maybeUnconnected,:)<0); -0873 -0874 %Get the mets involved in any of those reactions -0875 problematic=any(S(:,deadRxns)~=0,2); -0876 -0877 %If any of these are in the maybeUnconnected list then the metabolite is -0878 %unconnected -0879 unconnected(problematic & maybeUnconnected)=true; -0880 -0881 %Map back to metsToCheck -0882 if nargin>2 -0883 unconnected=metsToCheck(unconnected(nEM+1:end)); -0884 else -0885 unconnected=find(unconnected); -0886 end -0887 end -0888 -0889 %Given a set of unconnected metabolites, this function tries to move each -0890 %gene that could connect any of them, calculates the number of newly -0891 %connected metabolites minus the number of newly disconnected metabolites. -0892 %As some metabolites are very connected, only 25 genes are checked. Genes -0893 %that have a low score in their current compartment are more likely to be -0894 %moved -0895 function [geneIndex, moveTo, deltaConnected, deltaScore]=selectGenes(S,nEM,nMets,nER,nRxns,model,unconnected,g2c,GSS) -0896 %If moveTo is 0 then the gene cannot connect any of the metabolites -0897 moveTo=zeros(numel(model.genes),1); -0898 deltaConnected=zeros(numel(model.genes),1); -0899 -0900 %First get where the metabolites are now -0901 nComps=size(g2c,2); -0902 comps=ceil((unconnected-nEM)/((size(S,1)-nEM)/nComps)); -0903 -0904 %Find the corresponding metabolite indexes if they all were in the default -0905 %compartment -0906 dcIndexes=unique(unconnected-(comps-1)*nMets); -0907 -0908 %Then find them if they were in any other compartment -0909 allIndexes=dcIndexes; -0910 for i=1:nComps-1 -0911 allIndexes=[allIndexes;dcIndexes+nMets*i]; -0912 end -0913 -0914 %Also check which reversible reactions that could be used -0915 I=sum(S>2,1) | sum(S>2,1); -0916 revS=S; -0917 revS(:,I)=revS(:,I)*-1; +0822 %Finds which metabolites are unconnected, in the sense that they are never +0823 %a product or only a product in a reversible reaction where one reactant is +0824 %only a product in the opposite direction of that reaction. This function +0825 %ignores exchange metabolites. Returns a vector of metabolite indexes. +0826 %metsToCheck is an array of metabolite indexes to check for connectivity. +0827 %If not supplied then all metabolites are checked +0828 function unconnected=findUnconnected(S,nEM,metsToCheck) +0829 if nargin>2 +0830 %Do this by deleting everything from the network that is not in +0831 %metsToCheck and that is not exchange metabolites +0832 I=false(size(S,1),1); +0833 I(1:nEM)=true; +0834 I(metsToCheck)=true; +0835 S=S(I,:); +0836 end +0837 +0838 em=false(size(S,1),1); +0839 em(1:nEM)=true; +0840 +0841 %Construct a matrix in which the reversible reactions are inverted +0842 I=sum(S>2,1) | sum(S>2,1); +0843 revS=S; +0844 revS(:,I)=revS(:,I)*-1; +0845 +0846 %First calculate the ones that are ok +0847 %Produced in 2 rxns, is exchange, is not used at all, is produced in +0848 %non-reversible, involved in more than 1 reversible reactions +0849 connected=sum(S>0,2)>1 | em | sum(S~=0,2)==0 | sum(S(:,~I)>0,2)>0 | sum(S(:,I)~=0,2)>1; +0850 +0851 %Then get the ones that are unconnected because they are never produced +0852 unconnected=sum(S>0 | revS>0,2)==0 & connected==false; +0853 +0854 %Then get the ones that are potentially unconnected +0855 maybeUnconnected=~connected & ~unconnected; +0856 %maybeUnconnected=find(maybeUnconnectedS); +0857 +0858 %The metabolites in maybeUnconnected are involved in one reversible +0859 %reaction and not produced in any other reaction. This means that the +0860 %reactions which have at least one met in maybeUnconnected as reactant and +0861 %one as product are unconnected. The metabolites in maybeUnconnected that +0862 %are present in those reactions are then dead ends +0863 deadRxns=any(S(maybeUnconnected,:)>0) & any(S(maybeUnconnected,:)<0); +0864 +0865 %Get the mets involved in any of those reactions +0866 problematic=any(S(:,deadRxns)~=0,2); +0867 +0868 %If any of these are in the maybeUnconnected list then the metabolite is +0869 %unconnected +0870 unconnected(problematic & maybeUnconnected)=true; +0871 +0872 %Map back to metsToCheck +0873 if nargin>2 +0874 unconnected=metsToCheck(unconnected(nEM+1:end)); +0875 else +0876 unconnected=find(unconnected); +0877 end +0878 end +0879 +0880 %Given a set of unconnected metabolites, this function tries to move each +0881 %gene that could connect any of them, calculates the number of newly +0882 %connected metabolites minus the number of newly disconnected metabolites. +0883 %As some metabolites are very connected, only 25 genes are checked. Genes +0884 %that have a low score in their current compartment are more likely to be +0885 %moved +0886 function [geneIndex, moveTo, deltaConnected, deltaScore]=selectGenes(S,nEM,nMets,nER,nRxns,model,unconnected,g2c,GSS) +0887 %If moveTo is 0 then the gene cannot connect any of the metabolites +0888 moveTo=zeros(numel(model.genes),1); +0889 deltaConnected=zeros(numel(model.genes),1); +0890 +0891 %First get where the metabolites are now +0892 nComps=size(g2c,2); +0893 comps=ceil((unconnected-nEM)/((size(S,1)-nEM)/nComps)); +0894 +0895 %Find the corresponding metabolite indexes if they all were in the default +0896 %compartment +0897 dcIndexes=unique(unconnected-(comps-1)*nMets); +0898 +0899 %Then find them if they were in any other compartment +0900 allIndexes=dcIndexes; +0901 for i=1:nComps-1 +0902 allIndexes=[allIndexes;dcIndexes+nMets*i]; +0903 end +0904 +0905 %Also check which reversible reactions that could be used +0906 I=sum(S>2,1) | sum(S>2,1); +0907 revS=S; +0908 revS(:,I)=revS(:,I)*-1; +0909 +0910 %Find all reactions that could make any of the unconnected metabolites in +0911 %some other compartment +0912 newMets=setdiff(allIndexes,unconnected); +0913 [~, potential]=find(S(newMets,:)>0 | revS(newMets,:)>0); +0914 potential(potential<=nER | potential>nER+nRxns*nComps)=[]; %No exchange rxns or transport rxns +0915 +0916 %Map J to the real metabolic reactions in model +0917 rxnComps=ceil((potential-nER)/(nRxns)); 0918 -0919 %Find all reactions that could make any of the unconnected metabolites in -0920 %some other compartment -0921 newMets=setdiff(allIndexes,unconnected); -0922 [~, potential]=find(S(newMets,:)>0 | revS(newMets,:)>0); -0923 potential(potential<=nER | potential>nER+nRxns*nComps)=[]; %No exchange rxns or transport rxns -0924 -0925 %Map J to the real metabolic reactions in model -0926 rxnComps=ceil((potential-nER)/(nRxns)); -0927 -0928 %Find the corresponding reaction indexes if they all were in the default -0929 %compartment -0930 dcRxnIndexes=potential-(rxnComps-1)*nRxns; +0919 %Find the corresponding reaction indexes if they all were in the default +0920 %compartment +0921 dcRxnIndexes=potential-(rxnComps-1)*nRxns; +0922 +0923 %Get the genes for those reactions +0924 genes=find(sum(model.rxnGeneMat(dcRxnIndexes,:)>0,1)); +0925 +0926 %For some cases there can be very many reactions to connect something. This +0927 %is in particular true in the beginning of the optimization if, say, ATP is +0928 %unconnected. Therefore limit the number of genes to be checked to 25. +0929 %Weigh so that genes with bad scores in their current compartment are more +0930 %likely to be moved. 0931 -0932 %Get the genes for those reactions -0933 genes=find(sum(model.rxnGeneMat(dcRxnIndexes,:)>0,1)); +0932 %Get scores for these genes +0933 [~, J]=find(g2c(genes,:)); 0934 -0935 %For some cases there can be very many reactions to connect something. This -0936 %is in particular true in the beginning of the optimization if, say, ATP is -0937 %unconnected. Therefore limit the number of genes to be checked to 25. -0938 %Weigh so that genes with bad scores in their current compartment are more -0939 %likely to be moved. -0940 -0941 %Get scores for these genes -0942 [~, J]=find(g2c(genes,:)); -0943 -0944 %Add a small weight so that genes in their best compartment could be moved -0945 %as well -0946 geneScores=GSS.scores(sub2ind(size(g2c),genes(:),J)); -0947 modGeneScores=1.1-geneScores; -0948 if numel(genes)>25 -0949 rGenes=genes(randsample(numel(genes),min(numel(genes),25),true,modGeneScores)); -0950 -0951 %The sampling with weights could give duplicates -0952 rGenes=unique(rGenes); -0953 -0954 %Reorder the geneScores to match -0955 [~, I]=ismember(rGenes,genes); -0956 geneScores=geneScores(I); -0957 genes=rGenes; -0958 end -0959 for i=1:numel(genes) -0960 %Since one gene is moved at a time, only metabolites involved in any of -0961 %the reactions for that gene can become unconnected. This helps to -0962 %speed up the algorithm. First get all involved reactions in the -0963 %default compartment -0964 rxns=find(model.rxnGeneMat(:,genes(i))); +0935 %Add a small weight so that genes in their best compartment could be moved +0936 %as well +0937 geneScores=GSS.scores(sub2ind(size(g2c),genes(:),J)); +0938 modGeneScores=1.1-geneScores; +0939 if numel(genes)>25 +0940 rGenes=genes(randsample(numel(genes),min(numel(genes),25),true,modGeneScores)); +0941 +0942 %The sampling with weights could give duplicates +0943 rGenes=unique(rGenes); +0944 +0945 %Reorder the geneScores to match +0946 [~, I]=ismember(rGenes,genes); +0947 geneScores=geneScores(I); +0948 genes=rGenes; +0949 end +0950 for i=1:numel(genes) +0951 %Since one gene is moved at a time, only metabolites involved in any of +0952 %the reactions for that gene can become unconnected. This helps to +0953 %speed up the algorithm. First get all involved reactions in the +0954 %default compartment +0955 rxns=find(model.rxnGeneMat(:,genes(i))); +0956 +0957 %Then get their mets +0958 mets=find(sum(model.S(:,rxns)~=0,2)>0); +0959 +0960 %Then get their indexes in all compartments +0961 allIndexes=mets; +0962 for j=1:nComps-1 +0963 allIndexes=[allIndexes;mets+nMets*j]; +0964 end 0965 -0966 %Then get their mets -0967 mets=find(sum(model.S(:,rxns)~=0,2)>0); -0968 -0969 %Then get their indexes in all compartments -0970 allIndexes=mets; -0971 for j=1:nComps-1 -0972 allIndexes=[allIndexes;mets+nMets*j]; -0973 end -0974 -0975 %Check which of the unconnected metabolites that these reactions -0976 %correspond to. This could have been done earlier, but it is fast. The -0977 %reversibility check is skipped because it is unlikely to be an issue -0978 %here. Worst case is that the gene is tested once to much -0979 [I, ~]=find(model.S(:,rxns)); -0980 moveToComps=unique(comps(ismember(dcIndexes,I))); -0981 -0982 %Try to move the gene to each of the compartments -0983 bestMove=-inf; -0984 bestComp=[]; -0985 for j=1:numel(moveToComps) -0986 newS=moveGene(S,model,g2c,genes(i),moveToComps(j),nRxns,nMets); -0987 -0988 %Check how many metabolites that are unconnected after moving the -0989 %gene -0990 dConnected=numel(unconnected)-numel(findUnconnected(newS,nEM,[allIndexes;unconnected])); -0991 if dConnected>bestMove -0992 bestMove=dConnected; -0993 bestComp=moveToComps(j); -0994 end -0995 end -0996 -0997 %Add the difference in connectivity and where the genes should be moved -0998 moveTo(genes(i))=bestComp; -0999 deltaConnected(genes(i))=bestMove; -1000 end -1001 -1002 %Finish up -1003 geneIndex=genes(:); -1004 moveTo=moveTo(geneIndex); -1005 deltaConnected=deltaConnected(geneIndex); -1006 deltaScore=GSS.scores(sub2ind(size(g2c),geneIndex(:),moveTo))-geneScores; -1007 end -1008 -1009 %Small function to add a transport reactions between two metabolites. -1010 %Transport reactions are written as having a coefficient 2.0 for both -1011 %reactant and product. This is not a "real" reaction, but since all normal -1012 %reactions have coefficient -1/1 or -10/10 it is a compact way of writing -1013 %it -1014 function S=addTransportToS(S,nRxns,nER,nMets,nEM,nComps,metA,metB) -1015 mets=[metA;metB]; -1016 %Find the current compartments for the metabolites -1017 comps=ceil((mets-nEM)/((size(S,1)-nEM)/nComps)); -1018 -1019 if sum(comps==1)~=1 -1020 EM='Tried to create a transport reaction from a non-default compartment'; -1021 error('RAVEN:badInput', '%s', EM); -1022 end -1023 -1024 %Calculate the reaction index -1025 rIndex=(nER+nRxns*nComps)+mets(comps~=1)-nEM-nMets; -1026 -1027 S(mets,rIndex)=2; -1028 end -1029 -1030 %Scores a network based on the localization of the genes and the number of -1031 %transporter reactions used -1032 function [score, geneScore, transportCost]=scoreLocalization(S,g2c,GSS,transportCost) -1033 [I, J]=find(g2c); -1034 geneScore=sum(GSS.scores(sub2ind(size(g2c),I,J))); -1035 [I, ~]=find(S==2); -1036 I=unique(I); -1037 transportCost=sum(transportCost(I)); -1038 score=geneScore-transportCost; -1039 end -1040 -1041 % To avoid dependency on stats toolbox, use this alternative implementation -1042 % of randsample, source: -1043 % https://github.com/gpeyre/numerical-tours/blob/dacee30081c04ef5f67b26b387ead85f2b193af9/matlab/toolbox_signal/randsample.m -1044 function y = randsample(n, k, replace, w) -1045 %RANDSAMPLE Random sample, with or without replacement. -1046 % Y = RANDSAMPLE(N,K) returns Y as a vector of K values sampled uniformly -1047 % at random, without replacement, from the integers 1:N. -1048 % -1049 % Y = RANDSAMPLE(POPULATION,K) returns K values sampled uniformly at -1050 % random, without replacement, from the values in the vector POPULATION. -1051 % -1052 % Y = RANDSAMPLE(...,REPLACE) returns a sample taken with replacement if -1053 % REPLACE is true, or without replacement if REPLACE is false (the default). +0966 %Check which of the unconnected metabolites that these reactions +0967 %correspond to. This could have been done earlier, but it is fast. The +0968 %reversibility check is skipped because it is unlikely to be an issue +0969 %here. Worst case is that the gene is tested once to much +0970 [I, ~]=find(model.S(:,rxns)); +0971 moveToComps=unique(comps(ismember(dcIndexes,I))); +0972 +0973 %Try to move the gene to each of the compartments +0974 bestMove=-inf; +0975 bestComp=[]; +0976 for j=1:numel(moveToComps) +0977 newS=moveGene(S,model,g2c,genes(i),moveToComps(j),nRxns,nMets); +0978 +0979 %Check how many metabolites that are unconnected after moving the +0980 %gene +0981 dConnected=numel(unconnected)-numel(findUnconnected(newS,nEM,[allIndexes;unconnected])); +0982 if dConnected>bestMove +0983 bestMove=dConnected; +0984 bestComp=moveToComps(j); +0985 end +0986 end +0987 +0988 %Add the difference in connectivity and where the genes should be moved +0989 moveTo(genes(i))=bestComp; +0990 deltaConnected(genes(i))=bestMove; +0991 end +0992 +0993 %Finish up +0994 geneIndex=genes(:); +0995 moveTo=moveTo(geneIndex); +0996 deltaConnected=deltaConnected(geneIndex); +0997 deltaScore=GSS.scores(sub2ind(size(g2c),geneIndex(:),moveTo))-geneScores; +0998 end +0999 +1000 %Small function to add a transport reactions between two metabolites. +1001 %Transport reactions are written as having a coefficient 2.0 for both +1002 %reactant and product. This is not a "real" reaction, but since all normal +1003 %reactions have coefficient -1/1 or -10/10 it is a compact way of writing +1004 %it +1005 function S=addTransportToS(S,nRxns,nER,nMets,nEM,nComps,metA,metB) +1006 mets=[metA;metB]; +1007 %Find the current compartments for the metabolites +1008 comps=ceil((mets-nEM)/((size(S,1)-nEM)/nComps)); +1009 +1010 if sum(comps==1)~=1 +1011 EM='Tried to create a transport reaction from a non-default compartment'; +1012 error('RAVEN:badInput', '%s', EM); +1013 end +1014 +1015 %Calculate the reaction index +1016 rIndex=(nER+nRxns*nComps)+mets(comps~=1)-nEM-nMets; +1017 +1018 S(mets,rIndex)=2; +1019 end +1020 +1021 %Scores a network based on the localization of the genes and the number of +1022 %transporter reactions used +1023 function [score, geneScore, transportCost]=scoreLocalization(S,g2c,GSS,transportCost) +1024 [I, J]=find(g2c); +1025 geneScore=sum(GSS.scores(sub2ind(size(g2c),I,J))); +1026 [I, ~]=find(S==2); +1027 I=unique(I); +1028 transportCost=sum(transportCost(I)); +1029 score=geneScore-transportCost; +1030 end +1031 +1032 % To avoid dependency on stats toolbox, use this alternative implementation +1033 % of randsample, source: +1034 % https://github.com/gpeyre/numerical-tours/blob/dacee30081c04ef5f67b26b387ead85f2b193af9/matlab/toolbox_signal/randsample.m +1035 function y = randsample(n, k, replace, w) +1036 %RANDSAMPLE Random sample, with or without replacement. +1037 % Y = RANDSAMPLE(N,K) returns Y as a vector of K values sampled uniformly +1038 % at random, without replacement, from the integers 1:N. +1039 % +1040 % Y = RANDSAMPLE(POPULATION,K) returns K values sampled uniformly at +1041 % random, without replacement, from the values in the vector POPULATION. +1042 % +1043 % Y = RANDSAMPLE(...,REPLACE) returns a sample taken with replacement if +1044 % REPLACE is true, or without replacement if REPLACE is false (the default). +1045 % +1046 % Y = RANDSAMPLE(...,true,W) returns a weighted sample, using positive +1047 % weights W, taken with replacement. W is often a vector of probabilities. +1048 % This function does not support weighted sampling without replacement. +1049 % +1050 % Example: Generate a random sequence of the characters ACGT, with +1051 % replacement, according to specified probabilities. +1052 % +1053 % R = randsample("ACGT",48,true,[0.15 0.35 0.35 0.15]) 1054 % -1055 % Y = RANDSAMPLE(...,true,W) returns a weighted sample, using positive -1056 % weights W, taken with replacement. W is often a vector of probabilities. -1057 % This function does not support weighted sampling without replacement. -1058 % -1059 % Example: Generate a random sequence of the characters ACGT, with -1060 % replacement, according to specified probabilities. -1061 % -1062 % R = randsample("ACGT",48,true,[0.15 0.35 0.35 0.15]) -1063 % -1064 % See also RAND, RANDPERM. -1065 -1066 % Copyright 1993-2008 The MathWorks, Inc. -1067 % $Revision: 1.1.4.3 $ $Date: 2008/12/01 08:09:34 $ -1068 -1069 if nargin < 2 -1070 error('stats:randsample:TooFewInputs','Requires two input arguments.'); -1071 elseif numel(n) == 1 -1072 population = []; -1073 else -1074 population = n; -1075 n = numel(population); -1076 if length(population)~=n -1077 error('stats:randsample:BadPopulation','POPULATION must be a vector.'); -1078 end -1079 end -1080 -1081 if nargin < 3 -1082 replace = false; -1083 end -1084 -1085 if nargin < 4 -1086 w = []; -1087 elseif ~isempty(w) -1088 if length(w) ~= n -1089 if isempty(population) -1090 error('stats:randsample:InputSizeMismatch',... -1091 'W must have length equal to N.'); -1092 else -1093 error('stats:randsample:InputSizeMismatch',... -1094 'W must have the same length as the population.'); -1095 end -1096 else -1097 p = w(:)' / sum(w); -1098 end -1099 end -1100 -1101 switch replace -1102 -1103 % Sample with replacement -1104 case {true, 'true', 1} -1105 if isempty(w) -1106 y = ceil(n .* rand(k,1)); -1107 else -1108 [dum, y] = histc(rand(k,1),[0 cumsum(p)]); -1109 end -1110 -1111 % Sample without replacement -1112 case {false, 'false', 0} -1113 if k > n -1114 if isempty(population) -1115 error('stats:randsample:SampleTooLarge',... -1116 'K must be less than or equal to N for sampling without replacement.'); -1117 else -1118 error('stats:randsample:SampleTooLarge',... -1119 'K must be less than or equal to the population size.'); -1120 end -1121 end -1122 -1123 if isempty(w) -1124 % If the sample is a sizeable fraction of the population, -1125 % just randomize the whole population (which involves a full -1126 % sort of n random values), and take the first k. -1127 if 4*k > n -1128 rp = randperm(n); -1129 y = rp(1:k); -1130 -1131 % If the sample is a small fraction of the population, a full sort -1132 % is wasteful. Repeatedly sample with replacement until there are -1133 % k unique values. -1134 else -1135 x = zeros(1,n); % flags -1136 sumx = 0; -1137 while sumx < k -1138 x(ceil(n * rand(1,k-sumx))) = 1; % sample w/replacement -1139 sumx = sum(x); % count how many unique elements so far -1140 end -1141 y = find(x > 0); -1142 y = y(randperm(k)); -1143 end -1144 else -1145 error('stats:randsample:NoWeighting',... -1146 'Weighted sampling without replacement is not supported.'); -1147 end -1148 otherwise -1149 error('stats:randsample:BadReplaceValue',... -1150 'REPLACE must be either true or false.'); -1151 end -1152 -1153 if ~isempty(population) -1154 y = population(y); -1155 else -1156 y = y(:); -1157 end -1158 end

    +1055 % See also RAND, RANDPERM. +1056 +1057 % Copyright 1993-2008 The MathWorks, Inc. +1058 % $Revision: 1.1.4.3 $ $Date: 2008/12/01 08:09:34 $ +1059 +1060 if nargin < 2 +1061 error('stats:randsample:TooFewInputs','Requires two input arguments.'); +1062 elseif numel(n) == 1 +1063 population = []; +1064 else +1065 population = n; +1066 n = numel(population); +1067 if length(population)~=n +1068 error('stats:randsample:BadPopulation','POPULATION must be a vector.'); +1069 end +1070 end +1071 +1072 if nargin < 3 +1073 replace = false; +1074 end +1075 +1076 if nargin < 4 +1077 w = []; +1078 elseif ~isempty(w) +1079 if length(w) ~= n +1080 if isempty(population) +1081 error('stats:randsample:InputSizeMismatch',... +1082 'W must have length equal to N.'); +1083 else +1084 error('stats:randsample:InputSizeMismatch',... +1085 'W must have the same length as the population.'); +1086 end +1087 else +1088 p = w(:)' / sum(w); +1089 end +1090 end +1091 +1092 switch replace +1093 +1094 % Sample with replacement +1095 case {true, 'true', 1} +1096 if isempty(w) +1097 y = ceil(n .* rand(k,1)); +1098 else +1099 [dum, y] = histc(rand(k,1),[0 cumsum(p)]); +1100 end +1101 +1102 % Sample without replacement +1103 case {false, 'false', 0} +1104 if k > n +1105 if isempty(population) +1106 error('stats:randsample:SampleTooLarge',... +1107 'K must be less than or equal to N for sampling without replacement.'); +1108 else +1109 error('stats:randsample:SampleTooLarge',... +1110 'K must be less than or equal to the population size.'); +1111 end +1112 end +1113 +1114 if isempty(w) +1115 % If the sample is a sizeable fraction of the population, +1116 % just randomize the whole population (which involves a full +1117 % sort of n random values), and take the first k. +1118 if 4*k > n +1119 rp = randperm(n); +1120 y = rp(1:k); +1121 +1122 % If the sample is a small fraction of the population, a full sort +1123 % is wasteful. Repeatedly sample with replacement until there are +1124 % k unique values. +1125 else +1126 x = zeros(1,n); % flags +1127 sumx = 0; +1128 while sumx < k +1129 x(ceil(n * rand(1,k-sumx))) = 1; % sample w/replacement +1130 sumx = sum(x); % count how many unique elements so far +1131 end +1132 y = find(x > 0); +1133 y = y(randperm(k)); +1134 end +1135 else +1136 error('stats:randsample:NoWeighting',... +1137 'Weighted sampling without replacement is not supported.'); +1138 end +1139 otherwise +1140 error('stats:randsample:BadReplaceValue',... +1141 'REPLACE must be either true or false.'); +1142 end +1143 +1144 if ~isempty(population) +1145 y = population(y); +1146 else +1147 y = y(:); +1148 end +1149 end

    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/manipulation/addExchangeRxns.html b/docs/html/manipulation/addExchangeRxns.html index 4f3834cc..05cfefc3 100644 --- a/docs/html/manipulation/addExchangeRxns.html +++ b/docs/html/manipulation/addExchangeRxns.html @@ -29,9 +29,11 @@

    SYNOPSIS ^DESCRIPTION ^

     addExchangeRxns  Add exchange reactions for some metabolites.
     
    - This is a faster version than addRxns when adding exchange reactions.
    - New reactions are named "metName exchange (OUT/IN/BOTH)" while reaction
    - ids are formatted as "EXC_OUT/IN/BOTH_METID".
    + The function builds the stoichiometric columns directly from the metabolite list,
    + without the equation parsing of addRxns, which makes it faster for adding exchange
    + reactions. New reactions are
    + named "metName exchange (TYPE)" and have the ids "EXC_TYPE_METID", where TYPE is IN,
    + OUT or BOTH according to reactionType.
     
      Parameters
      ----------
    @@ -42,15 +44,15 @@ 

    DESCRIPTION ^SOURCE CODE ^
    0001 function [model, addedRxns]=addExchangeRxns(model,reactionType,varargin)
     0002 % addExchangeRxns  Add exchange reactions for some metabolites.
     0003 %
    -0004 % This is a faster version than addRxns when adding exchange reactions.
    -0005 % New reactions are named "metName exchange (OUT/IN/BOTH)" while reaction
    -0006 % ids are formatted as "EXC_OUT/IN/BOTH_METID".
    -0007 %
    -0008 % Parameters
    -0009 % ----------
    -0010 % model : struct
    -0011 %     a model structure.
    -0012 % reactionType : char
    -0013 %     the type of reactions to add:
    -0014 %
    -0015 %     - 'in'   : input reactions
    -0016 %     - 'out'  : output reactions
    -0017 %     - 'both' : reversible input/output reactions. Positive direction
    -0018 %       corresponds to output
    -0019 %
    -0020 % Name-Value Arguments
    -0021 % --------------------
    -0022 % mets : cell or logical or double
    -0023 %     either a cell array of metabolite IDs, a logical vector with the same
    -0024 %     number of elements as metabolites in the model, or a vector of
    -0025 %     indexes to add for (default model.mets).
    -0026 %
    -0027 % Returns
    -0028 % -------
    -0029 % model : struct
    -0030 %     updated model structure.
    -0031 % addedRxns : cell
    -0032 %     ids of the added reactions.
    -0033 %
    -0034 % Examples
    -0035 % --------
    -0036 %     [model, addedRxns] = addExchangeRxns(model, reactionType, mets);
    -0037 
    -0038 p=parseRAVENargs(varargin, {'mets',[]});
    -0039 mets=p.mets;
    -0040 if isempty(mets)
    -0041     mets=model.mets;
    -0042 elseif ~islogical(mets) && ~isnumeric(mets)
    -0043     mets=convertCharArray(mets);
    -0044 end
    -0045 J=getIndexes(model,mets,'mets',false);
    -0046 mets=model.mets(J);
    -0047 
    -0048 reactionType=char(upper(reactionType));
    +0004 % The function builds the stoichiometric columns directly from the metabolite list,
    +0005 % without the equation parsing of addRxns, which makes it faster for adding exchange
    +0006 % reactions. New reactions are
    +0007 % named "metName exchange (TYPE)" and have the ids "EXC_TYPE_METID", where TYPE is IN,
    +0008 % OUT or BOTH according to reactionType.
    +0009 %
    +0010 % Parameters
    +0011 % ----------
    +0012 % model : struct
    +0013 %     a model structure.
    +0014 % reactionType : char
    +0015 %     the type of reactions to add:
    +0016 %
    +0017 %     - 'in'   : input reactions
    +0018 %     - 'out'  : output reactions
    +0019 %     - 'both' : reversible input and output reactions. The positive direction
    +0020 %       corresponds to output
    +0021 %
    +0022 % Name-Value Arguments
    +0023 % --------------------
    +0024 % mets : cell or logical or double
    +0025 %     a cell array of metabolite ids, a logical vector with one element per metabolite of
    +0026 %     the model, or a vector of metabolite indexes to add reactions for
    +0027 %     (default model.mets).
    +0028 %
    +0029 % Returns
    +0030 % -------
    +0031 % model : struct
    +0032 %     updated model structure.
    +0033 % addedRxns : cell
    +0034 %     ids of the added reactions.
    +0035 %
    +0036 % Examples
    +0037 % --------
    +0038 %     [model, addedRxns] = addExchangeRxns(model, reactionType, mets);
    +0039 
    +0040 p=parseRAVENargs(varargin, {'mets',[]});
    +0041 mets=p.mets;
    +0042 if isempty(mets)
    +0043     mets=model.mets;
    +0044 elseif ~islogical(mets) && ~isnumeric(mets)
    +0045     mets=convertCharArray(mets);
    +0046 end
    +0047 J=getIndexes(model,mets,'mets',false);
    +0048 mets=model.mets(J);
     0049 
    -0050 %Production is positive for OUT and BOTH
    -0051 if strcmp(reactionType,'IN')
    -0052     I=speye(numel(model.mets));
    -0053 else
    -0054     I=speye(numel(model.mets))*-1;
    -0055 end
    -0056 I=I(:,J);
    -0057 
    -0058 %Add an I matrix which corresponds to production of all metabolites
    -0059 model.S=[model.S I];
    -0060 if strcmp(reactionType,'BOTH')
    -0061     model.lb=[model.lb;ones(numel(J),1)*-1000];
    -0062     model.rev=[model.rev;ones(numel(J),1)];
    -0063 else
    -0064     model.lb=[model.lb;zeros(numel(J),1)];
    -0065     model.rev=[model.rev;zeros(numel(J),1)];
    -0066 end
    -0067 model.ub=[model.ub;ones(numel(J),1)*1000];
    -0068 model.c=[model.c;zeros(numel(J),1)];
    -0069 
    -0070 filler=cell(numel(J),1);
    -0071 filler(:)={''};
    -0072 addedRxns=strcat('EXC_',reactionType,'_',mets);
    -0073 addedRxnNames=strcat(model.metNames(J),' exchange (',reactionType,')');
    -0074 model.rxns=[model.rxns;addedRxns];
    -0075 model.rxnNames=[model.rxnNames;addedRxnNames];
    -0076 
    -0077 if isfield(model,'eccodes')
    -0078     model.eccodes=[model.eccodes;filler];
    -0079 end
    -0080 if isfield(model,'subSystems')
    -0081     fillerSub = filler;
    -0082     if any(cellfun(@(x) iscell(x), model.subSystems))
    -0083         fillerSub(:)={{''}};
    -0084     end
    -0085     model.subSystems=[model.subSystems;fillerSub];
    -0086 end
    -0087 if isfield(model,'grRules')
    -0088     model.grRules=[model.grRules;filler];
    -0089 end
    -0090 if isfield(model,'rxnFrom')
    -0091     model.rxnFrom=[model.rxnFrom;filler];
    -0092 end
    -0093 if isfield(model,'rxnMiriams')
    -0094     model.rxnMiriams=[model.rxnMiriams;filler];
    -0095 end
    -0096 if isfield(model,'rxnGeneMat')
    -0097     model.rxnGeneMat=[model.rxnGeneMat;sparse(numel(J),numel(model.genes))];
    -0098 end
    -0099 if isfield(model,'rxnComps')
    -0100     model.rxnComps=[model.rxnComps;ones(numel(J),1)];
    -0101     fprintf('NOTE: The exchange reactions are assigned to the first compartment\n');
    -0102 end
    -0103 if isfield(model,'rxnNotes')
    -0104     model.rxnNotes=[model.rxnNotes;filler];
    -0105 end
    -0106 if isfield(model,'rxnReferences')
    -0107     model.rxnReferences=[model.rxnReferences;filler];
    -0108 end
    -0109 if isfield(model,'rxnConfidenceScores')
    -0110     model.rxnConfidenceScores=[model.rxnConfidenceScores;NaN(numel(J),1)];
    -0111 end
    -0112 if isfield(model,'rxnDeltaG')
    -0113     model.rxnDeltaG=[model.rxnDeltaG;NaN(numel(J),1)];
    -0114 end
    -0115 if isfield(model,'pwys')
    -0116     model.pwys=[model.pwys;filler];
    -0117 end
    -0118 if isfield(model,'spontaneous')
    -0119     model.spontaneous=[model.spontaneous;false(numel(J),1)];
    -0120 end
    -0121 end

    +0050 reactionType=char(upper(reactionType)); +0051 +0052 %Production is positive for OUT and BOTH +0053 if strcmp(reactionType,'IN') +0054 I=speye(numel(model.mets)); +0055 else +0056 I=speye(numel(model.mets))*-1; +0057 end +0058 I=I(:,J); +0059 +0060 %Add an I matrix which corresponds to production of all metabolites +0061 model.S=[model.S I]; +0062 if strcmp(reactionType,'BOTH') +0063 model.lb=[model.lb;ones(numel(J),1)*-1000]; +0064 model.rev=[model.rev;ones(numel(J),1)]; +0065 else +0066 model.lb=[model.lb;zeros(numel(J),1)]; +0067 model.rev=[model.rev;zeros(numel(J),1)]; +0068 end +0069 model.ub=[model.ub;ones(numel(J),1)*1000]; +0070 model.c=[model.c;zeros(numel(J),1)]; +0071 +0072 filler=cell(numel(J),1); +0073 filler(:)={''}; +0074 addedRxns=strcat('EXC_',reactionType,'_',mets); +0075 addedRxnNames=strcat(model.metNames(J),' exchange (',reactionType,')'); +0076 model.rxns=[model.rxns;addedRxns]; +0077 model.rxnNames=[model.rxnNames;addedRxnNames]; +0078 +0079 if isfield(model,'eccodes') +0080 model.eccodes=[model.eccodes;filler]; +0081 end +0082 if isfield(model,'subSystems') +0083 fillerSub = filler; +0084 if any(cellfun(@(x) iscell(x), model.subSystems)) +0085 fillerSub(:)={{''}}; +0086 end +0087 model.subSystems=[model.subSystems;fillerSub]; +0088 end +0089 if isfield(model,'grRules') +0090 model.grRules=[model.grRules;filler]; +0091 end +0092 if isfield(model,'rxnFrom') +0093 model.rxnFrom=[model.rxnFrom;filler]; +0094 end +0095 if isfield(model,'rxnMiriams') +0096 model.rxnMiriams=[model.rxnMiriams;filler]; +0097 end +0098 if isfield(model,'rxnGeneMat') +0099 model.rxnGeneMat=[model.rxnGeneMat;sparse(numel(J),numel(model.genes))]; +0100 end +0101 if isfield(model,'rxnComps') +0102 model.rxnComps=[model.rxnComps;ones(numel(J),1)]; +0103 fprintf('NOTE: The exchange reactions are assigned to the first compartment\n'); +0104 end +0105 if isfield(model,'rxnNotes') +0106 model.rxnNotes=[model.rxnNotes;filler]; +0107 end +0108 if isfield(model,'rxnReferences') +0109 model.rxnReferences=[model.rxnReferences;filler]; +0110 end +0111 if isfield(model,'rxnConfidenceScores') +0112 model.rxnConfidenceScores=[model.rxnConfidenceScores;NaN(numel(J),1)]; +0113 end +0114 if isfield(model,'rxnDeltaG') +0115 model.rxnDeltaG=[model.rxnDeltaG;NaN(numel(J),1)]; +0116 end +0117 if isfield(model,'pwys') +0118 model.pwys=[model.pwys;filler]; +0119 end +0120 if isfield(model,'spontaneous') +0121 model.spontaneous=[model.spontaneous;false(numel(J),1)]; +0122 end +0123 end

    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/manipulation/addGenesRaven.html b/docs/html/manipulation/addGenesRaven.html index 2dc1596e..e490b69b 100644 --- a/docs/html/manipulation/addGenesRaven.html +++ b/docs/html/manipulation/addGenesRaven.html @@ -29,8 +29,7 @@

    SYNOPSIS ^DESCRIPTION ^

     addGenesRaven  Add genes to a model.
     
    - This function does not make extensive checks about MIRIAM formats,
    - forbidden characters or such.
    + The function checks neither MIRIAM formats nor forbidden characters in gene ids.
     
      Parameters
      ----------
    @@ -39,15 +38,15 @@ 

    DESCRIPTION ^CROSS-REFERENCE INFORMATION ^
 </ul>
 This function is called by:
 <ul style= -
  • addRxns addRxns Add reactions to a model.
  • changeGrRules changeGrRules Change multiple grRules at the same time.
  • +
  • addRxns addRxns Add reactions to a model.
  • changeGrRules changeGrRules Change the grRules of multiple reactions.
  • @@ -75,166 +74,165 @@

    SOURCE CODE ^
    0001 function newModel=addGenesRaven(model,genesToAdd)
     0002 % addGenesRaven  Add genes to a model.
     0003 %
    -0004 % This function does not make extensive checks about MIRIAM formats,
    -0005 % forbidden characters or such.
    -0006 %
    -0007 % Parameters
    -0008 % ----------
    -0009 % model : struct
    -0010 %     a model structure.
    -0011 % genesToAdd : struct
    -0012 %     the genes to add, which can have the following fields:
    -0013 %
    -0014 %     - genes : cell array with unique strings that identifies each gene.
    -0015 %       Only characters which are allowed in SBML ids are allowed (mainly
    -0016 %       a-z, 0-9 and "_"). However, there is no check for this performed,
    -0017 %       as it only matters if the model should be exported to SBML
    -0018 %     - geneShortNames : cell array of gene abbreviations (optional,
    -0019 %       default "")
    -0020 %     - geneMiriams : cell array with MIRIAM structures (optional,
    -0021 %       default [])
    -0022 %     - proteins : cell array of protein names associated to each gene
    -0023 %       (optional, default "")
    -0024 %
    -0025 % Returns
    -0026 % -------
    -0027 % newModel : struct
    -0028 %     an updated model structure.
    -0029 %
    -0030 % Examples
    -0031 % --------
    -0032 %     newModel = addGenesRaven(model, genesToAdd);
    -0033 
    -0034 newModel=model;
    -0035 
    -0036 if isempty(genesToAdd)
    -0037     return;
    -0038 end
    -0039 
    -0040 %Check some stuff regarding the required fields
    -0041 if ~isfield(genesToAdd,'genes')
    -0042     EM='genes is a required field in genesToAdd';
    -0043     error('RAVEN:badInput', '%s', EM);
    -0044 else
    -0045     genesToAdd.genes=convertCharArray(genesToAdd.genes);
    -0046 end
    -0047 
    -0048 nOldGenes=numel(model.genes);
    -0049 largeFiller=cell(nOldGenes,1);
    -0050 largeFiller(:)={''};
    -0051 
    -0052 %Check that no gene ids are already present in the model
    -0053 I=ismember(genesToAdd.genes,model.genes);
    -0054 if all(I)
    -0055     warning('All genes in genesToAdd.genes are already present in model.genes');
    -0056     return
    -0057 elseif any(I)
    -0058     existingGenes=strjoin(genesToAdd.genes(I), ', ');
    -0059     warning(['The following genes are already present in model.genes and will therefore not be added: ', existingGenes])
    -0060     genesToAdd.genes(I)=[];
    -0061     if isfield(genesToAdd,'geneShortNames')
    -0062         genesToAdd.geneShortNames(I)=[];
    -0063     end
    -0064     if isfield(genesToAdd,'proteins')
    -0065         genesToAdd.proteins(I)=[];
    -0066     end
    -0067     if isfield(genesToAdd,'geneMiriams')
    -0068         genesToAdd.geneMiriams(I)=[];
    -0069     end
    -0070     if isfield(genesToAdd,'geneComps')
    -0071         genesToAdd.geneComps(I)=[];
    -0072     end
    -0073 end
    -0074 newModel.genes=[newModel.genes;genesToAdd.genes(:)];
    -0075 
    -0076 %Number of genes actually being added, now that any already-present genes
    -0077 %have been trimmed out; the per-field length checks below compare against
    -0078 %this
    -0079 nGenes=numel(genesToAdd.genes);
    -0080 filler=cell(nGenes,1);
    -0081 filler(:)={''};
    -0082 
    -0083 %Some more checks and if they pass then add each field to the structure
    -0084 if isfield(genesToAdd,'geneShortNames')
    -0085     genesToAdd.geneShortNames=convertCharArray(genesToAdd.geneShortNames);
    -0086     if numel(genesToAdd.geneShortNames)~=nGenes
    -0087         EM='genesToAdd.geneShortNames must have the same number of elements as genesToAdd.genes';
    -0088         error('RAVEN:badInput', '%s', EM);
    -0089     end
    -0090     %Add empty field if it does not exist
    -0091     if ~isfield(newModel,'geneShortNames')
    -0092         newModel.geneShortNames=largeFiller;
    -0093     end
    -0094     newModel.geneShortNames=[newModel.geneShortNames;genesToAdd.geneShortNames(:)];
    -0095 else
    -0096     %Add empty strings if the structure is in the model
    -0097     if isfield(newModel,'geneShortNames')
    -0098         newModel.geneShortNames=[newModel.geneShortNames;filler];
    -0099     end
    -0100 end
    -0101 if isfield(genesToAdd,'proteins')
    -0102     genesToAdd.proteins=convertCharArray(genesToAdd.proteins);
    -0103     if numel(genesToAdd.proteins)~=nGenes
    -0104         EM='genesToAdd.proteins must have the same number of elements as genesToAdd.genes';
    -0105         error('RAVEN:badInput', '%s', EM);
    -0106     end
    -0107     %Add empty field if it does not exist
    -0108     if ~isfield(newModel,'proteins')
    -0109         newModel.proteins=largeFiller;
    -0110     end
    -0111     newModel.proteins=[newModel.proteins;genesToAdd.proteins(:)];
    -0112 else
    -0113     %Add empty strings if the structure is in the model
    -0114     if isfield(newModel,'proteins')
    -0115         newModel.proteins=[newModel.proteins;filler];
    -0116     end
    -0117 end
    +0004 % The function checks neither MIRIAM formats nor forbidden characters in gene ids.
    +0005 %
    +0006 % Parameters
    +0007 % ----------
    +0008 % model : struct
    +0009 %     a model structure.
    +0010 % genesToAdd : struct
    +0011 %     the genes to add, which can have the following fields:
    +0012 %
    +0013 %     - genes : cell array with unique strings that identify each gene.
    +0014 %       The characters allowed in SBML ids (mainly a-z, 0-9 and "_") are the
    +0015 %       valid ones, but the function does not check them; they matter only for
    +0016 %       export to SBML
    +0017 %     - geneShortNames : cell array of gene abbreviations (optional,
    +0018 %       default "")
    +0019 %     - geneMiriams : cell array with MIRIAM structures (optional,
    +0020 %       default [])
    +0021 %     - proteins : cell array of the protein names associated with each gene
    +0022 %       (optional, default "")
    +0023 %
    +0024 % Returns
    +0025 % -------
    +0026 % newModel : struct
    +0027 %     an updated model structure.
    +0028 %
    +0029 % Examples
    +0030 % --------
    +0031 %     newModel = addGenesRaven(model, genesToAdd);
    +0032 
    +0033 newModel=model;
    +0034 
    +0035 if isempty(genesToAdd)
    +0036     return;
    +0037 end
    +0038 
    +0039 %Check some stuff regarding the required fields
    +0040 if ~isfield(genesToAdd,'genes')
    +0041     EM='genes is a required field in genesToAdd';
    +0042     error('RAVEN:badInput', '%s', EM);
    +0043 else
    +0044     genesToAdd.genes=convertCharArray(genesToAdd.genes);
    +0045 end
    +0046 
    +0047 nOldGenes=numel(model.genes);
    +0048 largeFiller=cell(nOldGenes,1);
    +0049 largeFiller(:)={''};
    +0050 
    +0051 %Check that no gene ids are already present in the model
    +0052 I=ismember(genesToAdd.genes,model.genes);
    +0053 if all(I)
    +0054     warning('All genes in genesToAdd.genes are already present in model.genes');
    +0055     return
    +0056 elseif any(I)
    +0057     existingGenes=strjoin(genesToAdd.genes(I), ', ');
    +0058     warning(['The following genes are already present in model.genes and will therefore not be added: ', existingGenes])
    +0059     genesToAdd.genes(I)=[];
    +0060     if isfield(genesToAdd,'geneShortNames')
    +0061         genesToAdd.geneShortNames(I)=[];
    +0062     end
    +0063     if isfield(genesToAdd,'proteins')
    +0064         genesToAdd.proteins(I)=[];
    +0065     end
    +0066     if isfield(genesToAdd,'geneMiriams')
    +0067         genesToAdd.geneMiriams(I)=[];
    +0068     end
    +0069     if isfield(genesToAdd,'geneComps')
    +0070         genesToAdd.geneComps(I)=[];
    +0071     end
    +0072 end
    +0073 newModel.genes=[newModel.genes;genesToAdd.genes(:)];
    +0074 
    +0075 %Number of genes actually being added, now that any already-present genes
    +0076 %have been trimmed out; the per-field length checks below compare against
    +0077 %this
    +0078 nGenes=numel(genesToAdd.genes);
    +0079 filler=cell(nGenes,1);
    +0080 filler(:)={''};
    +0081 
    +0082 %Some more checks and if they pass then add each field to the structure
    +0083 if isfield(genesToAdd,'geneShortNames')
    +0084     genesToAdd.geneShortNames=convertCharArray(genesToAdd.geneShortNames);
    +0085     if numel(genesToAdd.geneShortNames)~=nGenes
    +0086         EM='genesToAdd.geneShortNames must have the same number of elements as genesToAdd.genes';
    +0087         error('RAVEN:badInput', '%s', EM);
    +0088     end
    +0089     %Add empty field if it does not exist
    +0090     if ~isfield(newModel,'geneShortNames')
    +0091         newModel.geneShortNames=largeFiller;
    +0092     end
    +0093     newModel.geneShortNames=[newModel.geneShortNames;genesToAdd.geneShortNames(:)];
    +0094 else
    +0095     %Add empty strings if the structure is in the model
    +0096     if isfield(newModel,'geneShortNames')
    +0097         newModel.geneShortNames=[newModel.geneShortNames;filler];
    +0098     end
    +0099 end
    +0100 if isfield(genesToAdd,'proteins')
    +0101     genesToAdd.proteins=convertCharArray(genesToAdd.proteins);
    +0102     if numel(genesToAdd.proteins)~=nGenes
    +0103         EM='genesToAdd.proteins must have the same number of elements as genesToAdd.genes';
    +0104         error('RAVEN:badInput', '%s', EM);
    +0105     end
    +0106     %Add empty field if it does not exist
    +0107     if ~isfield(newModel,'proteins')
    +0108         newModel.proteins=largeFiller;
    +0109     end
    +0110     newModel.proteins=[newModel.proteins;genesToAdd.proteins(:)];
    +0111 else
    +0112     %Add empty strings if the structure is in the model
    +0113     if isfield(newModel,'proteins')
    +0114         newModel.proteins=[newModel.proteins;filler];
    +0115     end
    +0116 end
    +0117 
     0118 
    -0119 
    -0120 %Do not check the type of geneMiriams
    -0121 if isfield(genesToAdd,'geneMiriams')
    -0122     if numel(genesToAdd.geneMiriams)~=nGenes
    -0123         EM='genesToAdd.geneMiriams must have the same number of elements as genesToAdd.genes';
    -0124         error('RAVEN:badInput', '%s', EM);
    -0125     end
    -0126     %Add empty field if it does not exist
    -0127     if ~isfield(newModel,'geneMiriams')
    -0128         newModel.geneMiriams=cell(nOldGenes,1);
    -0129     end
    -0130     newModel.geneMiriams=[newModel.geneMiriams;genesToAdd.geneMiriams(:)];
    -0131 else
    -0132     if isfield(newModel,'geneMiriams')
    -0133         newModel.geneMiriams=[newModel.geneMiriams;cell(nGenes,1)];
    -0134     end
    -0135 end
    -0136 
    -0137 if isfield(genesToAdd,'geneComps')
    -0138     if numel(genesToAdd.geneComps)~=nGenes
    -0139         EM='genesToAdd.geneComps must have the same number of elements as genesToAdd.genes';
    -0140         error('RAVEN:badInput', '%s', EM);
    -0141     end
    -0142     %Add empty field if it does not exist
    -0143     if ~isfield(newModel,'geneComps')
    -0144         newModel.geneComps=ones(nOldGenes,1);
    -0145         EM='Adding genes with compartment information to a model without such information. All existing genes will be assigned to the first compartment';
    -0146         warning('RAVEN:warning', '%s', EM);
    -0147     end
    -0148     newModel.geneComps=[newModel.geneComps;genesToAdd.geneComps(:)];
    -0149 else
    -0150     if isfield(newModel,'geneComps')
    -0151         newModel.geneComps=[newModel.geneComps;ones(nGenes,1)];
    -0152         fprintf('NOTE: The added genes will be assigned to the first compartment\n');
    -0153     end
    -0154 end
    -0155 
    -0156 if isfield(newModel,'geneFrom')
    -0157     newModel.geneFrom=[newModel.geneFrom;filler];
    -0158 end
    -0159 
    -0160 if isfield(newModel,'rxnGeneMat')
    -0161     newModel.rxnGeneMat=[newModel.rxnGeneMat,zeros(length(newModel.rxns),nGenes)];
    -0162 end
    -0163 end

    +0119 %Do not check the type of geneMiriams +0120 if isfield(genesToAdd,'geneMiriams') +0121 if numel(genesToAdd.geneMiriams)~=nGenes +0122 EM='genesToAdd.geneMiriams must have the same number of elements as genesToAdd.genes'; +0123 error('RAVEN:badInput', '%s', EM); +0124 end +0125 %Add empty field if it does not exist +0126 if ~isfield(newModel,'geneMiriams') +0127 newModel.geneMiriams=cell(nOldGenes,1); +0128 end +0129 newModel.geneMiriams=[newModel.geneMiriams;genesToAdd.geneMiriams(:)]; +0130 else +0131 if isfield(newModel,'geneMiriams') +0132 newModel.geneMiriams=[newModel.geneMiriams;cell(nGenes,1)]; +0133 end +0134 end +0135 +0136 if isfield(genesToAdd,'geneComps') +0137 if numel(genesToAdd.geneComps)~=nGenes +0138 EM='genesToAdd.geneComps must have the same number of elements as genesToAdd.genes'; +0139 error('RAVEN:badInput', '%s', EM); +0140 end +0141 %Add empty field if it does not exist +0142 if ~isfield(newModel,'geneComps') +0143 newModel.geneComps=ones(nOldGenes,1); +0144 EM='Adding genes with compartment information to a model without such information. All existing genes will be assigned to the first compartment'; +0145 warning('RAVEN:warning', '%s', EM); +0146 end +0147 newModel.geneComps=[newModel.geneComps;genesToAdd.geneComps(:)]; +0148 else +0149 if isfield(newModel,'geneComps') +0150 newModel.geneComps=[newModel.geneComps;ones(nGenes,1)]; +0151 fprintf('NOTE: The added genes will be assigned to the first compartment\n'); +0152 end +0153 end +0154 +0155 if isfield(newModel,'geneFrom') +0156 newModel.geneFrom=[newModel.geneFrom;filler]; +0157 end +0158 +0159 if isfield(newModel,'rxnGeneMat') +0160 newModel.rxnGeneMat=[newModel.rxnGeneMat,zeros(length(newModel.rxns),nGenes)]; +0161 end +0162 end

    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/manipulation/addMets.html b/docs/html/manipulation/addMets.html index e9e0679c..98f47576 100644 --- a/docs/html/manipulation/addMets.html +++ b/docs/html/manipulation/addMets.html @@ -29,8 +29,7 @@

    SYNOPSIS ^DESCRIPTION ^

     addMets  Add metabolites to a model.
     
    - This function does not make extensive checks about MIRIAM formats,
    - forbidden characters or such.
    + The function checks neither MIRIAM formats nor forbidden characters in metabolite ids.
     
      Parameters
      ----------
    @@ -39,13 +38,14 @@ 

    DESCRIPTION ^DESCRIPTION ^DESCRIPTION ^SOURCE CODE ^
    0001 function newModel=addMets(model,metsToAdd,varargin)
     0002 % addMets  Add metabolites to a model.
     0003 %
    -0004 % This function does not make extensive checks about MIRIAM formats,
    -0005 % forbidden characters or such.
    -0006 %
    -0007 % Parameters
    -0008 % ----------
    -0009 % model : struct
    -0010 %     a model structure.
    -0011 % metsToAdd : struct
    -0012 %     the metabolite structure, which can have the following fields:
    -0013 %
    -0014 %     - mets : cell array with unique strings that identifies each
    -0015 %       metabolite (optional, default IDs of new metabolites are numbered
    -0016 %       with the prefix defined below)
    -0017 %     - metNames : cell array with the names of each metabolite
    +0004 % The function checks neither MIRIAM formats nor forbidden characters in metabolite ids.
    +0005 %
    +0006 % Parameters
    +0007 % ----------
    +0008 % model : struct
    +0009 %     a model structure.
    +0010 % metsToAdd : struct
    +0011 %     the metabolite structure, which can have the following fields:
    +0012 %
    +0013 %     - mets : cell array with unique strings that identify each
    +0014 %       metabolite (optional, default: ids numbered with the prefix defined
    +0015 %       below)
    +0016 %     - metNames : cell array with the names of each metabolite (optional if
    +0017 %       mets is given, default: the ids)
     0018 %     - compartments : cell array with the compartment of each metabolite.
    -0019 %       Should match model.comps. If this is a string rather than a cell
    -0020 %       array it is assumed that all mets are in that compartment
    +0019 %       Each entry must match model.comps. A single string, or a cell array
    +0020 %       with one entry, assigns all metabolites to that compartment
     0021 %     - b : Nx1 or Nx2 matrix with equality constraints for each
     0022 %       metabolite (optional, default 0)
     0023 %     - unconstrained : vector describing if each metabolite is an exchange
    @@ -136,354 +134,352 @@ 

    SOURCE CODE ^% default []) 0030 % - metCharges : metabolite charge (optional, default NaN) 0031 % - metDeltaG : Gibbs free energy of formation at biochemical standard -0032 % condition in kJ/mole (optional, default NaN) +0032 % condition in kJ/mol (optional, default NaN) 0033 % - metNotes : cell array with metabolite notes as strings (optional, 0034 % default "") 0035 % 0036 % Name-Value Arguments 0037 % -------------------- 0038 % copyInfo : logical -0039 % when adding metabolites to a compartment where it previously did not -0040 % exist, the function will copy any available annotation from the -0041 % metabolite in another compartment (default true). -0042 % prefix : char -0043 % when metsToAdd.mets is not specified, new metabolite IDs are -0044 % generated with the prefix specified here. If IDs with the prefix are -0045 % already used in the model then the numbering will start from the -0046 % highest existing integer+1 (default "m_"). -0047 % -0048 % Returns -0049 % ------- -0050 % newModel : struct -0051 % an updated model structure. -0052 % -0053 % Examples -0054 % -------- -0055 % newModel = addMets(model, metsToAdd, copyInfo, prefix); -0056 % -0057 % Notes -0058 % ----- -0059 % If multiple metabolites are added at once, the metMiriams cell array -0060 % should be defined as (example with ChEBI and KEGG): -0061 % -0062 % metsToAdd.metMiriams{1} = struct("name",{{"chebi";"kegg.compound"}},... -0063 % "value",{{"CHEBI:18072";"C11821"}}); -0064 % metsToAdd.metMiriams{2} = struct("name",{{"chebi";"kegg.compound"}},... -0065 % "value",{{"CHEBI:31132";"C12248"}}); -0066 -0067 p=parseRAVENargs(varargin, {'copyInfo',true; 'prefix','m_'}); -0068 copyInfo=p.copyInfo; -0069 prefix=char(p.prefix); +0039 % true to copy the available annotation from the metabolite with the same name in +0040 % another compartment of the model (default true). +0041 % prefix : char +0042 % prefix of the generated metabolite ids when metsToAdd.mets is not specified. If ids +0043 % with the prefix exist in the model, the numbering starts at the highest +0044 % existing number plus 1 (default "m_"). +0045 % +0046 % Returns +0047 % ------- +0048 % newModel : struct +0049 % an updated model structure. +0050 % +0051 % Examples +0052 % -------- +0053 % newModel = addMets(model, metsToAdd, copyInfo, prefix); +0054 % +0055 % Notes +0056 % ----- +0057 % When multiple metabolites are added at once, metMiriams is a cell array with one MIRIAM +0058 % structure per metabolite (example with ChEBI and KEGG): +0059 % +0060 % metsToAdd.metMiriams{1} = struct("name",{{"chebi";"kegg.compound"}},... +0061 % "value",{{"CHEBI:18072";"C11821"}}); +0062 % metsToAdd.metMiriams{2} = struct("name",{{"chebi";"kegg.compound"}},... +0063 % "value",{{"CHEBI:31132";"C12248"}}); +0064 +0065 p=parseRAVENargs(varargin, {'copyInfo',true; 'prefix','m_'}); +0066 copyInfo=p.copyInfo; +0067 prefix=char(p.prefix); +0068 +0069 newModel=model; 0070 -0071 newModel=model; -0072 -0073 if isempty(metsToAdd) -0074 return; -0075 end -0076 -0077 %Check some stuff regarding the required fields -0078 if ~isfield(metsToAdd,'mets') -0079 metsToAdd.metNames=convertCharArray(metsToAdd.metNames); -0080 metsToAdd.mets=generateNewIds(newModel,'mets',prefix,numel(metsToAdd.metNames)); -0081 else -0082 metsToAdd.mets=convertCharArray(metsToAdd.mets); -0083 end -0084 if ~isfield(metsToAdd,'metNames') -0085 metsToAdd.metNames=metsToAdd.mets; -0086 else -0087 metsToAdd.metNames=convertCharArray(metsToAdd.metNames); -0088 end -0089 if ~isfield(metsToAdd,'compartments') -0090 EM='compartments is a required field in metsToAdd'; -0091 error('RAVEN:badInput', '%s', EM); -0092 else -0093 metsToAdd.compartments=convertCharArray(metsToAdd.compartments); -0094 %If only one compartment is given, assume it is for all metabolites -0095 if numel(metsToAdd.compartments)==1 && numel(metsToAdd.mets)>1 -0096 temp=cell(numel(metsToAdd.mets),1); -0097 temp(:)=metsToAdd.compartments; -0098 metsToAdd.compartments=temp; -0099 end -0100 end -0101 -0102 %Number of metabolites -0103 nMets=numel(metsToAdd.mets); -0104 nOldMets=numel(model.mets); -0105 filler=cell(nMets,1); -0106 filler(:)={''}; -0107 largeFiller=cell(nOldMets,1); -0108 largeFiller(:)={''}; -0109 -0110 %Check that no metabolite ids are already present in the model -0111 I=ismember(metsToAdd.mets,model.mets); -0112 if any(I) -0113 error('One or more elements in metsToAdd.mets are already present in model.mets: %s',... -0114 strjoin(metsToAdd.mets(I),', ')); -0115 else -0116 newModel.mets=[newModel.mets;metsToAdd.mets(:)]; -0117 end -0118 -0119 %Check that all the compartments could be found -0120 [I, compMap]=ismember(metsToAdd.compartments,model.comps); -0121 if ~all(I) -0122 EM='metsToAdd.compartments must match model.comps'; -0123 error('RAVEN:badInput', '%s', EM); -0124 end -0125 -0126 %Check that the metabolite names are not present in the same compartment. -0127 %Not the neatest way maybe.. -0128 t1=strcat(metsToAdd.metNames(:),'***',metsToAdd.compartments(:)); -0129 t2=strcat(model.metNames,'***',model.comps(model.metComps)); -0130 if any(ismember(t1,t2)) -0131 EM='One or more elements in metsToAdd.metNames already exist in the same compartments as the one it is being added to'; -0132 error('RAVEN:badInput', '%s', EM); -0133 end -0134 -0135 %Some more checks and if they pass then add each field to the structure -0136 if numel(metsToAdd.metNames)~=nMets -0137 EM='metsToAdd.metNames must have the same number of elements as metsToAdd.mets'; -0138 error('RAVEN:badInput', '%s', EM); -0139 else -0140 newModel.metNames=[newModel.metNames;metsToAdd.metNames(:)]; -0141 end -0142 -0143 if numel(compMap)~=nMets -0144 EM='metsToAdd.compartments must have the same number of elements as metsToAdd.mets'; -0145 error('RAVEN:badInput', '%s', EM); -0146 else -0147 newModel.metComps=[newModel.metComps;compMap(:)]; -0148 end -0149 -0150 if isfield(metsToAdd,'b') -0151 if size(metsToAdd.b,1)~=nMets -0152 EM='metsToAdd.b must have the same number of elements as metsToAdd.mets'; -0153 error('RAVEN:badInput', '%s', EM); -0154 else -0155 %Add empty field if it does not exist -0156 if ~isfield(newModel,'b') -0157 newModel.b=zeros(nOldMets,1); -0158 end -0159 -0160 %If the original is only one vector -0161 if size(metsToAdd.b,2)>size(newModel.b,2) -0162 newModel.b=[newModel.b newModel.b]; -0163 end -0164 %Add the new ones -0165 newModel.b=[newModel.b;metsToAdd.b]; -0166 end -0167 else -0168 if isfield(newModel,'b') -0169 %Add the default -0170 newModel.b=[newModel.b;zeros(nMets,size(newModel.b,2))]; -0171 end -0172 end -0173 -0174 if isfield(metsToAdd,'unconstrained') -0175 if numel(metsToAdd.unconstrained)~=nMets -0176 EM='metsToAdd.unconstrained must have the same number of elements as metsToAdd.mets'; -0177 error('RAVEN:badInput', '%s', EM); -0178 else -0179 %Add empty field if it does not exist -0180 if ~isfield(newModel,'unconstrained') -0181 newModel.unconstrained=zeros(nOldMets,1); -0182 end -0183 -0184 %Add the new ones -0185 newModel.unconstrained=[newModel.unconstrained;metsToAdd.unconstrained(:)]; -0186 end -0187 else -0188 if isfield(newModel,'unconstrained') -0189 %Add the default -0190 newModel.unconstrained=[newModel.unconstrained;zeros(nMets,1)]; -0191 end -0192 end -0193 -0194 if isfield(metsToAdd,'inchis') -0195 metsToAdd.inchis=convertCharArray(metsToAdd.inchis); -0196 if numel(metsToAdd.inchis)~=nMets -0197 EM='metsToAdd.inchis must have the same number of elements as metsToAdd.mets'; -0198 error('RAVEN:badInput', '%s', EM); -0199 end -0200 %Add empty field if it does not exist -0201 if ~isfield(newModel,'inchis') -0202 newModel.inchis=largeFiller; -0203 end -0204 newModel.inchis=[newModel.inchis;metsToAdd.inchis(:)]; -0205 else -0206 %Add empty strings if the structure is in the model -0207 if isfield(newModel,'inchis') -0208 newModel.inchis=[newModel.inchis;filler]; -0209 end -0210 end -0211 -0212 -0213 if isfield(metsToAdd,'metSmiles') -0214 metsToAdd.metSmiles=convertCharArray(metsToAdd.metSmiles); -0215 if numel(metsToAdd.metSmiles)~=nMets -0216 EM='metsToAdd.metSmiles must have the same number of elements as metsToAdd.mets'; -0217 error('RAVEN:badInput', '%s', EM); -0218 end -0219 %Add empty field if it does not exist -0220 if ~isfield(newModel,'metSmiles') -0221 newModel.metSmiles=largeFiller; -0222 end -0223 newModel.metSmiles=[newModel.metSmiles;metsToAdd.metSmiles(:)]; -0224 else -0225 %Add empty strings if the structure is in the model -0226 if isfield(newModel,'metSmiles') -0227 newModel.metSmiles=[newModel.metSmiles;filler]; -0228 end -0229 end -0230 -0231 if isfield(metsToAdd,'metFormulas') -0232 metsToAdd.metFormulas=convertCharArray(metsToAdd.metFormulas); -0233 if numel(metsToAdd.metFormulas)~=nMets -0234 EM='metsToAdd.metFormulas must have the same number of elements as metsToAdd.mets'; -0235 error('RAVEN:badInput', '%s', EM); -0236 end -0237 %Add empty field if it does not exist -0238 if ~isfield(newModel,'metFormulas') -0239 newModel.metFormulas=largeFiller; -0240 end -0241 newModel.metFormulas=[newModel.metFormulas;metsToAdd.metFormulas(:)]; -0242 else -0243 %Add default -0244 if isfield(newModel,'metFormulas') -0245 newModel.metFormulas=[newModel.metFormulas;filler]; -0246 end -0247 end -0248 -0249 if isfield(metsToAdd,'metCharges') -0250 if numel(metsToAdd.metCharges)~=nMets -0251 EM='metsToAdd.metCharges must have the same number of elements as metsToAdd.mets'; -0252 error('RAVEN:badInput', '%s', EM); -0253 end -0254 if ~isnumeric(metsToAdd.metCharges) -0255 EM='metsToAdd.metCharges must be of type "double"'; -0256 error('RAVEN:badInput', '%s', EM); -0257 end -0258 if ~isfield(newModel,'metCharges') -0259 newModel.metCharges=NaN(numel(largeFiller),1); -0260 end -0261 newModel.metCharges=[newModel.metCharges;metsToAdd.metCharges(:)]; -0262 else -0263 %Add default -0264 if isfield(newModel,'metCharges') -0265 newModel.metCharges=[newModel.metCharges;NaN(numel(filler),1)]; -0266 end -0267 end -0268 -0269 if isfield(metsToAdd,'metDeltaG') -0270 if numel(metsToAdd.metDeltaG)~=nMets -0271 EM='metsToAdd.metDeltaG must have the same number of elements as metsToAdd.mets'; -0272 error('RAVEN:badInput', '%s', EM); -0273 end -0274 if ~isnumeric(metsToAdd.metDeltaG) -0275 EM='metsToAdd.metDeltaG must be of type "double"'; -0276 error('RAVEN:badInput', '%s', EM); -0277 end -0278 if ~isfield(newModel,'metDeltaG') -0279 newModel.metDeltaG=NaN(numel(largeFiller),1); -0280 end -0281 newModel.metDeltaG=[newModel.metDeltaG;metsToAdd.metDeltaG(:)]; -0282 else -0283 %Add default -0284 if isfield(newModel,'metDeltaG') -0285 newModel.metDeltaG=[newModel.metDeltaG;NaN(numel(filler),1)]; -0286 end -0287 end -0288 -0289 -0290 if isfield(metsToAdd,'metNotes') -0291 metsToAdd.metNotes=convertCharArray(metsToAdd.metNotes); -0292 if numel(metsToAdd.metNotes)==1 && numel(metsToAdd.mets)>1 -0293 temp=cell(numel(metsToAdd.mets),1); -0294 temp(:)=metsToAdd.metNotes; -0295 metsToAdd.metNotes=temp; -0296 end -0297 if numel(metsToAdd.metNotes)~=nMets -0298 EM='metsToAdd.metNotes must have the same number of elements as metsToAdd.mets'; -0299 error('RAVEN:badInput', '%s', EM); -0300 end -0301 %Add empty field if it does not exist -0302 if ~isfield(newModel,'metNotes') -0303 newModel.metNotes=largeFiller; -0304 end -0305 newModel.metNotes=[newModel.metNotes;metsToAdd.metNotes(:)]; -0306 else -0307 %Add empty strings if the structure is in the model -0308 if isfield(newModel,'metNotes') -0309 newModel.metNotes=[newModel.metNotes;filler]; -0310 end -0311 end -0312 -0313 %Do not check the type of metMiriams -0314 if isfield(metsToAdd,'metMiriams') -0315 if numel(metsToAdd.metMiriams)==1 && numel(metsToAdd.mets)>1 -0316 temp=cell(numel(metsToAdd.mets),1); -0317 temp(:)={metsToAdd.metMiriams}; -0318 metsToAdd.metMiriams=temp; -0319 end -0320 if numel(metsToAdd.metMiriams)~=nMets -0321 EM='metsToAdd.metMiriams must have the same number of elements as metsToAdd.mets'; -0322 error('RAVEN:badInput', '%s', EM); -0323 end -0324 %Add empty field if it does not exist -0325 if ~isfield(newModel,'metMiriams') -0326 newModel.metMiriams=cell(nOldMets,1); -0327 end -0328 newModel.metMiriams=[newModel.metMiriams;metsToAdd.metMiriams(:)]; -0329 else -0330 if isfield(newModel,'metMiriams') -0331 newModel.metMiriams=[newModel.metMiriams;cell(nMets,1)]; -0332 end -0333 end -0334 -0335 if isfield(newModel,'metFrom') -0336 newModel.metFrom=[newModel.metFrom;filler]; -0337 end -0338 -0339 %Expand the S matrix -0340 newModel.S=[newModel.S;sparse(nMets,size(newModel.S,2))]; -0341 -0342 if copyInfo==true -0343 [I, J]=ismember(metsToAdd.metNames,model.metNames); -0344 J=J(I); -0345 %I is the indexes of the new metabolites for which a metabolite with -0346 %the same name exists -0347 I=find(I)+nOldMets; -0348 %Go through each of the added mets and copy annotation if it does not -0349 %exist -0350 for i=1:numel(I) -0351 if isfield(newModel,'inchis') -0352 if isempty(newModel.inchis{I(i)}) -0353 newModel.inchis(I(i))=newModel.inchis(J(i)); -0354 end -0355 end -0356 if isfield(newModel,'metSmiles') -0357 if isempty(newModel.metSmiles{I(i)}) -0358 newModel.metSmiles(I(i))=newModel.metSmiles(J(i)); -0359 end -0360 end -0361 if isfield(newModel,'metFormulas') -0362 if isempty(newModel.metFormulas{I(i)}) -0363 newModel.metFormulas(I(i))=newModel.metFormulas(J(i)); -0364 end -0365 end -0366 if isfield(newModel,'metMiriams') -0367 if isempty(newModel.metMiriams{I(i)}) -0368 newModel.metMiriams(I(i))=newModel.metMiriams(J(i)); -0369 end -0370 end -0371 if isfield(newModel,'metCharges') -0372 newModel.metCharges(I(i))=newModel.metCharges(J(i)); -0373 end -0374 if isfield(newModel,'metDeltaG') -0375 newModel.metDeltaG(I(i))=newModel.metDeltaG(J(i)); -0376 end -0377 end -0378 end -0379 end

    +0071 if isempty(metsToAdd) +0072 return; +0073 end +0074 +0075 %Check some stuff regarding the required fields +0076 if ~isfield(metsToAdd,'mets') +0077 metsToAdd.metNames=convertCharArray(metsToAdd.metNames); +0078 metsToAdd.mets=generateNewIds(newModel,'mets',prefix,numel(metsToAdd.metNames)); +0079 else +0080 metsToAdd.mets=convertCharArray(metsToAdd.mets); +0081 end +0082 if ~isfield(metsToAdd,'metNames') +0083 metsToAdd.metNames=metsToAdd.mets; +0084 else +0085 metsToAdd.metNames=convertCharArray(metsToAdd.metNames); +0086 end +0087 if ~isfield(metsToAdd,'compartments') +0088 EM='compartments is a required field in metsToAdd'; +0089 error('RAVEN:badInput', '%s', EM); +0090 else +0091 metsToAdd.compartments=convertCharArray(metsToAdd.compartments); +0092 %If only one compartment is given, assume it is for all metabolites +0093 if numel(metsToAdd.compartments)==1 && numel(metsToAdd.mets)>1 +0094 temp=cell(numel(metsToAdd.mets),1); +0095 temp(:)=metsToAdd.compartments; +0096 metsToAdd.compartments=temp; +0097 end +0098 end +0099 +0100 %Number of metabolites +0101 nMets=numel(metsToAdd.mets); +0102 nOldMets=numel(model.mets); +0103 filler=cell(nMets,1); +0104 filler(:)={''}; +0105 largeFiller=cell(nOldMets,1); +0106 largeFiller(:)={''}; +0107 +0108 %Check that no metabolite ids are already present in the model +0109 I=ismember(metsToAdd.mets,model.mets); +0110 if any(I) +0111 error('One or more elements in metsToAdd.mets are already present in model.mets: %s',... +0112 strjoin(metsToAdd.mets(I),', ')); +0113 else +0114 newModel.mets=[newModel.mets;metsToAdd.mets(:)]; +0115 end +0116 +0117 %Check that all the compartments could be found +0118 [I, compMap]=ismember(metsToAdd.compartments,model.comps); +0119 if ~all(I) +0120 EM='metsToAdd.compartments must match model.comps'; +0121 error('RAVEN:badInput', '%s', EM); +0122 end +0123 +0124 %Check that the metabolite names are not present in the same compartment. +0125 %Not the neatest way maybe.. +0126 t1=strcat(metsToAdd.metNames(:),'***',metsToAdd.compartments(:)); +0127 t2=strcat(model.metNames,'***',model.comps(model.metComps)); +0128 if any(ismember(t1,t2)) +0129 EM='One or more elements in metsToAdd.metNames already exist in the same compartments as the one it is being added to'; +0130 error('RAVEN:badInput', '%s', EM); +0131 end +0132 +0133 %Some more checks and if they pass then add each field to the structure +0134 if numel(metsToAdd.metNames)~=nMets +0135 EM='metsToAdd.metNames must have the same number of elements as metsToAdd.mets'; +0136 error('RAVEN:badInput', '%s', EM); +0137 else +0138 newModel.metNames=[newModel.metNames;metsToAdd.metNames(:)]; +0139 end +0140 +0141 if numel(compMap)~=nMets +0142 EM='metsToAdd.compartments must have the same number of elements as metsToAdd.mets'; +0143 error('RAVEN:badInput', '%s', EM); +0144 else +0145 newModel.metComps=[newModel.metComps;compMap(:)]; +0146 end +0147 +0148 if isfield(metsToAdd,'b') +0149 if size(metsToAdd.b,1)~=nMets +0150 EM='metsToAdd.b must have the same number of elements as metsToAdd.mets'; +0151 error('RAVEN:badInput', '%s', EM); +0152 else +0153 %Add empty field if it does not exist +0154 if ~isfield(newModel,'b') +0155 newModel.b=zeros(nOldMets,1); +0156 end +0157 +0158 %If the original is only one vector +0159 if size(metsToAdd.b,2)>size(newModel.b,2) +0160 newModel.b=[newModel.b newModel.b]; +0161 end +0162 %Add the new ones +0163 newModel.b=[newModel.b;metsToAdd.b]; +0164 end +0165 else +0166 if isfield(newModel,'b') +0167 %Add the default +0168 newModel.b=[newModel.b;zeros(nMets,size(newModel.b,2))]; +0169 end +0170 end +0171 +0172 if isfield(metsToAdd,'unconstrained') +0173 if numel(metsToAdd.unconstrained)~=nMets +0174 EM='metsToAdd.unconstrained must have the same number of elements as metsToAdd.mets'; +0175 error('RAVEN:badInput', '%s', EM); +0176 else +0177 %Add empty field if it does not exist +0178 if ~isfield(newModel,'unconstrained') +0179 newModel.unconstrained=zeros(nOldMets,1); +0180 end +0181 +0182 %Add the new ones +0183 newModel.unconstrained=[newModel.unconstrained;metsToAdd.unconstrained(:)]; +0184 end +0185 else +0186 if isfield(newModel,'unconstrained') +0187 %Add the default +0188 newModel.unconstrained=[newModel.unconstrained;zeros(nMets,1)]; +0189 end +0190 end +0191 +0192 if isfield(metsToAdd,'inchis') +0193 metsToAdd.inchis=convertCharArray(metsToAdd.inchis); +0194 if numel(metsToAdd.inchis)~=nMets +0195 EM='metsToAdd.inchis must have the same number of elements as metsToAdd.mets'; +0196 error('RAVEN:badInput', '%s', EM); +0197 end +0198 %Add empty field if it does not exist +0199 if ~isfield(newModel,'inchis') +0200 newModel.inchis=largeFiller; +0201 end +0202 newModel.inchis=[newModel.inchis;metsToAdd.inchis(:)]; +0203 else +0204 %Add empty strings if the structure is in the model +0205 if isfield(newModel,'inchis') +0206 newModel.inchis=[newModel.inchis;filler]; +0207 end +0208 end +0209 +0210 +0211 if isfield(metsToAdd,'metSmiles') +0212 metsToAdd.metSmiles=convertCharArray(metsToAdd.metSmiles); +0213 if numel(metsToAdd.metSmiles)~=nMets +0214 EM='metsToAdd.metSmiles must have the same number of elements as metsToAdd.mets'; +0215 error('RAVEN:badInput', '%s', EM); +0216 end +0217 %Add empty field if it does not exist +0218 if ~isfield(newModel,'metSmiles') +0219 newModel.metSmiles=largeFiller; +0220 end +0221 newModel.metSmiles=[newModel.metSmiles;metsToAdd.metSmiles(:)]; +0222 else +0223 %Add empty strings if the structure is in the model +0224 if isfield(newModel,'metSmiles') +0225 newModel.metSmiles=[newModel.metSmiles;filler]; +0226 end +0227 end +0228 +0229 if isfield(metsToAdd,'metFormulas') +0230 metsToAdd.metFormulas=convertCharArray(metsToAdd.metFormulas); +0231 if numel(metsToAdd.metFormulas)~=nMets +0232 EM='metsToAdd.metFormulas must have the same number of elements as metsToAdd.mets'; +0233 error('RAVEN:badInput', '%s', EM); +0234 end +0235 %Add empty field if it does not exist +0236 if ~isfield(newModel,'metFormulas') +0237 newModel.metFormulas=largeFiller; +0238 end +0239 newModel.metFormulas=[newModel.metFormulas;metsToAdd.metFormulas(:)]; +0240 else +0241 %Add default +0242 if isfield(newModel,'metFormulas') +0243 newModel.metFormulas=[newModel.metFormulas;filler]; +0244 end +0245 end +0246 +0247 if isfield(metsToAdd,'metCharges') +0248 if numel(metsToAdd.metCharges)~=nMets +0249 EM='metsToAdd.metCharges must have the same number of elements as metsToAdd.mets'; +0250 error('RAVEN:badInput', '%s', EM); +0251 end +0252 if ~isnumeric(metsToAdd.metCharges) +0253 EM='metsToAdd.metCharges must be of type "double"'; +0254 error('RAVEN:badInput', '%s', EM); +0255 end +0256 if ~isfield(newModel,'metCharges') +0257 newModel.metCharges=NaN(numel(largeFiller),1); +0258 end +0259 newModel.metCharges=[newModel.metCharges;metsToAdd.metCharges(:)]; +0260 else +0261 %Add default +0262 if isfield(newModel,'metCharges') +0263 newModel.metCharges=[newModel.metCharges;NaN(numel(filler),1)]; +0264 end +0265 end +0266 +0267 if isfield(metsToAdd,'metDeltaG') +0268 if numel(metsToAdd.metDeltaG)~=nMets +0269 EM='metsToAdd.metDeltaG must have the same number of elements as metsToAdd.mets'; +0270 error('RAVEN:badInput', '%s', EM); +0271 end +0272 if ~isnumeric(metsToAdd.metDeltaG) +0273 EM='metsToAdd.metDeltaG must be of type "double"'; +0274 error('RAVEN:badInput', '%s', EM); +0275 end +0276 if ~isfield(newModel,'metDeltaG') +0277 newModel.metDeltaG=NaN(numel(largeFiller),1); +0278 end +0279 newModel.metDeltaG=[newModel.metDeltaG;metsToAdd.metDeltaG(:)]; +0280 else +0281 %Add default +0282 if isfield(newModel,'metDeltaG') +0283 newModel.metDeltaG=[newModel.metDeltaG;NaN(numel(filler),1)]; +0284 end +0285 end +0286 +0287 +0288 if isfield(metsToAdd,'metNotes') +0289 metsToAdd.metNotes=convertCharArray(metsToAdd.metNotes); +0290 if numel(metsToAdd.metNotes)==1 && numel(metsToAdd.mets)>1 +0291 temp=cell(numel(metsToAdd.mets),1); +0292 temp(:)=metsToAdd.metNotes; +0293 metsToAdd.metNotes=temp; +0294 end +0295 if numel(metsToAdd.metNotes)~=nMets +0296 EM='metsToAdd.metNotes must have the same number of elements as metsToAdd.mets'; +0297 error('RAVEN:badInput', '%s', EM); +0298 end +0299 %Add empty field if it does not exist +0300 if ~isfield(newModel,'metNotes') +0301 newModel.metNotes=largeFiller; +0302 end +0303 newModel.metNotes=[newModel.metNotes;metsToAdd.metNotes(:)]; +0304 else +0305 %Add empty strings if the structure is in the model +0306 if isfield(newModel,'metNotes') +0307 newModel.metNotes=[newModel.metNotes;filler]; +0308 end +0309 end +0310 +0311 %Do not check the type of metMiriams +0312 if isfield(metsToAdd,'metMiriams') +0313 if numel(metsToAdd.metMiriams)==1 && numel(metsToAdd.mets)>1 +0314 temp=cell(numel(metsToAdd.mets),1); +0315 temp(:)={metsToAdd.metMiriams}; +0316 metsToAdd.metMiriams=temp; +0317 end +0318 if numel(metsToAdd.metMiriams)~=nMets +0319 EM='metsToAdd.metMiriams must have the same number of elements as metsToAdd.mets'; +0320 error('RAVEN:badInput', '%s', EM); +0321 end +0322 %Add empty field if it does not exist +0323 if ~isfield(newModel,'metMiriams') +0324 newModel.metMiriams=cell(nOldMets,1); +0325 end +0326 newModel.metMiriams=[newModel.metMiriams;metsToAdd.metMiriams(:)]; +0327 else +0328 if isfield(newModel,'metMiriams') +0329 newModel.metMiriams=[newModel.metMiriams;cell(nMets,1)]; +0330 end +0331 end +0332 +0333 if isfield(newModel,'metFrom') +0334 newModel.metFrom=[newModel.metFrom;filler]; +0335 end +0336 +0337 %Expand the S matrix +0338 newModel.S=[newModel.S;sparse(nMets,size(newModel.S,2))]; +0339 +0340 if copyInfo==true +0341 [I, J]=ismember(metsToAdd.metNames,model.metNames); +0342 J=J(I); +0343 %I is the indexes of the new metabolites for which a metabolite with +0344 %the same name exists +0345 I=find(I)+nOldMets; +0346 %Go through each of the added mets and copy annotation if it does not +0347 %exist +0348 for i=1:numel(I) +0349 if isfield(newModel,'inchis') +0350 if isempty(newModel.inchis{I(i)}) +0351 newModel.inchis(I(i))=newModel.inchis(J(i)); +0352 end +0353 end +0354 if isfield(newModel,'metSmiles') +0355 if isempty(newModel.metSmiles{I(i)}) +0356 newModel.metSmiles(I(i))=newModel.metSmiles(J(i)); +0357 end +0358 end +0359 if isfield(newModel,'metFormulas') +0360 if isempty(newModel.metFormulas{I(i)}) +0361 newModel.metFormulas(I(i))=newModel.metFormulas(J(i)); +0362 end +0363 end +0364 if isfield(newModel,'metMiriams') +0365 if isempty(newModel.metMiriams{I(i)}) +0366 newModel.metMiriams(I(i))=newModel.metMiriams(J(i)); +0367 end +0368 end +0369 if isfield(newModel,'metCharges') +0370 newModel.metCharges(I(i))=newModel.metCharges(J(i)); +0371 end +0372 if isfield(newModel,'metDeltaG') +0373 newModel.metDeltaG(I(i))=newModel.metDeltaG(J(i)); +0374 end +0375 end +0376 end +0377 end

    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/manipulation/addRxns.html b/docs/html/manipulation/addRxns.html index df2b0e5f..2da6093f 100644 --- a/docs/html/manipulation/addRxns.html +++ b/docs/html/manipulation/addRxns.html @@ -29,12 +29,12 @@

    SYNOPSIS ^DESCRIPTION ^

     addRxns  Add reactions to a model.
     
    - This function does not make extensive checks about formatting of
    - gene-reaction rules.
    + The function checks that grRules has one entry per reaction and that every gene of
    + a rule is in model.genes (an error otherwise, unless allowNewGenes is true).
    + standardizeGrRules then writes the rules in the standard format.
     
    - When adding metabolites to a compartment where they previously do not
    - exist, the function will copy any available information from the
    - metabolite in another compartment.
    + New metabolites receive the available annotation of the metabolite with the same name
    + in another compartment, when one exists.
     
      Parameters
      ----------
    @@ -43,26 +43,26 @@ 

    DESCRIPTION ^DESCRIPTION ^DESCRIPTION ^DESCRIPTION ^CROSS-REFERENCE INFORMATION ^

    This function calls: +
  • addGenesRaven addGenesRaven Add genes to a model.
  • addMets addMets Add metabolites to a model.
  • standardizeGrRules standardizeGrRules Standardise gene-reaction rules in a model.
  • This function is called by:
    • addRxnsGenesMets addRxnsGenesMets Copy reactions from a source model into another model.
    • changeRxns changeRxns Modify the equations of reactions in a model.
    @@ -147,12 +143,12 @@

    SOURCE CODE ^
    0001 function newModel=addRxns(model,rxnsToAdd,varargin)
     0002 % addRxns  Add reactions to a model.
     0003 %
    -0004 % This function does not make extensive checks about formatting of
    -0005 % gene-reaction rules.
    -0006 %
    -0007 % When adding metabolites to a compartment where they previously do not
    -0008 % exist, the function will copy any available information from the
    -0009 % metabolite in another compartment.
    +0004 % The function checks that grRules has one entry per reaction and that every gene of
    +0005 % a rule is in model.genes (an error otherwise, unless allowNewGenes is true).
    +0006 % standardizeGrRules then writes the rules in the standard format.
    +0007 %
    +0008 % New metabolites receive the available annotation of the metabolite with the same name
    +0009 % in another compartment, when one exists.
     0010 %
     0011 % Parameters
     0012 % ----------
    @@ -161,26 +157,26 @@ 

    SOURCE CODE ^% rxnsToAdd : struct 0016 % the reaction structure, which can have the following fields: 0017 % -0018 % - rxns : cell array with unique strings that identifies each reaction +0018 % - rxns : cell array with unique strings that identify each reaction 0019 % - equations : cell array with equation strings. Decimal coefficients 0020 % are expressed as "1.2". Reversibility is indicated by "<=>" or "=>" 0021 % - mets : (alternative to equations) cell array with the metabolites -0022 % involved in each reaction as nested arrays. E.g.: -0023 % {{"met1","met2"},{"met1","met3","met4"}}. In the case of one single -0024 % reaction added, it can be a string array: {"met1","met2"} +0022 % involved in each reaction as nested arrays, for example +0023 % {{"met1","met2"},{"met1","met3","met4"}}. For a single added +0024 % reaction, it can be a string array: {"met1","met2"} 0025 % - stoichCoeffs : (alternative to equations) cell array with the -0026 % corresponding stoichiometries as nested vectors. E.g.: -0027 % {[-1,+2],[-1,-1,+1]}. In the case of one single reaction added, it -0028 % can be a vector: [-1,+2] +0026 % corresponding stoichiometries as nested vectors, for example +0027 % {[-1,+2],[-1,-1,+1]}. For a single added reaction, it can be a +0028 % vector: [-1,+2] 0029 % - rxnNames : cell array with the names of each reaction (optional, 0030 % default "") 0031 % - lb : vector with the lower bounds (optional, default -0032 % model.annotations.defaultLB or -inf for reversible reactions and 0 -0033 % for irreversible when "equations" is used. When "mets" and -0034 % "stoichCoeffs" are used it defaults for all to -0035 % model.annotations.defaultLB or -inf) +0032 % model.annotation.defaultLB or -inf for reversible reactions and 0 +0033 % for irreversible reactions when "equations" is used. When "mets" and +0034 % "stoichCoeffs" are used, the default for all reactions is +0035 % model.annotation.defaultLB or -inf) 0036 % - ub : vector with the upper bounds (optional, default -0037 % model.annotations.defaultUB or inf) +0037 % model.annotation.defaultUB or inf) 0038 % - c : vector with the objective function coefficients (optional, 0039 % default 0) 0040 % - eccodes : cell array with the EC-numbers for each reaction. Delimit @@ -188,708 +184,704 @@

    SOURCE CODE ^% - subSystems : cell array with the subsystems for each reaction 0043 % (optional, default "") 0044 % - grRules : cell array with the gene-reaction relationship for each -0045 % reaction. E.g. "(A and B) or (C)" means that the reaction could be -0046 % catalyzed by a complex between A & B or by C on its own. All the -0047 % genes have to be present in model.genes. Add genes with -0048 % addGenesRaven before calling this function if needed (optional, -0049 % default "") -0050 % - rxnMiriams : cell array with Miriam structures (optional, -0051 % default []) -0052 % - rxnComps : cell array with compartments (as in model.comps) -0053 % (optional, default {}) -0054 % - rxnNotes : cell array with reaction notes (optional, default "") -0055 % - rxnDeltaG : Gibbs free energy at biochemical standard condition in -0056 % kJ/mole (optional, default NaN) -0057 % - rxnReferences : cell array with reaction references (optional, -0058 % default "") -0059 % - rxnConfidenceScores : vector with reaction confidence scores -0060 % (optional, default NaN) -0061 % -0062 % Name-Value Arguments -0063 % -------------------- -0064 % eqnType : double or char -0065 % describes how the equation string should be interpreted (default 1). -0066 % Accepts integer values 1/2/3 or the string aliases "id", "name", -0067 % "name[comp]": -0068 % -0069 % - 1 / "id" : the metabolites are matched to model.mets. New -0070 % metabolites (if allowed) are added to "compartment" -0071 % - 2 / "name" : the metabolites are matched to model.metNames and all -0072 % metabolites are assigned to "compartment". Any new metabolites that -0073 % are added will be assigned IDs "m1", "m2"... If IDs on the same -0074 % form are already used in the model then the numbering will start -0075 % from the highest used integer+1 -0076 % - 3 / "name[comp]" : the metabolites are written as "metNames[comps]". -0077 % Only compartments in model.comps are allowed. Any new metabolites -0078 % that are added will be assigned IDs "m1", "m2"... If IDs on the -0079 % same form are already used in the model then the numbering will -0080 % start from the highest used integer+1 -0081 % compartment : char -0082 % the compartment the metabolites should be placed in when using -0083 % eqnType=2. Must match model.comps (optional when eqnType=1 or -0084 % eqnType=3). -0085 % allowNewMets : logical or char -0086 % true if the function is allowed to add new metabolites. Can also be a -0087 % string, which will be used as prefix for the new metabolite IDs. It -0088 % is highly recommended to first add any new metabolites with addMets -0089 % rather than automatically through this function. addMets supports -0090 % more annotation of metabolites, allows for the use of exchange -0091 % metabolites, and using it reduces the risk of parsing errors -0092 % (default false). -0093 % allowNewGenes : logical -0094 % true if the function is allowed to add new genes (default false). -0095 % -0096 % Returns -0097 % ------- -0098 % newModel : struct -0099 % an updated model structure. -0100 % -0101 % Examples -0102 % -------- -0103 % newModel = addRxns(model, rxnsToAdd, eqnType, compartment, ... -0104 % allowNewMets, allowNewGenes); -0105 -0106 p=parseRAVENargs(varargin, {'eqnType',1; 'compartment',[]; 'allowNewMets',false; 'allowNewGenes',false}); -0107 eqnType=p.eqnType; -0108 if ischar(eqnType) || isstring(eqnType) -0109 switch lower(char(eqnType)) -0110 case 'id' -0111 eqnType=1; -0112 case 'name' -0113 eqnType=2; -0114 case 'name[comp]' -0115 eqnType=3; -0116 otherwise -0117 error('RAVEN:badInput', '%s', ['Unknown eqnType string "' char(eqnType) '". Use ''id'', ''name'', or ''name[comp]''.']); -0118 end -0119 end -0120 if ~isnumeric(eqnType) -0121 EM='eqnType must be numeric or one of the string aliases ''id'', ''name'', ''name[comp]'''; -0122 error('RAVEN:badInput', '%s', EM); -0123 elseif ~ismember(eqnType,[1 2 3]) -0124 EM='eqnType must be 1, 2, or 3'; -0125 error('RAVEN:badInput', '%s', EM); +0045 % reaction. "(A and B) or (C)" means that the reaction is +0046 % catalysed by a complex of A and B or by C on its own. All genes must +0047 % be present in model.genes, unless allowNewGenes is true; addGenesRaven +0048 % adds genes (optional, default "") +0049 % - rxnMiriams : cell array with Miriam structures (optional, +0050 % default []) +0051 % - rxnComps : cell array with compartments (as in model.comps) +0052 % (optional, default {}) +0053 % - rxnNotes : cell array with reaction notes (optional, default "") +0054 % - rxnDeltaG : Gibbs free energy at biochemical standard condition in +0055 % kJ/mol (optional, default NaN) +0056 % - rxnReferences : cell array with reaction references (optional, +0057 % default "") +0058 % - rxnConfidenceScores : vector with reaction confidence scores +0059 % (optional, default NaN) +0060 % +0061 % Name-Value Arguments +0062 % -------------------- +0063 % eqnType : double or char +0064 % the interpretation of the equation strings (default 1). The values are the +0065 % integers 1, 2 and 3, or the string aliases "id", "name" and "name[comp]": +0066 % +0067 % - 1 or "id" : the metabolites are matched to model.mets. New +0068 % metabolites (if allowed) are added to "compartment" +0069 % - 2 or "name" : the metabolites are matched to model.metNames and all +0070 % metabolites are assigned to "compartment". New metabolites get +0071 % ids generated by generateNewIds with the prefix "m_" (or the +0072 % prefix given in allowNewMets), numbered from the highest existing +0073 % number with that prefix plus 1 +0074 % - 3 or "name[comp]" : the metabolites are written as "metNames[comps]". +0075 % Only compartments in model.comps are allowed. New metabolites get +0076 % ids generated in the same way as for eqnType 2 +0077 % compartment : char +0078 % the compartment of the metabolites for eqnType=2 and of new metabolites for +0079 % eqnType=1. Must match model.comps. Not used for eqnType=3. +0080 % allowNewMets : logical or char +0081 % true to allow the function to add new metabolites. A string also allows it and +0082 % is used as the prefix of the new metabolite ids. The function calls addMets +0083 % with copyInfo true, so a new metabolite takes the charge and deltaG from a +0084 % metabolite with the same name in another compartment, and InChI, SMILES, +0085 % formula and MIRIAM annotation from it when its own are empty. When the +0086 % annotation of new metabolites is to be set explicitly, or when they are to be +0087 % exchange (unconstrained) metabolites, adding them with addMets first gives +0088 % that control (default false). +0089 % allowNewGenes : logical +0090 % true to allow the function to add new genes (default false). +0091 % +0092 % Returns +0093 % ------- +0094 % newModel : struct +0095 % an updated model structure. +0096 % +0097 % Examples +0098 % -------- +0099 % newModel = addRxns(model, rxnsToAdd, eqnType, compartment, ... +0100 % allowNewMets, allowNewGenes); +0101 +0102 p=parseRAVENargs(varargin, {'eqnType',1; 'compartment',[]; 'allowNewMets',false; 'allowNewGenes',false}); +0103 eqnType=p.eqnType; +0104 if ischar(eqnType) || isstring(eqnType) +0105 switch lower(char(eqnType)) +0106 case 'id' +0107 eqnType=1; +0108 case 'name' +0109 eqnType=2; +0110 case 'name[comp]' +0111 eqnType=3; +0112 otherwise +0113 error('RAVEN:badInput', '%s', ['Unknown eqnType string "' char(eqnType) '". Use ''id'', ''name'', or ''name[comp]''.']); +0114 end +0115 end +0116 if ~isnumeric(eqnType) +0117 EM='eqnType must be numeric or one of the string aliases ''id'', ''name'', ''name[comp]'''; +0118 error('RAVEN:badInput', '%s', EM); +0119 elseif ~ismember(eqnType,[1 2 3]) +0120 EM='eqnType must be 1, 2, or 3'; +0121 error('RAVEN:badInput', '%s', EM); +0122 end +0123 compartment=p.compartment; +0124 if ~isempty(compartment) +0125 compartment=char(compartment); 0126 end -0127 compartment=p.compartment; -0128 if ~isempty(compartment) -0129 compartment=char(compartment); +0127 allowNewMets=p.allowNewMets; +0128 if ~islogical(allowNewMets) +0129 allowNewMets=char(allowNewMets); 0130 end -0131 allowNewMets=p.allowNewMets; -0132 if ~islogical(allowNewMets) -0133 allowNewMets=char(allowNewMets); -0134 end -0135 allowNewGenes=p.allowNewGenes; -0136 -0137 if allowNewGenes & isfield(rxnsToAdd,'grRules') -0138 % getGenesFromGrRules splits only on space-bounded "and"/"or" (or their -0139 % "&"/"|" equivalents), so a gene id that merely contains "and"/"or" as -0140 % a substring (e.g. "band1", "orfeo2") is not mistaken for an operator -0141 % the way a plain regexp split on the bare words would. -0142 genesToAdd.genes = getGenesFromGrRules(convertCharArray(rxnsToAdd.grRules)); -0143 genesToAdd.genes = setdiff(genesToAdd.genes,model.genes); % Only keep new genes -0144 if isfield(model,'geneComps') -0145 % No compartment information is available for a gene parsed out of -0146 % a grRule, so it is assigned to the first compartment, matching -0147 % addGenesRaven's own default for a gene added with none specified. -0148 genesToAdd.geneComps(1:numel(genesToAdd.genes)) = repmat(1,numel(genesToAdd.genes),1); -0149 end -0150 if ~isempty(genesToAdd.genes) -0151 fprintf('\nNew genes added to the model:\n') -0152 %A new gene id is arbitrary text and may contain "%"; print as -0153 %literal data rather than as an fprintf format string. -0154 fprintf('%s\n', strjoin(genesToAdd.genes,newline)) -0155 newModel=addGenesRaven(model,genesToAdd); -0156 else -0157 newModel=model; -0158 end -0159 else -0160 newModel=model; -0161 end -0162 -0163 %If no reactions should be added -0164 if isempty(rxnsToAdd) -0165 return; -0166 end -0167 -0168 if eqnType==2 || (eqnType==1 && allowNewMets==true) -0169 compartment=char(compartment); -0170 if ~ismember(compartment,model.comps) -0171 EM='compartment must match one of the compartments in model.comps'; -0172 error('RAVEN:badInput', '%s', EM); -0173 end -0174 end -0175 -0176 if ~isfield(rxnsToAdd,'rxns') -0177 EM='rxns is a required field in rxnsToAdd'; -0178 error('RAVEN:badInput', '%s', EM); -0179 else -0180 rxnsToAdd.rxns=convertCharArray(rxnsToAdd.rxns); -0181 %To fit with some later printing -0182 rxnsToAdd.rxns=rxnsToAdd.rxns(:); +0131 allowNewGenes=p.allowNewGenes; +0132 +0133 if allowNewGenes & isfield(rxnsToAdd,'grRules') +0134 % getGenesFromGrRules splits only on space-bounded "and"/"or" (or their +0135 % "&"/"|" equivalents), so a gene id that merely contains "and"/"or" as +0136 % a substring (e.g. "band1", "orfeo2") is not mistaken for an operator +0137 % the way a plain regexp split on the bare words would. +0138 genesToAdd.genes = getGenesFromGrRules(convertCharArray(rxnsToAdd.grRules)); +0139 genesToAdd.genes = setdiff(genesToAdd.genes,model.genes); % Only keep new genes +0140 if isfield(model,'geneComps') +0141 % No compartment information is available for a gene parsed out of +0142 % a grRule, so it is assigned to the first compartment, matching +0143 % addGenesRaven's own default for a gene added with none specified. +0144 genesToAdd.geneComps(1:numel(genesToAdd.genes)) = repmat(1,numel(genesToAdd.genes),1); +0145 end +0146 if ~isempty(genesToAdd.genes) +0147 fprintf('\nNew genes added to the model:\n') +0148 %A new gene id is arbitrary text and may contain "%"; print as +0149 %literal data rather than as an fprintf format string. +0150 fprintf('%s\n', strjoin(genesToAdd.genes,newline)) +0151 newModel=addGenesRaven(model,genesToAdd); +0152 else +0153 newModel=model; +0154 end +0155 else +0156 newModel=model; +0157 end +0158 +0159 %If no reactions should be added +0160 if isempty(rxnsToAdd) +0161 return; +0162 end +0163 +0164 if eqnType==2 || (eqnType==1 && allowNewMets==true) +0165 compartment=char(compartment); +0166 if ~ismember(compartment,model.comps) +0167 EM='compartment must match one of the compartments in model.comps'; +0168 error('RAVEN:badInput', '%s', EM); +0169 end +0170 end +0171 +0172 if ~isfield(rxnsToAdd,'rxns') +0173 EM='rxns is a required field in rxnsToAdd'; +0174 error('RAVEN:badInput', '%s', EM); +0175 else +0176 rxnsToAdd.rxns=convertCharArray(rxnsToAdd.rxns); +0177 %To fit with some later printing +0178 rxnsToAdd.rxns=rxnsToAdd.rxns(:); +0179 end +0180 if ~(isfield(rxnsToAdd,'equations') || (isfield(rxnsToAdd,'mets') && isfield(rxnsToAdd,'stoichCoeffs'))) +0181 EM='Either "equations" or "mets"+"stoichCoeffs" are required fields in rxnsToAdd'; +0182 error('RAVEN:badInput', '%s', EM); 0183 end -0184 if ~(isfield(rxnsToAdd,'equations') || (isfield(rxnsToAdd,'mets') && isfield(rxnsToAdd,'stoichCoeffs'))) -0185 EM='Either "equations" or "mets"+"stoichCoeffs" are required fields in rxnsToAdd'; -0186 error('RAVEN:badInput', '%s', EM); -0187 end -0188 -0189 if any(ismember(rxnsToAdd.rxns,model.rxns)) -0190 EM='One or more reaction id was already present in the model. Use changeRxns or remove the existing reactions before adding new ones'; -0191 error('RAVEN:badInput', '%s', EM); -0192 end +0184 +0185 if any(ismember(rxnsToAdd.rxns,model.rxns)) +0186 EM='One or more reaction id was already present in the model. Use changeRxns or remove the existing reactions before adding new ones'; +0187 error('RAVEN:badInput', '%s', EM); +0188 end +0189 +0190 %Normal case: equations provided +0191 if isfield(rxnsToAdd,'equations') +0192 rxnsToAdd.equations=convertCharArray(rxnsToAdd.equations); 0193 -0194 %Normal case: equations provided -0195 if isfield(rxnsToAdd,'equations') -0196 rxnsToAdd.equations=convertCharArray(rxnsToAdd.equations); -0197 -0198 %Alternative case: mets+stoichiometry provided -0199 else -0200 %In the case of 1 rxn added (array of strings + vector), transform to -0201 %cells of length=1: -0202 if iscellstr(rxnsToAdd.mets) -0203 rxnsToAdd.mets={rxnsToAdd.mets}; -0204 end -0205 if isnumeric(rxnsToAdd.stoichCoeffs) -0206 rxnsToAdd.stoichCoeffs = {rxnsToAdd.stoichCoeffs}; -0207 end -0208 %Now both rxnsToAdd.mets and rxnsToAdd.stoichCoeffs should be cell -0209 %arrays & of the same size: -0210 if ~iscell(rxnsToAdd.mets) || ~iscell(rxnsToAdd.stoichCoeffs) -0211 EM='rxnsToAdd.mets & rxnsToAdd.stoichCoeffs must be cell arrays'; -0212 error('RAVEN:badInput', '%s', EM); -0213 elseif length(rxnsToAdd.stoichCoeffs) ~= length(rxnsToAdd.mets) -0214 EM = 'rxnsToAdd.stoichCoeffs must have the same number of elements as rxnsToAdd.mets'; -0215 error('RAVEN:badInput', '%s', EM); -0216 end -0217 %In this case we need lb to decide if the reaction is reversible or not: -0218 if ~isfield(rxnsToAdd,'lb') -0219 %Fill with standard if it does not exist -0220 rxnsToAdd.lb=-inf(size(rxnsToAdd.mets)); -0221 elseif ~isnumeric(rxnsToAdd.lb) -0222 EM = 'rxnsToAdd.lb must be a vector'; -0223 error('RAVEN:badInput', '%s', EM); -0224 elseif length(rxnsToAdd.lb) ~= length(rxnsToAdd.mets) -0225 EM = 'rxnsToAdd.lb must have the same number of elements as rxnsToAdd.mets'; -0226 error('RAVEN:badInput', '%s', EM); -0227 end -0228 %Now we construct equations, to comply with the rest of the script: -0229 rxnsToAdd.equations = cell(size(rxnsToAdd.mets)); -0230 for i = 1:length(rxnsToAdd.mets) -0231 mets = rxnsToAdd.mets{i}; -0232 stoichCoeffs = rxnsToAdd.stoichCoeffs{i}; -0233 if isfield(rxnsToAdd,'ub') -0234 isrev = rxnsToAdd.lb(i) < 0 && rxnsToAdd.ub(i) > 0; -0235 else -0236 isrev = rxnsToAdd.lb(i) < 0; -0237 end -0238 rxnsToAdd.equations{i} = buildEquation(mets,stoichCoeffs,isrev); -0239 end -0240 end -0241 -0242 nRxns=numel(rxnsToAdd.rxns); -0243 nOldRxns=numel(model.rxns); -0244 filler=cell(nRxns,1); -0245 filler(:)={''}; -0246 cellfiller=cell(nRxns,1); -0247 cellfiller(:)={{''}}; -0248 largeFiller=cell(nOldRxns,1); -0249 largeFiller(:)={''}; -0250 largeCellFiller=cell(nOldRxns,1); -0251 largeCellFiller(:)={{''}}; -0252 -0253 %***Add everything to the model except for the equations. -0254 if numel(rxnsToAdd.equations)~=nRxns -0255 EM='rxnsToAdd.equations must have the same number of elements as rxnsToAdd.rxns'; -0256 error('RAVEN:badInput', '%s', EM); -0257 end -0258 -0259 %Parse the equations. This is done at this early stage since I need the -0260 %reversibility info -0261 [S, mets, badRxns, reversible]=constructS(rxnsToAdd.equations); -0262 if any(badRxns) -0263 EM='The following equations have one or more metabolites both as substrate and product. Only the net equations will be added:'; -0264 warning('RAVEN:warning', '%s', ravenList(EM, rxnsToAdd.rxns(badRxns))); -0265 end -0266 -0267 newModel.rev=[newModel.rev;reversible]; -0268 newModel.rxns=[newModel.rxns;rxnsToAdd.rxns(:)]; -0269 -0270 if isfield(rxnsToAdd,'rxnNames') -0271 rxnsToAdd.rxnNames=convertCharArray(rxnsToAdd.rxnNames); -0272 if numel(rxnsToAdd.rxnNames)~=nRxns -0273 EM='rxnsToAdd.rxnNames must have the same number of elements as rxnsToAdd.rxns'; -0274 error('RAVEN:badInput', '%s', EM); +0194 %Alternative case: mets+stoichiometry provided +0195 else +0196 %In the case of 1 rxn added (array of strings + vector), transform to +0197 %cells of length=1: +0198 if iscellstr(rxnsToAdd.mets) +0199 rxnsToAdd.mets={rxnsToAdd.mets}; +0200 end +0201 if isnumeric(rxnsToAdd.stoichCoeffs) +0202 rxnsToAdd.stoichCoeffs = {rxnsToAdd.stoichCoeffs}; +0203 end +0204 %Now both rxnsToAdd.mets and rxnsToAdd.stoichCoeffs should be cell +0205 %arrays & of the same size: +0206 if ~iscell(rxnsToAdd.mets) || ~iscell(rxnsToAdd.stoichCoeffs) +0207 EM='rxnsToAdd.mets & rxnsToAdd.stoichCoeffs must be cell arrays'; +0208 error('RAVEN:badInput', '%s', EM); +0209 elseif length(rxnsToAdd.stoichCoeffs) ~= length(rxnsToAdd.mets) +0210 EM = 'rxnsToAdd.stoichCoeffs must have the same number of elements as rxnsToAdd.mets'; +0211 error('RAVEN:badInput', '%s', EM); +0212 end +0213 %In this case we need lb to decide if the reaction is reversible or not: +0214 if ~isfield(rxnsToAdd,'lb') +0215 %Fill with standard if it does not exist +0216 rxnsToAdd.lb=-inf(size(rxnsToAdd.mets)); +0217 elseif ~isnumeric(rxnsToAdd.lb) +0218 EM = 'rxnsToAdd.lb must be a vector'; +0219 error('RAVEN:badInput', '%s', EM); +0220 elseif length(rxnsToAdd.lb) ~= length(rxnsToAdd.mets) +0221 EM = 'rxnsToAdd.lb must have the same number of elements as rxnsToAdd.mets'; +0222 error('RAVEN:badInput', '%s', EM); +0223 end +0224 %Now we construct equations, to comply with the rest of the script: +0225 rxnsToAdd.equations = cell(size(rxnsToAdd.mets)); +0226 for i = 1:length(rxnsToAdd.mets) +0227 mets = rxnsToAdd.mets{i}; +0228 stoichCoeffs = rxnsToAdd.stoichCoeffs{i}; +0229 if isfield(rxnsToAdd,'ub') +0230 isrev = rxnsToAdd.lb(i) < 0 && rxnsToAdd.ub(i) > 0; +0231 else +0232 isrev = rxnsToAdd.lb(i) < 0; +0233 end +0234 rxnsToAdd.equations{i} = buildEquation(mets,stoichCoeffs,isrev); +0235 end +0236 end +0237 +0238 nRxns=numel(rxnsToAdd.rxns); +0239 nOldRxns=numel(model.rxns); +0240 filler=cell(nRxns,1); +0241 filler(:)={''}; +0242 cellfiller=cell(nRxns,1); +0243 cellfiller(:)={{''}}; +0244 largeFiller=cell(nOldRxns,1); +0245 largeFiller(:)={''}; +0246 largeCellFiller=cell(nOldRxns,1); +0247 largeCellFiller(:)={{''}}; +0248 +0249 %***Add everything to the model except for the equations. +0250 if numel(rxnsToAdd.equations)~=nRxns +0251 EM='rxnsToAdd.equations must have the same number of elements as rxnsToAdd.rxns'; +0252 error('RAVEN:badInput', '%s', EM); +0253 end +0254 +0255 %Parse the equations. This is done at this early stage since I need the +0256 %reversibility info +0257 [S, mets, badRxns, reversible]=constructS(rxnsToAdd.equations); +0258 if any(badRxns) +0259 EM='The following equations have one or more metabolites both as substrate and product. Only the net equations will be added:'; +0260 warning('RAVEN:warning', '%s', ravenList(EM, rxnsToAdd.rxns(badRxns))); +0261 end +0262 +0263 newModel.rev=[newModel.rev;reversible]; +0264 newModel.rxns=[newModel.rxns;rxnsToAdd.rxns(:)]; +0265 +0266 if isfield(rxnsToAdd,'rxnNames') +0267 rxnsToAdd.rxnNames=convertCharArray(rxnsToAdd.rxnNames); +0268 if numel(rxnsToAdd.rxnNames)~=nRxns +0269 EM='rxnsToAdd.rxnNames must have the same number of elements as rxnsToAdd.rxns'; +0270 error('RAVEN:badInput', '%s', EM); +0271 end +0272 %Fill with standard if it does not exist +0273 if ~isfield(newModel,'rxnNames') +0274 newModel.rxnNames=largeFiller; 0275 end -0276 %Fill with standard if it does not exist -0277 if ~isfield(newModel,'rxnNames') -0278 newModel.rxnNames=largeFiller; -0279 end -0280 newModel.rxnNames=[newModel.rxnNames;rxnsToAdd.rxnNames(:)]; -0281 else -0282 %Fill with standard if it does not exist -0283 if isfield(newModel,'rxnNames') -0284 newModel.rxnNames=[newModel.rxnNames;filler]; -0285 end -0286 end -0287 -0288 if isfield(newModel,'annotation') & isfield(newModel.annotation,'defaultLB') -0289 newLb=newModel.annotation.defaultLB; -0290 else -0291 newLb=-inf; -0292 end -0293 -0294 if isfield(rxnsToAdd,'lb') -0295 if numel(rxnsToAdd.lb)~=nRxns -0296 EM='rxnsToAdd.lb must have the same number of elements as rxnsToAdd.rxns'; -0297 error('RAVEN:badInput', '%s', EM); -0298 end -0299 %Fill with standard if it does not exist -0300 if ~isfield(newModel,'lb') -0301 newModel.lb=zeros(nOldRxns,1); -0302 newModel.lb(newModel.rev~=0)=newLb; -0303 end -0304 newModel.lb=[newModel.lb;rxnsToAdd.lb(:)]; -0305 else -0306 %Fill with standard if it does not exist -0307 if isfield(newModel,'lb') -0308 I=zeros(nRxns,1); -0309 I(reversible~=0)=newLb; -0310 newModel.lb=[newModel.lb;I]; -0311 end -0312 end -0313 -0314 if isfield(newModel,'annotation') & isfield(newModel.annotation,'defaultUB') -0315 newUb=newModel.annotation.defaultUB; -0316 else -0317 newUb=inf; -0318 end -0319 -0320 if isfield(rxnsToAdd,'ub') -0321 if numel(rxnsToAdd.ub)~=nRxns -0322 EM='rxnsToAdd.ub must have the same number of elements as rxnsToAdd.rxns'; -0323 error('RAVEN:badInput', '%s', EM); +0276 newModel.rxnNames=[newModel.rxnNames;rxnsToAdd.rxnNames(:)]; +0277 else +0278 %Fill with standard if it does not exist +0279 if isfield(newModel,'rxnNames') +0280 newModel.rxnNames=[newModel.rxnNames;filler]; +0281 end +0282 end +0283 +0284 if isfield(newModel,'annotation') & isfield(newModel.annotation,'defaultLB') +0285 newLb=newModel.annotation.defaultLB; +0286 else +0287 newLb=-inf; +0288 end +0289 +0290 if isfield(rxnsToAdd,'lb') +0291 if numel(rxnsToAdd.lb)~=nRxns +0292 EM='rxnsToAdd.lb must have the same number of elements as rxnsToAdd.rxns'; +0293 error('RAVEN:badInput', '%s', EM); +0294 end +0295 %Fill with standard if it does not exist +0296 if ~isfield(newModel,'lb') +0297 newModel.lb=zeros(nOldRxns,1); +0298 newModel.lb(newModel.rev~=0)=newLb; +0299 end +0300 newModel.lb=[newModel.lb;rxnsToAdd.lb(:)]; +0301 else +0302 %Fill with standard if it does not exist +0303 if isfield(newModel,'lb') +0304 I=zeros(nRxns,1); +0305 I(reversible~=0)=newLb; +0306 newModel.lb=[newModel.lb;I]; +0307 end +0308 end +0309 +0310 if isfield(newModel,'annotation') & isfield(newModel.annotation,'defaultUB') +0311 newUb=newModel.annotation.defaultUB; +0312 else +0313 newUb=inf; +0314 end +0315 +0316 if isfield(rxnsToAdd,'ub') +0317 if numel(rxnsToAdd.ub)~=nRxns +0318 EM='rxnsToAdd.ub must have the same number of elements as rxnsToAdd.rxns'; +0319 error('RAVEN:badInput', '%s', EM); +0320 end +0321 %Fill with standard if it does not exist +0322 if ~isfield(newModel,'ub') +0323 newModel.ub=repmat(newUb,nOldRxns,1); 0324 end -0325 %Fill with standard if it does not exist -0326 if ~isfield(newModel,'ub') -0327 newModel.ub=repmat(newUb,nOldRxns,1); -0328 end -0329 newModel.ub=[newModel.ub;rxnsToAdd.ub(:)]; -0330 else -0331 %Fill with standard if it does not exist -0332 if isfield(newModel,'ub') -0333 newModel.ub=[newModel.ub;repmat(newUb,nRxns,1)]; -0334 end -0335 end -0336 -0337 if isfield(rxnsToAdd,'c') -0338 if numel(rxnsToAdd.c)~=nRxns -0339 EM='rxnsToAdd.c must have the same number of elements as rxnsToAdd.rxns'; -0340 error('RAVEN:badInput', '%s', EM); +0325 newModel.ub=[newModel.ub;rxnsToAdd.ub(:)]; +0326 else +0327 %Fill with standard if it does not exist +0328 if isfield(newModel,'ub') +0329 newModel.ub=[newModel.ub;repmat(newUb,nRxns,1)]; +0330 end +0331 end +0332 +0333 if isfield(rxnsToAdd,'c') +0334 if numel(rxnsToAdd.c)~=nRxns +0335 EM='rxnsToAdd.c must have the same number of elements as rxnsToAdd.rxns'; +0336 error('RAVEN:badInput', '%s', EM); +0337 end +0338 %Fill with standard if it does not exist +0339 if ~isfield(newModel,'c') +0340 newModel.c=zeros(nOldRxns,1); 0341 end -0342 %Fill with standard if it does not exist -0343 if ~isfield(newModel,'c') -0344 newModel.c=zeros(nOldRxns,1); -0345 end -0346 newModel.c=[newModel.c;rxnsToAdd.c(:)]; -0347 else -0348 %Fill with standard if it does not exist -0349 if isfield(newModel,'c') -0350 newModel.c=[newModel.c;zeros(nRxns,1)]; -0351 end -0352 end -0353 -0354 if isfield(rxnsToAdd,'eccodes') -0355 rxnsToAdd.eccodes=convertCharArray(rxnsToAdd.eccodes); -0356 if numel(rxnsToAdd.eccodes)~=nRxns -0357 EM='rxnsToAdd.eccodes must have the same number of elements as rxnsToAdd.rxns'; -0358 error('RAVEN:badInput', '%s', EM); +0342 newModel.c=[newModel.c;rxnsToAdd.c(:)]; +0343 else +0344 %Fill with standard if it does not exist +0345 if isfield(newModel,'c') +0346 newModel.c=[newModel.c;zeros(nRxns,1)]; +0347 end +0348 end +0349 +0350 if isfield(rxnsToAdd,'eccodes') +0351 rxnsToAdd.eccodes=convertCharArray(rxnsToAdd.eccodes); +0352 if numel(rxnsToAdd.eccodes)~=nRxns +0353 EM='rxnsToAdd.eccodes must have the same number of elements as rxnsToAdd.rxns'; +0354 error('RAVEN:badInput', '%s', EM); +0355 end +0356 %Fill with standard if it does not exist +0357 if ~isfield(newModel,'eccodes') +0358 newModel.eccodes=largeFiller; 0359 end -0360 %Fill with standard if it does not exist -0361 if ~isfield(newModel,'eccodes') -0362 newModel.eccodes=largeFiller; -0363 end -0364 newModel.eccodes=[newModel.eccodes;rxnsToAdd.eccodes(:)]; -0365 else -0366 %Fill with standard if it does not exist -0367 if isfield(newModel,'eccodes') -0368 newModel.eccodes=[newModel.eccodes;filler]; -0369 end -0370 end -0371 -0372 if isfield(rxnsToAdd,'subSystems') -0373 % Has to be cell array -0374 if ischar(rxnsToAdd.subSystems) -0375 rxnsToAdd.subSystems = {rxnsToAdd.subSystems}; +0360 newModel.eccodes=[newModel.eccodes;rxnsToAdd.eccodes(:)]; +0361 else +0362 %Fill with standard if it does not exist +0363 if isfield(newModel,'eccodes') +0364 newModel.eccodes=[newModel.eccodes;filler]; +0365 end +0366 end +0367 +0368 if isfield(rxnsToAdd,'subSystems') +0369 % Has to be cell array +0370 if ischar(rxnsToAdd.subSystems) +0371 rxnsToAdd.subSystems = {rxnsToAdd.subSystems}; +0372 end +0373 % If all nested cells are 1x1, then unnest +0374 if all(cellfun(@(x) iscell(x) && isscalar(x), rxnsToAdd.subSystems)) +0375 rxnsToAdd.subSystems = transpose([rxnsToAdd.subSystems{:}]); 0376 end -0377 % If all nested cells are 1x1, then unnest -0378 if all(cellfun(@(x) iscell(x) && isscalar(x), rxnsToAdd.subSystems)) -0379 rxnsToAdd.subSystems = transpose([rxnsToAdd.subSystems{:}]); -0380 end -0381 % Cell array should now be as simple as possible. Check if it is nested -0382 subSysRxnsNested = any(cellfun(@(x) iscell(x), rxnsToAdd.subSystems)); -0383 if isfield(newModel,'subSystems') -0384 subSysModelNested = any(cellfun(@(x) iscell(x), newModel.subSystems)); -0385 else -0386 subSysModelNested = subSysRxnsNested; -0387 if subSysRxnsNested -0388 newModel.subSystems=largeCellFiller; -0389 else -0390 newModel.subSystems=largeFiller; -0391 end -0392 end -0393 if subSysRxnsNested && ~subSysModelNested -0394 % Make all existing subSystems nested -0395 newModel.subSystems = cellfun(@(x) {x}, newModel.subSystems, 'uni', 0); -0396 elseif ~subSysRxnsNested && subSysModelNested -0397 rxnsToAdd.subSystems = cellfun(@(x) {x}, rxnsToAdd.subSystems, 'uni', 0); +0377 % Cell array should now be as simple as possible. Check if it is nested +0378 subSysRxnsNested = any(cellfun(@(x) iscell(x), rxnsToAdd.subSystems)); +0379 if isfield(newModel,'subSystems') +0380 subSysModelNested = any(cellfun(@(x) iscell(x), newModel.subSystems)); +0381 else +0382 subSysModelNested = subSysRxnsNested; +0383 if subSysRxnsNested +0384 newModel.subSystems=largeCellFiller; +0385 else +0386 newModel.subSystems=largeFiller; +0387 end +0388 end +0389 if subSysRxnsNested && ~subSysModelNested +0390 % Make all existing subSystems nested +0391 newModel.subSystems = cellfun(@(x) {x}, newModel.subSystems, 'uni', 0); +0392 elseif ~subSysRxnsNested && subSysModelNested +0393 rxnsToAdd.subSystems = cellfun(@(x) {x}, rxnsToAdd.subSystems, 'uni', 0); +0394 end +0395 if numel(rxnsToAdd.subSystems)~=nRxns +0396 EM='rxnsToAdd.subSystems must have the same number of elements as rxnsToAdd.rxns'; +0397 error('RAVEN:badInput', '%s', EM); 0398 end -0399 if numel(rxnsToAdd.subSystems)~=nRxns -0400 EM='rxnsToAdd.subSystems must have the same number of elements as rxnsToAdd.rxns'; -0401 error('RAVEN:badInput', '%s', EM); -0402 end -0403 newModel.subSystems=[newModel.subSystems;rxnsToAdd.subSystems(:)]; -0404 else -0405 %Fill with standard if it does not exist -0406 if isfield(newModel,'subSystems') -0407 if any(cellfun(@(x) iscell(x), newModel.subSystems)) -0408 newModel.subSystems=[newModel.subSystems;cellfiller]; -0409 else -0410 newModel.subSystems=[newModel.subSystems;filler]; -0411 end -0412 end -0413 end -0414 if isfield(rxnsToAdd,'rxnMiriams') -0415 if numel(rxnsToAdd.rxnMiriams)~=nRxns -0416 EM='rxnsToAdd.rxnMiriams must have the same number of elements as rxnsToAdd.rxns'; -0417 error('RAVEN:badInput', '%s', EM); +0399 newModel.subSystems=[newModel.subSystems;rxnsToAdd.subSystems(:)]; +0400 else +0401 %Fill with standard if it does not exist +0402 if isfield(newModel,'subSystems') +0403 if any(cellfun(@(x) iscell(x), newModel.subSystems)) +0404 newModel.subSystems=[newModel.subSystems;cellfiller]; +0405 else +0406 newModel.subSystems=[newModel.subSystems;filler]; +0407 end +0408 end +0409 end +0410 if isfield(rxnsToAdd,'rxnMiriams') +0411 if numel(rxnsToAdd.rxnMiriams)~=nRxns +0412 EM='rxnsToAdd.rxnMiriams must have the same number of elements as rxnsToAdd.rxns'; +0413 error('RAVEN:badInput', '%s', EM); +0414 end +0415 %Fill with standard if it does not exist +0416 if ~isfield(newModel,'rxnMiriams') +0417 newModel.rxnMiriams=cell(nOldRxns,1); 0418 end -0419 %Fill with standard if it does not exist -0420 if ~isfield(newModel,'rxnMiriams') -0421 newModel.rxnMiriams=cell(nOldRxns,1); -0422 end -0423 newModel.rxnMiriams=[newModel.rxnMiriams;rxnsToAdd.rxnMiriams(:)]; -0424 else -0425 %Fill with standard if it does not exist -0426 if isfield(newModel,'rxnMiriams') -0427 newModel.rxnMiriams=[newModel.rxnMiriams;cell(nRxns,1)]; -0428 end -0429 end -0430 if isfield(rxnsToAdd,'rxnComps') -0431 if numel(rxnsToAdd.rxnComps)~=nRxns -0432 EM='rxnsToAdd.rxnComps must have the same number of elements as rxnsToAdd.rxns'; -0433 error('RAVEN:badInput', '%s', EM); -0434 end -0435 %Fill with standard if it does not exist -0436 if ~isfield(newModel,'rxnComps') -0437 newModel.rxnComps=ones(nOldRxns,1); -0438 EM='Adding reactions with compartment information to a model without such information. All existing reactions will be assigned to the first compartment'; -0439 warning('RAVEN:warning', '%s', EM); -0440 end -0441 newModel.rxnComps=[newModel.rxnComps;rxnsToAdd.rxnComps(:)]; -0442 else -0443 %Fill with standard if it does not exist -0444 if isfield(newModel,'rxnComps') -0445 newModel.rxnComps=[newModel.rxnComps;ones(nRxns,1)]; -0446 %fprintf("NOTE: The added reactions will be assigned to the first -0447 %compartment\n"); -0448 end -0449 end -0450 if isfield(rxnsToAdd,'grRules') -0451 rxnsToAdd.grRules=convertCharArray(rxnsToAdd.grRules); -0452 if numel(rxnsToAdd.grRules)~=nRxns -0453 EM='rxnsToAdd.grRules must have the same number of elements as rxnsToAdd.rxns'; -0454 error('RAVEN:badInput', '%s', EM); +0419 newModel.rxnMiriams=[newModel.rxnMiriams;rxnsToAdd.rxnMiriams(:)]; +0420 else +0421 %Fill with standard if it does not exist +0422 if isfield(newModel,'rxnMiriams') +0423 newModel.rxnMiriams=[newModel.rxnMiriams;cell(nRxns,1)]; +0424 end +0425 end +0426 if isfield(rxnsToAdd,'rxnComps') +0427 if numel(rxnsToAdd.rxnComps)~=nRxns +0428 EM='rxnsToAdd.rxnComps must have the same number of elements as rxnsToAdd.rxns'; +0429 error('RAVEN:badInput', '%s', EM); +0430 end +0431 %Fill with standard if it does not exist +0432 if ~isfield(newModel,'rxnComps') +0433 newModel.rxnComps=ones(nOldRxns,1); +0434 EM='Adding reactions with compartment information to a model without such information. All existing reactions will be assigned to the first compartment'; +0435 warning('RAVEN:warning', '%s', EM); +0436 end +0437 newModel.rxnComps=[newModel.rxnComps;rxnsToAdd.rxnComps(:)]; +0438 else +0439 %Fill with standard if it does not exist +0440 if isfield(newModel,'rxnComps') +0441 newModel.rxnComps=[newModel.rxnComps;ones(nRxns,1)]; +0442 %fprintf("NOTE: The added reactions will be assigned to the first +0443 %compartment\n"); +0444 end +0445 end +0446 if isfield(rxnsToAdd,'grRules') +0447 rxnsToAdd.grRules=convertCharArray(rxnsToAdd.grRules); +0448 if numel(rxnsToAdd.grRules)~=nRxns +0449 EM='rxnsToAdd.grRules must have the same number of elements as rxnsToAdd.rxns'; +0450 error('RAVEN:badInput', '%s', EM); +0451 end +0452 %Fill with standard if it does not exist +0453 if ~isfield(newModel,'grRules') +0454 newModel.grRules=largeFiller; 0455 end -0456 %Fill with standard if it does not exist -0457 if ~isfield(newModel,'grRules') -0458 newModel.grRules=largeFiller; -0459 end -0460 newModel.grRules=[newModel.grRules;rxnsToAdd.grRules(:)]; -0461 else -0462 %Fill with standard if it does not exist -0463 if isfield(newModel,'grRules') -0464 newModel.grRules=[newModel.grRules;filler]; -0465 end -0466 end -0467 -0468 if isfield(rxnsToAdd,'rxnFrom') -0469 rxnsToAdd.rxnFrom=convertCharArray(rxnsToAdd.rxnFrom); -0470 if numel(rxnsToAdd.rxnFrom)~=nRxns -0471 EM='rxnsToAdd.rxnFrom must have the same number of elements as rxnsToAdd.rxns'; -0472 error('RAVEN:badInput', '%s', EM); +0456 newModel.grRules=[newModel.grRules;rxnsToAdd.grRules(:)]; +0457 else +0458 %Fill with standard if it does not exist +0459 if isfield(newModel,'grRules') +0460 newModel.grRules=[newModel.grRules;filler]; +0461 end +0462 end +0463 +0464 if isfield(rxnsToAdd,'rxnFrom') +0465 rxnsToAdd.rxnFrom=convertCharArray(rxnsToAdd.rxnFrom); +0466 if numel(rxnsToAdd.rxnFrom)~=nRxns +0467 EM='rxnsToAdd.rxnFrom must have the same number of elements as rxnsToAdd.rxns'; +0468 error('RAVEN:badInput', '%s', EM); +0469 end +0470 %Fill with standard if it does not exist +0471 if ~isfield(newModel,'rxnFrom') +0472 newModel.rxnFrom=largeFiller; 0473 end -0474 %Fill with standard if it does not exist -0475 if ~isfield(newModel,'rxnFrom') -0476 newModel.rxnFrom=largeFiller; -0477 end -0478 newModel.rxnFrom=[newModel.rxnFrom;rxnsToAdd.rxnFrom(:)]; -0479 else -0480 %Fill with standard if it does not exist -0481 if isfield(newModel,'rxnFrom') -0482 newModel.rxnFrom=[newModel.rxnFrom;filler]; -0483 end -0484 end -0485 -0486 if isfield(rxnsToAdd,'rxnNotes') -0487 rxnsToAdd.rxnNotes=convertCharArray(rxnsToAdd.rxnNotes); -0488 if numel(rxnsToAdd.rxnNotes)~=nRxns -0489 EM='rxnsToAdd.rxnNotes must have the same number of elements as rxnsToAdd.rxns'; -0490 error('RAVEN:badInput', '%s', EM); +0474 newModel.rxnFrom=[newModel.rxnFrom;rxnsToAdd.rxnFrom(:)]; +0475 else +0476 %Fill with standard if it does not exist +0477 if isfield(newModel,'rxnFrom') +0478 newModel.rxnFrom=[newModel.rxnFrom;filler]; +0479 end +0480 end +0481 +0482 if isfield(rxnsToAdd,'rxnNotes') +0483 rxnsToAdd.rxnNotes=convertCharArray(rxnsToAdd.rxnNotes); +0484 if numel(rxnsToAdd.rxnNotes)~=nRxns +0485 EM='rxnsToAdd.rxnNotes must have the same number of elements as rxnsToAdd.rxns'; +0486 error('RAVEN:badInput', '%s', EM); +0487 end +0488 %Fill with standard if it does not exist +0489 if ~isfield(newModel,'rxnNotes') +0490 newModel.rxnNotes=largeFiller; 0491 end -0492 %Fill with standard if it does not exist -0493 if ~isfield(newModel,'rxnNotes') -0494 newModel.rxnNotes=largeFiller; -0495 end -0496 newModel.rxnNotes=[newModel.rxnNotes;rxnsToAdd.rxnNotes(:)]; -0497 else -0498 %Fill with standard if it does not exist -0499 if isfield(newModel,'rxnNotes') -0500 newModel.rxnNotes=[newModel.rxnNotes;filler]; -0501 end -0502 end -0503 -0504 if isfield(rxnsToAdd,'rxnReferences') -0505 rxnsToAdd.rxnReferences=convertCharArray(rxnsToAdd.rxnReferences); -0506 if numel(rxnsToAdd.rxnReferences)~=nRxns -0507 EM='rxnsToAdd.rxnReferences must have the same number of elements as rxnsToAdd.rxns'; -0508 error('RAVEN:badInput', '%s', EM); +0492 newModel.rxnNotes=[newModel.rxnNotes;rxnsToAdd.rxnNotes(:)]; +0493 else +0494 %Fill with standard if it does not exist +0495 if isfield(newModel,'rxnNotes') +0496 newModel.rxnNotes=[newModel.rxnNotes;filler]; +0497 end +0498 end +0499 +0500 if isfield(rxnsToAdd,'rxnReferences') +0501 rxnsToAdd.rxnReferences=convertCharArray(rxnsToAdd.rxnReferences); +0502 if numel(rxnsToAdd.rxnReferences)~=nRxns +0503 EM='rxnsToAdd.rxnReferences must have the same number of elements as rxnsToAdd.rxns'; +0504 error('RAVEN:badInput', '%s', EM); +0505 end +0506 %Fill with standard if it does not exist +0507 if ~isfield(newModel,'rxnReferences') +0508 newModel.rxnReferences=largeFiller; 0509 end -0510 %Fill with standard if it does not exist -0511 if ~isfield(newModel,'rxnReferences') -0512 newModel.rxnReferences=largeFiller; -0513 end -0514 newModel.rxnReferences=[newModel.rxnReferences;rxnsToAdd.rxnReferences(:)]; -0515 else -0516 %Fill with standard if it does not exist -0517 if isfield(newModel,'rxnReferences') -0518 newModel.rxnReferences=[newModel.rxnReferences;filler]; -0519 end -0520 end -0521 -0522 if isfield(rxnsToAdd,'pwys') -0523 rxnsToAdd.pwys=convertCharArray(rxnsToAdd.pwys); -0524 if numel(rxnsToAdd.pwys)~=nRxns -0525 EM='rxnsToAdd.pwys must have the same number of elements as rxnsToAdd.rxns'; -0526 error('RAVEN:badInput', '%s', EM); +0510 newModel.rxnReferences=[newModel.rxnReferences;rxnsToAdd.rxnReferences(:)]; +0511 else +0512 %Fill with standard if it does not exist +0513 if isfield(newModel,'rxnReferences') +0514 newModel.rxnReferences=[newModel.rxnReferences;filler]; +0515 end +0516 end +0517 +0518 if isfield(rxnsToAdd,'pwys') +0519 rxnsToAdd.pwys=convertCharArray(rxnsToAdd.pwys); +0520 if numel(rxnsToAdd.pwys)~=nRxns +0521 EM='rxnsToAdd.pwys must have the same number of elements as rxnsToAdd.rxns'; +0522 error('RAVEN:badInput', '%s', EM); +0523 end +0524 %Fill with standard if it does not exist +0525 if ~isfield(newModel,'pwys') +0526 newModel.pwys=largeFiller; 0527 end -0528 %Fill with standard if it does not exist -0529 if ~isfield(newModel,'pwys') -0530 newModel.pwys=largeFiller; -0531 end -0532 newModel.pwys=[newModel.pwys;rxnsToAdd.pwys(:)]; -0533 else -0534 %Fill with standard if it does not exist -0535 if isfield(newModel,'pwys') -0536 newModel.pwys=[newModel.pwys;filler]; -0537 end -0538 end -0539 -0540 if isfield(rxnsToAdd,'spontaneous') -0541 if numel(rxnsToAdd.spontaneous)~=nRxns -0542 EM='rxnsToAdd.spontaneous must have the same number of elements as rxnsToAdd.rxns'; -0543 error('RAVEN:badInput', '%s', EM); +0528 newModel.pwys=[newModel.pwys;rxnsToAdd.pwys(:)]; +0529 else +0530 %Fill with standard if it does not exist +0531 if isfield(newModel,'pwys') +0532 newModel.pwys=[newModel.pwys;filler]; +0533 end +0534 end +0535 +0536 if isfield(rxnsToAdd,'spontaneous') +0537 if numel(rxnsToAdd.spontaneous)~=nRxns +0538 EM='rxnsToAdd.spontaneous must have the same number of elements as rxnsToAdd.rxns'; +0539 error('RAVEN:badInput', '%s', EM); +0540 end +0541 %Fill with standard if it does not exist +0542 if ~isfield(newModel,'spontaneous') +0543 newModel.spontaneous=false(nOldRxns,1); 0544 end -0545 %Fill with standard if it does not exist -0546 if ~isfield(newModel,'spontaneous') -0547 newModel.spontaneous=false(nOldRxns,1); -0548 end -0549 newModel.spontaneous=[newModel.spontaneous;logical(rxnsToAdd.spontaneous(:))]; -0550 else -0551 %Fill with standard if it does not exist -0552 if isfield(newModel,'spontaneous') -0553 newModel.spontaneous=[newModel.spontaneous;false(nRxns,1)]; -0554 end -0555 end -0556 -0557 if isfield(rxnsToAdd,'rxnConfidenceScores') -0558 if numel(rxnsToAdd.rxnConfidenceScores)~=nRxns -0559 EM='rxnsToAdd.rxnConfidenceScores must have the same number of elements as rxnsToAdd.rxns'; -0560 error('RAVEN:badInput', '%s', EM); +0545 newModel.spontaneous=[newModel.spontaneous;logical(rxnsToAdd.spontaneous(:))]; +0546 else +0547 %Fill with standard if it does not exist +0548 if isfield(newModel,'spontaneous') +0549 newModel.spontaneous=[newModel.spontaneous;false(nRxns,1)]; +0550 end +0551 end +0552 +0553 if isfield(rxnsToAdd,'rxnConfidenceScores') +0554 if numel(rxnsToAdd.rxnConfidenceScores)~=nRxns +0555 EM='rxnsToAdd.rxnConfidenceScores must have the same number of elements as rxnsToAdd.rxns'; +0556 error('RAVEN:badInput', '%s', EM); +0557 end +0558 %Fill with standard if it does not exist +0559 if ~isfield(newModel,'rxnConfidenceScores') +0560 newModel.rxnConfidenceScores=NaN(nOldRxns,1); 0561 end -0562 %Fill with standard if it does not exist -0563 if ~isfield(newModel,'rxnConfidenceScores') -0564 newModel.rxnConfidenceScores=NaN(nOldRxns,1); -0565 end -0566 newModel.rxnConfidenceScores=[newModel.rxnConfidenceScores;rxnsToAdd.rxnConfidenceScores(:)]; -0567 else -0568 %Fill with standard if it does not exist -0569 if isfield(newModel,'rxnConfidenceScores') -0570 newModel.rxnConfidenceScores=[newModel.rxnConfidenceScores;NaN(nRxns,1)]; -0571 end -0572 end -0573 -0574 if isfield(rxnsToAdd,'rxnDeltaG') -0575 if numel(rxnsToAdd.rxnDeltaG)~=nRxns -0576 EM='rxnsToAdd.rxnDeltaG must have the same number of elements as rxnsToAdd.rxns'; -0577 error('RAVEN:badInput', '%s', EM); +0562 newModel.rxnConfidenceScores=[newModel.rxnConfidenceScores;rxnsToAdd.rxnConfidenceScores(:)]; +0563 else +0564 %Fill with standard if it does not exist +0565 if isfield(newModel,'rxnConfidenceScores') +0566 newModel.rxnConfidenceScores=[newModel.rxnConfidenceScores;NaN(nRxns,1)]; +0567 end +0568 end +0569 +0570 if isfield(rxnsToAdd,'rxnDeltaG') +0571 if numel(rxnsToAdd.rxnDeltaG)~=nRxns +0572 EM='rxnsToAdd.rxnDeltaG must have the same number of elements as rxnsToAdd.rxns'; +0573 error('RAVEN:badInput', '%s', EM); +0574 end +0575 %Fill with standard if it does not exist +0576 if ~isfield(newModel,'rxnDeltaG') +0577 newModel.rxnDeltaG=NaN(nOldRxns,1); 0578 end -0579 %Fill with standard if it does not exist -0580 if ~isfield(newModel,'rxnDeltaG') -0581 newModel.rxnDeltaG=NaN(nOldRxns,1); -0582 end -0583 newModel.rxnDeltaG=[newModel.rxnDeltaG;rxnsToAdd.rxnDeltaG(:)]; -0584 else -0585 %Fill with standard if it does not exist -0586 if isfield(newModel,'rxnDeltaG') -0587 newModel.rxnDeltaG=[newModel.rxnDeltaG;NaN(nRxns,1)]; -0588 end -0589 end -0590 -0591 -0592 %***Start parsing the equations and adding the info to the S matrix The -0593 %mets are matched to model.mets -0594 if eqnType==1 -0595 [I, J]=ismember(mets,model.mets); -0596 if ~all(I) -0597 if allowNewMets==true || ischar(allowNewMets) -0598 %Add the new mets -0599 metsToAdd.mets=mets(~I); -0600 metsToAdd.metNames=metsToAdd.mets; -0601 metsToAdd.compartments=compartment; -0602 if ischar(allowNewMets) -0603 newModel=addMets(newModel,metsToAdd,true,allowNewMets); -0604 else -0605 newModel=addMets(newModel,metsToAdd,true); -0606 end -0607 else -0608 EM='One or more equations contain metabolites that are not in model.mets. Set allowNewMets to true to allow this function to add metabolites or use addMets to add them before calling this function. Are you sure that eqnType=1?'; -0609 error('RAVEN:badInput', '%s', EM); -0610 end -0611 end -0612 %Calculate the indexes of the metabolites and add the info -0613 metIndexes=J; -0614 metIndexes(~I)=numel(newModel.mets)-sum(~I)+1:numel(newModel.mets); -0615 end -0616 -0617 %Do some stuff that is the same for eqnType=2 and eqnType=3 -0618 if eqnType==2 || eqnType==3 -0619 %For later.. -0620 t2=strcat(model.metNames,'***',model.comps(model.metComps)); -0621 end -0622 -0623 %The mets are matched to model.metNames and assigned to "compartment" -0624 if eqnType==2 -0625 %%Check that the metabolite names are not present in the same -0626 %%compartment. -0627 %Not the neatest way maybe.. -0628 t1=strcat(mets,'***',compartment); -0629 [I, J]=ismember(t1,t2); -0630 -0631 if ~all(I) -0632 if allowNewMets==true || ischar(allowNewMets) -0633 %Add the new mets -0634 metsToAdd.metNames=mets(~I); -0635 metsToAdd.compartments=compartment; -0636 if ischar(allowNewMets) -0637 newModel=addMets(newModel,metsToAdd,true,allowNewMets); -0638 else -0639 newModel=addMets(newModel,metsToAdd,true); -0640 end -0641 else -0642 EM='One or more equations contain metabolites that are not in model.mets. Set allowNewMets to true to allow this function to add metabolites or use addMets to add them before calling this function'; -0643 error('RAVEN:badInput', '%s', EM); -0644 end -0645 end -0646 -0647 %Calculate the indexes of the metabolites -0648 metIndexes=J; -0649 metIndexes(~I)=numel(newModel.mets)-sum(~I)+1:numel(newModel.mets); -0650 end -0651 -0652 %The equations are on the form metNames[compName] -0653 if eqnType==3 -0654 %Parse the metabolite names -0655 metNames=cell(numel(mets),1); -0656 compartments=metNames; -0657 for i=1:numel(mets) -0658 starts=max(strfind(mets{i},'[')); -0659 ends=max(strfind(mets{i},']')); -0660 -0661 %Check that the formatting is correct -0662 if isempty(starts) || isempty(ends) || ends<numel(mets{i}) -0663 EM=['The metabolite ' mets{i} ' is not correctly formatted for eqnType=3']; -0664 error('RAVEN:badInput', '%s', EM); -0665 end -0666 -0667 %Check that the compartment is correct -0668 compartments{i}=mets{i}(starts+1:ends-1); -0669 I=ismember(compartments(i),newModel.comps); -0670 if ~I -0671 EM=['The metabolite ' mets{i} ' has a compartment that is not in model.comps']; -0672 error('RAVEN:badInput', '%s', EM); -0673 end -0674 metNames{i}=mets{i}(1:starts-1); -0675 end +0579 newModel.rxnDeltaG=[newModel.rxnDeltaG;rxnsToAdd.rxnDeltaG(:)]; +0580 else +0581 %Fill with standard if it does not exist +0582 if isfield(newModel,'rxnDeltaG') +0583 newModel.rxnDeltaG=[newModel.rxnDeltaG;NaN(nRxns,1)]; +0584 end +0585 end +0586 +0587 +0588 %***Start parsing the equations and adding the info to the S matrix The +0589 %mets are matched to model.mets +0590 if eqnType==1 +0591 [I, J]=ismember(mets,model.mets); +0592 if ~all(I) +0593 if allowNewMets==true || ischar(allowNewMets) +0594 %Add the new mets +0595 metsToAdd.mets=mets(~I); +0596 metsToAdd.metNames=metsToAdd.mets; +0597 metsToAdd.compartments=compartment; +0598 if ischar(allowNewMets) +0599 newModel=addMets(newModel,metsToAdd,true,allowNewMets); +0600 else +0601 newModel=addMets(newModel,metsToAdd,true); +0602 end +0603 else +0604 EM='One or more equations contain metabolites that are not in model.mets. Set allowNewMets to true to allow this function to add metabolites or use addMets to add them before calling this function. Are you sure that eqnType=1?'; +0605 error('RAVEN:badInput', '%s', EM); +0606 end +0607 end +0608 %Calculate the indexes of the metabolites and add the info +0609 metIndexes=J; +0610 metIndexes(~I)=numel(newModel.mets)-sum(~I)+1:numel(newModel.mets); +0611 end +0612 +0613 %Do some stuff that is the same for eqnType=2 and eqnType=3 +0614 if eqnType==2 || eqnType==3 +0615 %For later.. +0616 t2=strcat(model.metNames,'***',model.comps(model.metComps)); +0617 end +0618 +0619 %The mets are matched to model.metNames and assigned to "compartment" +0620 if eqnType==2 +0621 %%Check that the metabolite names are not present in the same +0622 %%compartment. +0623 %Not the neatest way maybe.. +0624 t1=strcat(mets,'***',compartment); +0625 [I, J]=ismember(t1,t2); +0626 +0627 if ~all(I) +0628 if allowNewMets==true || ischar(allowNewMets) +0629 %Add the new mets +0630 metsToAdd.metNames=mets(~I); +0631 metsToAdd.compartments=compartment; +0632 if ischar(allowNewMets) +0633 newModel=addMets(newModel,metsToAdd,true,allowNewMets); +0634 else +0635 newModel=addMets(newModel,metsToAdd,true); +0636 end +0637 else +0638 EM='One or more equations contain metabolites that are not in model.mets. Set allowNewMets to true to allow this function to add metabolites or use addMets to add them before calling this function'; +0639 error('RAVEN:badInput', '%s', EM); +0640 end +0641 end +0642 +0643 %Calculate the indexes of the metabolites +0644 metIndexes=J; +0645 metIndexes(~I)=numel(newModel.mets)-sum(~I)+1:numel(newModel.mets); +0646 end +0647 +0648 %The equations are on the form metNames[compName] +0649 if eqnType==3 +0650 %Parse the metabolite names +0651 metNames=cell(numel(mets),1); +0652 compartments=metNames; +0653 for i=1:numel(mets) +0654 starts=max(strfind(mets{i},'[')); +0655 ends=max(strfind(mets{i},']')); +0656 +0657 %Check that the formatting is correct +0658 if isempty(starts) || isempty(ends) || ends<numel(mets{i}) +0659 EM=['The metabolite ' mets{i} ' is not correctly formatted for eqnType=3']; +0660 error('RAVEN:badInput', '%s', EM); +0661 end +0662 +0663 %Check that the compartment is correct +0664 compartments{i}=mets{i}(starts+1:ends-1); +0665 I=ismember(compartments(i),newModel.comps); +0666 if ~I +0667 EM=['The metabolite ' mets{i} ' has a compartment that is not in model.comps']; +0668 error('RAVEN:badInput', '%s', EM); +0669 end +0670 metNames{i}=mets{i}(1:starts-1); +0671 end +0672 +0673 %Check if the metabolite exists already +0674 t1=strcat(metNames,'***',compartments); +0675 [I, J]=ismember(t1,t2); 0676 -0677 %Check if the metabolite exists already -0678 t1=strcat(metNames,'***',compartments); -0679 [I, J]=ismember(t1,t2); -0680 -0681 if ~all(I) -0682 if allowNewMets==true | ischar(allowNewMets) -0683 %Add the new mets -0684 metsToAdd.metNames=metNames(~I); -0685 metsToAdd.compartments=compartments(~I); -0686 if ischar(allowNewMets) -0687 newModel=addMets(newModel,metsToAdd,true,allowNewMets); -0688 else -0689 newModel=addMets(newModel,metsToAdd,true); -0690 end -0691 else -0692 EM='One or more equations contain metabolites that are not in model.metNames. Set allowNewMets to true to allow this function to add metabolites or use addMets to add them before calling this function'; -0693 error('RAVEN:badInput', '%s', EM); -0694 end -0695 end -0696 -0697 %Calculate the indexes of the metabolites -0698 metIndexes=J; -0699 metIndexes(~I)=numel(newModel.mets)-sum(~I)+1:numel(newModel.mets); -0700 end -0701 -0702 %Add the info to the stoichiometric matrix -0703 newModel.S=[newModel.S sparse(size(newModel.S,1),nRxns)]; -0704 -0705 for i=1:nRxns -0706 newModel.S(metIndexes,nOldRxns+i)=S(:,i); -0707 %Parse the grRules and check whether all genes in grRules appear in -0708 %genes -0709 if isfield(newModel,'grRules') -0710 rule=newModel.grRules{nOldRxns+i}; -0711 rule=strrep(rule,'(',''); -0712 rule=strrep(rule,')',''); -0713 rule=strrep(rule,' or ',' '); -0714 rule=strrep(rule,' and ',' '); -0715 genes=regexp(rule,' ','split'); -0716 [I, J]=ismember(genes,newModel.genes); -0717 if ~all(I) && any(rule) -0718 EM=['Not all genes for reaction ' rxnsToAdd.rxns{i} ' were found in model.genes. If needed, add genes with addGenesRaven before calling this function, or set the ''allowNewGenes'' flag to true']; -0719 error('RAVEN:badInput', '%s', EM); -0720 end -0721 end -0722 end -0723 -0724 %Make temporary minimal model structure with only new rxns, to parse to -0725 %standardizeGrRules -0726 newRxnsModel.genes=newModel.genes; -0727 newRxnsModel.grRules=newModel.grRules(length(model.rxns)+1:end); -0728 newRxnsModel.rxns=newModel.rxns(length(model.rxns)+1:end); -0729 -0730 %Fix grRules and reconstruct rxnGeneMat -0731 [grRules,rxnGeneMat] = standardizeGrRules(newRxnsModel,true); -0732 newModel.rxnGeneMat = [newModel.rxnGeneMat; rxnGeneMat]; -0733 newModel.grRules = [newModel.grRules(1:nOldRxns); grRules]; -0734 -0735 if isfield(newModel,'equations') -0736 %Reuse the equations as given where available (preserves the caller's -0737 %own formatting); reconstruct them from the now-populated S otherwise, -0738 %e.g. when rxnsToAdd used mets/stoichCoeffs instead of equations. -0739 if isfield(rxnsToAdd,'equations') -0740 newEquations=convertCharArray(rxnsToAdd.equations); -0741 else -0742 newEquations=constructEquations(newModel,newModel.rxns(nOldRxns+1:end)); -0743 end -0744 newModel.equations=[newModel.equations;newEquations(:)]; -0745 end -0746 end

    +0677 if ~all(I) +0678 if allowNewMets==true | ischar(allowNewMets) +0679 %Add the new mets +0680 metsToAdd.metNames=metNames(~I); +0681 metsToAdd.compartments=compartments(~I); +0682 if ischar(allowNewMets) +0683 newModel=addMets(newModel,metsToAdd,true,allowNewMets); +0684 else +0685 newModel=addMets(newModel,metsToAdd,true); +0686 end +0687 else +0688 EM='One or more equations contain metabolites that are not in model.metNames. Set allowNewMets to true to allow this function to add metabolites or use addMets to add them before calling this function'; +0689 error('RAVEN:badInput', '%s', EM); +0690 end +0691 end +0692 +0693 %Calculate the indexes of the metabolites +0694 metIndexes=J; +0695 metIndexes(~I)=numel(newModel.mets)-sum(~I)+1:numel(newModel.mets); +0696 end +0697 +0698 %Add the info to the stoichiometric matrix +0699 newModel.S=[newModel.S sparse(size(newModel.S,1),nRxns)]; +0700 +0701 for i=1:nRxns +0702 newModel.S(metIndexes,nOldRxns+i)=S(:,i); +0703 %Parse the grRules and check whether all genes in grRules appear in +0704 %genes +0705 if isfield(newModel,'grRules') +0706 rule=newModel.grRules{nOldRxns+i}; +0707 rule=strrep(rule,'(',''); +0708 rule=strrep(rule,')',''); +0709 rule=strrep(rule,' or ',' '); +0710 rule=strrep(rule,' and ',' '); +0711 genes=regexp(rule,' ','split'); +0712 [I, J]=ismember(genes,newModel.genes); +0713 if ~all(I) && any(rule) +0714 EM=['Not all genes for reaction ' rxnsToAdd.rxns{i} ' were found in model.genes. If needed, add genes with addGenesRaven before calling this function, or set the ''allowNewGenes'' flag to true']; +0715 error('RAVEN:badInput', '%s', EM); +0716 end +0717 end +0718 end +0719 +0720 %Make temporary minimal model structure with only new rxns, to parse to +0721 %standardizeGrRules +0722 newRxnsModel.genes=newModel.genes; +0723 newRxnsModel.grRules=newModel.grRules(length(model.rxns)+1:end); +0724 newRxnsModel.rxns=newModel.rxns(length(model.rxns)+1:end); +0725 +0726 %Fix grRules and reconstruct rxnGeneMat +0727 [grRules,rxnGeneMat] = standardizeGrRules(newRxnsModel,true); +0728 newModel.rxnGeneMat = [newModel.rxnGeneMat; rxnGeneMat]; +0729 newModel.grRules = [newModel.grRules(1:nOldRxns); grRules]; +0730 +0731 if isfield(newModel,'equations') +0732 %Reuse the equations as given where available (preserves the caller's +0733 %own formatting); reconstruct them from the now-populated S otherwise, +0734 %e.g. when rxnsToAdd used mets/stoichCoeffs instead of equations. +0735 if isfield(rxnsToAdd,'equations') +0736 newEquations=convertCharArray(rxnsToAdd.equations); +0737 else +0738 newEquations=constructEquations(newModel,newModel.rxns(nOldRxns+1:end)); +0739 end +0740 newModel.equations=[newModel.equations;newEquations(:)]; +0741 end +0742 end

    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/manipulation/addRxnsGenesMets.html b/docs/html/manipulation/addRxnsGenesMets.html index 5868ae95..888ecdcf 100644 --- a/docs/html/manipulation/addRxnsGenesMets.html +++ b/docs/html/manipulation/addRxnsGenesMets.html @@ -29,24 +29,22 @@

    SYNOPSIS ^DESCRIPTION ^

     addRxnsGenesMets  Copy reactions from a source model into another model.
     
    - Copies reactions from a source model to a new model, including (new)
    - metabolites and genes.
    + Copies reactions from a source model to a draft model, together with their new
    + metabolites and genes. Reactions that are already in the draft model are skipped.
     
    - This function only works if the draft model and source model follow the
    - same metabolite and compartment naming convention. Metabolites are only
    - matched by metaboliteName[compartment]. Useful if one wants to copy
    - additional reactions from source to draft after getModelFromHomology was
    - used involving the same models.
    + The draft model and the source model must follow the same metabolite and compartment
    + naming convention, because metabolites are matched only by metaboliteName[compartment].
    + The function can copy additional reactions from a source model to a draft model that
    + getModelFromHomology created from the same models.
     
      Parameters
      ----------
      model : struct
    -     draft model where reactions should be copied to.
    +     the draft model that receives the reactions.
      sourceModel : struct
    -     model where reactions and metabolites are sourced from.
    +     the model that the reactions and metabolites are copied from.
      rxns : cell or char
    -     reaction IDs (from source model). Can also be a string if only one
    -     reaction is added.
    +     reaction ids from the source model. A string adds one reaction.
     
      Name-Value Arguments
      --------------------
    @@ -56,22 +54,21 @@ 

    DESCRIPTION ^SOURCE CODE ^
    0001 function model=addRxnsGenesMets(model,sourceModel,rxns,varargin)
     0002 % addRxnsGenesMets  Copy reactions from a source model into another model.
     0003 %
    -0004 % Copies reactions from a source model to a new model, including (new)
    -0005 % metabolites and genes.
    +0004 % Copies reactions from a source model to a draft model, together with their new
    +0005 % metabolites and genes. Reactions that are already in the draft model are skipped.
     0006 %
    -0007 % This function only works if the draft model and source model follow the
    -0008 % same metabolite and compartment naming convention. Metabolites are only
    -0009 % matched by metaboliteName[compartment]. Useful if one wants to copy
    -0010 % additional reactions from source to draft after getModelFromHomology was
    -0011 % used involving the same models.
    -0012 %
    -0013 % Parameters
    -0014 % ----------
    -0015 % model : struct
    -0016 %     draft model where reactions should be copied to.
    -0017 % sourceModel : struct
    -0018 %     model where reactions and metabolites are sourced from.
    -0019 % rxns : cell or char
    -0020 %     reaction IDs (from source model). Can also be a string if only one
    -0021 %     reaction is added.
    -0022 %
    -0023 % Name-Value Arguments
    -0024 % --------------------
    -0025 % addGene : logical or char or cell
    -0026 %     three options (default false):
    -0027 %
    -0028 %     - false : no genes are annotated to the new reactions
    -0029 %     - true : grRules are copied from the sourceModel and new genes are
    -0030 %       added when required
    -0031 %     - string or cell array : new grRules are specified as string or cell
    -0032 %       array, and any new genes are added when required
    -0033 % rxnNote : cell or char
    -0034 %     strings explaining why reactions were copied to the model, to be
    -0035 %     included as newModel.rxnNotes. Can also be a string if the same
    -0036 %     rxnNotes should be added for each new reaction, or only one reaction
    -0037 %     is to be added (default "Added via addRxnsAndMets()").
    -0038 % confidence : double
    -0039 %     integer specifying confidence score for all reactions, following
    -0040 %     doi:10.1038/nprot.2009.203 (default 0):
    -0041 %
    -0042 %     - 4 : biochemical data: direct evidence from enzyme assays
    -0043 %     - 3 : genetic data: knockout/-in or overexpression analysis
    -0044 %     - 2 : physiological data: indirect evidence, e.g. secretion products
    -0045 %       or defined medium requirement; sequence data: genome annotation
    -0046 %     - 1 : modeling data: required for functional model, hypothetical
    -0047 %       reaction
    -0048 %     - 0 : no evidence
    -0049 %
    -0050 % Returns
    -0051 % -------
    -0052 % model : struct
    -0053 %     an updated model structure.
    -0054 %
    -0055 % Examples
    -0056 % --------
    -0057 %     newModel = addRxnsGenesMets(model, sourceModel, rxns, addGene, ...
    -0058 %                                 rxnNote, confidence);
    -0059 
    -0060 p=parseRAVENargs(varargin, {'addGene',false; 'rxnNote',[]; 'confidence',0});
    -0061 confidence=p.confidence;
    -0062 rxns=convertCharArray(rxns);
    -0063 if isempty(p.rxnNote)
    -0064     rxnNote={'Added via addRxnsGenesMets()'};
    -0065 else
    -0066     rxnNote=convertCharArray(p.rxnNote);
    -0067 end
    -0068 if numel(rxnNote)==1 && numel(rxns)>1
    -0069     rxnNoteArray=cell(1,numel(rxns));
    -0070     rxnNoteArray(:)=rxnNote;
    -0071     rxnNote=rxnNoteArray;
    -0072 end
    -0073 addGene=p.addGene;
    -0074 
    -0075 % Obtain indexes of reactions in source model
    -0076 [notNewRxn,oldRxn]=ismember(rxns,model.rxns);
    -0077 rxns=rxns(~notNewRxn);
    -0078 if isempty(rxns)
    -0079     error('All reactions are already in the model.');
    -0080 elseif ~isempty(notNewRxn)
    -0081     fprintf('The following reactions were already present in the model and will not be added:\n')
    -0082     %Reaction ids are arbitrary text and may contain "%"; print as
    -0083     %literal data rather than as an fprintf format string.
    -0084     fprintf('%s\n', strjoin(model.rxns(oldRxn(find(oldRxn))),newline))
    -0085 end
    -0086 
    -0087 [~, rxnIdx]=ismember(rxns,sourceModel.rxns); % Get rxnIDs
    -0088 if any(rxnIdx==0)
    -0089     error('RAVEN:badInput', '%s', ravenList('The following reaction IDs could not be found in the source model:', rxns(rxnIdx==0)));
    -0090 end
    -0091 
    -0092 % Add new metabolites
    -0093 metIdx=find(any(sourceModel.S(:,rxnIdx),2)); % Get metabolite IDs
    -0094 % Many of the metabolites in are already in the draft model, so only add
    -0095 % the new metabolites
    -0096 
    -0097 % Match by metNames[metComps]. First make these structures for each model.
    -0098 metCompsN =cellstr(num2str(model.metComps));
    -0099 map=containers.Map(cellstr(num2str(transpose([1:length(model.comps)]))),model.comps);
    -0100 metCompsN = map.values(metCompsN);
    -0101 metCompsN = strcat(model.metNames,'[',metCompsN,']');
    -0102 
    -0103 sourcemetCompsN=cellstr(num2str(sourceModel.metComps));
    -0104 map=containers.Map(cellstr(num2str(transpose([1:length(sourceModel.comps)]))),sourceModel.comps);
    -0105 sourcemetCompsN = map.values(sourcemetCompsN);
    -0106 sourcemetCompsN=strcat(sourceModel.metNames,'[',sourcemetCompsN,']');
    +0007 % The draft model and the source model must follow the same metabolite and compartment
    +0008 % naming convention, because metabolites are matched only by metaboliteName[compartment].
    +0009 % The function can copy additional reactions from a source model to a draft model that
    +0010 % getModelFromHomology created from the same models.
    +0011 %
    +0012 % Parameters
    +0013 % ----------
    +0014 % model : struct
    +0015 %     the draft model that receives the reactions.
    +0016 % sourceModel : struct
    +0017 %     the model that the reactions and metabolites are copied from.
    +0018 % rxns : cell or char
    +0019 %     reaction ids from the source model. A string adds one reaction.
    +0020 %
    +0021 % Name-Value Arguments
    +0022 % --------------------
    +0023 % addGene : logical or char or cell
    +0024 %     three options (default false):
    +0025 %
    +0026 %     - false : no genes are annotated to the new reactions
    +0027 %     - true : grRules are copied from the sourceModel and new genes are
    +0028 %       added when required
    +0029 %     - string or cell array : the grRules of the new reactions, as a string or a
    +0030 %       cell array, and new genes are added when required
    +0031 % rxnNote : cell or char
    +0032 %     strings that explain why the reactions were copied, stored in rxnNotes of the new
    +0033 %     reactions. A string applies the same note to every new reaction
    +0034 %     (default "Added via addRxnsGenesMets()").
    +0035 % confidence : double
    +0036 %     integer confidence score for all new reactions, following
    +0037 %     doi:10.1038/nprot.2009.203 (default 0):
    +0038 %
    +0039 %     - 4 : biochemical data: direct evidence from enzyme assays
    +0040 %     - 3 : genetic data: knockout/-in or overexpression analysis
    +0041 %     - 2 : physiological data: indirect evidence, for example secretion products
    +0042 %       or defined medium requirement; sequence data: genome annotation
    +0043 %     - 1 : modelling data: required for a functional model, hypothetical
    +0044 %       reaction
    +0045 %     - 0 : no evidence
    +0046 %
    +0047 % Returns
    +0048 % -------
    +0049 % model : struct
    +0050 %     an updated model structure.
    +0051 %
    +0052 % Examples
    +0053 % --------
    +0054 %     newModel = addRxnsGenesMets(model, sourceModel, rxns, addGene, ...
    +0055 %                                 rxnNote, confidence);
    +0056 
    +0057 p=parseRAVENargs(varargin, {'addGene',false; 'rxnNote',[]; 'confidence',0});
    +0058 confidence=p.confidence;
    +0059 rxns=convertCharArray(rxns);
    +0060 if isempty(p.rxnNote)
    +0061     rxnNote={'Added via addRxnsGenesMets()'};
    +0062 else
    +0063     rxnNote=convertCharArray(p.rxnNote);
    +0064 end
    +0065 if numel(rxnNote)==1 && numel(rxns)>1
    +0066     rxnNoteArray=cell(1,numel(rxns));
    +0067     rxnNoteArray(:)=rxnNote;
    +0068     rxnNote=rxnNoteArray;
    +0069 end
    +0070 addGene=p.addGene;
    +0071 
    +0072 % Obtain indexes of reactions in source model
    +0073 [notNewRxn,oldRxn]=ismember(rxns,model.rxns);
    +0074 rxns=rxns(~notNewRxn);
    +0075 if isempty(rxns)
    +0076     error('All reactions are already in the model.');
    +0077 elseif ~isempty(notNewRxn)
    +0078     fprintf('The following reactions were already present in the model and will not be added:\n')
    +0079     %Reaction ids are arbitrary text and may contain "%"; print as
    +0080     %literal data rather than as an fprintf format string.
    +0081     fprintf('%s\n', strjoin(model.rxns(oldRxn(find(oldRxn))),newline))
    +0082 end
    +0083 
    +0084 [~, rxnIdx]=ismember(rxns,sourceModel.rxns); % Get rxnIDs
    +0085 if any(rxnIdx==0)
    +0086     error('RAVEN:badInput', '%s', ravenList('The following reaction IDs could not be found in the source model:', rxns(rxnIdx==0)));
    +0087 end
    +0088 
    +0089 % Add new metabolites
    +0090 metIdx=find(any(sourceModel.S(:,rxnIdx),2)); % Get metabolite IDs
    +0091 % Many of the metabolites in are already in the draft model, so only add
    +0092 % the new metabolites
    +0093 
    +0094 % Match by metNames[metComps]. First make these structures for each model.
    +0095 metCompsN =cellstr(num2str(model.metComps));
    +0096 map=containers.Map(cellstr(num2str(transpose([1:length(model.comps)]))),model.comps);
    +0097 metCompsN = map.values(metCompsN);
    +0098 metCompsN = strcat(model.metNames,'[',metCompsN,']');
    +0099 
    +0100 sourcemetCompsN=cellstr(num2str(sourceModel.metComps));
    +0101 map=containers.Map(cellstr(num2str(transpose([1:length(sourceModel.comps)]))),sourceModel.comps);
    +0102 sourcemetCompsN = map.values(sourcemetCompsN);
    +0103 sourcemetCompsN=strcat(sourceModel.metNames,'[',sourcemetCompsN,']');
    +0104 
    +0105 newMetCompsN=sourcemetCompsN(metIdx);
    +0106 notNewMet=newMetCompsN(ismember(newMetCompsN,metCompsN));
     0107 
    -0108 newMetCompsN=sourcemetCompsN(metIdx);
    -0109 notNewMet=newMetCompsN(ismember(newMetCompsN,metCompsN));
    -0110 
    -0111 if ~isempty(notNewMet)
    -0112     fprintf('\nThe following metabolites were already present in the model and will not be added:\n')
    -0113     %Metabolite names are arbitrary text and may contain "%"; print as
    -0114     %literal data rather than as an fprintf format string.
    -0115     fprintf('%s\n', strjoin(transpose(notNewMet),newline))
    -0116 end
    -0117 
    -0118 metIdx=metIdx(~ismember(sourcemetCompsN(metIdx),metCompsN));
    -0119 
    -0120 if ~isempty(metIdx)
    -0121     fprintf('\nThe following metabolites will be added to the model:\n')
    -0122     %Metabolite names are arbitrary text and may contain "%"; print as
    -0123     %literal data rather than as an fprintf format string.
    -0124     fprintf('%s\n', strjoin(transpose(sourcemetCompsN(metIdx)),newline))
    -0125     
    -0126     if isfield(sourceModel,'mets')
    -0127         metsToAdd.mets=sourceModel.mets(metIdx);
    +0108 if ~isempty(notNewMet)
    +0109     fprintf('\nThe following metabolites were already present in the model and will not be added:\n')
    +0110     %Metabolite names are arbitrary text and may contain "%"; print as
    +0111     %literal data rather than as an fprintf format string.
    +0112     fprintf('%s\n', strjoin(transpose(notNewMet),newline))
    +0113 end
    +0114 
    +0115 metIdx=metIdx(~ismember(sourcemetCompsN(metIdx),metCompsN));
    +0116 
    +0117 if ~isempty(metIdx)
    +0118     fprintf('\nThe following metabolites will be added to the model:\n')
    +0119     %Metabolite names are arbitrary text and may contain "%"; print as
    +0120     %literal data rather than as an fprintf format string.
    +0121     fprintf('%s\n', strjoin(transpose(sourcemetCompsN(metIdx)),newline))
    +0122     
    +0123     if isfield(sourceModel,'mets')
    +0124         metsToAdd.mets=sourceModel.mets(metIdx);
    +0125     end
    +0126     if isfield(sourceModel,'metNames')
    +0127         metsToAdd.metNames=sourceModel.metNames(metIdx);
     0128     end
    -0129     if isfield(sourceModel,'metNames')
    -0130         metsToAdd.metNames=sourceModel.metNames(metIdx);
    +0129     if isfield(sourceModel,'metFormulas')
    +0130         metsToAdd.metFormulas=sourceModel.metFormulas(metIdx);
     0131     end
    -0132     if isfield(sourceModel,'metFormulas')
    -0133         metsToAdd.metFormulas=sourceModel.metFormulas(metIdx);
    +0132     if isfield(sourceModel,'metCharges')
    +0133         metsToAdd.metCharges=sourceModel.metCharges(metIdx);
     0134     end
    -0135     if isfield(sourceModel,'metCharges')
    -0136         metsToAdd.metCharges=sourceModel.metCharges(metIdx);
    +0135     if isfield(sourceModel,'metMiriams')
    +0136         metsToAdd.metMiriams=sourceModel.metMiriams(metIdx);
     0137     end
    -0138     if isfield(sourceModel,'metMiriams')
    -0139         metsToAdd.metMiriams=sourceModel.metMiriams(metIdx);
    +0138     if isfield(sourceModel,'metNotes')
    +0139         metsToAdd.metNotes=sourceModel.metNotes(metIdx);
     0140     end
    -0141     if isfield(sourceModel,'metNotes')
    -0142         metsToAdd.metNotes=sourceModel.metNotes(metIdx);
    +0141     if isfield(sourceModel,'inchis')
    +0142         metsToAdd.inchis=sourceModel.inchis(metIdx);
     0143     end
    -0144     if isfield(sourceModel,'inchis')
    -0145         metsToAdd.inchis=sourceModel.inchis(metIdx);
    +0144     if isfield(sourceModel,'metSmiles')
    +0145         metsToAdd.metSmiles=sourceModel.metSmiles(metIdx);
     0146     end
    -0147     if isfield(sourceModel,'metSmiles')
    -0148         metsToAdd.metSmiles=sourceModel.metSmiles(metIdx);
    +0147     if isfield(sourceModel,'metDeltaG')
    +0148         metsToAdd.metDeltaG=sourceModel.metDeltaG(metIdx);
     0149     end
    -0150     if isfield(sourceModel,'metDeltaG')
    -0151         metsToAdd.metDeltaG=sourceModel.metDeltaG(metIdx);
    -0152     end
    -0153     
    -0154     metsToAdd.compartments=strtrim(cellstr(num2str(sourceModel.metComps(metIdx)))); % Convert from compartment string to compartment number
    -0155     [~,idx]=ismember(metsToAdd.compartments,strsplit(num2str(1:length(sourceModel.comps)))); % Match compartment number to compartment abbreviation
    -0156     metsToAdd.compartments=sourceModel.comps(idx); % Fill in compartment abbreviations
    -0157     
    -0158     model=addMets(model,metsToAdd);
    -0159 end
    -0160 fprintf('\nNumber of metabolites added to the model:\n')
    -0161 fprintf([num2str(numel(metIdx)),'\n'])
    -0162 
    -0163 % Add new genes
    -0164 if ~islogical(addGene) | addGene ~= false
    -0165     if ischar(addGene)
    -0166         rxnToAdd.grRules={addGene};
    -0167     elseif iscell(addGene)
    -0168         rxnToAdd.grRules=addGene(~notNewRxn); 
    -0169     else
    -0170         rxnToAdd.grRules=sourceModel.grRules(rxnIdx); % Get the relevant grRules
    -0171     end
    -0172 end
    -0173 % Add new reactions
    -0174 rxnToAdd.equations=constructEquations(sourceModel,rxnIdx);
    -0175 rxnToAdd.rxnNames=sourceModel.rxnNames(rxnIdx);
    -0176 rxnToAdd.rxns=sourceModel.rxns(rxnIdx);
    -0177 rxnToAdd.lb=sourceModel.lb(rxnIdx);
    -0178 rxnToAdd.ub=sourceModel.ub(rxnIdx);
    -0179 rxnToAdd.rxnNotes(:)=rxnNote(~notNewRxn);
    -0180 rxnToAdd.rxnConfidenceScores=NaN(1,numel(rxnToAdd.rxns));
    -0181 if ~isnumeric(confidence)
    -0182     EM='confidence score must be numeric';
    -0183     error('RAVEN:badInput', '%s', EM);
    -0184 end
    -0185 rxnToAdd.rxnConfidenceScores(:)=confidence;
    -0186 if isfield(sourceModel,'rxnDeltaG')
    -0187     rxnToAdd.rxnDeltaG=sourceModel.rxnDeltaG(rxnIdx);
    +0150     
    +0151     metsToAdd.compartments=strtrim(cellstr(num2str(sourceModel.metComps(metIdx)))); % Convert from compartment string to compartment number
    +0152     [~,idx]=ismember(metsToAdd.compartments,strsplit(num2str(1:length(sourceModel.comps)))); % Match compartment number to compartment abbreviation
    +0153     metsToAdd.compartments=sourceModel.comps(idx); % Fill in compartment abbreviations
    +0154     
    +0155     model=addMets(model,metsToAdd);
    +0156 end
    +0157 fprintf('\nNumber of metabolites added to the model:\n')
    +0158 fprintf([num2str(numel(metIdx)),'\n'])
    +0159 
    +0160 % Add new genes
    +0161 if ~islogical(addGene) | addGene ~= false
    +0162     if ischar(addGene)
    +0163         rxnToAdd.grRules={addGene};
    +0164     elseif iscell(addGene)
    +0165         rxnToAdd.grRules=addGene(~notNewRxn); 
    +0166     else
    +0167         rxnToAdd.grRules=sourceModel.grRules(rxnIdx); % Get the relevant grRules
    +0168     end
    +0169 end
    +0170 % Add new reactions
    +0171 rxnToAdd.equations=constructEquations(sourceModel,rxnIdx);
    +0172 rxnToAdd.rxnNames=sourceModel.rxnNames(rxnIdx);
    +0173 rxnToAdd.rxns=sourceModel.rxns(rxnIdx);
    +0174 rxnToAdd.lb=sourceModel.lb(rxnIdx);
    +0175 rxnToAdd.ub=sourceModel.ub(rxnIdx);
    +0176 rxnToAdd.rxnNotes(:)=rxnNote(~notNewRxn);
    +0177 rxnToAdd.rxnConfidenceScores=NaN(1,numel(rxnToAdd.rxns));
    +0178 if ~isnumeric(confidence)
    +0179     EM='confidence score must be numeric';
    +0180     error('RAVEN:badInput', '%s', EM);
    +0181 end
    +0182 rxnToAdd.rxnConfidenceScores(:)=confidence;
    +0183 if isfield(sourceModel,'rxnDeltaG')
    +0184     rxnToAdd.rxnDeltaG=sourceModel.rxnDeltaG(rxnIdx);
    +0185 end
    +0186 if isfield(sourceModel,'subSystems')
    +0187     rxnToAdd.subSystems=sourceModel.subSystems(rxnIdx);
     0188 end
    -0189 if isfield(sourceModel,'subSystems')
    -0190     rxnToAdd.subSystems=sourceModel.subSystems(rxnIdx);
    +0189 if isfield(sourceModel,'eccodes')
    +0190     rxnToAdd.eccodes=sourceModel.eccodes(rxnIdx);
     0191 end
    -0192 if isfield(sourceModel,'eccodes')
    -0193     rxnToAdd.eccodes=sourceModel.eccodes(rxnIdx);
    -0194 end
    -0195 model=addRxns(model,rxnToAdd,3,'',false,true);
    -0196 
    -0197 fprintf('\nNumber of reactions added to the model:\n')
    -0198 fprintf([num2str(numel(rxnIdx)),'\n'])
    -0199 end

    +0192 model=addRxns(model,rxnToAdd,3,'',false,true); +0193 +0194 fprintf('\nNumber of reactions added to the model:\n') +0195 fprintf([num2str(numel(rxnIdx)),'\n']) +0196 end

    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/manipulation/addTransport.html b/docs/html/manipulation/addTransport.html index c03de865..244b4bb2 100644 --- a/docs/html/manipulation/addTransport.html +++ b/docs/html/manipulation/addTransport.html @@ -29,20 +29,22 @@

    SYNOPSIS ^DESCRIPTION ^

     addTransport  Add transport reactions between compartments.
     
    - This is a faster version than addRxns when adding transport reactions.
    - New reaction names are formatted as "metaboliteName, fromComp-toComp",
    - while new reaction IDs are sequentially counted with a tr_ prefix:
    - e.g. tr_0001, tr_0002, etc.
    + The function builds the stoichiometric columns directly from the metabolite names,
    + without the equation parsing of addRxns, which makes it faster for adding transport
    + reactions. New reaction names
    + have the format "metaboliteName transport, fromCompName-toCompName", where the
    + compartment names are the entries of model.compNames. New reaction ids are numbered
    + sequentially with the prefix tr_ (tr_0001, tr_0002, ...).
     
      Parameters
      ----------
      model : struct
          a model structure.
      fromComp : char
    -     the id of the compartment to transport from (should match
    -     model.comps).
    +     the id of the compartment to transport from. Must match one entry of model.comps.
      toComps : cell
    -     compartment names to transport to (should match model.comps).
    +     the ids of the compartments to transport to. Each must match an entry of
    +     model.comps.
     
      Name-Value Arguments
      --------------------
    @@ -52,11 +54,11 @@ 

    DESCRIPTION ^SOURCE CODE ^
    0001 function [model, addedRxns]=addTransport(model,fromComp,toComps,varargin)
     0002 % addTransport  Add transport reactions between compartments.
     0003 %
    -0004 % This is a faster version than addRxns when adding transport reactions.
    -0005 % New reaction names are formatted as "metaboliteName, fromComp-toComp",
    -0006 % while new reaction IDs are sequentially counted with a tr_ prefix:
    -0007 % e.g. tr_0001, tr_0002, etc.
    -0008 %
    -0009 % Parameters
    -0010 % ----------
    -0011 % model : struct
    -0012 %     a model structure.
    -0013 % fromComp : char
    -0014 %     the id of the compartment to transport from (should match
    -0015 %     model.comps).
    -0016 % toComps : cell
    -0017 %     compartment names to transport to (should match model.comps).
    -0018 %
    -0019 % Name-Value Arguments
    -0020 % --------------------
    -0021 % metNames : cell
    -0022 %     the metabolite names to add transport for (default all metabolites
    -0023 %     in fromComp).
    -0024 % isRev : logical
    -0025 %     true if the transport reactions should be reversible (default true).
    -0026 % onlyToExisting : logical
    -0027 %     true if transport of a metabolite should only be added if it already
    -0028 %     exists in toComp. If false, then new metabolites are added with
    -0029 %     addMets first (default true).
    -0030 % prefix : char
    -0031 %     prefix to reaction IDs (default "tr_").
    -0032 %
    -0033 % Returns
    -0034 % -------
    -0035 % model : struct
    -0036 %     updated model structure.
    -0037 % addedRxns : cell
    -0038 %     ids of the added reactions.
    -0039 %
    -0040 % Examples
    -0041 % --------
    -0042 %     [model, addedRxns] = addTransport(model, fromComp, toComps, ...
    -0043 %                          metNames, isRev, onlyToExisting, prefix);
    -0044 
    -0045 fromComp=char(fromComp);
    -0046 [I, fromID]=ismember(model.comps,fromComp);
    -0047 fromID=find(fromID);
    -0048 if sum(I)~=1
    -0049     EM='fromComps must have exactly one match in model.comps';
    -0050     error('RAVEN:badInput', '%s', EM);
    -0051 end
    -0052 toComps=convertCharArray(toComps);
    -0053 [I, toIDs]=ismember(toComps,model.comps);
    -0054 if ~all(I)
    -0055     EM='All compartments in toComps must have a match in model.comps';
    -0056     error('RAVEN:badInput', '%s', EM);
    -0057 end
    -0058 p=parseRAVENargs(varargin, {'metNames',[]; 'isRev',true; 'onlyToExisting',true; 'prefix','tr_'});
    -0059 isRev=p.isRev;
    -0060 onlyToExisting=p.onlyToExisting;
    -0061 prefix=char(p.prefix);
    -0062 if isempty(p.metNames)
    -0063     %Find all metabolites in fromComp
    -0064     metNames=model.metNames(model.metComps==fromID);
    -0065 else
    -0066     metNames=convertCharArray(p.metNames);
    -0067 end
    -0068 %Normalise to a column vector regardless of the orientation the caller
    -0069 %passed metNames in (e.g. a literal {'a','b','c'} is a row): everything
    -0070 %derived from it below is concatenated against model fields, which are
    -0071 %always columns.
    -0072 metNames=metNames(:);
    -0073 
    -0074 %Check that the names are unique
    -0075 if numel(unique(metNames))~=numel(metNames)
    -0076     error('RAVEN:badInput', '%s', 'Not all metabolite names are unique');
    -0077 end
    -0078 
    -0079 %Get the indexes of the mets in fromComp
    -0080 I=find(model.metComps==fromID);
    -0081 [J, K]=ismember(metNames,model.metNames(I));
    -0082 if ~all(J)
    -0083     EM='Not all metabolites in metNames exist in fromComp';
    -0084     error('RAVEN:badInput', '%s', EM);
    -0085 end
    -0086 fromMets=I(K); %These are the ids of the metabolites to transport. The order corresponds to metNames
    -0087 addedRxns={};
    -0088 %Loop through and add for each compartment in toComps
    -0089 for i=1:numel(toComps)
    -0090     fromMetsInComp=fromMets; %If onlyToExisting==true then not all mets are transported to each compartment
    -0091     %Get the indexes of the mets in the compartment
    -0092     I=find(model.metComps==toIDs(i));
    -0093     [J, K]=ismember(metNames,model.metNames(I));
    -0094     if onlyToExisting==true || all(J)
    -0095         toMets=I(K(J)); %Only look at the existing ones
    -0096         fromMetsInComp=fromMetsInComp(J);
    -0097         namesInComp=metNames(J); %Matches fromMetsInComp/toMets below
    -0098     else
    -0099         %This is if not all metabolites exist in the target compartment,
    -0100         %and they should be added
    -0101         metsToAdd.metNames=metNames(J==0);
    -0102         metsToAdd.compartments=toComps{i};
    -0103         model=addMets(model,metsToAdd);
    -0104 
    -0105         %Redo the mapping when all mets are there. A bit lazy, but it is
    -0106         %fast anyways
    -0107         I=find(model.metComps==toIDs(i));
    -0108         [~, K]=ismember(metNames,model.metNames(I));
    -0109         toMets=I(K); %All are guaranteed to be found now
    -0110         namesInComp=metNames; %All requested metabolites now exist
    -0111     end
    -0112     
    -0113     %Construct the S matrix directly as sparse (indexed assignment on a
    -0114     %sparse matrix still overwrites rather than summing on a repeated
    -0115     %index, same as on a dense one): a dense (mets x newRxns) intermediate
    -0116     %is wasted allocation for a genome-scale model.
    -0117     nRxns=numel(fromMetsInComp);
    -0118     newS=sparse(numel(model.mets),nRxns);
    -0119     newS(sub2ind(size(newS),fromMetsInComp(:),(1:nRxns)'))=-1;
    -0120     newS(sub2ind(size(newS),toMets(:),(1:nRxns)'))=1;
    -0121 
    -0122     %Add the reactions
    -0123     model.S=[model.S newS];
    -0124     if isfield(model,'annotation') && isfield(model.annotation,'defaultLB')
    -0125         lb = model.annotation.defaultLB;
    -0126         ub = model.annotation.defaultUB;
    -0127     else
    -0128         lb = -inf;
    -0129         ub = inf;
    -0130     end
    -0131     if isRev==true
    -0132         model.lb=[model.lb;ones(nRxns,1)*lb];
    -0133         model.rev=[model.rev;ones(nRxns,1)];
    -0134     else
    -0135         model.lb=[model.lb;zeros(nRxns,1)];
    -0136         model.rev=[model.rev;zeros(nRxns,1)];
    -0137     end
    -0138     model.ub=[model.ub;ones(nRxns,1)*ub];
    -0139     model.c=[model.c;zeros(nRxns,1)];
    -0140     
    -0141     %Add annotation
    -0142     filler=cell(nRxns,1);
    -0143     filler(:)={''};
    -0144     addedRxnsID=generateNewIds(model,'rxns',prefix,nRxns);
    -0145     addedRxnsName=strcat(namesInComp, {' transport, '}, model.compNames(fromID), '-', model.compNames(toIDs(i)));
    -0146     addedRxnsName=addedRxnsName(:); %Defensive: keep column orientation regardless of namesInComp's
    -0147     model.rxns=[model.rxns;addedRxnsID];
    -0148     model.rxnNames=[model.rxnNames;addedRxnsName];
    -0149     
    -0150     if isfield(model,'eccodes')
    -0151         model.eccodes=[model.eccodes;filler];
    -0152     end
    -0153     if isfield(model,'subSystems')
    -0154         isNested = any(cellfun(@(x) iscell(x), model.subSystems));
    -0155         ssFiller = filler;
    -0156         if isRev==1
    -0157             ssFiller(:) = {['Transport between ' fromComp ' and ' toComps{i}]};
    -0158         else
    -0159             ssFiller(:) = {['Transport from ' fromComp ' to ' toComps{i}]};
    -0160         end
    -0161         if isNested
    -0162             ssFiller = cellfun(@(x) {x}, ssFiller, 'uni', 0);
    -0163         end
    -0164         model.subSystems=[model.subSystems;ssFiller];
    -0165     end
    -0166     if isfield(model,'grRules')
    -0167         model.grRules=[model.grRules;filler];
    -0168     end
    -0169     if isfield(model,'rxnFrom')
    -0170         model.rxnFrom=[model.rxnFrom;filler];
    -0171     end
    -0172     if isfield(model,'rxnMiriams')
    -0173         model.rxnMiriams=[model.rxnMiriams;cell(nRxns,1)];
    -0174     end
    -0175     if isfield(model,'rxnComps')
    -0176         model.rxnComps=[model.rxnComps;ones(nRxns,1)];
    -0177         fprintf('NOTE: The added transport reactions will be added to the first compartment\n');
    -0178     end
    -0179     if isfield(model,'rxnGeneMat')
    -0180         model.rxnGeneMat=[model.rxnGeneMat;sparse(nRxns,numel(model.genes))];
    -0181     end
    -0182     if isfield(model,'rxnNotes')
    -0183         model.rxnNotes=[model.rxnNotes;filler];
    -0184     end
    -0185     if isfield(model,'rxnReferences')
    -0186         model.rxnReferences=[model.rxnReferences;filler];
    -0187     end
    -0188     if isfield(model,'rxnConfidenceScores')
    -0189         model.rxnConfidenceScores=[model.rxnConfidenceScores;NaN(nRxns,1)];
    -0190     end
    -0191     if isfield(model,'rxnDeltaG')
    -0192         model.rxnDeltaG=[model.rxnDeltaG;NaN(nRxns,1)];
    -0193     end
    -0194     if isfield(model,'pwys')
    -0195         model.pwys=[model.pwys;filler];
    -0196     end
    -0197     if isfield(model,'spontaneous')
    -0198         model.spontaneous=[model.spontaneous;false(nRxns,1)];
    -0199     end
    -0200     addedRxns = [addedRxns; addedRxnsID];
    -0201 end
    -0202 end

    +0004 % The function builds the stoichiometric columns directly from the metabolite names, +0005 % without the equation parsing of addRxns, which makes it faster for adding transport +0006 % reactions. New reaction names +0007 % have the format "metaboliteName transport, fromCompName-toCompName", where the +0008 % compartment names are the entries of model.compNames. New reaction ids are numbered +0009 % sequentially with the prefix tr_ (tr_0001, tr_0002, ...). +0010 % +0011 % Parameters +0012 % ---------- +0013 % model : struct +0014 % a model structure. +0015 % fromComp : char +0016 % the id of the compartment to transport from. Must match one entry of model.comps. +0017 % toComps : cell +0018 % the ids of the compartments to transport to. Each must match an entry of +0019 % model.comps. +0020 % +0021 % Name-Value Arguments +0022 % -------------------- +0023 % metNames : cell +0024 % the metabolite names to add transport for (default all metabolites +0025 % in fromComp). +0026 % isRev : logical +0027 % true if the transport reactions should be reversible (default true). +0028 % onlyToExisting : logical +0029 % true to add transport of a metabolite only if the metabolite exists in the target +0030 % compartment. If false, the missing metabolites are first added with addMets +0031 % (default true). +0032 % prefix : char +0033 % prefix of the reaction ids (default "tr_"). +0034 % +0035 % Returns +0036 % ------- +0037 % model : struct +0038 % updated model structure. +0039 % addedRxns : cell +0040 % ids of the added reactions. +0041 % +0042 % Examples +0043 % -------- +0044 % [model, addedRxns] = addTransport(model, fromComp, toComps, ... +0045 % metNames, isRev, onlyToExisting, prefix); +0046 +0047 fromComp=char(fromComp); +0048 [I, fromID]=ismember(model.comps,fromComp); +0049 fromID=find(fromID); +0050 if sum(I)~=1 +0051 EM='fromComps must have exactly one match in model.comps'; +0052 error('RAVEN:badInput', '%s', EM); +0053 end +0054 toComps=convertCharArray(toComps); +0055 [I, toIDs]=ismember(toComps,model.comps); +0056 if ~all(I) +0057 EM='All compartments in toComps must have a match in model.comps'; +0058 error('RAVEN:badInput', '%s', EM); +0059 end +0060 p=parseRAVENargs(varargin, {'metNames',[]; 'isRev',true; 'onlyToExisting',true; 'prefix','tr_'}); +0061 isRev=p.isRev; +0062 onlyToExisting=p.onlyToExisting; +0063 prefix=char(p.prefix); +0064 if isempty(p.metNames) +0065 %Find all metabolites in fromComp +0066 metNames=model.metNames(model.metComps==fromID); +0067 else +0068 metNames=convertCharArray(p.metNames); +0069 end +0070 %Normalise to a column vector regardless of the orientation the caller +0071 %passed metNames in (e.g. a literal {'a','b','c'} is a row): everything +0072 %derived from it below is concatenated against model fields, which are +0073 %always columns. +0074 metNames=metNames(:); +0075 +0076 %Check that the names are unique +0077 if numel(unique(metNames))~=numel(metNames) +0078 error('RAVEN:badInput', '%s', 'Not all metabolite names are unique'); +0079 end +0080 +0081 %Get the indexes of the mets in fromComp +0082 I=find(model.metComps==fromID); +0083 [J, K]=ismember(metNames,model.metNames(I)); +0084 if ~all(J) +0085 EM='Not all metabolites in metNames exist in fromComp'; +0086 error('RAVEN:badInput', '%s', EM); +0087 end +0088 fromMets=I(K); %These are the ids of the metabolites to transport. The order corresponds to metNames +0089 addedRxns={}; +0090 %Loop through and add for each compartment in toComps +0091 for i=1:numel(toComps) +0092 fromMetsInComp=fromMets; %If onlyToExisting==true then not all mets are transported to each compartment +0093 %Get the indexes of the mets in the compartment +0094 I=find(model.metComps==toIDs(i)); +0095 [J, K]=ismember(metNames,model.metNames(I)); +0096 if onlyToExisting==true || all(J) +0097 toMets=I(K(J)); %Only look at the existing ones +0098 fromMetsInComp=fromMetsInComp(J); +0099 namesInComp=metNames(J); %Matches fromMetsInComp/toMets below +0100 else +0101 %This is if not all metabolites exist in the target compartment, +0102 %and they should be added +0103 metsToAdd.metNames=metNames(J==0); +0104 metsToAdd.compartments=toComps{i}; +0105 model=addMets(model,metsToAdd); +0106 +0107 %Redo the mapping when all mets are there. A bit lazy, but it is +0108 %fast anyways +0109 I=find(model.metComps==toIDs(i)); +0110 [~, K]=ismember(metNames,model.metNames(I)); +0111 toMets=I(K); %All are guaranteed to be found now +0112 namesInComp=metNames; %All requested metabolites now exist +0113 end +0114 +0115 %Construct the S matrix directly as sparse (indexed assignment on a +0116 %sparse matrix still overwrites rather than summing on a repeated +0117 %index, same as on a dense one): a dense (mets x newRxns) intermediate +0118 %is wasted allocation for a genome-scale model. +0119 nRxns=numel(fromMetsInComp); +0120 newS=sparse(numel(model.mets),nRxns); +0121 newS(sub2ind(size(newS),fromMetsInComp(:),(1:nRxns)'))=-1; +0122 newS(sub2ind(size(newS),toMets(:),(1:nRxns)'))=1; +0123 +0124 %Add the reactions +0125 model.S=[model.S newS]; +0126 if isfield(model,'annotation') && isfield(model.annotation,'defaultLB') +0127 lb = model.annotation.defaultLB; +0128 ub = model.annotation.defaultUB; +0129 else +0130 lb = -inf; +0131 ub = inf; +0132 end +0133 if isRev==true +0134 model.lb=[model.lb;ones(nRxns,1)*lb]; +0135 model.rev=[model.rev;ones(nRxns,1)]; +0136 else +0137 model.lb=[model.lb;zeros(nRxns,1)]; +0138 model.rev=[model.rev;zeros(nRxns,1)]; +0139 end +0140 model.ub=[model.ub;ones(nRxns,1)*ub]; +0141 model.c=[model.c;zeros(nRxns,1)]; +0142 +0143 %Add annotation +0144 filler=cell(nRxns,1); +0145 filler(:)={''}; +0146 addedRxnsID=generateNewIds(model,'rxns',prefix,nRxns); +0147 addedRxnsName=strcat(namesInComp, {' transport, '}, model.compNames(fromID), '-', model.compNames(toIDs(i))); +0148 addedRxnsName=addedRxnsName(:); %Defensive: keep column orientation regardless of namesInComp's +0149 model.rxns=[model.rxns;addedRxnsID]; +0150 model.rxnNames=[model.rxnNames;addedRxnsName]; +0151 +0152 if isfield(model,'eccodes') +0153 model.eccodes=[model.eccodes;filler]; +0154 end +0155 if isfield(model,'subSystems') +0156 isNested = any(cellfun(@(x) iscell(x), model.subSystems)); +0157 ssFiller = filler; +0158 if isRev==1 +0159 ssFiller(:) = {['Transport between ' fromComp ' and ' toComps{i}]}; +0160 else +0161 ssFiller(:) = {['Transport from ' fromComp ' to ' toComps{i}]}; +0162 end +0163 if isNested +0164 ssFiller = cellfun(@(x) {x}, ssFiller, 'uni', 0); +0165 end +0166 model.subSystems=[model.subSystems;ssFiller]; +0167 end +0168 if isfield(model,'grRules') +0169 model.grRules=[model.grRules;filler]; +0170 end +0171 if isfield(model,'rxnFrom') +0172 model.rxnFrom=[model.rxnFrom;filler]; +0173 end +0174 if isfield(model,'rxnMiriams') +0175 model.rxnMiriams=[model.rxnMiriams;cell(nRxns,1)]; +0176 end +0177 if isfield(model,'rxnComps') +0178 model.rxnComps=[model.rxnComps;ones(nRxns,1)]; +0179 fprintf('NOTE: The added transport reactions will be added to the first compartment\n'); +0180 end +0181 if isfield(model,'rxnGeneMat') +0182 model.rxnGeneMat=[model.rxnGeneMat;sparse(nRxns,numel(model.genes))]; +0183 end +0184 if isfield(model,'rxnNotes') +0185 model.rxnNotes=[model.rxnNotes;filler]; +0186 end +0187 if isfield(model,'rxnReferences') +0188 model.rxnReferences=[model.rxnReferences;filler]; +0189 end +0190 if isfield(model,'rxnConfidenceScores') +0191 model.rxnConfidenceScores=[model.rxnConfidenceScores;NaN(nRxns,1)]; +0192 end +0193 if isfield(model,'rxnDeltaG') +0194 model.rxnDeltaG=[model.rxnDeltaG;NaN(nRxns,1)]; +0195 end +0196 if isfield(model,'pwys') +0197 model.pwys=[model.pwys;filler]; +0198 end +0199 if isfield(model,'spontaneous') +0200 model.spontaneous=[model.spontaneous;false(nRxns,1)]; +0201 end +0202 addedRxns = [addedRxns; addedRxnsID]; +0203 end +0204 end

    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/manipulation/changeGrRules.html b/docs/html/manipulation/changeGrRules.html index 99ee3e9b..3032223e 100644 --- a/docs/html/manipulation/changeGrRules.html +++ b/docs/html/manipulation/changeGrRules.html @@ -4,7 +4,7 @@ Description of changeGrRules - + @@ -21,13 +21,15 @@

    changeGrRules

    PURPOSE ^

    -
    changeGrRules Change multiple grRules at the same time.
    +
    changeGrRules Change the grRules of multiple reactions.

    SYNOPSIS ^

    function model = changeGrRules(model,rxns,grRules,varargin)

    DESCRIPTION ^

    -
     changeGrRules  Change multiple grRules at the same time.
    +
     changeGrRules  Change the grRules of multiple reactions.
    +
    + Genes in grRules that are not in the model are added to the model.
     
      Parameters
      ----------
    @@ -36,16 +38,15 @@ 

    DESCRIPTION ^DESCRIPTION ^CROSS-REFERENCE INFORMATION ^

    This function calls: +
  • addGenesRaven addGenesRaven Add genes to a model.
  • standardizeGrRules standardizeGrRules Standardise gene-reaction rules in a model.
  • This function is called by:
    @@ -70,77 +71,78 @@

    CROSS-REFERENCE INFORMATION ^
 
 <h2><a name=SOURCE CODE ^

    0001 function model = changeGrRules(model,rxns,grRules,varargin)
    -0002 % changeGrRules  Change multiple grRules at the same time.
    +0002 % changeGrRules  Change the grRules of multiple reactions.
     0003 %
    -0004 % Parameters
    -0005 % ----------
    -0006 % model : struct
    -0007 %     a model structure to change the gene association.
    -0008 % rxns : char or cell
    -0009 %     reaction IDs.
    -0010 % grRules : char or cell
    -0011 %     additional or replacement gene association. Should be written with
    -0012 %     ' and ' to indicate subunits, ' or ' to indicate isoenzymes, and
    -0013 %     brackets '()' to separate different instances.
    -0014 %
    -0015 % Name-Value Arguments
    -0016 % --------------------
    -0017 % replace : logical
    -0018 %     true if old gene association should be replaced with new association.
    -0019 %     False if new gene association should be concatenated to the old
    -0020 %     association (default true).
    -0021 %
    -0022 % Returns
    -0023 % -------
    -0024 % model : struct
    -0025 %     an updated model structure.
    -0026 %
    -0027 % Examples
    -0028 % --------
    -0029 %     model = changeGrRules(model, rxns, grRules, replace);
    -0030 
    -0031 p=parseRAVENargs(varargin, {'replace',true});
    -0032 replace=p.replace;
    -0033 
    -0034 rxns=convertCharArray(rxns);
    -0035 grRules=convertCharArray(grRules);
    -0036 
    -0037 if isscalar(grRules) && ~isscalar(rxns)
    -0038     grRules = repmat(grRules,1,numel(rxns));
    -0039 end
    -0040 if ~(numel(grRules)==numel(rxns))
    -0041     error('Number of rxns and grRules should be identical')
    -0042 end
    -0043 
    -0044 % Add genes to model
    -0045 geneList = getGenesFromGrRules(grRules);
    -0046 genesToAdd.genes=setdiff(geneList,model.genes); % Only keep the genes that are not yet part of the model.genes.
    -0047 if ~isempty(genesToAdd.genes)
    -0048     model=addGenesRaven(model,genesToAdd); % Add genes
    -0049 end
    -0050     
    -0051 % Find reaction and gene indices
    -0052 idx=getIndexes(model,rxns,'rxns');
    -0053 
    -0054 % Change gene associations
    -0055 if replace==true % Replace old gene associations
    -0056     model.grRules(idx)=grRules;
    -0057 else % Add gene associations, add new gene rules after 'OR'.
    -0058     % A reaction with no existing grRule yet gets the new rule directly:
    -0059     % wrapping it as '() or (...)' regardless left an empty first clause,
    -0060     % a string neither grRuleToDNF nor isDnfGrRule can parse.
    -0061     existingRules=model.grRules(idx);
    -0062     hasExisting=~cellfun(@isempty,existingRules);
    -0063     combined=grRules;
    -0064     combined(hasExisting)=strcat('(',existingRules(hasExisting),') or (',grRules(hasExisting),')');
    -0065     model.grRules(idx)=combined;
    -0066 end
    -0067 
    -0068 %Fix grRules and reconstruct rxnGeneMat
    -0069 [grRules,rxnGeneMat] = standardizeGrRules(model,true);
    -0070 model.grRules = grRules;
    -0071 model.rxnGeneMat = rxnGeneMat;
    -0072 end
    +0004 % Genes in grRules that are not in the model are added to the model. +0005 % +0006 % Parameters +0007 % ---------- +0008 % model : struct +0009 % a model structure to change the gene association. +0010 % rxns : char or cell +0011 % reaction IDs. +0012 % grRules : char or cell +0013 % the replacement or additional gene association. ' and ' indicates subunits, ' or ' +0014 % indicates isoenzymes, and brackets '()' separate different instances. A single +0015 % rule applies to all reactions in rxns. +0016 % +0017 % Name-Value Arguments +0018 % -------------------- +0019 % replace : logical +0020 % true to replace the old gene association with the new association. False to +0021 % combine them as "(old) or (new)" (default true). +0022 % +0023 % Returns +0024 % ------- +0025 % model : struct +0026 % an updated model structure. +0027 % +0028 % Examples +0029 % -------- +0030 % model = changeGrRules(model, rxns, grRules, replace); +0031 +0032 p=parseRAVENargs(varargin, {'replace',true}); +0033 replace=p.replace; +0034 +0035 rxns=convertCharArray(rxns); +0036 grRules=convertCharArray(grRules); +0037 +0038 if isscalar(grRules) && ~isscalar(rxns) +0039 grRules = repmat(grRules,1,numel(rxns)); +0040 end +0041 if ~(numel(grRules)==numel(rxns)) +0042 error('Number of rxns and grRules should be identical') +0043 end +0044 +0045 % Add genes to model +0046 geneList = getGenesFromGrRules(grRules); +0047 genesToAdd.genes=setdiff(geneList,model.genes); % Only keep the genes that are not yet part of the model.genes. +0048 if ~isempty(genesToAdd.genes) +0049 model=addGenesRaven(model,genesToAdd); % Add genes +0050 end +0051 +0052 % Find reaction and gene indices +0053 idx=getIndexes(model,rxns,'rxns'); +0054 +0055 % Change gene associations +0056 if replace==true % Replace old gene associations +0057 model.grRules(idx)=grRules; +0058 else % Add gene associations, add new gene rules after 'OR'. +0059 % A reaction with no existing grRule yet gets the new rule directly: +0060 % wrapping it as '() or (...)' regardless left an empty first clause, +0061 % a string neither grRuleToDNF nor isDnfGrRule can parse. +0062 existingRules=model.grRules(idx); +0063 hasExisting=~cellfun(@isempty,existingRules); +0064 combined=grRules; +0065 combined(hasExisting)=strcat('(',existingRules(hasExisting),') or (',grRules(hasExisting),')'); +0066 model.grRules(idx)=combined; +0067 end +0068 +0069 %Fix grRules and reconstruct rxnGeneMat +0070 [grRules,rxnGeneMat] = standardizeGrRules(model,true); +0071 model.grRules = grRules; +0072 model.rxnGeneMat = rxnGeneMat; +0073 end

    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/manipulation/changeRxns.html b/docs/html/manipulation/changeRxns.html index a4b01fd5..9376d8c3 100644 --- a/docs/html/manipulation/changeRxns.html +++ b/docs/html/manipulation/changeRxns.html @@ -36,9 +36,8 @@

    DESCRIPTION ^DESCRIPTION ^DESCRIPTION ^CROSS-REFERENCE INFORMATION ^

    @@ -116,152 +110,146 @@

    SOURCE CODE ^% rxns : cell 0009 % cell array with reaction ids. 0010 % equations : cell or struct -0011 % cell array with equations. Alternatively, it can be a structure with -0012 % the fields "mets" and "stoichCoeffs", in the same fashion as addRxns. -0013 % E.g.: -0014 % -0015 % - equations.mets = {{"met1","met2"},{"met1","met3"}} -0016 % - equations.stoichCoeffs = {[-1,+2],[-1,+1]} -0017 % -0018 % Name-Value Arguments -0019 % -------------------- -0020 % eqnType : double -0021 % describes how the equation string should be interpreted (default 1): -0022 % -0023 % - 1 : the metabolites are matched to model.mets. New metabolites (if -0024 % allowed) are added to "compartment". -0025 % - 2 : the metabolites are matched to model.metNames and all -0026 % metabolites are assigned to "compartment". Any new metabolites that -0027 % are added will be assigned IDs "m1", "m2"... If IDs on the same form -0028 % are already used in the model then the numbering will start from the -0029 % highest used integer+1. -0030 % - 3 : the metabolites are written as "metNames[compNames]". Only -0031 % compartments in model.compNames are allowed. Any new metabolites -0032 % that are added will be assigned IDs "m1", "m2"... If IDs on the same -0033 % form are already used in the model then the numbering will start -0034 % from the highest used integer+1. -0035 % compartment : char -0036 % a string with the compartment the metabolites should be placed in when -0037 % using eqnType=2. Must match model.compNames (optional when eqnType=1 or -0038 % eqnType=3). -0039 % allowNewMets : logical -0040 % true if the function is allowed to add new metabolites. It is highly -0041 % recommended to first add any new metabolites with addMets rather than -0042 % automatically through this function. addMets supports more annotation -0043 % of metabolites, allows for the use of exchange metabolites, and using -0044 % it reduces the risk of parsing errors (default false). -0045 % -0046 % Returns -0047 % ------- -0048 % model : struct -0049 % an updated model structure. -0050 % -0051 % Examples -0052 % -------- -0053 % model = changeRxns(model, rxns, equations, eqnType, compartment, allowNewMets); -0054 % -0055 % Notes -0056 % ----- -0057 % This function should be used with some care, since it does not care about -0058 % bounds on the reactions. Changing an irreversible reaction to a reversible -0059 % one (or the other way around) will only change the model.rev field and not -0060 % the model.lb/model.ub fields. The reaction will therefore still be having -0061 % the same reversibility because of the bounds. Use setParams to change the -0062 % bounds. -0063 % -0064 % When adding metabolites to a compartment where it previously does not -0065 % exist, the function will copy any available information from the metabolite -0066 % in another compartment. -0067 -0068 p=parseRAVENargs(varargin, {'eqnType',1; 'compartment',[]; 'allowNewMets',false}); -0069 eqnType=p.eqnType; -0070 compartment=p.compartment; -0071 allowNewMets=p.allowNewMets; -0072 -0073 rxns=convertCharArray(rxns); -0074 compartment=char(compartment); -0075 -0076 %Find the indexes of the reactions and throw an error if they are not all -0077 %found -0078 [I, J]=ismember(rxns,model.rxns); -0079 if ~all(I) -0080 EM='All reaction ids must exist in the model'; -0081 error('RAVEN:badInput', '%s', EM); -0082 end -0083 -0084 %The reactions are changed in the following manner. First create a -0085 %rxns-structure by copying info from the model. Then remove the old -0086 %reactions. Then add the updated ones using addRxns. Lastly, the model is -0087 %reordered to match the original order. This is done like this to make use -0088 %of the advanced parsing of equations that addRxns use. -0089 rxnsToChange.rxns=rxns; -0090 if isfield(equations,'mets') && isfield(equations,'stoichCoeffs') -0091 rxnsToChange.mets=equations.mets; -0092 rxnsToChange.stoichCoeffs=equations.stoichCoeffs; -0093 else -0094 rxnsToChange.equations=convertCharArray(equations); +0011 % cell array with equations, or a structure with the fields "mets" and +0012 % "stoichCoeffs" as in addRxns, for example: +0013 % +0014 % - equations.mets = {{"met1","met2"},{"met1","met3"}} +0015 % - equations.stoichCoeffs = {[-1,+2],[-1,+1]} +0016 % +0017 % Name-Value Arguments +0018 % -------------------- +0019 % eqnType : double +0020 % the interpretation of the equation strings (default 1): +0021 % +0022 % - 1 : the metabolites are matched to model.mets. New metabolites (if +0023 % allowed) are added to "compartment". +0024 % - 2 : the metabolites are matched to model.metNames and all +0025 % metabolites are assigned to "compartment". New metabolites get +0026 % ids generated by generateNewIds with the prefix "m_", numbered from the +0027 % highest existing number with that prefix plus 1. +0028 % - 3 : the metabolites are written as "metNames[comps]". Only +0029 % compartments in model.comps are allowed. New metabolites get ids +0030 % generated in the same way as for eqnType 2. +0031 % compartment : char +0032 % the compartment of the metabolites for eqnType=2 and of new metabolites for +0033 % eqnType=1. Must match model.comps. Not used for eqnType=3. +0034 % allowNewMets : logical +0035 % true to allow the function to add new metabolites. The function calls addMets +0036 % with copyInfo true, so a new metabolite takes the charge and deltaG from a +0037 % metabolite with the same name in another compartment, and InChI, SMILES, +0038 % formula and MIRIAM annotation from it when its own are empty. When the +0039 % annotation of new metabolites is to be set explicitly, or when they are to be +0040 % exchange (unconstrained) metabolites, adding them with addMets first gives +0041 % that control (default false). +0042 % +0043 % Returns +0044 % ------- +0045 % model : struct +0046 % an updated model structure. +0047 % +0048 % Examples +0049 % -------- +0050 % model = changeRxns(model, rxns, equations, eqnType, compartment, allowNewMets); +0051 % +0052 % Notes +0053 % ----- +0054 % The function keeps the bounds of the reactions. Changing an irreversible reaction to a +0055 % reversible one, or the reverse, changes the model.rev field but not model.lb and +0056 % model.ub, so the bounds still define the reversibility of the reaction. setParam +0057 % changes the bounds. +0058 % +0059 % New metabolites receive the annotation of the metabolite with the same name in +0060 % another compartment, when one exists (see allowNewMets). +0061 +0062 p=parseRAVENargs(varargin, {'eqnType',1; 'compartment',[]; 'allowNewMets',false}); +0063 eqnType=p.eqnType; +0064 compartment=p.compartment; +0065 allowNewMets=p.allowNewMets; +0066 +0067 rxns=convertCharArray(rxns); +0068 compartment=char(compartment); +0069 +0070 %Find the indexes of the reactions and throw an error if they are not all +0071 %found +0072 [I, J]=ismember(rxns,model.rxns); +0073 if ~all(I) +0074 EM='All reaction ids must exist in the model'; +0075 error('RAVEN:badInput', '%s', EM); +0076 end +0077 +0078 %The reactions are changed in the following manner. First create a +0079 %rxns-structure by copying info from the model. Then remove the old +0080 %reactions. Then add the updated ones using addRxns. Lastly, the model is +0081 %reordered to match the original order. This is done like this to make use +0082 %of the advanced parsing of equations that addRxns use. +0083 rxnsToChange.rxns=rxns; +0084 if isfield(equations,'mets') && isfield(equations,'stoichCoeffs') +0085 rxnsToChange.mets=equations.mets; +0086 rxnsToChange.stoichCoeffs=equations.stoichCoeffs; +0087 else +0088 rxnsToChange.equations=convertCharArray(equations); +0089 end +0090 if isfield(model,'rxnNames') +0091 rxnsToChange.rxnNames=model.rxnNames(J); +0092 end +0093 if isfield(model,'lb') +0094 rxnsToChange.lb=model.lb(J); 0095 end -0096 if isfield(model,'rxnNames') -0097 rxnsToChange.rxnNames=model.rxnNames(J); +0096 if isfield(model,'ub') +0097 rxnsToChange.ub=model.ub(J); 0098 end -0099 if isfield(model,'lb') -0100 rxnsToChange.lb=model.lb(J); +0099 if isfield(model,'c') +0100 rxnsToChange.c=model.c(J); 0101 end -0102 if isfield(model,'ub') -0103 rxnsToChange.ub=model.ub(J); +0102 if isfield(model,'eccodes') +0103 rxnsToChange.eccodes=model.eccodes(J); 0104 end -0105 if isfield(model,'c') -0106 rxnsToChange.c=model.c(J); +0105 if isfield(model,'subSystems') +0106 rxnsToChange.subSystems=model.subSystems(J); 0107 end -0108 if isfield(model,'eccodes') -0109 rxnsToChange.eccodes=model.eccodes(J); +0108 if isfield(model,'rxnComps') +0109 rxnsToChange.rxnComps=model.rxnComps(J); 0110 end -0111 if isfield(model,'subSystems') -0112 rxnsToChange.subSystems=model.subSystems(J); +0111 if isfield(model,'grRules') +0112 rxnsToChange.grRules=model.grRules(J); 0113 end -0114 if isfield(model,'rxnComps') -0115 rxnsToChange.rxnComps=model.rxnComps(J); +0114 if isfield(model,'rxnFrom') +0115 rxnsToChange.rxnFrom=model.rxnFrom(J); 0116 end -0117 if isfield(model,'grRules') -0118 rxnsToChange.grRules=model.grRules(J); +0117 if isfield(model,'rxnScores') +0118 rxnsToChange.rxnScores=model.rxnScores(J); 0119 end -0120 if isfield(model,'rxnFrom') -0121 rxnsToChange.rxnFrom=model.rxnFrom(J); +0120 if isfield(model,'rxnMiriams') +0121 rxnsToChange.rxnMiriams=model.rxnMiriams(J); 0122 end -0123 if isfield(model,'rxnScores') -0124 rxnsToChange.rxnScores=model.rxnScores(J); +0123 if isfield(model,'rxnNotes') +0124 rxnsToChange.rxnNotes=model.rxnNotes(J); 0125 end -0126 if isfield(model,'rxnMiriams') -0127 rxnsToChange.rxnMiriams=model.rxnMiriams(J); +0126 if isfield(model,'rxnReferences') +0127 rxnsToChange.rxnReferences=model.rxnReferences(J); 0128 end -0129 if isfield(model,'rxnNotes') -0130 rxnsToChange.rxnNotes=model.rxnNotes(J); +0129 if isfield(model,'rxnConfidenceScores') +0130 rxnsToChange.rxnConfidenceScores=model.rxnConfidenceScores(J); 0131 end -0132 if isfield(model,'rxnReferences') -0133 rxnsToChange.rxnReferences=model.rxnReferences(J); +0132 if isfield(model,'rxnDeltaG') +0133 rxnsToChange.rxnDeltaG=model.rxnDeltaG(J); 0134 end -0135 if isfield(model,'rxnConfidenceScores') -0136 rxnsToChange.rxnConfidenceScores=model.rxnConfidenceScores(J); +0135 if isfield(model,'pwys') +0136 rxnsToChange.pwys=model.pwys(J); 0137 end -0138 if isfield(model,'rxnDeltaG') -0139 rxnsToChange.rxnDeltaG=model.rxnDeltaG(J); -0140 end -0141 if isfield(model,'pwys') -0142 rxnsToChange.pwys=model.pwys(J); -0143 end +0138 +0139 %Calculate the new order of reactions +0140 order=ones(numel(model.rxns),1); +0141 order(J)=0; +0142 order=cumsum(order); +0143 order(J)=order(end)+1:order(end)+numel(rxns); 0144 -0145 %Calculate the new order of reactions -0146 order=ones(numel(model.rxns),1); -0147 order(J)=0; -0148 order=cumsum(order); -0149 order(J)=order(end)+1:order(end)+numel(rxns); -0150 -0151 %Remove the original reactions -0152 model=removeReactions(model,rxns); -0153 -0154 model=addRxns(model,rxnsToChange,eqnType,compartment,allowNewMets); -0155 model=permuteModel(model,order,'rxns'); -0156 end

    +0145 %Remove the original reactions +0146 model=removeReactions(model,rxns); +0147 +0148 model=addRxns(model,rxnsToChange,eqnType,compartment,allowNewMets); +0149 model=permuteModel(model,order,'rxns'); +0150 end

    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/manipulation/closeModel.html b/docs/html/manipulation/closeModel.html index 7cf4196c..cfaee767 100644 --- a/docs/html/manipulation/closeModel.html +++ b/docs/html/manipulation/closeModel.html @@ -4,7 +4,7 @@ Description of closeModel - + @@ -21,15 +21,21 @@

    closeModel

    PURPOSE ^

    -
    closeModel Add boundary metabolites and their exchange reactions.
    +
    closeModel Add boundary metabolites to exchange reactions.

    SYNOPSIS ^

    function closedModel=closeModel(model)

    DESCRIPTION ^

    -
     closeModel  Add boundary metabolites and their exchange reactions.
    +
     closeModel  Add boundary metabolites to exchange reactions.
     
    - Adds boundary metabolites and their participation in exchange reactions.
    + Adds a compartment "b" (boundary) and a boundary metabolite in it for every reaction
    + that has metabolites on one side only (exchange, sink and demand reactions). The
    + boundary metabolite takes part in the reaction with the coefficient 1 or -1 and is
    + marked as unconstrained in model.unconstrained; all other metabolites are marked as
    + constrained. A reaction with one metabolite gets the boundary metabolite "<id>_b" with
    + the annotation of that metabolite; a reaction with several metabolites gets
    + "<reaction id>_b" without annotation.
     
      Parameters
      ----------
    @@ -59,143 +65,149 @@ 

    CROSS-REFERENCE INFORMATION ^
 
 <h2><a name=SOURCE CODE ^

    0001 function closedModel=closeModel(model)
    -0002 % closeModel  Add boundary metabolites and their exchange reactions.
    +0002 % closeModel  Add boundary metabolites to exchange reactions.
     0003 %
    -0004 % Adds boundary metabolites and their participation in exchange reactions.
    -0005 %
    -0006 % Parameters
    -0007 % ----------
    -0008 % model : struct
    -0009 %     a model structure.
    -0010 %
    -0011 % Returns
    -0012 % -------
    -0013 % closedModel : struct
    -0014 %     an updated model structure with boundary metabolites added.
    -0015 %
    -0016 % Examples
    -0017 % --------
    -0018 %     closedModel = closeModel(model);
    -0019 
    -0020 closedModel=model;
    -0021 
    -0022 closedModel.comps{numel(closedModel.comps)+1,1}='b';
    -0023 closedModel.compNames{numel(closedModel.compNames)+1,1}='boundary';
    -0024 if isfield(closedModel,'compMiriams')
    -0025     closedModel.compMiriams{numel(closedModel.compMiriams)+1,1}=[];
    -0026 end
    -0027 closedModel.unconstrained=zeros(numel(closedModel.mets),1);
    -0028 
    -0029 for i=1:numel(closedModel.rxns)
    -0030     col=closedModel.S(:,i);
    -0031     %A boundary/exchange/sink/demand reaction has metabolites on only one
    -0032     %side, same definition as getExchangeRxns: no substrates or no
    -0033     %products. The previous coefficient-magnitude rule (all nonzero
    -0034     %coefficients summing to 1 in absolute value) missed a scaled
    -0035     %single-metabolite sink such as "2 A =>" (sum 2) outright, and only
    -0036     %matched a multi-metabolite one-sided reaction such as
    -0037     %"0.5 A + 0.5 B =>" by coincidence, when its magnitudes summed to
    -0038     %exactly 1 --- and even then mishandled it below, since find() on such
    -0039     %a reaction returns more than one index and only the first metabolite
    -0040     %was ever used to build the boundary metabolite, silently dropping the
    -0041     %rest.
    -0042     if any(col>0) && any(col<0)
    -0043         continue
    -0044     end
    -0045     metIdx=find(col);
    -0046     if isempty(metIdx)
    -0047         continue
    -0048     end
    -0049     %A single metabolite names and inherits properties from that
    -0050     %metabolite, as before. Several metabolites on the one populated side
    -0051     %have no single metabolite to name the boundary after or copy
    -0052     %properties from, so it is named after the reaction instead and left
    -0053     %without those properties.
    -0054     oneMet=isscalar(metIdx);
    -0055     if oneMet
    -0056         newMetId=strcat(closedModel.mets{metIdx},'_b');
    -0057     else
    -0058         newMetId=strcat(closedModel.rxns{i},'_b');
    -0059     end
    -0060     closedModel.mets{numel(closedModel.mets)+1}=newMetId;
    -0061     if isfield(closedModel,'metNames')
    -0062         if oneMet
    -0063             closedModel.metNames{numel(closedModel.metNames)+1}=closedModel.metNames{metIdx};
    -0064         else
    -0065             closedModel.metNames{numel(closedModel.metNames)+1}=newMetId;
    -0066         end
    -0067     end
    -0068     closedModel.metComps(numel(closedModel.metComps)+1)=numel(closedModel.comps);
    -0069     if isfield(closedModel,'inchis')
    -0070         if oneMet
    -0071             closedModel.inchis{numel(closedModel.inchis)+1}=closedModel.inchis{metIdx};
    -0072         else
    -0073             closedModel.inchis{numel(closedModel.inchis)+1}='';
    -0074         end
    -0075     end
    -0076     if isfield(closedModel,'metSmiles')
    -0077         if oneMet
    -0078             closedModel.metSmiles{numel(closedModel.metSmiles)+1}=closedModel.metSmiles{metIdx};
    -0079         else
    -0080             closedModel.metSmiles{numel(closedModel.metSmiles)+1}='';
    -0081         end
    -0082     end
    -0083     if isfield(closedModel,'metFormulas')
    -0084         if oneMet
    -0085             closedModel.metFormulas{numel(closedModel.metFormulas)+1}=closedModel.metFormulas{metIdx};
    -0086         else
    -0087             closedModel.metFormulas{numel(closedModel.metFormulas)+1}='';
    -0088         end
    -0089     end
    -0090     if isfield(closedModel,'metMiriams')
    -0091         if oneMet
    -0092             closedModel.metMiriams{numel(closedModel.metMiriams)+1}=closedModel.metMiriams{metIdx};
    -0093         else
    -0094             closedModel.metMiriams{numel(closedModel.metMiriams)+1}=[];
    -0095         end
    -0096     end
    -0097     if isfield(closedModel,'metFrom')
    -0098         closedModel.metFrom{numel(closedModel.metFrom)+1}='';
    -0099     end
    -0100     if isfield(closedModel,'metNotes')
    -0101         if oneMet
    -0102             closedModel.metNotes{numel(closedModel.metNotes)+1}=closedModel.metNotes{metIdx};
    -0103         else
    -0104             closedModel.metNotes{numel(closedModel.metNotes)+1}='';
    -0105         end
    -0106     end
    -0107     if isfield(closedModel,'metCharges')
    -0108         if oneMet
    -0109             closedModel.metCharges(numel(closedModel.metCharges)+1)=closedModel.metCharges(metIdx);
    -0110         else
    -0111             closedModel.metCharges(numel(closedModel.metCharges)+1)=0;
    -0112         end
    -0113     end
    -0114     if isfield(closedModel,'metDeltaG')
    -0115         if oneMet
    -0116             closedModel.metDeltaG(numel(closedModel.metDeltaG)+1)=closedModel.metDeltaG(metIdx);
    -0117         else
    -0118             closedModel.metDeltaG(numel(closedModel.metDeltaG)+1)=NaN;
    -0119         end
    -0120     end
    -0121     closedModel.unconstrained(numel(closedModel.unconstrained)+1)=1;
    -0122     % (end+1,:), not numel(b)+1: a linear index past the end of an N-by-2
    -0123     % b (net-production bounds) does not append a row, it errors ("grow
    -0124     % array along ambiguous dimension").
    -0125     closedModel.b(end+1,:)=0;
    -0126     closedModel.S=[closedModel.S;sparse(1,size(closedModel.S,2))];
    -0127     %Any nonzero value blocks the reaction once its slack is pinned to 0 by
    -0128     %model.b above, regardless of magnitude; the sign is chosen only so the
    -0129     %boundary row reads the same way a human would expect (produced by vs.
    -0130     %consumed by the reaction).
    -0131     if sum(col)>0
    -0132         closedModel.S(numel(closedModel.mets),i)=-1;
    -0133     else
    -0134         closedModel.S(numel(closedModel.mets),i)=1;
    -0135     end
    -0136 end
    -0137 
    -0138 end
    +0004 % Adds a compartment "b" (boundary) and a boundary metabolite in it for every reaction +0005 % that has metabolites on one side only (exchange, sink and demand reactions). The +0006 % boundary metabolite takes part in the reaction with the coefficient 1 or -1 and is +0007 % marked as unconstrained in model.unconstrained; all other metabolites are marked as +0008 % constrained. A reaction with one metabolite gets the boundary metabolite "<id>_b" with +0009 % the annotation of that metabolite; a reaction with several metabolites gets +0010 % "<reaction id>_b" without annotation. +0011 % +0012 % Parameters +0013 % ---------- +0014 % model : struct +0015 % a model structure. +0016 % +0017 % Returns +0018 % ------- +0019 % closedModel : struct +0020 % an updated model structure with boundary metabolites added. +0021 % +0022 % Examples +0023 % -------- +0024 % closedModel = closeModel(model); +0025 +0026 closedModel=model; +0027 +0028 closedModel.comps{numel(closedModel.comps)+1,1}='b'; +0029 closedModel.compNames{numel(closedModel.compNames)+1,1}='boundary'; +0030 if isfield(closedModel,'compMiriams') +0031 closedModel.compMiriams{numel(closedModel.compMiriams)+1,1}=[]; +0032 end +0033 closedModel.unconstrained=zeros(numel(closedModel.mets),1); +0034 +0035 for i=1:numel(closedModel.rxns) +0036 col=closedModel.S(:,i); +0037 %A boundary/exchange/sink/demand reaction has metabolites on only one +0038 %side, same definition as getExchangeRxns: no substrates or no +0039 %products. The previous coefficient-magnitude rule (all nonzero +0040 %coefficients summing to 1 in absolute value) missed a scaled +0041 %single-metabolite sink such as "2 A =>" (sum 2) outright, and only +0042 %matched a multi-metabolite one-sided reaction such as +0043 %"0.5 A + 0.5 B =>" by coincidence, when its magnitudes summed to +0044 %exactly 1 --- and even then mishandled it below, since find() on such +0045 %a reaction returns more than one index and only the first metabolite +0046 %was ever used to build the boundary metabolite, silently dropping the +0047 %rest. +0048 if any(col>0) && any(col<0) +0049 continue +0050 end +0051 metIdx=find(col); +0052 if isempty(metIdx) +0053 continue +0054 end +0055 %A single metabolite names and inherits properties from that +0056 %metabolite, as before. Several metabolites on the one populated side +0057 %have no single metabolite to name the boundary after or copy +0058 %properties from, so it is named after the reaction instead and left +0059 %without those properties. +0060 oneMet=isscalar(metIdx); +0061 if oneMet +0062 newMetId=strcat(closedModel.mets{metIdx},'_b'); +0063 else +0064 newMetId=strcat(closedModel.rxns{i},'_b'); +0065 end +0066 closedModel.mets{numel(closedModel.mets)+1}=newMetId; +0067 if isfield(closedModel,'metNames') +0068 if oneMet +0069 closedModel.metNames{numel(closedModel.metNames)+1}=closedModel.metNames{metIdx}; +0070 else +0071 closedModel.metNames{numel(closedModel.metNames)+1}=newMetId; +0072 end +0073 end +0074 closedModel.metComps(numel(closedModel.metComps)+1)=numel(closedModel.comps); +0075 if isfield(closedModel,'inchis') +0076 if oneMet +0077 closedModel.inchis{numel(closedModel.inchis)+1}=closedModel.inchis{metIdx}; +0078 else +0079 closedModel.inchis{numel(closedModel.inchis)+1}=''; +0080 end +0081 end +0082 if isfield(closedModel,'metSmiles') +0083 if oneMet +0084 closedModel.metSmiles{numel(closedModel.metSmiles)+1}=closedModel.metSmiles{metIdx}; +0085 else +0086 closedModel.metSmiles{numel(closedModel.metSmiles)+1}=''; +0087 end +0088 end +0089 if isfield(closedModel,'metFormulas') +0090 if oneMet +0091 closedModel.metFormulas{numel(closedModel.metFormulas)+1}=closedModel.metFormulas{metIdx}; +0092 else +0093 closedModel.metFormulas{numel(closedModel.metFormulas)+1}=''; +0094 end +0095 end +0096 if isfield(closedModel,'metMiriams') +0097 if oneMet +0098 closedModel.metMiriams{numel(closedModel.metMiriams)+1}=closedModel.metMiriams{metIdx}; +0099 else +0100 closedModel.metMiriams{numel(closedModel.metMiriams)+1}=[]; +0101 end +0102 end +0103 if isfield(closedModel,'metFrom') +0104 closedModel.metFrom{numel(closedModel.metFrom)+1}=''; +0105 end +0106 if isfield(closedModel,'metNotes') +0107 if oneMet +0108 closedModel.metNotes{numel(closedModel.metNotes)+1}=closedModel.metNotes{metIdx}; +0109 else +0110 closedModel.metNotes{numel(closedModel.metNotes)+1}=''; +0111 end +0112 end +0113 if isfield(closedModel,'metCharges') +0114 if oneMet +0115 closedModel.metCharges(numel(closedModel.metCharges)+1)=closedModel.metCharges(metIdx); +0116 else +0117 closedModel.metCharges(numel(closedModel.metCharges)+1)=0; +0118 end +0119 end +0120 if isfield(closedModel,'metDeltaG') +0121 if oneMet +0122 closedModel.metDeltaG(numel(closedModel.metDeltaG)+1)=closedModel.metDeltaG(metIdx); +0123 else +0124 closedModel.metDeltaG(numel(closedModel.metDeltaG)+1)=NaN; +0125 end +0126 end +0127 closedModel.unconstrained(numel(closedModel.unconstrained)+1)=1; +0128 % (end+1,:), not numel(b)+1: a linear index past the end of an N-by-2 +0129 % b (net-production bounds) does not append a row, it errors ("grow +0130 % array along ambiguous dimension"). +0131 closedModel.b(end+1,:)=0; +0132 closedModel.S=[closedModel.S;sparse(1,size(closedModel.S,2))]; +0133 %Any nonzero value blocks the reaction once its slack is pinned to 0 by +0134 %model.b above, regardless of magnitude; the sign is chosen only so the +0135 %boundary row reads the same way a human would expect (produced by vs. +0136 %consumed by the reaction). +0137 if sum(col)>0 +0138 closedModel.S(numel(closedModel.mets),i)=-1; +0139 else +0140 closedModel.S(numel(closedModel.mets),i)=1; +0141 end +0142 end +0143 +0144 end

    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/manipulation/contractModel.html b/docs/html/manipulation/contractModel.html index 0e35eda2..b271ee38 100644 --- a/docs/html/manipulation/contractModel.html +++ b/docs/html/manipulation/contractModel.html @@ -4,7 +4,7 @@ Description of contractModel - + @@ -21,22 +21,22 @@

    contractModel

    PURPOSE ^

    -
    contractModel Contract a model by grouping all identical reactions.
    +
    contractModel Contract a model by merging identical reactions.

    SYNOPSIS ^

    function [reducedModel, removedRxns, indexedDuplicateRxns]=contractModel(model,varargin)

    DESCRIPTION ^

    -
     contractModel  Contract a model by grouping all identical reactions.
    +
     contractModel  Contract a model by merging identical reactions.
     
    - This is what simplifyModel's deleteDuplicates option runs; it is not a
    - separate implementation. If the duplicated reactions have "_EXP_*" suffixes
    - (where * is a digit), then the model is assumed to have been passed through
    - expandModel, and these suffixes are removed here.
    + simplifyModel runs contractModel for its deleteDuplicates option. If the duplicated
    + reactions have "_EXP_*" suffixes (where * is a digit), the model has passed through
    + expandModel, and the function removes these suffixes.
     
    - Reactions are merged, so their gene associations are combined rather than
    - discarded. Use findDuplicateRxns instead to see which reactions are
    - duplicates without changing the model.
    + The gene associations and EC numbers of merged reactions are combined. The merged
    + reaction keeps the widest bounds and the largest objective coefficient, and a warning
    + is given when the duplicates differ in these. findDuplicateRxns lists duplicate
    + reactions without changing the model.
     
      Parameters
      ----------
    @@ -46,22 +46,22 @@ 

    DESCRIPTION ^DESCRIPTION ^CROSS-REFERENCE INFORMATION ^

    This function calls:
      -
    • removeReactions removeReactions Delete a set of reactions from a model.
    • sortModel sortModel Standardize a model's internal ordering: can orient
    • standardizeGrRules standardizeGrRules Standardize gene-reaction rules in a model.
    +
  • removeReactions removeReactions Delete a set of reactions from a model.
  • sortModel sortModel Sort the metabolites and reactions of a model.
  • standardizeGrRules standardizeGrRules Standardise gene-reaction rules in a model.
  • This function is called by:
    • mergeCompartments mergeCompartments Merge all compartments in a model.
    • replaceMets replaceMets Replace a metabolite with another already in the model.
    • simplifyModel simplifyModel Simplify a model by deleting reactions and metabolites.
    @@ -88,16 +87,16 @@

    SUBFUNCTIONS ^SOURCE CODE ^

    0001 function [reducedModel, removedRxns, indexedDuplicateRxns]=contractModel(model,varargin)
    -0002 % contractModel  Contract a model by grouping all identical reactions.
    +0002 % contractModel  Contract a model by merging identical reactions.
     0003 %
    -0004 % This is what simplifyModel's deleteDuplicates option runs; it is not a
    -0005 % separate implementation. If the duplicated reactions have "_EXP_*" suffixes
    -0006 % (where * is a digit), then the model is assumed to have been passed through
    -0007 % expandModel, and these suffixes are removed here.
    -0008 %
    -0009 % Reactions are merged, so their gene associations are combined rather than
    -0010 % discarded. Use findDuplicateRxns instead to see which reactions are
    -0011 % duplicates without changing the model.
    +0004 % simplifyModel runs contractModel for its deleteDuplicates option. If the duplicated
    +0005 % reactions have "_EXP_*" suffixes (where * is a digit), the model has passed through
    +0006 % expandModel, and the function removes these suffixes.
    +0007 %
    +0008 % The gene associations and EC numbers of merged reactions are combined. The merged
    +0009 % reaction keeps the widest bounds and the largest objective coefficient, and a warning
    +0010 % is given when the duplicates differ in these. findDuplicateRxns lists duplicate
    +0011 % reactions without changing the model.
     0012 %
     0013 % Parameters
     0014 % ----------
    @@ -107,22 +106,22 @@ 

    SOURCE CODE ^% Name-Value Arguments 0019 % -------------------- 0020 % distReverse : logical -0021 % distinguish reactions with same metabolites but different reversibility -0022 % as different reactions (default true). +0021 % true to treat reactions with the same metabolites but different reversibility as +0022 % different reactions (default true). 0023 % mets : char or cell -0024 % string or cell array of strings with metabolite identifiers, whose -0025 % involved reactions should be checked for duplication (by default all -0026 % reactions are considered). This option is used by replaceMets. +0024 % metabolite ids as a string or a cell array of strings; only the reactions that +0025 % involve these metabolites are checked for duplication (default: all reactions). +0026 % replaceMets uses this option. 0027 % 0028 % Returns 0029 % ------- 0030 % reducedModel : struct 0031 % a model structure without duplicate reactions. 0032 % removedRxns : cell -0033 % cell array for the removed duplicate reactions. +0033 % cell array with the ids of the removed duplicate reactions. 0034 % indexedDuplicateRxns : cell -0035 % indexed cell array for the removed duplicate reactions (multiple values -0036 % separated by semicolon). +0035 % cell array with one entry per reaction of reducedModel, holding the ids of the +0036 % duplicates removed into that reaction (multiple ids separated by semicolon). 0037 % 0038 % Examples 0039 % -------- @@ -130,156 +129,155 @@

    SOURCE CODE ^% 0042 % Notes 0043 % ----- -0044 % This code might not work for advanced grRules strings that involve nested -0045 % expressions of "and" and "or". -0046 -0047 p=parseRAVENargs(varargin, {'distReverse',true; 'mets',[]}); -0048 distReverse=p.distReverse; -0049 mets=p.mets; -0050 -0051 %First sort the model so that reversible reactions are in the same -0052 %direction -0053 modelS=sortModel(model,true); -0054 -0055 %Get a list of duplicate reactions -0056 if distReverse -0057 x=[modelS.S; model.rev']'; -0058 else -0059 x=modelS.S'; -0060 end -0061 if ~isempty(mets) -0062 toCheck = getIndexes(model,mets,'mets'); -0063 [~, toCheck] = find(modelS.S(toCheck,:)); -0064 x = [x, (1:size(x,1))']; % Make all other rxns unique by additional column -0065 x(toCheck,end) = 0; -0066 end -0067 -0068 [~,I,J] = unique(x,'rows','first'); -0069 -0070 %Initialize cell array of indexedDuplicateRxns -0071 indexedDuplicateRxns=cell(numel(model.rxns),1); -0072 indexedDuplicateRxns(:)={''}; -0073 -0074 duplicateRxns=setdiff(1:numel(model.rxns),I); -0075 mergeTo=I(J(duplicateRxns)); -0076 -0077 mergedRxns=unique(mergeTo); -0078 -0079 %Now add all the info from this one. Print a warning if they have different -0080 %bounds or objective function coefficients. Uses the widest bounds and -0081 %largest magnitude of objective coefficient -0082 for i=1:numel(mergedRxns) -0083 duplRxn=transpose([mergedRxns(i),duplicateRxns(mergeTo==mergedRxns(i))]); -0084 if numel(unique(model.lb(duplRxn)))>1 -0085 EM=['Duplicates of reaction ' model.rxns{mergedRxns(i)} ' have different lower bound. Uses the most negative/smallest lower bound']; -0086 warning('RAVEN:warning', '%s', EM); -0087 model.lb(mergedRxns(i))=min(model.lb(duplRxn)); -0088 end -0089 if numel(unique(model.ub(duplRxn)))>1 -0090 EM=['Duplicates of reaction ' model.rxns{mergedRxns(i)} ' have different upper bound. Uses the most positive/largest upper bound']; -0091 warning('RAVEN:warning', '%s', EM); -0092 model.ub(mergedRxns(i))=max(model.ub(duplRxn)); -0093 end -0094 if numel(unique(model.c(duplRxn)))>1 -0095 EM=['Duplicates of reaction ' model.rxns{mergedRxns(i)} ' has a different objective function coefficient. Uses the largest coefficient']; -0096 warning('RAVEN:warning', '%s', EM); -0097 model.c(mergedRxns(i))=max(model.c(duplRxn)); -0098 end -0099 if isfield(model,'grRules') && any(~isempty(model.grRules(duplRxn))) -0100 rules=model.grRules(duplRxn); -0101 allRules={}; -0102 for j=1:numel(rules) -0103 rules{j}=ignoreORinComplex(rules{j}); -0104 allRules=[allRules regexp(rules{j},' or ','split')]; -0105 end -0106 allRules=unique(allRules); -0107 allRules=strrep(allRules,'__OR__',' or '); -0108 andRules=contains(allRules,' and '); -0109 allRules(andRules)=strcat('(',allRules(andRules),')'); -0110 if numel(allRules)==1 -0111 model.grRules{mergedRxns(i)}=allRules{1}; -0112 else -0113 model.grRules{mergedRxns(i)}=strjoin(allRules,' or '); -0114 end -0115 end -0116 if isfield(model,'eccodes') && any(~isempty(model.eccodes(duplRxn))) -0117 codes=model.eccodes(duplRxn); -0118 allCodes={}; -0119 for j=1:numel(codes) -0120 allCodes=[allCodes regexp(codes{j},';','split')]; -0121 end -0122 allCodes=unique(allCodes); -0123 if numel(allCodes)==1 -0124 model.eccodes{mergedRxns(i)}=allCodes{1}; -0125 else -0126 model.eccodes{mergedRxns(i)}=strjoin(allCodes,';'); -0127 end -0128 end -0129 %Generate indexedDuplicateRxns cell array -0130 if numel(duplRxn)==2 -0131 indexedDuplicateRxns{duplRxn(1)}=model.rxns{duplRxn(2)}; -0132 else -0133 indexedDuplicateRxns{duplRxn(1)}=strjoin(model.rxns(duplRxn(2:end)),';'); -0134 end -0135 %If all reactions have _EXP_* (where * is digit) as suffix, then the -0136 %model had passed through expandModel(), and these suffixes are removed. -0137 if all(~cellfun(@isempty,regexp(model.rxns(duplRxn),'_EXP_\d+$'))) -0138 model.rxns(duplRxn(1))=regexprep(model.rxns(duplRxn(1)),'_EXP_\d+$',''); -0139 end -0140 end -0141 -0142 %Delete the duplicate reactions -0143 reducedModel=removeReactions(model,duplicateRxns); -0144 removedRxns=model.rxns(duplicateRxns); -0145 [~, index]=ismember(reducedModel.rxns,model.rxns); -0146 indexedDuplicateRxns=indexedDuplicateRxns(index); -0147 -0148 if isfield(reducedModel,'rxnGeneMat') -0149 %Fix grRules and reconstruct rxnGeneMat -0150 [grRules,rxnGeneMat] = standardizeGrRules(reducedModel,true); -0151 reducedModel.grRules = grRules; -0152 reducedModel.rxnGeneMat = rxnGeneMat; +0044 % The function may not work for grRules strings with nested expressions of "and" and "or". +0045 +0046 p=parseRAVENargs(varargin, {'distReverse',true; 'mets',[]}); +0047 distReverse=p.distReverse; +0048 mets=p.mets; +0049 +0050 %First sort the model so that reversible reactions are in the same +0051 %direction +0052 modelS=sortModel(model,true); +0053 +0054 %Get a list of duplicate reactions +0055 if distReverse +0056 x=[modelS.S; model.rev']'; +0057 else +0058 x=modelS.S'; +0059 end +0060 if ~isempty(mets) +0061 toCheck = getIndexes(model,mets,'mets'); +0062 [~, toCheck] = find(modelS.S(toCheck,:)); +0063 x = [x, (1:size(x,1))']; % Make all other rxns unique by additional column +0064 x(toCheck,end) = 0; +0065 end +0066 +0067 [~,I,J] = unique(x,'rows','first'); +0068 +0069 %Initialize cell array of indexedDuplicateRxns +0070 indexedDuplicateRxns=cell(numel(model.rxns),1); +0071 indexedDuplicateRxns(:)={''}; +0072 +0073 duplicateRxns=setdiff(1:numel(model.rxns),I); +0074 mergeTo=I(J(duplicateRxns)); +0075 +0076 mergedRxns=unique(mergeTo); +0077 +0078 %Now add all the info from this one. Print a warning if they have different +0079 %bounds or objective function coefficients. Uses the widest bounds and +0080 %largest magnitude of objective coefficient +0081 for i=1:numel(mergedRxns) +0082 duplRxn=transpose([mergedRxns(i),duplicateRxns(mergeTo==mergedRxns(i))]); +0083 if numel(unique(model.lb(duplRxn)))>1 +0084 EM=['Duplicates of reaction ' model.rxns{mergedRxns(i)} ' have different lower bound. Uses the most negative/smallest lower bound']; +0085 warning('RAVEN:warning', '%s', EM); +0086 model.lb(mergedRxns(i))=min(model.lb(duplRxn)); +0087 end +0088 if numel(unique(model.ub(duplRxn)))>1 +0089 EM=['Duplicates of reaction ' model.rxns{mergedRxns(i)} ' have different upper bound. Uses the most positive/largest upper bound']; +0090 warning('RAVEN:warning', '%s', EM); +0091 model.ub(mergedRxns(i))=max(model.ub(duplRxn)); +0092 end +0093 if numel(unique(model.c(duplRxn)))>1 +0094 EM=['Duplicates of reaction ' model.rxns{mergedRxns(i)} ' has a different objective function coefficient. Uses the largest coefficient']; +0095 warning('RAVEN:warning', '%s', EM); +0096 model.c(mergedRxns(i))=max(model.c(duplRxn)); +0097 end +0098 if isfield(model,'grRules') && any(~isempty(model.grRules(duplRxn))) +0099 rules=model.grRules(duplRxn); +0100 allRules={}; +0101 for j=1:numel(rules) +0102 rules{j}=ignoreORinComplex(rules{j}); +0103 allRules=[allRules regexp(rules{j},' or ','split')]; +0104 end +0105 allRules=unique(allRules); +0106 allRules=strrep(allRules,'__OR__',' or '); +0107 andRules=contains(allRules,' and '); +0108 allRules(andRules)=strcat('(',allRules(andRules),')'); +0109 if numel(allRules)==1 +0110 model.grRules{mergedRxns(i)}=allRules{1}; +0111 else +0112 model.grRules{mergedRxns(i)}=strjoin(allRules,' or '); +0113 end +0114 end +0115 if isfield(model,'eccodes') && any(~isempty(model.eccodes(duplRxn))) +0116 codes=model.eccodes(duplRxn); +0117 allCodes={}; +0118 for j=1:numel(codes) +0119 allCodes=[allCodes regexp(codes{j},';','split')]; +0120 end +0121 allCodes=unique(allCodes); +0122 if numel(allCodes)==1 +0123 model.eccodes{mergedRxns(i)}=allCodes{1}; +0124 else +0125 model.eccodes{mergedRxns(i)}=strjoin(allCodes,';'); +0126 end +0127 end +0128 %Generate indexedDuplicateRxns cell array +0129 if numel(duplRxn)==2 +0130 indexedDuplicateRxns{duplRxn(1)}=model.rxns{duplRxn(2)}; +0131 else +0132 indexedDuplicateRxns{duplRxn(1)}=strjoin(model.rxns(duplRxn(2:end)),';'); +0133 end +0134 %If all reactions have _EXP_* (where * is digit) as suffix, then the +0135 %model had passed through expandModel(), and these suffixes are removed. +0136 if all(~cellfun(@isempty,regexp(model.rxns(duplRxn),'_EXP_\d+$'))) +0137 model.rxns(duplRxn(1))=regexprep(model.rxns(duplRxn(1)),'_EXP_\d+$',''); +0138 end +0139 end +0140 +0141 %Delete the duplicate reactions +0142 reducedModel=removeReactions(model,duplicateRxns); +0143 removedRxns=model.rxns(duplicateRxns); +0144 [~, index]=ismember(reducedModel.rxns,model.rxns); +0145 indexedDuplicateRxns=indexedDuplicateRxns(index); +0146 +0147 if isfield(reducedModel,'rxnGeneMat') +0148 %Fix grRules and reconstruct rxnGeneMat +0149 [grRules,rxnGeneMat] = standardizeGrRules(reducedModel,true); +0150 reducedModel.grRules = grRules; +0151 reducedModel.rxnGeneMat = rxnGeneMat; +0152 end 0153 end -0154 end -0155 -0156 function grRule = ignoreORinComplex(grRule) -0157 %In a grRule, if OR relationship is nested in an AND relationship, then -0158 %obfuscate the OR before splitting isoenzymes -0159 grRule=['(' grRule ')']; -0160 brOpen=strfind(grRule,'('); -0161 brClose=strfind(grRule,')'); -0162 andPos=strfind(grRule,' and '); -0163 %Find opening bracket closest before AND -0164 stillCapturing = 0; -0165 for i=1:numel(andPos) -0166 searchPos = andPos(i); -0167 while stillCapturing == 0 -0168 closestOpen = brOpen(max(find(brOpen<searchPos))); -0169 inbetweenClose = brClose(brClose<searchPos & brClose>closestOpen); -0170 if ~isempty(inbetweenClose) -0171 searchPos=max(inbetweenClose); -0172 else -0173 stillCapturing = 1; -0174 beginPos = closestOpen; -0175 end -0176 end -0177 stillCapturing = 0; -0178 searchPos = andPos(i); -0179 while stillCapturing == 0 -0180 closestClose = brClose(min(find(brClose>searchPos))); -0181 inbetweenOpen = brOpen(brOpen>searchPos & brOpen<closestOpen); -0182 if ~isempty(inbetweenOpen) -0183 searchPos=min(closestClose); -0184 else -0185 stillCapturing = 1; -0186 endPos = closestClose; -0187 end -0188 end -0189 replacePart=regexprep(grRule(beginPos:endPos),' or ','__OR__'); -0190 grRule=strrep(grRule,grRule(beginPos:endPos),replacePart); -0191 end -0192 grRule=grRule(2:end-1); -0193 end

    +0154 +0155 function grRule = ignoreORinComplex(grRule) +0156 %In a grRule, if OR relationship is nested in an AND relationship, then +0157 %obfuscate the OR before splitting isoenzymes +0158 grRule=['(' grRule ')']; +0159 brOpen=strfind(grRule,'('); +0160 brClose=strfind(grRule,')'); +0161 andPos=strfind(grRule,' and '); +0162 %Find opening bracket closest before AND +0163 stillCapturing = 0; +0164 for i=1:numel(andPos) +0165 searchPos = andPos(i); +0166 while stillCapturing == 0 +0167 closestOpen = brOpen(max(find(brOpen<searchPos))); +0168 inbetweenClose = brClose(brClose<searchPos & brClose>closestOpen); +0169 if ~isempty(inbetweenClose) +0170 searchPos=max(inbetweenClose); +0171 else +0172 stillCapturing = 1; +0173 beginPos = closestOpen; +0174 end +0175 end +0176 stillCapturing = 0; +0177 searchPos = andPos(i); +0178 while stillCapturing == 0 +0179 closestClose = brClose(min(find(brClose>searchPos))); +0180 inbetweenOpen = brOpen(brOpen>searchPos & brOpen<closestOpen); +0181 if ~isempty(inbetweenOpen) +0182 searchPos=min(closestClose); +0183 else +0184 stillCapturing = 1; +0185 endPos = closestClose; +0186 end +0187 end +0188 replacePart=regexprep(grRule(beginPos:endPos),' or ','__OR__'); +0189 grRule=strrep(grRule,grRule(beginPos:endPos),replacePart); +0190 end +0191 grRule=grRule(2:end-1); +0192 end

    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/manipulation/convertToIrrev.html b/docs/html/manipulation/convertToIrrev.html index 7cdedaed..7f1c33cc 100644 --- a/docs/html/manipulation/convertToIrrev.html +++ b/docs/html/manipulation/convertToIrrev.html @@ -30,8 +30,8 @@

    DESCRIPTION ^
     convertToIrrev  Convert a model to irreversible form.
     
      Reversible reactions are split into one forward and one reverse
    - reaction. The reverse reactions are saved as 'rxnID_REV'. A warning is
    - shown if some reaction identifiers already end with '_REV'.
    + reaction. The reverse reactions are named 'rxnID_REV'. A warning is
    + shown if some reaction ids already end with '_REV'.
     
      Parameters
      ----------
    @@ -41,7 +41,7 @@ 

    DESCRIPTION ^SOURCE CODE ^% convertToIrrev Convert a model to irreversible form. 0003 % 0004 % Reversible reactions are split into one forward and one reverse -0005 % reaction. The reverse reactions are saved as 'rxnID_REV'. A warning is -0006 % shown if some reaction identifiers already end with '_REV'. +0005 % reaction. The reverse reactions are named 'rxnID_REV'. A warning is +0006 % shown if some reaction ids already end with '_REV'. 0007 % 0008 % Parameters 0009 % ---------- @@ -88,7 +88,7 @@

    SOURCE CODE ^% Name-Value Arguments 0014 % -------------------- 0015 % rxns : cell -0016 % cell array with the reactions to split, if reversible (default +0016 % cell array with the ids of the reactions to split, if reversible (default 0017 % model.rxns). 0018 % 0019 % Returns diff --git a/docs/html/manipulation/copyToComps.html b/docs/html/manipulation/copyToComps.html index 5e8a2896..84e36749 100644 --- a/docs/html/manipulation/copyToComps.html +++ b/docs/html/manipulation/copyToComps.html @@ -34,26 +34,22 @@

    DESCRIPTION ^DESCRIPTION ^CROSS-REFERENCE INFORMATION ^

    @@ -89,122 +85,118 @@

    SOURCE CODE ^% model : struct 0007 % a model structure. 0008 % toComps : cell -0009 % cell array of compartment ids. If there is no match to model.comps -0010 % then it is added as a new compartment (see compNames and -0011 % compOutside). -0012 % -0013 % Name-Value Arguments -0014 % -------------------- -0015 % rxns : cell or logical or double -0016 % either a cell array of reaction IDs, a logical vector with the same -0017 % number of elements as reactions in the model, or a vector of indexes -0018 % to copy (default model.rxns). -0019 % deleteOriginal : logical -0020 % true if the original reactions should be removed, making it move the -0021 % reactions instead (default false). -0022 % compNames : cell -0023 % cell array of compartment names. Used if new compartments should be -0024 % added (default toComps). -0025 % compOutside : cell -0026 % cell array of the id (as in comps) for the compartment surrounding -0027 % each of the compartments. Used if new compartments should be added -0028 % (default all {''}). -0029 % -0030 % Returns -0031 % ------- -0032 % model : struct -0033 % an updated model structure. +0009 % cell array of compartment ids. An id that is not in model.comps is added as a +0010 % new compartment (see compNames and compOutside). +0011 % +0012 % Name-Value Arguments +0013 % -------------------- +0014 % rxns : cell or logical or double +0015 % a cell array of reaction ids, a logical vector with one element per reaction of +0016 % the model, or a vector of reaction indexes to copy (default model.rxns). +0017 % deleteOriginal : logical +0018 % true to remove the original reactions, which moves the reactions (default false). +0019 % compNames : cell +0020 % cell array of compartment names. Used when new compartments are added +0021 % (default toComps). +0022 % compOutside : cell +0023 % cell array with the id (as in comps) of the compartment that surrounds each new +0024 % compartment. Used when new compartments are added (default all {''}). +0025 % +0026 % Returns +0027 % ------- +0028 % model : struct +0029 % an updated model structure. +0030 % +0031 % Examples +0032 % -------- +0033 % model=copyToComps(model,toComps,rxns,deleteOriginal,compNames,compOutside); 0034 % -0035 % Examples -0036 % -------- -0037 % model=copyToComps(model,toComps,rxns,deleteOriginal,compNames,compOutside); -0038 % -0039 % Notes -0040 % ----- -0041 % New reactions and metabolites will be named as "id_toComps(i)". -0042 -0043 p=parseRAVENargs(varargin, {'rxns',[]; 'deleteOriginal',false; 'compNames',[]; 'compOutside',[]}); -0044 rxns=p.rxns; -0045 if isempty(rxns) -0046 rxns=model.rxns; -0047 elseif ~islogical(rxns) && ~isnumeric(rxns) -0048 rxns=convertCharArray(rxns); -0049 end -0050 deleteOriginal=p.deleteOriginal; -0051 compNames=p.compNames; -0052 if isempty(compNames) -0053 compNames=toComps; -0054 else -0055 compNames=convertCharArray(compNames); -0056 end -0057 compOutside=p.compOutside; -0058 if isempty(compOutside) -0059 % Default is {''} for every new compartment, not '': compOutside(i) -0060 % below indexes into it once per new compartment, which a bare '' -0061 % cannot support. -0062 compOutside=repmat({''},size(compNames)); -0063 else -0064 compOutside=convertCharArray(compOutside); -0065 if length(compOutside) ~= length(compNames) -0066 error('compOutside and compNames should be of equal size.'); -0067 end -0068 end -0069 -0070 originalID=model.id; -0071 originalName=model.name; -0072 -0073 rxns=getIndexes(model,rxns,'rxns'); -0074 -0075 for i=1:numel(toComps) -0076 %Check if the compartment exists, otherwise add it -0077 [I,J]=ismember(toComps(i),model.comps); -0078 if I==false -0079 model.comps=[model.comps;toComps(i)]; -0080 model.compNames=[model.compNames;compNames(i)]; -0081 if isfield(model,'compOutside') -0082 model.compOutside=[model.compOutside;compOutside(i)]; -0083 end -0084 if isfield(model,'compMiriams') -0085 model.compMiriams=[model.compMiriams;cell(1,1)]; -0086 end -0087 J=numel(model.comps); -0088 end -0089 %Copy the reactions by making a model structure with only them, then -0090 %change the localization, and finally merge with the original model -0091 modelToAdd=model; -0092 modelToAdd=removeReactions(modelToAdd,setdiff(1:numel(model.rxns),rxns),true,true); -0093 modelToAdd.rxns=strcat(modelToAdd.rxns,'_',toComps(i)); -0094 modelToAdd.mets=strcat(modelToAdd.mets,'_',toComps(i)); -0095 modelToAdd.comps=modelToAdd.comps(J); -0096 modelToAdd.compNames=modelToAdd.compNames(J); -0097 if isfield(modelToAdd,'compOutside') -0098 modelToAdd.compOutside=modelToAdd.compOutside(J); -0099 end -0100 if isfield(modelToAdd,'compMiriams') -0101 modelToAdd.compMiriams=modelToAdd.compMiriams(J); +0035 % Notes +0036 % ----- +0037 % New reactions and metabolites have the ids "id_toComps(i)". +0038 +0039 p=parseRAVENargs(varargin, {'rxns',[]; 'deleteOriginal',false; 'compNames',[]; 'compOutside',[]}); +0040 rxns=p.rxns; +0041 if isempty(rxns) +0042 rxns=model.rxns; +0043 elseif ~islogical(rxns) && ~isnumeric(rxns) +0044 rxns=convertCharArray(rxns); +0045 end +0046 deleteOriginal=p.deleteOriginal; +0047 compNames=p.compNames; +0048 if isempty(compNames) +0049 compNames=toComps; +0050 else +0051 compNames=convertCharArray(compNames); +0052 end +0053 compOutside=p.compOutside; +0054 if isempty(compOutside) +0055 % Default is {''} for every new compartment, not '': compOutside(i) +0056 % below indexes into it once per new compartment, which a bare '' +0057 % cannot support. +0058 compOutside=repmat({''},size(compNames)); +0059 else +0060 compOutside=convertCharArray(compOutside); +0061 if length(compOutside) ~= length(compNames) +0062 error('compOutside and compNames should be of equal size.'); +0063 end +0064 end +0065 +0066 originalID=model.id; +0067 originalName=model.name; +0068 +0069 rxns=getIndexes(model,rxns,'rxns'); +0070 +0071 for i=1:numel(toComps) +0072 %Check if the compartment exists, otherwise add it +0073 [I,J]=ismember(toComps(i),model.comps); +0074 if I==false +0075 model.comps=[model.comps;toComps(i)]; +0076 model.compNames=[model.compNames;compNames(i)]; +0077 if isfield(model,'compOutside') +0078 model.compOutside=[model.compOutside;compOutside(i)]; +0079 end +0080 if isfield(model,'compMiriams') +0081 model.compMiriams=[model.compMiriams;cell(1,1)]; +0082 end +0083 J=numel(model.comps); +0084 end +0085 %Copy the reactions by making a model structure with only them, then +0086 %change the localization, and finally merge with the original model +0087 modelToAdd=model; +0088 modelToAdd=removeReactions(modelToAdd,setdiff(1:numel(model.rxns),rxns),true,true); +0089 modelToAdd.rxns=strcat(modelToAdd.rxns,'_',toComps(i)); +0090 modelToAdd.mets=strcat(modelToAdd.mets,'_',toComps(i)); +0091 modelToAdd.comps=modelToAdd.comps(J); +0092 modelToAdd.compNames=modelToAdd.compNames(J); +0093 if isfield(modelToAdd,'compOutside') +0094 modelToAdd.compOutside=modelToAdd.compOutside(J); +0095 end +0096 if isfield(modelToAdd,'compMiriams') +0097 modelToAdd.compMiriams=modelToAdd.compMiriams(J); +0098 end +0099 modelToAdd.metComps=ones(numel(modelToAdd.mets),1); +0100 if isfield(modelToAdd,'metFrom') +0101 modelToAdd = rmfield(modelToAdd,'metFrom'); 0102 end -0103 modelToAdd.metComps=ones(numel(modelToAdd.mets),1); -0104 if isfield(modelToAdd,'metFrom') -0105 modelToAdd = rmfield(modelToAdd,'metFrom'); -0106 end -0107 if isfield(modelToAdd,'rxnFrom') -0108 modelToAdd = rmfield(modelToAdd,'rxnFrom'); -0109 end -0110 if isfield(modelToAdd,'geneFrom') -0111 modelToAdd = rmfield(modelToAdd,'geneFrom'); -0112 end -0113 -0114 %Merge the models -0115 model=mergeModels({model;modelToAdd},'metNames',[],true); +0103 if isfield(modelToAdd,'rxnFrom') +0104 modelToAdd = rmfield(modelToAdd,'rxnFrom'); +0105 end +0106 if isfield(modelToAdd,'geneFrom') +0107 modelToAdd = rmfield(modelToAdd,'geneFrom'); +0108 end +0109 +0110 %Merge the models +0111 model=mergeModels({model;modelToAdd},'metNames',[],true); +0112 end +0113 +0114 if deleteOriginal==true +0115 model=removeReactions(model,rxns,true,true,true); %Also delete unused compartments 0116 end 0117 -0118 if deleteOriginal==true -0119 model=removeReactions(model,rxns,true,true,true); %Also delete unused compartments -0120 end -0121 -0122 model.id=originalID; -0123 model.name=originalName; -0124 end

    +0118 model.id=originalID; +0119 model.name=originalName; +0120 end

    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/manipulation/expandModel.html b/docs/html/manipulation/expandModel.html index c6974b0a..6f6371aa 100644 --- a/docs/html/manipulation/expandModel.html +++ b/docs/html/manipulation/expandModel.html @@ -29,8 +29,8 @@

    SYNOPSIS ^DESCRIPTION ^

     expandModel  Expand reactions that use several gene associations.
     
    - Each reaction that uses several gene associations is split into several
    - reactions, each under the control of only one gene.
    + Each reaction with several gene associations (isozymes) is split into one reaction per
    + association.
     
      Parameters
      ----------
    @@ -40,12 +40,12 @@ 

    DESCRIPTION ^DESCRIPTION ^CROSS-REFERENCE INFORMATION ^

    This function calls: +
  • standardizeGrRules standardizeGrRules Standardise gene-reaction rules in a model.
  • This function is called by:
    @@ -76,8 +76,8 @@

    SOURCE CODE ^
    0001 function [newModel, rxnToCheck]=expandModel(model)
     0002 % expandModel  Expand reactions that use several gene associations.
     0003 %
    -0004 % Each reaction that uses several gene associations is split into several
    -0005 % reactions, each under the control of only one gene.
    +0004 % Each reaction with several gene associations (isozymes) is split into one reaction per
    +0005 % association.
     0006 %
     0007 % Parameters
     0008 % ----------
    @@ -87,12 +87,12 @@ 

    SOURCE CODE ^% Returns 0013 % ------- 0014 % newModel : struct -0015 % model structure with separate reactions for iso-enzymes, where the -0016 % reaction ids are renamed as id_EXP_1, id_EXP_2, etc. +0015 % model structure with separate reactions for isozymes. The ids of split reactions +0016 % are id_EXP_1, id_EXP_2 and so on, numbered per isozyme. 0017 % rxnToCheck : cell -0018 % cell array with original reaction identifiers for those whose grRule -0019 % was not in disjunctive normal form, and so had to be expanded by -0020 % distributivity, plus any whose grRule could not be parsed. +0018 % cell array with the original ids of the reactions whose grRule is not in +0019 % disjunctive normal form and was expanded by distributivity, and of the reactions +0020 % whose grRule cannot be parsed and is left unchanged. 0021 % 0022 % Examples 0023 % -------- @@ -101,11 +101,11 @@

    SOURCE CODE ^% Notes 0027 % ----- 0028 % The isozymes are the AND-clauses of the grRule in disjunctive normal form, -0029 % so nested expressions are expanded correctly: "g1 and (g2 or g3)" yields -0030 % "g1 and g2" and "g1 and g3". Reactions listed in rxnToCheck are expanded -0031 % correctly too; they are reported only because a rule needing -0032 % distributivity is often a sign that the GPR was not what its author -0033 % intended. Use standardizeGrRules/findPotentialErrors to inspect them. +0029 % so nested expressions are expanded: "g1 and (g2 or g3)" yields "g1 and g2" and +0030 % "g1 and g3". Reactions listed in rxnToCheck because of distributivity are expanded in +0031 % the same way; they are listed because a rule that needs distributivity can indicate +0032 % that the GPR differs from what its author intended. standardizeGrRules and +0033 % findPotentialErrors inspect them. 0034 0035 %Work out the isozymes of every reaction up front. The number of copies is 0036 %the number of DNF clauses, which is not the number of ' or ' substrings: diff --git a/docs/html/manipulation/findDuplicateRxns.html b/docs/html/manipulation/findDuplicateRxns.html index 0d07e767..b6e2ca74 100644 --- a/docs/html/manipulation/findDuplicateRxns.html +++ b/docs/html/manipulation/findDuplicateRxns.html @@ -29,21 +29,19 @@

    SYNOPSIS ^DESCRIPTION ^

     findDuplicateRxns  Find reactions that share identical stoichiometry.
     
    - The upstream version of yeast-GEM's findDuplicatedRxns.
    -
    - Only stoichiometry is compared — bounds, GPRs, and annotations are
    - ignored. The default treats A→B and B→A as duplicates (typical curation
    - use case: "find reactions that could be merged").
    + Only the stoichiometry is compared; bounds, GPRs and annotations are ignored. With
    + the default ignoreDirection, A→B and B→A are duplicates. The pairs identify
    + reactions that could be merged (contractModel merges duplicate reactions).
     
      Parameters
      ----------
      model : struct
    -     RAVEN model struct.
    +     a model structure.
     
      Name-Value Arguments
      --------------------
      ignoreDirection : logical
    -     treat A→B and B→A as duplicates (default true).
    +     true to treat A→B and B→A as duplicates (default true).
     
      Returns
      -------
    @@ -73,65 +71,63 @@ 

    SOURCE CODE ^
    0001 function pairs = findDuplicateRxns(model, varargin)
     0002 % findDuplicateRxns  Find reactions that share identical stoichiometry.
     0003 %
    -0004 % The upstream version of yeast-GEM's findDuplicatedRxns.
    -0005 %
    -0006 % Only stoichiometry is compared — bounds, GPRs, and annotations are
    -0007 % ignored. The default treats A→B and B→A as duplicates (typical curation
    -0008 % use case: "find reactions that could be merged").
    -0009 %
    -0010 % Parameters
    -0011 % ----------
    -0012 % model : struct
    -0013 %     RAVEN model struct.
    -0014 %
    -0015 % Name-Value Arguments
    -0016 % --------------------
    -0017 % ignoreDirection : logical
    -0018 %     treat A→B and B→A as duplicates (default true).
    -0019 %
    -0020 % Returns
    -0021 % -------
    -0022 % pairs : double
    -0023 %     Nx2 numeric array of reaction-index pairs (i, j) where reactions i
    -0024 %     and j share the same (possibly negated) stoichiometry, with i < j.
    -0025 %     Empty if the model has no duplicates.
    -0026 %
    -0027 % Examples
    -0028 % --------
    -0029 %     pairs = findDuplicateRxns(model);
    -0030 %     pairs = findDuplicateRxns(model, false);
    -0031 
    -0032 p=parseRAVENargs(varargin, {'ignoreDirection',true});
    -0033 ignoreDirection=p.ignoreDirection;
    +0004 % Only the stoichiometry is compared; bounds, GPRs and annotations are ignored. With
    +0005 % the default ignoreDirection, A→B and B→A are duplicates. The pairs identify
    +0006 % reactions that could be merged (contractModel merges duplicate reactions).
    +0007 %
    +0008 % Parameters
    +0009 % ----------
    +0010 % model : struct
    +0011 %     a model structure.
    +0012 %
    +0013 % Name-Value Arguments
    +0014 % --------------------
    +0015 % ignoreDirection : logical
    +0016 %     true to treat A→B and B→A as duplicates (default true).
    +0017 %
    +0018 % Returns
    +0019 % -------
    +0020 % pairs : double
    +0021 %     Nx2 numeric array of reaction-index pairs (i, j) where reactions i
    +0022 %     and j share the same (possibly negated) stoichiometry, with i < j.
    +0023 %     Empty if the model has no duplicates.
    +0024 %
    +0025 % Examples
    +0026 % --------
    +0027 %     pairs = findDuplicateRxns(model);
    +0028 %     pairs = findDuplicateRxns(model, false);
    +0029 
    +0030 p=parseRAVENargs(varargin, {'ignoreDirection',true});
    +0031 ignoreDirection=p.ignoreDirection;
    +0032 
    +0033 S=model.S';  % nRxns x nMets, kept sparse: unique(...,'rows') accepts sparse input directly
     0034 
    -0035 S=model.S';  % nRxns x nMets, kept sparse: unique(...,'rows') accepts sparse input directly
    -0036 
    -0037 if ignoreDirection
    -0038     % Canonical form: flip rows whose first nonzero entry is negative so
    -0039     % that A->B and B->A hash to the same fingerprint.
    -0040     for i=1:size(S,1)
    -0041         k=find(S(i,:),1);
    -0042         if ~isempty(k) && S(i,k)<0
    -0043             S(i,:)=-S(i,:);
    -0044         end
    -0045     end
    -0046 end
    +0035 if ignoreDirection
    +0036     % Canonical form: flip rows whose first nonzero entry is negative so
    +0037     % that A->B and B->A hash to the same fingerprint.
    +0038     for i=1:size(S,1)
    +0039         k=find(S(i,:),1);
    +0040         if ~isempty(k) && S(i,k)<0
    +0041             S(i,:)=-S(i,:);
    +0042         end
    +0043     end
    +0044 end
    +0045 
    +0046 [~,~,ic]=unique(S,'rows');  % ic(i) = group label for reaction i
     0047 
    -0048 [~,~,ic]=unique(S,'rows');  % ic(i) = group label for reaction i
    -0049 
    -0050 pairs=zeros(0,2);
    -0051 for g=1:max(ic)
    -0052     idx=sort(find(ic==g));
    -0053     for ii=1:numel(idx)-1
    -0054         for jj=ii+1:numel(idx)
    -0055             pairs(end+1,:)=[idx(ii),idx(jj)]; %#ok<AGROW>
    -0056         end
    -0057     end
    -0058 end
    -0059 if ~isempty(pairs)
    -0060     pairs=sortrows(pairs);
    -0061 end
    -0062 end

    +0048 pairs=zeros(0,2); +0049 for g=1:max(ic) +0050 idx=sort(find(ic==g)); +0051 for ii=1:numel(idx)-1 +0052 for jj=ii+1:numel(idx) +0053 pairs(end+1,:)=[idx(ii),idx(jj)]; %#ok<AGROW> +0054 end +0055 end +0056 end +0057 if ~isempty(pairs) +0058 pairs=sortrows(pairs); +0059 end +0060 end


    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/manipulation/findPotentialErrors.html b/docs/html/manipulation/findPotentialErrors.html index 85395a27..c1607bf2 100644 --- a/docs/html/manipulation/findPotentialErrors.html +++ b/docs/html/manipulation/findPotentialErrors.html @@ -29,11 +29,10 @@

    SYNOPSIS ^DESCRIPTION ^

     findPotentialErrors  Identify reactions with non-DNF GPR rules.
     
    - Detects grRules that are not in disjunctive normal form (DNF) —
    - i.e. rules where an OR operator is nested inside an AND context.
    - Standard RAVEN format requires DNF: groups of genes that must all be
    - present together (enzyme complexes) are written as conjunctions, and
    - the reaction is catalysed by at least one complex (disjunction of
    + Detects grRules that are not in disjunctive normal form (DNF), that is, rules in
    + which an OR operator is nested inside an AND context. The RAVEN format requires DNF:
    + groups of genes that must all be present together (enzyme complexes) are written as
    + conjunctions, and the reaction is catalysed by at least one complex (a disjunction of
      conjunctions).
     
      Parameters
    @@ -44,23 +43,22 @@ 

    DESCRIPTION ^CROSS-REFERENCE INFORMATION ^
 </ul>
 This function is called by:
 <ul style= -
  • standardizeGrRules standardizeGrRules Standardize gene-reaction rules in a model.
  • +
  • standardizeGrRules standardizeGrRules Standardise gene-reaction rules in a model.
  • @@ -89,89 +87,87 @@

    SOURCE CODE ^
    0001 function issues=findPotentialErrors(model)
     0002 % findPotentialErrors  Identify reactions with non-DNF GPR rules.
     0003 %
    -0004 % Detects grRules that are not in disjunctive normal form (DNF) —
    -0005 % i.e. rules where an OR operator is nested inside an AND context.
    -0006 % Standard RAVEN format requires DNF: groups of genes that must all be
    -0007 % present together (enzyme complexes) are written as conjunctions, and
    -0008 % the reaction is catalysed by at least one complex (disjunction of
    -0009 % conjunctions).
    -0010 %
    -0011 % Parameters
    -0012 % ----------
    -0013 % model : struct
    -0014 %     a model structure with grRules and rxns fields.
    -0015 %
    -0016 % Returns
    -0017 % -------
    -0018 % issues : struct
    -0019 %     struct array — one element per problematic reaction — with fields:
    -0020 %
    -0021 %     - index  : double — 1-based index into model.rxns.
    -0022 %     - rxn    : char   — reaction ID.
    -0023 %     - grRule : char   — the problematic grRule string.
    -0024 %     - reason : char   — short explanation.
    -0025 %
    -0026 % Notes
    -0027 % -----
    -0028 % Detection walks the parse tree from parseGrRule rather than searching the
    -0029 % raw string, so gene IDs that contain substrings such as "and" or "or" do
    -0030 % not cause false positives or negatives.
    -0031 %
    -0032 % A rule is non-DNF when an AND operator has an OR anywhere beneath it.
    -0033 % Bracketing alone never makes a rule non-DNF: "((G1 and G2) or G3)" and
    -0034 % "(G1 or G2) or (G3 and G4)" are both fine.
    -0035 % Use standardizeGrRules to attempt automatic repair.
    -0036 %
    -0037 % Rules that cannot be parsed at all are reported as issues too, with the
    -0038 % parser's message as the reason.
    -0039 %
    -0040 % Examples
    -0041 % --------
    -0042 %     issues = findPotentialErrors(model);
    -0043 %     if ~isempty(issues)
    -0044 %         fprintf("%d reactions with non-DNF grRules\n", numel(issues));
    -0045 %     end
    -0046 %     indexes2check = vertcat(issues.index);
    +0004 % Detects grRules that are not in disjunctive normal form (DNF), that is, rules in
    +0005 % which an OR operator is nested inside an AND context. The RAVEN format requires DNF:
    +0006 % groups of genes that must all be present together (enzyme complexes) are written as
    +0007 % conjunctions, and the reaction is catalysed by at least one complex (a disjunction of
    +0008 % conjunctions).
    +0009 %
    +0010 % Parameters
    +0011 % ----------
    +0012 % model : struct
    +0013 %     a model structure with grRules and rxns fields.
    +0014 %
    +0015 % Returns
    +0016 % -------
    +0017 % issues : struct
    +0018 %     struct array with one element per problematic reaction and the fields:
    +0019 %
    +0020 %     - index  : double, 1-based index into model.rxns.
    +0021 %     - rxn    : char, reaction id.
    +0022 %     - grRule : char, the problematic grRule string.
    +0023 %     - reason : char, short explanation.
    +0024 %
    +0025 % Notes
    +0026 % -----
    +0027 % Detection walks the parse tree from parseGrRule, so gene ids that contain substrings
    +0028 % such as "and" or "or" are not misread as operators.
    +0029 %
    +0030 % A rule is non-DNF when an AND operator has an OR anywhere beneath it.
    +0031 % Bracketing alone does not make a rule non-DNF: "((G1 and G2) or G3)" and
    +0032 % "(G1 or G2) or (G3 and G4)" are both in DNF. standardizeGrRules attempts an
    +0033 % automatic repair.
    +0034 %
    +0035 % Rules that cannot be parsed at all are reported as issues too, with the
    +0036 % parser's message as the reason.
    +0037 %
    +0038 % Examples
    +0039 % --------
    +0040 %     issues = findPotentialErrors(model);
    +0041 %     if ~isempty(issues)
    +0042 %         fprintf("%d reactions with non-DNF grRules\n", numel(issues));
    +0043 %     end
    +0044 %     indexes2check = vertcat(issues.index);
    +0045 
    +0046 issues=struct('index',{},'rxn',{},'grRule',{},'reason',{});
     0047 
    -0048 issues=struct('index',{},'rxn',{},'grRule',{},'reason',{});
    -0049 
    -0050 if ~isfield(model,'grRules') || ~isfield(model,'rxns')
    -0051     return
    -0052 end
    -0053 
    -0054 for i=1:numel(model.grRules)
    -0055     rule=strtrim(model.grRules{i});
    -0056     if isempty(rule)
    -0057         continue
    -0058     end
    -0059 
    -0060     try
    -0061         isDnf=isDnfGrRule(rule);
    -0062     catch ME
    -0063         if ~strcmp(ME.identifier,'RAVEN:badGrRule')
    -0064             rethrow(ME)
    -0065         end
    -0066         % A rule that cannot be parsed is itself worth reporting: it is
    -0067         % certainly not usable by the isoenzyme/complex reasoning, and
    -0068         % silently skipping it would hide the very thing this function
    -0069         % exists to surface.
    -0070         issues(end+1,1)=struct( ...
    -0071             'index',  i, ...
    -0072             'rxn',    model.rxns{i}, ...
    -0073             'grRule', rule, ...
    -0074             'reason', ['Cannot be parsed: ' ME.message]); %#ok<AGROW>
    -0075         continue
    -0076     end
    -0077 
    -0078     if ~isDnf
    -0079         issues(end+1,1)=struct( ...
    -0080             'index',  i, ...
    -0081             'rxn',    model.rxns{i}, ...
    -0082             'grRule', rule, ...
    -0083             'reason', 'Non-DNF: OR operator nested inside AND context'); %#ok<AGROW>
    -0084     end
    -0085 end
    -0086 end

    +0048 if ~isfield(model,'grRules') || ~isfield(model,'rxns') +0049 return +0050 end +0051 +0052 for i=1:numel(model.grRules) +0053 rule=strtrim(model.grRules{i}); +0054 if isempty(rule) +0055 continue +0056 end +0057 +0058 try +0059 isDnf=isDnfGrRule(rule); +0060 catch ME +0061 if ~strcmp(ME.identifier,'RAVEN:badGrRule') +0062 rethrow(ME) +0063 end +0064 % A rule that cannot be parsed is itself worth reporting: it is +0065 % certainly not usable by the isoenzyme/complex reasoning, and +0066 % silently skipping it would hide the very thing this function +0067 % exists to surface. +0068 issues(end+1,1)=struct( ... +0069 'index', i, ... +0070 'rxn', model.rxns{i}, ... +0071 'grRule', rule, ... +0072 'reason', ['Cannot be parsed: ' ME.message]); %#ok<AGROW> +0073 continue +0074 end +0075 +0076 if ~isDnf +0077 issues(end+1,1)=struct( ... +0078 'index', i, ... +0079 'rxn', model.rxns{i}, ... +0080 'grRule', rule, ... +0081 'reason', 'Non-DNF: OR operator nested inside AND context'); %#ok<AGROW> +0082 end +0083 end +0084 end


    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/manipulation/generateNewIds.html b/docs/html/manipulation/generateNewIds.html index 67af47e2..0c0b9413 100644 --- a/docs/html/manipulation/generateNewIds.html +++ b/docs/html/manipulation/generateNewIds.html @@ -29,8 +29,8 @@

    SYNOPSIS ^DESCRIPTION ^

     generateNewIds  Generate a list of new metabolite or reaction ids.
     
    - The ids are sequentially numbered with a defined prefix. The model is
    - queried for the highest existing number of that type of id. Suffixes are
    + The ids are numbered sequentially with a defined prefix. The highest existing number
    + among the ids of that type with the prefix is read from the model. Suffixes are
      read with str2double. A suffix counts as a number when the result is a
      finite integer value below 2^52; text, Inf, NaN, fractions (r_2.5) and
      numbers of 2^52 or more (r_1e300, r_1e400) do not count.
    @@ -42,17 +42,16 @@ 

    DESCRIPTION ^SOURCE CODE ^
    0001 function newIds=generateNewIds(model,type,prefix,varargin)
     0002 % generateNewIds  Generate a list of new metabolite or reaction ids.
     0003 %
    -0004 % The ids are sequentially numbered with a defined prefix. The model is
    -0005 % queried for the highest existing number of that type of id. Suffixes are
    +0004 % The ids are numbered sequentially with a defined prefix. The highest existing number
    +0005 % among the ids of that type with the prefix is read from the model. Suffixes are
     0006 % read with str2double. A suffix counts as a number when the result is a
     0007 % finite integer value below 2^52; text, Inf, NaN, fractions (r_2.5) and
     0008 % numbers of 2^52 or more (r_1e300, r_1e400) do not count.
    @@ -92,77 +91,76 @@ 

    SOURCE CODE ^% type : char 0015 % type of id, 'rxns' or 'mets'. 0016 % prefix : char -0017 % prefix to be used in all ids, e.g. 's_' or 'r_'. +0017 % prefix to be used in all ids, for example 's_' or 'r_'. 0018 % 0019 % Name-Value Arguments 0020 % -------------------- 0021 % quantity : double -0022 % number of new ids that should be generated (default 1). +0022 % number of new ids to generate (default 1). 0023 % numLength : double -0024 % length of the numerical part of the id. E.g. 4 gives ids like r_0001 -0025 % and 6 gives ids like r_000001. If the prefix is already used in the -0026 % model, then the model-defined length will be used instead -0027 % (default 4). -0028 % -0029 % Returns -0030 % ------- -0031 % newIds : cell -0032 % cell array with the generated ids. -0033 % -0034 % Examples -0035 % -------- -0036 % newIds = generateNewIds(model, type, prefix, quantity, numLength); -0037 type=char(type); -0038 prefix=char(prefix); -0039 -0040 if type=='rxns' -0041 existingIds=model.rxns; -0042 elseif type=='mets' -0043 existingIds=model.mets; -0044 else -0045 error('type should be either ''rxns'' or ''mets''.') -0046 end -0047 p=parseRAVENargs(varargin, {'quantity',1; 'numLength',4}); -0048 quantity=p.quantity; -0049 numLength=p.numLength; -0050 -0051 % Subset only existingIds that have the prefix. Escaped: prefix is -0052 % arbitrary caller-supplied text, not a regex pattern. -0053 escapedPrefix=regexptranslate('escape',prefix); -0054 existingIds=existingIds(~cellfun(@isempty,regexp(existingIds,['^' escapedPrefix]))); -0055 -0056 if ~isempty(existingIds) -0057 existingIds=regexprep(existingIds,['^' escapedPrefix],''); -0058 numbers=str2double(existingIds); -0059 notInteger=~isfinite(numbers) | imag(numbers)~=0 | real(numbers)~=floor(real(numbers)) | abs(real(numbers))>=flintmax/2; -0060 numbers=real(numbers); -0061 numbers(notInteger)=NaN; -0062 if all(isnan(numbers)) -0063 [~,I]=sort(existingIds); -0064 lastId=existingIds{I(end)}; -0065 numLength=length(lastId); -0066 lastId=NaN; -0067 else -0068 [lastId,I]=max(numbers); -0069 numLength=length(existingIds{I}); -0070 end -0071 else -0072 lastId=0; -0073 fprintf(['No ' type ' ids with prefix "' prefix ... -0074 '" currently exist in the model. The first new id will be "' ... -0075 [prefix,num2str(1,['%0' num2str(numLength) 'd'])] '"\n'],'%s') -0076 end -0077 -0078 if isnan(lastId) -0079 lastId = 0; -0080 end -0081 -0082 newIds=cell(quantity,1); -0083 -0084 for k=1:quantity -0085 newIds{k}=[prefix,num2str(k+lastId,['%0' num2str(numLength) 'd'])]; -0086 end -0087 end

    +0024 % length of the numerical part of the id; 4 gives ids like r_0001 and 6 gives ids like +0025 % r_000001. If the prefix is used in the model, the length of the existing ids +0026 % applies instead (default 4). +0027 % +0028 % Returns +0029 % ------- +0030 % newIds : cell +0031 % cell array with the generated ids. +0032 % +0033 % Examples +0034 % -------- +0035 % newIds = generateNewIds(model, type, prefix, quantity, numLength); +0036 type=char(type); +0037 prefix=char(prefix); +0038 +0039 if type=='rxns' +0040 existingIds=model.rxns; +0041 elseif type=='mets' +0042 existingIds=model.mets; +0043 else +0044 error('type should be either ''rxns'' or ''mets''.') +0045 end +0046 p=parseRAVENargs(varargin, {'quantity',1; 'numLength',4}); +0047 quantity=p.quantity; +0048 numLength=p.numLength; +0049 +0050 % Subset only existingIds that have the prefix. Escaped: prefix is +0051 % arbitrary caller-supplied text, not a regex pattern. +0052 escapedPrefix=regexptranslate('escape',prefix); +0053 existingIds=existingIds(~cellfun(@isempty,regexp(existingIds,['^' escapedPrefix]))); +0054 +0055 if ~isempty(existingIds) +0056 existingIds=regexprep(existingIds,['^' escapedPrefix],''); +0057 numbers=str2double(existingIds); +0058 notInteger=~isfinite(numbers) | imag(numbers)~=0 | real(numbers)~=floor(real(numbers)) | abs(real(numbers))>=flintmax/2; +0059 numbers=real(numbers); +0060 numbers(notInteger)=NaN; +0061 if all(isnan(numbers)) +0062 [~,I]=sort(existingIds); +0063 lastId=existingIds{I(end)}; +0064 numLength=length(lastId); +0065 lastId=NaN; +0066 else +0067 [lastId,I]=max(numbers); +0068 numLength=length(existingIds{I}); +0069 end +0070 else +0071 lastId=0; +0072 fprintf(['No ' type ' ids with prefix "' prefix ... +0073 '" currently exist in the model. The first new id will be "' ... +0074 [prefix,num2str(1,['%0' num2str(numLength) 'd'])] '"\n'],'%s') +0075 end +0076 +0077 if isnan(lastId) +0078 lastId = 0; +0079 end +0080 +0081 newIds=cell(quantity,1); +0082 +0083 for k=1:quantity +0084 newIds{k}=[prefix,num2str(k+lastId,['%0' num2str(numLength) 'd'])]; +0085 end +0086 end

    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/manipulation/index.html b/docs/html/manipulation/index.html index 2787ef16..18a75fc2 100644 --- a/docs/html/manipulation/index.html +++ b/docs/html/manipulation/index.html @@ -19,7 +19,7 @@

    Index for manipulation

    Matlab files in this directory:

    -
     addExchangeRxnsaddExchangeRxns Add exchange reactions for some metabolites.
     addGenesRavenaddGenesRaven Add genes to a model.
     addMetsaddMets Add metabolites to a model.
     addRxnsaddRxns Add reactions to a model.
     addRxnsGenesMetsaddRxnsGenesMets Copy reactions from a source model into another model.
     addTransportaddTransport Add transport reactions between compartments.
     changeGrRuleschangeGrRules Change multiple grRules at the same time.
     changeRxnschangeRxns Modify the equations of reactions in a model.
     closeModelcloseModel Add boundary metabolites and their exchange reactions.
     contractModelcontractModel Contract a model by grouping all identical reactions.
     convertToIrrevconvertToIrrev Convert a model to irreversible form.
     copyToCompscopyToComps Copy reactions to new compartment(s).
     deleteUnusedGenesdeleteUnusedGenes Delete all genes not associated to any reaction.
     expandModelexpandModel Expand reactions that use several gene associations.
     findDuplicateRxnsfindDuplicateRxns Find reactions that share identical stoichiometry.
     findPotentialErrorsfindPotentialErrors Identify reactions with non-DNF GPR rules.
     generateNewIdsgenerateNewIds Generate a list of new metabolite or reaction ids.
     mergeCompartmentsmergeCompartments Merge all compartments in a model.
     mergeModelsmergeModels Merge models into one model structure.
     permuteModelpermuteModel Change the order of the reactions or metabolites in a model.
     removeBadRxnsremoveBadRxns Remove reactions that enable production/consumption from nothing.
     removeGenesremoveGenes Delete a set of genes from a model.
     removeLowScoreGenesremoveLowScoreGenes Remove low-scoring genes from model.
     removeMetsremoveMets Delete a set of metabolites from a model.
     removeReactionsremoveReactions Delete a set of reactions from a model.
     replaceMetsreplaceMets Replace a metabolite with another already in the model.
     reverseRxnsreverseRxns
     setExchangeBoundssetExchangeBounds Define exchange flux bounds for a set of metabolites.
     setParamsetParam Set parameters for reactions.
     simplifyModelsimplifyModel Simplify a model by deleting reactions and metabolites.
     sortIdentifierssortIdentifiers Sort model identifiers alphabetically.
     sortModelsortModel Standardize a model's internal ordering: can orient
     standardizeGrRulesstandardizeGrRules Standardize gene-reaction rules in a model.
    + addExchangeRxnsaddExchangeRxns Add exchange reactions for some metabolites.  addGenesRavenaddGenesRaven Add genes to a model.  addMetsaddMets Add metabolites to a model.  addRxnsaddRxns Add reactions to a model.  addRxnsGenesMetsaddRxnsGenesMets Copy reactions from a source model into another model.  addTransportaddTransport Add transport reactions between compartments.  changeGrRuleschangeGrRules Change the grRules of multiple reactions.  changeRxnschangeRxns Modify the equations of reactions in a model.  closeModelcloseModel Add boundary metabolites to exchange reactions.  contractModelcontractModel Contract a model by merging identical reactions.  convertToIrrevconvertToIrrev Convert a model to irreversible form.  copyToCompscopyToComps Copy reactions to new compartment(s).  deleteUnusedGenesdeleteUnusedGenes Delete all genes not associated to any reaction.  expandModelexpandModel Expand reactions that use several gene associations.  findDuplicateRxnsfindDuplicateRxns Find reactions that share identical stoichiometry.  findPotentialErrorsfindPotentialErrors Identify reactions with non-DNF GPR rules.  generateNewIdsgenerateNewIds Generate a list of new metabolite or reaction ids.  mergeCompartmentsmergeCompartments Merge all compartments in a model.  mergeModelsmergeModels Merge models into one model structure.  permuteModelpermuteModel Change the order of the reactions or metabolites in a model.  removeBadRxnsremoveBadRxns Remove reactions that enable production or consumption from nothing.  removeGenesremoveGenes Delete a set of genes from a model.  removeLowScoreGenesremoveLowScoreGenes Remove low-score genes from a model.  removeMetsremoveMets Delete a set of metabolites from a model.  removeReactionsremoveReactions Delete a set of reactions from a model.  replaceMetsreplaceMets Replace a metabolite with another already in the model.  reverseRxnsreverseRxns Reverse the direction of reactions in a model.  setExchangeBoundssetExchangeBounds Define exchange flux bounds for a set of metabolites.  setParamsetParam Set parameters for reactions.  simplifyModelsimplifyModel Simplify a model by deleting reactions and metabolites.  sortIdentifierssortIdentifiers Sort model identifiers alphabetically.  sortModelsortModel Sort the metabolites and reactions of a model.  standardizeGrRulesstandardizeGrRules Standardise gene-reaction rules in a model. diff --git a/docs/html/manipulation/mergeCompartments.html b/docs/html/manipulation/mergeCompartments.html index f634bb5d..2c650705 100644 --- a/docs/html/manipulation/mergeCompartments.html +++ b/docs/html/manipulation/mergeCompartments.html @@ -37,28 +37,29 @@

    DESCRIPTION ^DESCRIPTION ^CROSS-REFERENCE INFORMATION ^

    This function calls:
      -
    • contractModel contractModel Contract a model by grouping all identical reactions.
    • removeMets removeMets Delete a set of metabolites from a model.
    • removeReactions removeReactions Delete a set of reactions from a model.
    +
  • contractModel contractModel Contract a model by merging identical reactions.
  • removeMets removeMets Delete a set of metabolites from a model.
  • removeReactions removeReactions Delete a set of reactions from a model.
  • This function is called by:
    @@ -102,46 +102,46 @@

    SOURCE CODE ^% Name-Value Arguments 0010 % -------------------- 0011 % keepUnconstrained : logical -0012 % keep metabolites that are unconstrained in a "unconstrained" -0013 % compartment. If these are merged the exchange reactions will most often -0014 % be deleted (default false). -0015 % deleteRxnsWithOneMet : logical -0016 % delete reactions with only one metabolite. These reactions come from -0017 % reactions such as A[c] + B[c] => A[m]. In some models hydrogen is -0018 % balanced around each membrane with reactions like this (default -0019 % false). -0020 % distReverse : logical -0021 % distinguish reactions with same metabolites but different reversibility -0022 % as different reactions (default true). -0023 % -0024 % Returns -0025 % ------- -0026 % model : struct -0027 % a model with all reactions located to one compartment. -0028 % deletedRxns : cell -0029 % reactions that were deleted because of only having one metabolite -0030 % after merging. +0012 % true to keep the unconstrained metabolites in a separate compartment "b" +0013 % (name "Unconstrained"). If they are merged, an exchange reaction between an +0014 % unconstrained metabolite and a metabolite of the same name has no net +0015 % stoichiometry after merging and is deleted (default false). +0016 % deleteRxnsWithOneMet : logical +0017 % true to delete reactions that have one metabolite after merging and had more +0018 % before. Such reactions arise from reactions such as A[c] + B[c] => A[m]. Some +0019 % models balance hydrogen around each membrane with reactions like this +0020 % (default false). +0021 % distReverse : logical +0022 % true to treat reactions with the same metabolites but different reversibility as +0023 % different reactions (default true). +0024 % +0025 % Returns +0026 % ------- +0027 % model : struct +0028 % a model with all reactions in one compartment. +0029 % deletedRxns : cell +0030 % the ids of the reactions deleted because they have one metabolite after merging. 0031 % duplicateRxns : cell -0032 % identical reactions that occurred in different compartments and were -0033 % deleted because they turned to be duplicated after merging. -0034 % -0035 % Examples -0036 % -------- -0037 % [model, deletedRxns, duplicateRxns] = mergeCompartments(model); -0038 % -0039 % Notes -0040 % ----- -0041 % Merges all compartments into one "s" compartment (for "System"). This can -0042 % be useful for example to ensure that there are metabolic capabilities to -0043 % synthesize all metabolites. +0032 % one entry per reaction of the returned model: '' when no duplicate was merged +0033 % into it, otherwise the ids of the identical reactions from different +0034 % compartments that were removed into it, joined by ";". +0035 % +0036 % Examples +0037 % -------- +0038 % [model, deletedRxns, duplicateRxns] = mergeCompartments(model); +0039 % +0040 % Notes +0041 % ----- +0042 % Merges all compartments into one compartment "s" (name "System"). The merged model +0043 % can be used to test whether the network can synthesise all metabolites. 0044 % 0045 % Metabolites with the same name are merged into one. The merged metabolite 0046 % keeps the id and the other fields of one of the copies: the copy that is 0047 % already in compartment "s" if there is one, otherwise the copy whose id 0048 % comes first in character code order (for example ACCOA_c before ACCOA_m). 0049 % With keepUnconstrained only the copies that are not unconstrained are -0050 % candidates. If the metabolite IDs reflect the compartment that they are in -0051 % the IDs may no longer be representative. +0050 % candidates. If the metabolite ids contain the compartment, the ids of merged +0051 % metabolites can name a compartment that no longer exists. 0052 0053 p=parseRAVENargs(varargin, {'keepUnconstrained',false; 'deleteRxnsWithOneMet',false; 'distReverse',true}); 0054 keepUnconstrained=p.keepUnconstrained; diff --git a/docs/html/manipulation/mergeModels.html b/docs/html/manipulation/mergeModels.html index 1758cfd1..53000f3a 100644 --- a/docs/html/manipulation/mergeModels.html +++ b/docs/html/manipulation/mergeModels.html @@ -29,10 +29,11 @@

    SYNOPSIS ^DESCRIPTION ^

     mergeModels  Merge models into one model structure.
     
    - Merges models into one model structure. Reactions are added without any
    - checks, so duplicate reactions might appear. Metabolites are matched by
    - their name and compartment (metaboliteName[comp]), while genes are matched
    - by their name.
    + Merges models into one model structure. Reactions are added without a check for
    + duplicate reactions, so duplicates can appear. A reaction id that already exists in
    + the merged model is renamed by appending "_" and the id of its source model.
    + Metabolites are matched by name and compartment (metaboliteName[comp]) and genes by
    + name.
     
      Parameters
      ----------
    @@ -42,23 +43,22 @@ 

    DESCRIPTION ^DESCRIPTION ^CROSS-REFERENCE INFORMATION ^

    This function calls: +
  • copyToComps copyToComps Copy reactions to new compartment(s).
  • standardizeGrRules standardizeGrRules Standardise gene-reaction rules in a model.
  • This function is called by:
    • copyToComps copyToComps Copy reactions to new compartment(s).
    @@ -80,36 +80,36 @@

    SOURCE CODE ^
    0001 function model=mergeModels(models,varargin)
     0002 % mergeModels  Merge models into one model structure.
     0003 %
    -0004 % Merges models into one model structure. Reactions are added without any
    -0005 % checks, so duplicate reactions might appear. Metabolites are matched by
    -0006 % their name and compartment (metaboliteName[comp]), while genes are matched
    -0007 % by their name.
    -0008 %
    -0009 % Parameters
    -0010 % ----------
    -0011 % models : cell
    -0012 %     a cell array with model structures.
    -0013 %
    -0014 % Name-Value Arguments
    -0015 % --------------------
    -0016 % metParam : char
    -0017 %     string, metabolite name ("metNames") or ID ("mets") are used for
    -0018 %     matching (default "metNames").
    -0019 % supressWarnings : logical
    -0020 %     whether warnings should be supressed (default false).
    -0021 % copyToComps : logical
    -0022 %     internal flag used by copyToComps.m when it calls mergeModels to
    -0023 %     duplicate a model into a new compartment; leave at default when
    -0024 %     calling mergeModels directly (default false).
    -0025 %
    -0026 % Returns
    -0027 % -------
    -0028 % model : struct
    -0029 %     a model structure with the merged model. Follows the structure of
    -0030 %     normal models but also has "rxnFrom/metFrom/geneFrom" fields to
    -0031 %     indicate from which model each reaction/metabolite/gene was taken. If
    -0032 %     the model already has "rxnFrom/metFrom/geneFrom" fields, then these
    -0033 %     fields are not modified.
    +0004 % Merges models into one model structure. Reactions are added without a check for
    +0005 % duplicate reactions, so duplicates can appear. A reaction id that already exists in
    +0006 % the merged model is renamed by appending "_" and the id of its source model.
    +0007 % Metabolites are matched by name and compartment (metaboliteName[comp]) and genes by
    +0008 % name.
    +0009 %
    +0010 % Parameters
    +0011 % ----------
    +0012 % models : cell
    +0013 %     a cell array with model structures.
    +0014 %
    +0015 % Name-Value Arguments
    +0016 % --------------------
    +0017 % metParam : char
    +0018 %     the metabolite field used for matching: "metNames" (name and compartment) or
    +0019 %     "mets" (id) (default "metNames").
    +0020 % supressWarnings : logical
    +0021 %     true to suppress warnings (default false).
    +0022 % copyToComps : logical
    +0023 %     internal flag set by copyToComps when it calls mergeModels to duplicate a model into
    +0024 %     a new compartment. Left at the default when calling mergeModels directly
    +0025 %     (default false).
    +0026 %
    +0027 % Returns
    +0028 % -------
    +0029 % model : struct
    +0030 %     the merged model structure. In addition to the usual fields, it has the fields
    +0031 %     rxnFrom, metFrom and geneFrom, which hold the id of the model that each reaction,
    +0032 %     metabolite and gene was taken from. If a model already has these fields, its
    +0033 %     entries are not modified.
     0034 %
     0035 % Examples
     0036 % --------
    diff --git a/docs/html/manipulation/permuteModel.html b/docs/html/manipulation/permuteModel.html
    index 24723ecc..f42e9850 100644
    --- a/docs/html/manipulation/permuteModel.html
    +++ b/docs/html/manipulation/permuteModel.html
    @@ -56,7 +56,7 @@ 

    CROSS-REFERENCE INFORMATION ^
 </ul>
 This function is called by:
 <ul style= -
  • changeRxns changeRxns Modify the equations of reactions in a model.
  • sortIdentifiers sortIdentifiers Sort model identifiers alphabetically.
  • sortModel sortModel Standardize a model's internal ordering: can orient
  • +
  • changeRxns changeRxns Modify the equations of reactions in a model.
  • sortIdentifiers sortIdentifiers Sort model identifiers alphabetically.
  • sortModel sortModel Sort the metabolites and reactions of a model.
  • diff --git a/docs/html/manipulation/removeBadRxns.html b/docs/html/manipulation/removeBadRxns.html index a9dd8445..36cddb9b 100644 --- a/docs/html/manipulation/removeBadRxns.html +++ b/docs/html/manipulation/removeBadRxns.html @@ -4,7 +4,7 @@ Description of removeBadRxns - + @@ -21,95 +21,85 @@

    removeBadRxns

    PURPOSE ^

    -
    removeBadRxns Remove reactions that enable production/consumption from nothing.
    +
    removeBadRxns Remove reactions that enable production or consumption from nothing.

    SYNOPSIS ^

    function [newModel, removedRxns]=removeBadRxns(model,varargin)

    DESCRIPTION ^

    -
     removeBadRxns  Remove reactions that enable production/consumption from nothing.
    +
     removeBadRxns  Remove reactions that enable production or consumption from nothing.
     
    - Iteratively removes reactions which enable production/consumption of some
    - metabolite without any uptake/excretion.
    + Iteratively removes reactions that enable production or consumption of a metabolite
    + without any uptake or excretion.
     
      Parameters
      ----------
      model : struct
    -     a model structure. For the intended function, the model should not
    -     allow for any uptake/excretion. The easiest way to achieve this is to
    -     import the model using importModel("filename", false).
    +     a model structure. The model must not allow any uptake or excretion; the function
    +     warns about open exchange reactions when the model has no unconstrained field.
    +     importModel("filename", false) imports a model without uptake and excretion.
     
      Name-Value Arguments
      --------------------
      rxnRules : double
          which reactions may be removed (default 1):
     
    -     - 1 : only remove reactions which are unbalanced
    -     - 2 : also remove reactions which could not be checked for mass
    -       balancing
    -     - 3 : all reactions can be removed
    +     - 1 : remove only unbalanced reactions
    +     - 2 : also remove reactions that could not be checked for mass
    +       balance
    +     - 3 : any reaction can be removed
      ignoreMets : cell or logical or double
    -     either a cell array of metabolite IDs, a logical vector with the same
    -     number of elements as metabolites in the model, or a vector of indexes
    -     for metabolites to exclude from this analysis (default []).
    +     a cell array of metabolite ids, a logical vector with one element per metabolite
    +     of the model, or a vector of metabolite indexes to exclude from the analysis
    +     (default []).
      isNames : logical
    -     true if the supplied mets represent metabolite names (as opposed to
    -     IDs). This is a way to delete metabolites in several compartments at
    -     once without knowing the exact IDs. This only works if ignoreMets is a
    +     true if ignoreMets holds metabolite names (false: ids), which excludes the
    +     metabolites of that name in all compartments. Applies only if ignoreMets is a
          cell array (default false).
      balanceElements : cell
    -     a cell array with the elements for which to balance the reactions. May
    -     contain any combination of the elements defined in parseFormulas
    -     (default {"C";"P";"S";"N";"O"}).
    +     a cell array with the elements for which to balance the reactions, chosen from
    +     the elements defined in parseFormulas (default {"C";"P";"S";"N";"O"}).
      refModel : struct
    -     a reference model which can be used to ensure that the resulting model
    -     is still functional. The intended use is that the reference model is a
    -     copy of model, but with uptake/excretion allowed and some objectives
    -     (such as production of biomass) constrained to a non-zero flux. Before
    -     a reaction is removed from "model" the function first checks that the
    -     same deletion in "refModel" does not render the problem unfeasible.
    +     a reference model, intended as a copy of model with uptake and excretion allowed
    +     and some objectives (such as production of biomass) constrained to a non-zero
    +     flux. A supplied reference model triggers a warning that the feature is not
    +     supported, followed by an error.
      ignoreIntBounds : logical
    -     true if internal bounds (including reversibility) should be ignored.
    -     Exchange reactions are not affected. This can be used to find
    -     unbalanced solutions which are not possible using the default
    -     constraints (default false).
    +     true to ignore internal bounds (including reversibility). Exchange reactions are
    +     not affected. Ignoring the bounds finds unbalanced solutions that the default
    +     constraints do not allow (default false).
      printReport : logical
    -     true if a report should be printed (default false).
    +     true to print a report (default false).
      seed : double
    -     seed for the random number generator, so that which reaction is
    -     removed among several equally-valid candidates is reproducible
    -     across runs (default: current rng state is left untouched).
    +     seed for the random number generator. A seed makes the choice among several
    +     candidate reactions reproducible (default: the current rng state is not
    +     changed).
     
      Returns
      -------
      newModel : struct
    -     a model structure after the problematic reactions have been deleted.
    +     the model structure without the problematic reactions.
      removedRxns : cell
    -     a cell array with the reactions that were removed.
    +     a cell array with the ids of the removed reactions.
     
      Notes
      -----
    - The purpose of this function is to remove reactions which enable
    - production/consumption of metabolites even when exchange reactions are not
    - used. Many models, especially if they are automatically inferred from
    - databases, will have unbalanced reactions which allow for
    - net-production/consumption of metabolites without any consumption or
    - excretion. A common reason for this is when general compounds have
    - different meaning in different reactions (as DNA has in these two
    - reactions):
    + The function removes reactions that enable production or consumption of metabolites
    + when no exchange reaction is used. Models inferred automatically from databases can
    + contain unbalanced reactions that allow net production or consumption of metabolites
    + without any consumption or excretion. One cause is a general compound that has a
    + different meaning in different reactions (as DNA has in these two reactions):
     
          dATP + dGTP + dCTP + dTTP <=> DNA + 4 PPi
          0.25 dATP + 0.25 dGTP + 0.25 dCTP + 0.25 dTTP <=> DNA + PPi
     
    - Reactions that are problematic like this are always elementally
    - unbalanced, but it is not always the case that you would like to exclude
    - all unbalanced reactions from your model. This function tries to remove as
    - few problematic reactions as possible so that the model cannot
    - produce/consume anything from nothing. This is done by repeatedly calling
    - findLeakMetabolite, checking if any of the involved reactions are
    - elementally unbalanced, removing one of them, and then iterating until no
    - metabolites can be produced/consumed. The 'produce' direction is run
    - before the 'consume' one.
    + Such reactions are elementally unbalanced, but the model can contain further
    + unbalanced reactions that are not problematic. The function removes as few reactions
    + as possible so that the model cannot produce or consume anything from nothing. It
    + calls findLeakMetabolite repeatedly, selects at random one of the elementally
    + unbalanced reactions that carry flux in the solution, removes it, and iterates until
    + no metabolite can be produced or consumed. The 'produce' direction runs before the
    + 'consume' direction.
     
      Examples
      --------
    @@ -130,258 +120,248 @@ 

    CROSS-REFERENCE INFORMATION ^
 
 <h2><a name=SOURCE CODE ^

    0001 function [newModel, removedRxns]=removeBadRxns(model,varargin)
    -0002 % removeBadRxns  Remove reactions that enable production/consumption from nothing.
    +0002 % removeBadRxns  Remove reactions that enable production or consumption from nothing.
     0003 %
    -0004 % Iteratively removes reactions which enable production/consumption of some
    -0005 % metabolite without any uptake/excretion.
    +0004 % Iteratively removes reactions that enable production or consumption of a metabolite
    +0005 % without any uptake or excretion.
     0006 %
     0007 % Parameters
     0008 % ----------
     0009 % model : struct
    -0010 %     a model structure. For the intended function, the model should not
    -0011 %     allow for any uptake/excretion. The easiest way to achieve this is to
    -0012 %     import the model using importModel("filename", false).
    +0010 %     a model structure. The model must not allow any uptake or excretion; the function
    +0011 %     warns about open exchange reactions when the model has no unconstrained field.
    +0012 %     importModel("filename", false) imports a model without uptake and excretion.
     0013 %
     0014 % Name-Value Arguments
     0015 % --------------------
     0016 % rxnRules : double
     0017 %     which reactions may be removed (default 1):
     0018 %
    -0019 %     - 1 : only remove reactions which are unbalanced
    -0020 %     - 2 : also remove reactions which could not be checked for mass
    -0021 %       balancing
    -0022 %     - 3 : all reactions can be removed
    +0019 %     - 1 : remove only unbalanced reactions
    +0020 %     - 2 : also remove reactions that could not be checked for mass
    +0021 %       balance
    +0022 %     - 3 : any reaction can be removed
     0023 % ignoreMets : cell or logical or double
    -0024 %     either a cell array of metabolite IDs, a logical vector with the same
    -0025 %     number of elements as metabolites in the model, or a vector of indexes
    -0026 %     for metabolites to exclude from this analysis (default []).
    +0024 %     a cell array of metabolite ids, a logical vector with one element per metabolite
    +0025 %     of the model, or a vector of metabolite indexes to exclude from the analysis
    +0026 %     (default []).
     0027 % isNames : logical
    -0028 %     true if the supplied mets represent metabolite names (as opposed to
    -0029 %     IDs). This is a way to delete metabolites in several compartments at
    -0030 %     once without knowing the exact IDs. This only works if ignoreMets is a
    -0031 %     cell array (default false).
    -0032 % balanceElements : cell
    -0033 %     a cell array with the elements for which to balance the reactions. May
    -0034 %     contain any combination of the elements defined in parseFormulas
    -0035 %     (default {"C";"P";"S";"N";"O"}).
    -0036 % refModel : struct
    -0037 %     a reference model which can be used to ensure that the resulting model
    -0038 %     is still functional. The intended use is that the reference model is a
    -0039 %     copy of model, but with uptake/excretion allowed and some objectives
    -0040 %     (such as production of biomass) constrained to a non-zero flux. Before
    -0041 %     a reaction is removed from "model" the function first checks that the
    -0042 %     same deletion in "refModel" does not render the problem unfeasible.
    -0043 % ignoreIntBounds : logical
    -0044 %     true if internal bounds (including reversibility) should be ignored.
    -0045 %     Exchange reactions are not affected. This can be used to find
    -0046 %     unbalanced solutions which are not possible using the default
    -0047 %     constraints (default false).
    -0048 % printReport : logical
    -0049 %     true if a report should be printed (default false).
    -0050 % seed : double
    -0051 %     seed for the random number generator, so that which reaction is
    -0052 %     removed among several equally-valid candidates is reproducible
    -0053 %     across runs (default: current rng state is left untouched).
    -0054 %
    -0055 % Returns
    -0056 % -------
    -0057 % newModel : struct
    -0058 %     a model structure after the problematic reactions have been deleted.
    -0059 % removedRxns : cell
    -0060 %     a cell array with the reactions that were removed.
    -0061 %
    -0062 % Notes
    -0063 % -----
    -0064 % The purpose of this function is to remove reactions which enable
    -0065 % production/consumption of metabolites even when exchange reactions are not
    -0066 % used. Many models, especially if they are automatically inferred from
    -0067 % databases, will have unbalanced reactions which allow for
    -0068 % net-production/consumption of metabolites without any consumption or
    -0069 % excretion. A common reason for this is when general compounds have
    -0070 % different meaning in different reactions (as DNA has in these two
    -0071 % reactions):
    -0072 %
    -0073 %     dATP + dGTP + dCTP + dTTP <=> DNA + 4 PPi
    -0074 %     0.25 dATP + 0.25 dGTP + 0.25 dCTP + 0.25 dTTP <=> DNA + PPi
    +0028 %     true if ignoreMets holds metabolite names (false: ids), which excludes the
    +0029 %     metabolites of that name in all compartments. Applies only if ignoreMets is a
    +0030 %     cell array (default false).
    +0031 % balanceElements : cell
    +0032 %     a cell array with the elements for which to balance the reactions, chosen from
    +0033 %     the elements defined in parseFormulas (default {"C";"P";"S";"N";"O"}).
    +0034 % refModel : struct
    +0035 %     a reference model, intended as a copy of model with uptake and excretion allowed
    +0036 %     and some objectives (such as production of biomass) constrained to a non-zero
    +0037 %     flux. A supplied reference model triggers a warning that the feature is not
    +0038 %     supported, followed by an error.
    +0039 % ignoreIntBounds : logical
    +0040 %     true to ignore internal bounds (including reversibility). Exchange reactions are
    +0041 %     not affected. Ignoring the bounds finds unbalanced solutions that the default
    +0042 %     constraints do not allow (default false).
    +0043 % printReport : logical
    +0044 %     true to print a report (default false).
    +0045 % seed : double
    +0046 %     seed for the random number generator. A seed makes the choice among several
    +0047 %     candidate reactions reproducible (default: the current rng state is not
    +0048 %     changed).
    +0049 %
    +0050 % Returns
    +0051 % -------
    +0052 % newModel : struct
    +0053 %     the model structure without the problematic reactions.
    +0054 % removedRxns : cell
    +0055 %     a cell array with the ids of the removed reactions.
    +0056 %
    +0057 % Notes
    +0058 % -----
    +0059 % The function removes reactions that enable production or consumption of metabolites
    +0060 % when no exchange reaction is used. Models inferred automatically from databases can
    +0061 % contain unbalanced reactions that allow net production or consumption of metabolites
    +0062 % without any consumption or excretion. One cause is a general compound that has a
    +0063 % different meaning in different reactions (as DNA has in these two reactions):
    +0064 %
    +0065 %     dATP + dGTP + dCTP + dTTP <=> DNA + 4 PPi
    +0066 %     0.25 dATP + 0.25 dGTP + 0.25 dCTP + 0.25 dTTP <=> DNA + PPi
    +0067 %
    +0068 % Such reactions are elementally unbalanced, but the model can contain further
    +0069 % unbalanced reactions that are not problematic. The function removes as few reactions
    +0070 % as possible so that the model cannot produce or consume anything from nothing. It
    +0071 % calls findLeakMetabolite repeatedly, selects at random one of the elementally
    +0072 % unbalanced reactions that carry flux in the solution, removes it, and iterates until
    +0073 % no metabolite can be produced or consumed. The 'produce' direction runs before the
    +0074 % 'consume' direction.
     0075 %
    -0076 % Reactions that are problematic like this are always elementally
    -0077 % unbalanced, but it is not always the case that you would like to exclude
    -0078 % all unbalanced reactions from your model. This function tries to remove as
    -0079 % few problematic reactions as possible so that the model cannot
    -0080 % produce/consume anything from nothing. This is done by repeatedly calling
    -0081 % findLeakMetabolite, checking if any of the involved reactions are
    -0082 % elementally unbalanced, removing one of them, and then iterating until no
    -0083 % metabolites can be produced/consumed. The 'produce' direction is run
    -0084 % before the 'consume' one.
    -0085 %
    -0086 % Examples
    -0087 % --------
    -0088 %     [newModel, removedRxns] = removeBadRxns(model, rxnRules, ignoreMets, ...
    -0089 %         isNames, balanceElements, refModel, ignoreIntBounds, printReport);
    -0090 
    -0091 p=parseRAVENargs(varargin, {'rxnRules',1; 'ignoreMets',[]; 'isNames',false; 'balanceElements',{'C';'P';'S';'N';'O'}; 'refModel',[]; 'ignoreIntBounds',false; 'printReport',false; 'seed',[]});
    -0092 if ~isempty(p.seed)
    -0093     rng(p.seed);
    -0094 end
    -0095 rxnRules=p.rxnRules;
    -0096 ignoreMets=p.ignoreMets;
    -0097 if ~isempty(ignoreMets) && ~islogical(ignoreMets) && ~isnumeric(ignoreMets)
    -0098     ignoreMets=convertCharArray(ignoreMets);
    -0099 end
    -0100 isNames=p.isNames;
    -0101 balanceElements=convertCharArray(p.balanceElements);
    -0102 refModel=p.refModel;
    -0103 if ~isempty(refModel)
    -0104     EM='The feature to supply a reference model is currently not supported';
    -0105     warning('RAVEN:warning', '%s', EM);
    -0106 end
    -0107 ignoreIntBounds=p.ignoreIntBounds;
    -0108 printReport=p.printReport;
    -0109 
    -0110 %Check if the model has open exchange reactions and print a warning in that
    -0111 %case
    -0112 if ~isfield(model,'unconstrained')
    -0113     [~, I]=getExchangeRxns(model);
    -0114     if any(I)
    -0115         if any(model.lb(I)~=0) || any(model.ub(I)~=0)
    -0116             EM='The model contains open exchange reactions. This is not the intended use of this function. Consider importing your model using importModel(filename,false)';
    -0117             warning('RAVEN:warning', '%s', EM);
    -0118         end
    -0119     end
    -0120 end
    -0121 
    -0122 %Check that the model is feasible
    -0123 sol=solveLP(model);
    -0124 if isempty(sol.f)
    -0125     EM='The model is not feasible. Consider removing lower bounds (such as ATP maintenance)';
    -0126     error('RAVEN:badInput', '%s', EM);
    -0127 end
    -0128 
    -0129 %Check that the reference model is feasible
    -0130 if any(refModel)
    -0131     sol=solveLP(refModel);
    -0132     if isempty(sol.f)
    -0133         EM='The reference model is not feasible';
    -0134         error('RAVEN:badInput', '%s', EM);
    -0135     end
    +0076 % Examples
    +0077 % --------
    +0078 %     [newModel, removedRxns] = removeBadRxns(model, rxnRules, ignoreMets, ...
    +0079 %         isNames, balanceElements, refModel, ignoreIntBounds, printReport);
    +0080 
    +0081 p=parseRAVENargs(varargin, {'rxnRules',1; 'ignoreMets',[]; 'isNames',false; 'balanceElements',{'C';'P';'S';'N';'O'}; 'refModel',[]; 'ignoreIntBounds',false; 'printReport',false; 'seed',[]});
    +0082 if ~isempty(p.seed)
    +0083     rng(p.seed);
    +0084 end
    +0085 rxnRules=p.rxnRules;
    +0086 ignoreMets=p.ignoreMets;
    +0087 if ~isempty(ignoreMets) && ~islogical(ignoreMets) && ~isnumeric(ignoreMets)
    +0088     ignoreMets=convertCharArray(ignoreMets);
    +0089 end
    +0090 isNames=p.isNames;
    +0091 balanceElements=convertCharArray(p.balanceElements);
    +0092 refModel=p.refModel;
    +0093 if ~isempty(refModel)
    +0094     EM='The feature to supply a reference model is currently not supported';
    +0095     warning('RAVEN:warning', '%s', EM);
    +0096 end
    +0097 ignoreIntBounds=p.ignoreIntBounds;
    +0098 printReport=p.printReport;
    +0099 
    +0100 %Check if the model has open exchange reactions and print a warning in that
    +0101 %case
    +0102 if ~isfield(model,'unconstrained')
    +0103     [~, I]=getExchangeRxns(model);
    +0104     if any(I)
    +0105         if any(model.lb(I)~=0) || any(model.ub(I)~=0)
    +0106             EM='The model contains open exchange reactions. This is not the intended use of this function. Consider importing your model using importModel(filename,false)';
    +0107             warning('RAVEN:warning', '%s', EM);
    +0108         end
    +0109     end
    +0110 end
    +0111 
    +0112 %Check that the model is feasible
    +0113 sol=solveLP(model);
    +0114 if isempty(sol.f)
    +0115     EM='The model is not feasible. Consider removing lower bounds (such as ATP maintenance)';
    +0116     error('RAVEN:badInput', '%s', EM);
    +0117 end
    +0118 
    +0119 %Check that the reference model is feasible
    +0120 if any(refModel)
    +0121     sol=solveLP(refModel);
    +0122     if isempty(sol.f)
    +0123         EM='The reference model is not feasible';
    +0124         error('RAVEN:badInput', '%s', EM);
    +0125     end
    +0126 end
    +0127 
    +0128 %Initialize some stuff
    +0129 removedRxns={};
    +0130 balanceStructure=getElementalBalance(model);
    +0131 
    +0132 %Check which elements to balance for
    +0133 if ~isempty(setdiff(balanceElements,balanceStructure.elements.abbrevs))
    +0134     EM='Could not recognize all elements to balance for';
    +0135     error('RAVEN:badInput', '%s', EM);
     0136 end
    -0137 
    -0138 %Initialize some stuff
    -0139 removedRxns={};
    -0140 balanceStructure=getElementalBalance(model);
    -0141 
    -0142 %Check which elements to balance for
    -0143 if ~isempty(setdiff(balanceElements,balanceStructure.elements.abbrevs))
    -0144     EM='Could not recognize all elements to balance for';
    -0145     error('RAVEN:badInput', '%s', EM);
    -0146 end
    -0147 bal=ismember(balanceStructure.elements.abbrevs,balanceElements);
    -0148 
    -0149 %Main loop. First run for production, second for consumption
    -0150 warned=false(2,1); %This is to prevent the same warning being printed multiple times if rxnRules==3
    -0151 for i=1:2
    -0152     while 1
    -0153         %Make some metabolite using as few reactions as possible
    -0154         if i==1
    -0155             [solution, metabolite]=findLeakMetabolite(model,'produce', ...
    -0156                 'ignoreMets',ignoreMets,'isNames',isNames,'minNrFluxes',false, ...
    -0157                 'allowExcretion',true,'ignoreIntBounds',ignoreIntBounds);
    -0158             if ~isempty(solution)
    -0159                 if printReport
    -0160                     fprintf(['Can make: ' model.metNames{metabolite(1)} '\n']);
    -0161                 end
    -0162             else
    -0163                 %If no solution could be found, then finish
    -0164                 break;
    -0165             end
    -0166         else
    -0167             [solution, metabolite]=findLeakMetabolite(model,'consume', ...
    -0168                 'ignoreMets',ignoreMets,'isNames',isNames,'minNrFluxes',false, ...
    -0169                 'ignoreIntBounds',ignoreIntBounds);
    -0170             if ~isempty(solution)
    -0171                 if printReport
    -0172                     fprintf(['Can consume: ' model.metNames{metabolite(1)} '\n']);
    -0173                 end
    -0174             else
    -0175                 %If no solution could be found, then finish
    -0176                 break;
    -0177             end
    -0178         end
    -0179         
    -0180         %Find all reactions that are unbalanced and still carry flux
    -0181         I=find(abs(solution)>10^-8 & balanceStructure.balanceStatus>=0 & ~all(balanceStructure.leftComp(:,bal)==balanceStructure.rightComp(:,bal),2));
    -0182         
    -0183         %If there are unbalanced rxns then delete one of them and iterate
    -0184         if any(I)
    -0185             rxnToRemove=I(randperm(numel(I),1));
    -0186         else
    -0187             %If there are no unbalanced rxns in the solution
    -0188             if rxnRules==1
    -0189                 if i==1
    -0190                     EM=['No unbalanced reactions were found in the solution, but the model can still make "' model.metNames{metabolite} '". Aborting search. Consider setting rxnRules to 2 or 3 for a more exhaustive search'];
    -0191                     warning('RAVEN:warning', '%s', EM);
    -0192                 else
    -0193                     EM=['No unbalanced reactions were found in the solution, but the model can still consume "' model.metNames{metabolite} '". Aborting search. Consider setting rxnRules to 2 or 3 for a more exhaustive search'];
    -0194                     warning('RAVEN:warning', '%s', EM);
    -0195                 end
    -0196                 break;
    -0197             else
    -0198                 %Find reactions which are not checked for mass balancing,
    -0199                 %but that still carry flux
    -0200                 I=find(abs(solution)>10^-8 & balanceStructure.balanceStatus<0);
    -0201                 
    -0202                 %If there are any such reactions, remove one of them and
    -0203                 %iterate
    -0204                 if any(I)
    -0205                     rxnToRemove=I(randperm(numel(I),1));
    -0206                 else
    -0207                     if rxnRules==2
    -0208                         %This happens when all reactions used are balanced
    -0209                         %according to the metabolite formulas. This cannot
    -0210                         %be, and indicates that one or more of the formulas
    -0211                         %are wrong. Print a warning and delete any reaction
    -0212                         %with flux
    -0213                         if i==1
    -0214                             EM=['No unbalanced or unparsable reactions were found in the solution, but the model can still make "' model.metNames{metabolite} '". Aborting search. Consider setting rxnRules to 3 for a more exhaustive search'];
    -0215                             warning('RAVEN:warning', '%s', EM);
    -0216                         else
    -0217                             EM=['No unbalanced or unparsable reactions were found in the solution, but the model can still consume "' model.metNames{metabolite} '". Aborting search. Consider setting rxnRules to 3 for a more exhaustive search'];
    -0218                             warning('RAVEN:warning', '%s', EM);
    -0219                         end
    -0220                         break;
    -0221                     else
    -0222                         if i==1
    -0223                             if warned(1)==false
    -0224                                 EM=['No unbalanced or unparsable reactions were found in the solution, but the model can still make "' model.metNames{metabolite} '". This indicates some error in the metabolite formulas. Removing random reactions in the solution'];
    -0225                                 warning('RAVEN:warning', '%s', EM);
    -0226                                 warned(1)=true;
    -0227                             end
    -0228                         else
    -0229                             if warned(2)==false
    -0230                                 EM=['No unbalanced or unparsable reactions were found in the solution, but the model can still consume "' model.metNames{metabolite} '". This indicates some error in the metabolite formulas. Removing random reactions in the solution'];
    -0231                                 warning('RAVEN:warning', '%s', EM);
    -0232                                 warned(2)=true;
    -0233                             end
    -0234                         end
    -0235                         I=find(abs(solution)>10^-8);
    -0236                         rxnToRemove=I(randperm(numel(I),1));
    -0237                     end
    -0238                 end
    -0239             end
    -0240         end
    -0241         removedRxns=[removedRxns;model.rxns(rxnToRemove)];
    -0242         if printReport
    -0243             fprintf(['\tRemoved: '  model.rxns{rxnToRemove} '\n']);
    -0244         end
    -0245         model=removeReactions(model,rxnToRemove);
    -0246         balanceStructure.balanceStatus(rxnToRemove)=[];
    -0247         balanceStructure.leftComp(rxnToRemove,:)=[];
    -0248         balanceStructure.rightComp(rxnToRemove,:)=[];
    -0249     end
    -0250 end
    -0251 
    -0252 newModel=model;
    -0253 end
    +0137 bal=ismember(balanceStructure.elements.abbrevs,balanceElements); +0138 +0139 %Main loop. First run for production, second for consumption +0140 warned=false(2,1); %This is to prevent the same warning being printed multiple times if rxnRules==3 +0141 for i=1:2 +0142 while 1 +0143 %Make some metabolite using as few reactions as possible +0144 if i==1 +0145 [solution, metabolite]=findLeakMetabolite(model,'produce', ... +0146 'ignoreMets',ignoreMets,'isNames',isNames,'minNrFluxes',false, ... +0147 'allowExcretion',true,'ignoreIntBounds',ignoreIntBounds); +0148 if ~isempty(solution) +0149 if printReport +0150 fprintf(['Can make: ' model.metNames{metabolite(1)} '\n']); +0151 end +0152 else +0153 %If no solution could be found, then finish +0154 break; +0155 end +0156 else +0157 [solution, metabolite]=findLeakMetabolite(model,'consume', ... +0158 'ignoreMets',ignoreMets,'isNames',isNames,'minNrFluxes',false, ... +0159 'ignoreIntBounds',ignoreIntBounds); +0160 if ~isempty(solution) +0161 if printReport +0162 fprintf(['Can consume: ' model.metNames{metabolite(1)} '\n']); +0163 end +0164 else +0165 %If no solution could be found, then finish +0166 break; +0167 end +0168 end +0169 +0170 %Find all reactions that are unbalanced and still carry flux +0171 I=find(abs(solution)>10^-8 & balanceStructure.balanceStatus>=0 & ~all(balanceStructure.leftComp(:,bal)==balanceStructure.rightComp(:,bal),2)); +0172 +0173 %If there are unbalanced rxns then delete one of them and iterate +0174 if any(I) +0175 rxnToRemove=I(randperm(numel(I),1)); +0176 else +0177 %If there are no unbalanced rxns in the solution +0178 if rxnRules==1 +0179 if i==1 +0180 EM=['No unbalanced reactions were found in the solution, but the model can still make "' model.metNames{metabolite} '". Aborting search. Consider setting rxnRules to 2 or 3 for a more exhaustive search']; +0181 warning('RAVEN:warning', '%s', EM); +0182 else +0183 EM=['No unbalanced reactions were found in the solution, but the model can still consume "' model.metNames{metabolite} '". Aborting search. Consider setting rxnRules to 2 or 3 for a more exhaustive search']; +0184 warning('RAVEN:warning', '%s', EM); +0185 end +0186 break; +0187 else +0188 %Find reactions which are not checked for mass balancing, +0189 %but that still carry flux +0190 I=find(abs(solution)>10^-8 & balanceStructure.balanceStatus<0); +0191 +0192 %If there are any such reactions, remove one of them and +0193 %iterate +0194 if any(I) +0195 rxnToRemove=I(randperm(numel(I),1)); +0196 else +0197 if rxnRules==2 +0198 %This happens when all reactions used are balanced +0199 %according to the metabolite formulas. This cannot +0200 %be, and indicates that one or more of the formulas +0201 %are wrong. Print a warning and delete any reaction +0202 %with flux +0203 if i==1 +0204 EM=['No unbalanced or unparsable reactions were found in the solution, but the model can still make "' model.metNames{metabolite} '". Aborting search. Consider setting rxnRules to 3 for a more exhaustive search']; +0205 warning('RAVEN:warning', '%s', EM); +0206 else +0207 EM=['No unbalanced or unparsable reactions were found in the solution, but the model can still consume "' model.metNames{metabolite} '". Aborting search. Consider setting rxnRules to 3 for a more exhaustive search']; +0208 warning('RAVEN:warning', '%s', EM); +0209 end +0210 break; +0211 else +0212 if i==1 +0213 if warned(1)==false +0214 EM=['No unbalanced or unparsable reactions were found in the solution, but the model can still make "' model.metNames{metabolite} '". This indicates some error in the metabolite formulas. Removing random reactions in the solution']; +0215 warning('RAVEN:warning', '%s', EM); +0216 warned(1)=true; +0217 end +0218 else +0219 if warned(2)==false +0220 EM=['No unbalanced or unparsable reactions were found in the solution, but the model can still consume "' model.metNames{metabolite} '". This indicates some error in the metabolite formulas. Removing random reactions in the solution']; +0221 warning('RAVEN:warning', '%s', EM); +0222 warned(2)=true; +0223 end +0224 end +0225 I=find(abs(solution)>10^-8); +0226 rxnToRemove=I(randperm(numel(I),1)); +0227 end +0228 end +0229 end +0230 end +0231 removedRxns=[removedRxns;model.rxns(rxnToRemove)]; +0232 if printReport +0233 fprintf(['\tRemoved: ' model.rxns{rxnToRemove} '\n']); +0234 end +0235 model=removeReactions(model,rxnToRemove); +0236 balanceStructure.balanceStatus(rxnToRemove)=[]; +0237 balanceStructure.leftComp(rxnToRemove,:)=[]; +0238 balanceStructure.rightComp(rxnToRemove,:)=[]; +0239 end +0240 end +0241 +0242 newModel=model; +0243 end

    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/manipulation/removeGenes.html b/docs/html/manipulation/removeGenes.html index 8b5d420a..41f41b71 100644 --- a/docs/html/manipulation/removeGenes.html +++ b/docs/html/manipulation/removeGenes.html @@ -40,13 +40,12 @@

    DESCRIPTION ^DESCRIPTION ^DESCRIPTION ^CROSS-REFERENCE INFORMATION ^

    This function calls: +
  • removeReactions removeReactions Delete a set of reactions from a model.
  • standardizeGrRules standardizeGrRules Standardise gene-reaction rules in a model.
  • This function is called by:
    @@ -95,177 +97,179 @@

    SOURCE CODE ^% Name-Value Arguments 0013 % -------------------- 0014 % removeUnusedMets : logical -0015 % remove metabolites that are no longer in use (default false). +0015 % remove metabolites that no remaining reaction uses (default false). 0016 % removeBlockedRxns : logical -0017 % remove reactions that get blocked after deleting the genes (default -0018 % false). +0017 % remove reactions that cannot carry flux after the genes are deleted. +0018 % When false, the bounds of these reactions are set to 0 (default false). 0019 % standardizeRules : logical -0020 % format gene rules to be compliant with the standard format (default -0021 % true). -0022 % -0023 % Returns -0024 % ------- -0025 % reducedModel : struct -0026 % an updated model structure. Genes in genesToRemove that are not in the -0027 % model are ignored. When none of them is in the model, no gene, reaction -0028 % or bound changes (with standardizeRules true the gene rules are still -0029 % written in the standard format). Otherwise, besides genesToRemove, every -0030 % gene that no remaining reaction used before the removal is deleted as -0031 % well, also when it was not requested (for example a gene that no -0032 % reaction uses). A gene whose only reaction is kept but blocked -0033 % (removeBlockedRxns false), such as the other subunit of a complex that -0034 % lost one, stays. -0035 % -0036 % Examples -0037 % -------- -0038 % reducedModel = removeGenes(model, genesToRemove, removeUnusedMets, ... -0039 % removeBlockedRxns, standardizeRules); -0040 -0041 p=parseRAVENargs(varargin, {'removeUnusedMets',false; 'removeBlockedRxns',false; 'standardizeRules',true}); -0042 removeUnusedMets=p.removeUnusedMets; -0043 removeBlockedRxns=p.removeBlockedRxns; -0044 standardizeRules=p.standardizeRules; -0045 %Format grRules and rxnGeneMatrix: -0046 if standardizeRules -0047 [grRules,rxnGeneMat,toCheck] = standardizeGrRules(model,true); -0048 model.grRules = grRules; -0049 model.rxnGeneMat = rxnGeneMat; -0050 else -0051 toCheck = []; -0052 rxnGeneMat = model.rxnGeneMat; -0053 end -0054 reducedModel = model; -0055 %Only remove genes that are actually in the model -0056 if ~(islogical(genesToRemove) || isnumeric(genesToRemove)) -0057 genesToRemove=convertCharArray(genesToRemove); -0058 genesToRemove=genesToRemove(ismember(genesToRemove,model.genes)); -0059 end -0060 if ~isempty(genesToRemove) -0061 indexesToRemove = getIndexes(model,genesToRemove,'genes'); -0062 if ~isempty(indexesToRemove) -0063 %Make 0 corresponding columns from rxnGeneMat: -0064 reducedModel.rxnGeneMat(:,indexesToRemove) = 0; -0065 genes = model.genes(indexesToRemove); -0066 %Check if conflicting grRules contain any of the genes to remove -0067 if ~isempty(toCheck) -0068 for i=1:numel(toCheck) -0069 index = toCheck(i); -0070 gIdxs = find(rxnGeneMat(index,:)); -0071 g2check = model.genes(gIdxs); -0072 if any(ismember(g2check,genes)) -0073 warning(['Conflicting grRule #' num2str(index) ' (' model.rxns{index} ') contains at least one of the genes to be removed, this grRule will be bypassed in order to avoid logical errors']) -0074 end -0075 end -0076 end -0077 canCarryFlux = true(size(model.rxns)); -0078 %Loop through genes and adapt rxns: -0079 for i = 1:length(genes) -0080 %Find all reactions for this gene and loop through them: -0081 geneRxns = find(rxnGeneMat(:,indexesToRemove(i))); -0082 if ~isempty(geneRxns) -0083 for j = 1:numel(geneRxns) -0084 index = geneRxns(j); -0085 grRule = reducedModel.grRules{index}; -0086 if ~ismember(index,toCheck) && canCarryFlux(index) && ~isempty(grRule) -0087 %Check if rxn can carry flux without this gene: -0088 canCarryFlux(index) = canRxnCarryFlux(grRule,genes{i}); -0089 %Adapt gene rule & gene matrix: -0090 grRule = removeGeneFromRule(grRule,genes{i}); -0091 reducedModel.grRules{index} = grRule; -0092 end -0093 end -0094 end -0095 end -0096 %Delete or block the reactions that cannot carry flux: -0097 if removeBlockedRxns -0098 rxnsToRemove = reducedModel.rxns(~canCarryFlux); -0099 reducedModel = removeReactions(reducedModel,rxnsToRemove,removeUnusedMets,true); -0100 else -0101 reducedModel = removeReactions(reducedModel,[],removeUnusedMets,true); -0102 reducedModel.lb(~canCarryFlux) = 0; -0103 reducedModel.ub(~canCarryFlux) = 0; -0104 end -0105 end -0106 end -0107 %Format grRules and rxnGeneMatrix after all modifications -0108 if standardizeRules -0109 [grRules,rxnGeneMat] = standardizeGrRules(reducedModel,true); -0110 reducedModel.grRules = grRules; -0111 reducedModel.rxnGeneMat = rxnGeneMat; -0112 end -0113 end -0114 -0115 function canIt = canRxnCarryFlux(geneRule,geneToRemove) -0116 %Evaluate the rule with geneToRemove absent and every other gene present. -0117 %A complex (AND) needs all of its subunits, isozymes (OR) need only one. -0118 tree = parseGrRule(geneRule); -0119 if isempty(tree) -0120 canIt = true; -0121 return -0122 end -0123 canIt = evalWithout(tree,geneToRemove); +0020 % write the gene rules in the standard format (default true). +0021 % +0022 % Returns +0023 % ------- +0024 % reducedModel : struct +0025 % an updated model structure. Genes in genesToRemove that are not in the +0026 % model are ignored. When none of them is in the model, no gene, reaction +0027 % or bound changes (with standardizeRules true the gene rules are still +0028 % written in the standard format). Otherwise, besides genesToRemove, every +0029 % gene that no remaining reaction used before the removal is deleted as +0030 % well, also when it was not requested (for example a gene that no +0031 % reaction uses). A gene that only occurs in a rule part removed with +0032 % another gene stays, because the reaction used it before the removal +0033 % (for example G3 in (G1 and G3) or G2 after removing G1). A gene also +0034 % stays when its reaction remains, even if that reaction is blocked +0035 % (removeBlockedRxns false), such as the other subunit of a complex that +0036 % lost one. +0037 % +0038 % Examples +0039 % -------- +0040 % reducedModel = removeGenes(model, genesToRemove, removeUnusedMets, ... +0041 % removeBlockedRxns, standardizeRules); +0042 +0043 p=parseRAVENargs(varargin, {'removeUnusedMets',false; 'removeBlockedRxns',false; 'standardizeRules',true}); +0044 removeUnusedMets=p.removeUnusedMets; +0045 removeBlockedRxns=p.removeBlockedRxns; +0046 standardizeRules=p.standardizeRules; +0047 %Format grRules and rxnGeneMatrix: +0048 if standardizeRules +0049 [grRules,rxnGeneMat,toCheck] = standardizeGrRules(model,true); +0050 model.grRules = grRules; +0051 model.rxnGeneMat = rxnGeneMat; +0052 else +0053 toCheck = []; +0054 rxnGeneMat = model.rxnGeneMat; +0055 end +0056 reducedModel = model; +0057 %Only remove genes that are actually in the model +0058 if ~(islogical(genesToRemove) || isnumeric(genesToRemove)) +0059 genesToRemove=convertCharArray(genesToRemove); +0060 genesToRemove=genesToRemove(ismember(genesToRemove,model.genes)); +0061 end +0062 if ~isempty(genesToRemove) +0063 indexesToRemove = getIndexes(model,genesToRemove,'genes'); +0064 if ~isempty(indexesToRemove) +0065 %Make 0 corresponding columns from rxnGeneMat: +0066 reducedModel.rxnGeneMat(:,indexesToRemove) = 0; +0067 genes = model.genes(indexesToRemove); +0068 %Check if conflicting grRules contain any of the genes to remove +0069 if ~isempty(toCheck) +0070 for i=1:numel(toCheck) +0071 index = toCheck(i); +0072 gIdxs = find(rxnGeneMat(index,:)); +0073 g2check = model.genes(gIdxs); +0074 if any(ismember(g2check,genes)) +0075 warning(['Conflicting grRule #' num2str(index) ' (' model.rxns{index} ') contains at least one of the genes to be removed, this grRule will be bypassed in order to avoid logical errors']) +0076 end +0077 end +0078 end +0079 canCarryFlux = true(size(model.rxns)); +0080 %Loop through genes and adapt rxns: +0081 for i = 1:length(genes) +0082 %Find all reactions for this gene and loop through them: +0083 geneRxns = find(rxnGeneMat(:,indexesToRemove(i))); +0084 if ~isempty(geneRxns) +0085 for j = 1:numel(geneRxns) +0086 index = geneRxns(j); +0087 grRule = reducedModel.grRules{index}; +0088 if ~ismember(index,toCheck) && canCarryFlux(index) && ~isempty(grRule) +0089 %Check if rxn can carry flux without this gene: +0090 canCarryFlux(index) = canRxnCarryFlux(grRule,genes{i}); +0091 %Adapt gene rule & gene matrix: +0092 grRule = removeGeneFromRule(grRule,genes{i}); +0093 reducedModel.grRules{index} = grRule; +0094 end +0095 end +0096 end +0097 end +0098 %Delete or block the reactions that cannot carry flux: +0099 if removeBlockedRxns +0100 rxnsToRemove = reducedModel.rxns(~canCarryFlux); +0101 reducedModel = removeReactions(reducedModel,rxnsToRemove,removeUnusedMets,true); +0102 else +0103 reducedModel = removeReactions(reducedModel,[],removeUnusedMets,true); +0104 reducedModel.lb(~canCarryFlux) = 0; +0105 reducedModel.ub(~canCarryFlux) = 0; +0106 end +0107 end +0108 end +0109 %Format grRules and rxnGeneMatrix after all modifications +0110 if standardizeRules +0111 [grRules,rxnGeneMat] = standardizeGrRules(reducedModel,true); +0112 reducedModel.grRules = grRules; +0113 reducedModel.rxnGeneMat = rxnGeneMat; +0114 end +0115 end +0116 +0117 function canIt = canRxnCarryFlux(geneRule,geneToRemove) +0118 %Evaluate the rule with geneToRemove absent and every other gene present. +0119 %A complex (AND) needs all of its subunits, isozymes (OR) need only one. +0120 tree = parseGrRule(geneRule); +0121 if isempty(tree) +0122 canIt = true; +0123 return 0124 end -0125 -0126 function tf = evalWithout(node,geneToRemove) -0127 switch node.type -0128 case 'gene' -0129 tf = ~strcmp(node.id,geneToRemove); -0130 case 'and' -0131 tf = all(cellfun(@(c) evalWithout(c,geneToRemove),node.children)); -0132 case 'or' -0133 tf = any(cellfun(@(c) evalWithout(c,geneToRemove),node.children)); -0134 otherwise -0135 error('RAVEN:badGrRule',['Unexpected grRule node type: ' node.type]); -0136 end -0137 end -0138 -0139 function geneRule = removeGeneFromRule(geneRule,geneToRemove) -0140 %This function receives a gene rule and it returns it without the chosen -0141 %gene. A complex that loses a subunit is dropped whole; an isozyme is -0142 %dropped from its OR without disturbing the alternatives. -0143 tree = parseGrRule(geneRule); -0144 geneRule = grRuleToString(pruneGene(tree,geneToRemove)); -0145 end -0146 -0147 function node = pruneGene(node,geneToRemove) -0148 %Remove geneToRemove from the tree, returning [] when nothing is left that -0149 %can catalyse the reaction. -0150 if isempty(node) -0151 node = []; -0152 return -0153 end -0154 switch node.type -0155 case 'gene' -0156 if strcmp(node.id,geneToRemove) -0157 node = []; -0158 end -0159 case 'and' -0160 %A complex missing any subunit cannot form at all. -0161 for k = 1:numel(node.children) -0162 if isempty(pruneGene(node.children{k},geneToRemove)) -0163 node = []; -0164 return -0165 end -0166 end -0167 case 'or' -0168 kept = {}; -0169 for k = 1:numel(node.children) -0170 child = pruneGene(node.children{k},geneToRemove); -0171 if ~isempty(child) -0172 kept{end+1} = child; %#ok<AGROW> -0173 end -0174 end -0175 if isempty(kept) -0176 node = []; -0177 elseif isscalar(kept) -0178 node = kept{1}; -0179 else -0180 node.children = kept; -0181 end -0182 otherwise -0183 error('RAVEN:badGrRule',['Unexpected grRule node type: ' node.type]); -0184 end -0185 end

    +0125 canIt = evalWithout(tree,geneToRemove); +0126 end +0127 +0128 function tf = evalWithout(node,geneToRemove) +0129 switch node.type +0130 case 'gene' +0131 tf = ~strcmp(node.id,geneToRemove); +0132 case 'and' +0133 tf = all(cellfun(@(c) evalWithout(c,geneToRemove),node.children)); +0134 case 'or' +0135 tf = any(cellfun(@(c) evalWithout(c,geneToRemove),node.children)); +0136 otherwise +0137 error('RAVEN:badGrRule',['Unexpected grRule node type: ' node.type]); +0138 end +0139 end +0140 +0141 function geneRule = removeGeneFromRule(geneRule,geneToRemove) +0142 %This function receives a gene rule and it returns it without the chosen +0143 %gene. A complex that loses a subunit is dropped whole; an isozyme is +0144 %dropped from its OR without disturbing the alternatives. +0145 tree = parseGrRule(geneRule); +0146 geneRule = grRuleToString(pruneGene(tree,geneToRemove)); +0147 end +0148 +0149 function node = pruneGene(node,geneToRemove) +0150 %Remove geneToRemove from the tree, returning [] when nothing is left that +0151 %can catalyse the reaction. +0152 if isempty(node) +0153 node = []; +0154 return +0155 end +0156 switch node.type +0157 case 'gene' +0158 if strcmp(node.id,geneToRemove) +0159 node = []; +0160 end +0161 case 'and' +0162 %A complex missing any subunit cannot form at all. +0163 for k = 1:numel(node.children) +0164 if isempty(pruneGene(node.children{k},geneToRemove)) +0165 node = []; +0166 return +0167 end +0168 end +0169 case 'or' +0170 kept = {}; +0171 for k = 1:numel(node.children) +0172 child = pruneGene(node.children{k},geneToRemove); +0173 if ~isempty(child) +0174 kept{end+1} = child; %#ok<AGROW> +0175 end +0176 end +0177 if isempty(kept) +0178 node = []; +0179 elseif isscalar(kept) +0180 node = kept{1}; +0181 else +0182 node.children = kept; +0183 end +0184 otherwise +0185 error('RAVEN:badGrRule',['Unexpected grRule node type: ' node.type]); +0186 end +0187 end


    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/manipulation/removeLowScoreGenes.html b/docs/html/manipulation/removeLowScoreGenes.html index fc4034ad..7a67b836 100644 --- a/docs/html/manipulation/removeLowScoreGenes.html +++ b/docs/html/manipulation/removeLowScoreGenes.html @@ -4,7 +4,7 @@ Description of removeLowScoreGenes - + @@ -21,18 +21,18 @@

    removeLowScoreGenes

    PURPOSE ^

    -
    removeLowScoreGenes Remove low-scoring genes from model.
    +
    removeLowScoreGenes Remove low-score genes from a model.

    SYNOPSIS ^

    function [newModel,remGenes] = removeLowScoreGenes(model,geneScores,varargin)

    DESCRIPTION ^

    -
     removeLowScoreGenes  Remove low-scoring genes from model.
    +
     removeLowScoreGenes  Remove low-score genes from a model.
     
    - This function removes genes from a model based on their scores, a step used
    - by the tINIT package. The function recognizes and differentiates between
    - isozymes and subunits of an enzyme complex. Genes are removed from each
    - grRule, subject to the following conditions:
    + Removes genes from a model based on their scores, a step used by the tINIT
    + package. The function distinguishes isozymes from subunits of an enzyme
    + complex. Genes are removed from each grRule, subject to the following
    + conditions:
     
          1) At least one gene must remain associated with the reaction.
          2) Genes involved in a complex (joined by ANDs) are not removed.
    @@ -42,17 +42,18 @@ 

    DESCRIPTION ^DESCRIPTION ^CROSS-REFERENCE INFORMATION ^

    @@ -96,12 +99,12 @@

    SUBFUNCTIONS ^SOURCE CODE ^

    0001 function [newModel,remGenes] = removeLowScoreGenes(model,geneScores,varargin)
    -0002 % removeLowScoreGenes  Remove low-scoring genes from model.
    +0002 % removeLowScoreGenes  Remove low-score genes from a model.
     0003 %
    -0004 % This function removes genes from a model based on their scores, a step used
    -0005 % by the tINIT package. The function recognizes and differentiates between
    -0006 % isozymes and subunits of an enzyme complex. Genes are removed from each
    -0007 % grRule, subject to the following conditions:
    +0004 % Removes genes from a model based on their scores, a step used by the tINIT
    +0005 % package. The function distinguishes isozymes from subunits of an enzyme
    +0006 % complex. Genes are removed from each grRule, subject to the following
    +0007 % conditions:
     0008 %
     0009 %     1) At least one gene must remain associated with the reaction.
     0010 %     2) Genes involved in a complex (joined by ANDs) are not removed.
    @@ -111,358 +114,361 @@ 

    SOURCE CODE ^% model : struct 0015 % model structure from which genes are to be removed. 0016 % geneScores : double -0017 % a vector of scores associated with the model genes. Genes with a -0018 % positive score will remain in the model, whereas genes with a negative -0019 % score will try to be removed. -0020 % -0021 % If all genes associated with a reaction have a negative score, then the -0022 % least-negative gene will remain; if there is a tie, the gene listed first -0023 % in the rule will be kept. -0024 % -0025 % If a negative-scoring gene is a subunit in a complex, it will not be -0026 % removed; however, the entire complex may be removed. See the following -0027 % example cases: -0028 % -0029 % - Original: G1 or (G2 and G3 and G4); Negative: G1, G2; New: G2 and G3 -0030 % and G4. -0031 % - Original: G1 or (G2 and G3) or (G4 and G5); Negative: G1, G2; New: G4 -0032 % and G5 [using default complexScoring]. -0033 % - Original: (G1 and (G2 or G3) and G4); Negative: G2; New: (G1 and G3 -0034 % and G4). -0035 % -0036 % Name-Value Arguments -0037 % -------------------- -0038 % isozymeScoring : char -0039 % method used to combine the scores of multiple isozymes; "min", "max", -0040 % "median", or "average" (default "max"). -0041 % complexScoring : char -0042 % method used to combine the scores of enzyme complex subunits: "min", -0043 % "max", "median", or "average" (default "min"). -0044 % -0045 % Returns -0046 % ------- -0047 % newModel : struct -0048 % model with updated gene, grRules, and rxnGeneMat fields after attempting -0049 % to remove negative-score genes. -0050 % remGenes : cell -0051 % a list of negative-score genes that were fully removed from the model. -0052 % Negative-score genes that were removed from some grRules but not all -0053 % will not be included in this list. -0054 -0055 -0056 p=parseRAVENargs(varargin, {'isozymeScoring',[]; 'complexScoring','min'}); -0057 isozymeScoring=p.isozymeScoring; -0058 complexScoring=p.complexScoring; -0059 if isempty(isozymeScoring) -0060 isozymeScoring = 'max'; -0061 end -0062 -0063 if ~isequal(size(model.genes),size(geneScores)) -0064 error('The dimensions of model genes and geneScores do not match.'); -0065 end -0066 -0067 % convert logical operators to symbols -0068 grRules = model.grRules; -0069 if any(contains(grRules,{'&','|'})) -0070 symbolicRules = true; -0071 else -0072 symbolicRules = false; -0073 end -0074 grRules = regexprep(grRules,' and ',' & '); -0075 grRules = regexprep(grRules,' or ',' | '); -0076 -0077 % get unique list of grRules -0078 [uRules,~,rule_ind] = unique(grRules); +0017 % a vector of scores associated with the model genes, with the same +0018 % dimensions as model.genes. Genes with a score of zero or above and genes +0019 % without a score (NaN) remain in the rules; a gene that no rule uses +0020 % afterwards is removed from model.genes. Genes with a negative score +0021 % are removed from the rules when the conditions above allow it. +0022 % +0023 % If all genes associated with a reaction have a negative score, the +0024 % least-negative gene remains; in a tie, the gene listed first in the rule +0025 % is kept. +0026 % +0027 % A negative-scoring gene that is a subunit in a complex is not removed, +0028 % but the entire complex may be removed. Example cases: +0029 % +0030 % - Original: G1 or (G2 and G3 and G4); Negative: G1, G2; New: G2 and G3 +0031 % and G4. +0032 % - Original: G1 or (G2 and G3) or (G4 and G5); Negative: G1, G2; New: G4 +0033 % and G5 [using default complexScoring]. +0034 % - Original: (G1 and (G2 or G3) and G4); Negative: G2; New: (G1 and G3 +0035 % and G4). +0036 % +0037 % Name-Value Arguments +0038 % -------------------- +0039 % isozymeScoring : char +0040 % method used to combine the scores of multiple isozymes: "min", "max", +0041 % "median" or "average" (default "max"). +0042 % complexScoring : char +0043 % method used to combine the scores of enzyme complex subunits: "min", +0044 % "max", "median" or "average" (default "min"). +0045 % +0046 % Returns +0047 % ------- +0048 % newModel : struct +0049 % model with updated genes, grRules and rxnGeneMat fields, and with the +0050 % gene-associated fields (geneShortNames, proteins, geneMiriams, geneFrom, +0051 % geneComps) trimmed to the remaining genes. +0052 % remGenes : cell +0053 % the genes removed from model.genes: negative-score genes removed from every +0054 % grRule and genes that no rule uses, whatever their score. Negative-score +0055 % genes that are removed from some grRules but not all are not included in +0056 % this list. +0057 +0058 +0059 p=parseRAVENargs(varargin, {'isozymeScoring',[]; 'complexScoring','min'}); +0060 isozymeScoring=p.isozymeScoring; +0061 complexScoring=p.complexScoring; +0062 if isempty(isozymeScoring) +0063 isozymeScoring = 'max'; +0064 end +0065 +0066 if ~isequal(size(model.genes),size(geneScores)) +0067 error('The dimensions of model genes and geneScores do not match.'); +0068 end +0069 +0070 % convert logical operators to symbols +0071 grRules = model.grRules; +0072 if any(contains(grRules,{'&','|'})) +0073 symbolicRules = true; +0074 else +0075 symbolicRules = false; +0076 end +0077 grRules = regexprep(grRules,' and ',' & '); +0078 grRules = regexprep(grRules,' or ',' | '); 0079 -0080 % iterate through each rule -0081 newRules = uRules; %initialize newRules -0082 for i = 1:numel(uRules) -0083 if isempty(uRules{i}) || ~contains(uRules{i},'|') -0084 continue -0085 elseif contains(uRules{i},'&') -0086 newRules{i} = processComplexRule(uRules{i},model.genes,geneScores,isozymeScoring,complexScoring); -0087 else -0088 newRules{i} = processSimpleRule(uRules{i},model.genes,geneScores,isozymeScoring,complexScoring); -0089 end -0090 end -0091 -0092 % re-map unique rules to model -0093 newModel = model; -0094 newModel.grRules = newRules(rule_ind); -0095 -0096 % restore original logical operator formatting if it was changed -0097 if ~symbolicRules -0098 newModel.grRules = regexprep(newModel.grRules, ' \| ', ' or '); -0099 newModel.grRules = regexprep(newModel.grRules, ' & ', ' and '); -0100 end -0101 -0102 % regenerate "genes" and "rxnGeneMat" model fields -0103 [genes,rxnGeneMat] = getGenesFromGrRules(newModel.grRules); +0080 % get unique list of grRules +0081 [uRules,~,rule_ind] = unique(grRules); +0082 +0083 % iterate through each rule +0084 newRules = uRules; %initialize newRules +0085 for i = 1:numel(uRules) +0086 if isempty(uRules{i}) || ~contains(uRules{i},'|') +0087 continue +0088 elseif contains(uRules{i},'&') +0089 newRules{i} = processComplexRule(uRules{i},model.genes,geneScores,isozymeScoring,complexScoring); +0090 else +0091 newRules{i} = processSimpleRule(uRules{i},model.genes,geneScores,isozymeScoring,complexScoring); +0092 end +0093 end +0094 +0095 % re-map unique rules to model +0096 newModel = model; +0097 newModel.grRules = newRules(rule_ind); +0098 +0099 % restore original logical operator formatting if it was changed +0100 if ~symbolicRules +0101 newModel.grRules = regexprep(newModel.grRules, ' \| ', ' or '); +0102 newModel.grRules = regexprep(newModel.grRules, ' & ', ' and '); +0103 end 0104 -0105 % determine which of the original genes were removed -0106 remInd = ~ismember(model.genes,genes); -0107 remGenes = model.genes(remInd); -0108 -0109 % Keep the retained genes in their original order rather than the sorted -0110 % order returned by getGenesFromGrRules. Gene removal never introduces new -0111 % genes, so the remaining genes are model.genes(~remInd). Preserving this -0112 % order ensures the gene-associated fields trimmed below (geneShortNames, -0113 % proteins, etc.), which are indexed by remInd, stay aligned with genes. -0114 newModel.genes = model.genes(~remInd); -0115 [~,reorderInd] = ismember(newModel.genes,genes); -0116 newModel.rxnGeneMat = rxnGeneMat(:,reorderInd); -0117 -0118 if isfield(newModel,'geneShortNames') -0119 newModel.geneShortNames(remInd) = []; -0120 end -0121 if isfield(newModel,'proteins') -0122 newModel.proteins(remInd) = []; +0105 % regenerate "genes" and "rxnGeneMat" model fields +0106 [genes,rxnGeneMat] = getGenesFromGrRules(newModel.grRules); +0107 +0108 % determine which of the original genes were removed +0109 remInd = ~ismember(model.genes,genes); +0110 remGenes = model.genes(remInd); +0111 +0112 % Keep the retained genes in their original order rather than the sorted +0113 % order returned by getGenesFromGrRules. Gene removal never introduces new +0114 % genes, so the remaining genes are model.genes(~remInd). Preserving this +0115 % order ensures the gene-associated fields trimmed below (geneShortNames, +0116 % proteins, etc.), which are indexed by remInd, stay aligned with genes. +0117 newModel.genes = model.genes(~remInd); +0118 [~,reorderInd] = ismember(newModel.genes,genes); +0119 newModel.rxnGeneMat = rxnGeneMat(:,reorderInd); +0120 +0121 if isfield(newModel,'geneShortNames') +0122 newModel.geneShortNames(remInd) = []; 0123 end -0124 if isfield(newModel,'geneMiriams') -0125 newModel.geneMiriams(remInd) = []; +0124 if isfield(newModel,'proteins') +0125 newModel.proteins(remInd) = []; 0126 end -0127 if isfield(newModel,'geneFrom') -0128 newModel.geneFrom(remInd) = []; +0127 if isfield(newModel,'geneMiriams') +0128 newModel.geneMiriams(remInd) = []; 0129 end -0130 if isfield(newModel,'geneComps') -0131 newModel.geneComps(remInd) = []; +0130 if isfield(newModel,'geneFrom') +0131 newModel.geneFrom(remInd) = []; 0132 end -0133 -0134 +0133 if isfield(newModel,'geneComps') +0134 newModel.geneComps(remInd) = []; 0135 end 0136 0137 -0138 -0139 function [updatedRule,rScore] = processSimpleRule(rule,genes,gScores,isozymeScoring,complexScoring) -0140 % Either score or modify a reaction gene rule containig only ANDs or ORs. -0141 % -0142 % If the rule contains an enzyme complex (all ANDs), the complex will be -0143 % scored based on the score of its subunits. Subunits without a score (NaN) -0144 % will be excluded from the score calculation. -0145 % -0146 % If the rule contains only isozymes (all ORs), the negative-score genes -0147 % will be removed from the rule. Isozymes without a score (NaN) will not be -0148 % removed from the rule. The resuling rule will then be scored. -0149 -0150 -0151 % get IDs and indices of genes involved in rule -0152 ruleGenes = unique(regexp(rule,'[^&|\(\) ]+','match'),'stable'); -0153 [~,geneInd] = ismember(ruleGenes,genes); -0154 -0155 % rules with one or no genes remain unchanged -0156 if numel(ruleGenes) < 2 -0157 rScore = gScores(geneInd); -0158 updatedRule = rule; -0159 return -0160 end -0161 -0162 if ~contains(rule,'&') % rule contains isozymes -0163 -0164 scoreMethod = isozymeScoring; -0165 negInd = gScores(geneInd) < 0; % NaNs will return false here -0166 if all(negInd) -0167 % deterministic least-negative: sort descending; the stable sort -0168 % breaks ties by the order of the genes in the rule string -0169 [~,sortOrder] = sort(gScores(geneInd),'descend'); -0170 maxInd = sortOrder(1); -0171 updatedRule = ruleGenes{maxInd}; -0172 elseif sum(~negInd) == 1 -0173 updatedRule = ruleGenes{~negInd}; -0174 else -0175 updatedRule = strjoin(ruleGenes(~negInd),' | '); -0176 if startsWith(rule,'(') -0177 updatedRule = ['(',updatedRule,')']; -0178 end -0179 end -0180 -0181 % update ruleGenes and their indices -0182 ruleGenes = unique(regexp(updatedRule,'[^&|\(\) ]+','match'),'stable'); -0183 [~,geneInd] = ismember(ruleGenes,genes); -0184 -0185 elseif ~contains(rule,'|') % rule contains enzyme complex -0186 scoreMethod = complexScoring; -0187 updatedRule = rule; -0188 else -0189 error('This function cannot handle rules with both "OR" and "AND" expressions.'); -0190 end -0191 -0192 % score rule -0193 switch lower(scoreMethod) -0194 case 'min' -0195 rScore = min(gScores(geneInd),[],'omitnan'); -0196 case 'max' -0197 rScore = max(gScores(geneInd),[],'omitnan'); -0198 case 'median' -0199 rScore = median(gScores(geneInd),'omitnan'); -0200 case 'average' -0201 rScore = mean(gScores(geneInd),'omitnan'); -0202 end -0203 -0204 end -0205 +0138 end +0139 +0140 +0141 +0142 function [updatedRule,rScore] = processSimpleRule(rule,genes,gScores,isozymeScoring,complexScoring) +0143 % Either score or modify a reaction gene rule containig only ANDs or ORs. +0144 % +0145 % If the rule contains an enzyme complex (all ANDs), the complex will be +0146 % scored based on the score of its subunits. Subunits without a score (NaN) +0147 % will be excluded from the score calculation. +0148 % +0149 % If the rule contains only isozymes (all ORs), the negative-score genes +0150 % will be removed from the rule. Isozymes without a score (NaN) will not be +0151 % removed from the rule. The resuling rule will then be scored. +0152 +0153 +0154 % get IDs and indices of genes involved in rule +0155 ruleGenes = unique(regexp(rule,'[^&|\(\) ]+','match'),'stable'); +0156 [~,geneInd] = ismember(ruleGenes,genes); +0157 +0158 % rules with one or no genes remain unchanged +0159 if numel(ruleGenes) < 2 +0160 rScore = gScores(geneInd); +0161 updatedRule = rule; +0162 return +0163 end +0164 +0165 if ~contains(rule,'&') % rule contains isozymes +0166 +0167 scoreMethod = isozymeScoring; +0168 negInd = gScores(geneInd) < 0; % NaNs will return false here +0169 if all(negInd) +0170 % deterministic least-negative: sort descending; the stable sort +0171 % breaks ties by the order of the genes in the rule string +0172 [~,sortOrder] = sort(gScores(geneInd),'descend'); +0173 maxInd = sortOrder(1); +0174 updatedRule = ruleGenes{maxInd}; +0175 elseif sum(~negInd) == 1 +0176 updatedRule = ruleGenes{~negInd}; +0177 else +0178 updatedRule = strjoin(ruleGenes(~negInd),' | '); +0179 if startsWith(rule,'(') +0180 updatedRule = ['(',updatedRule,')']; +0181 end +0182 end +0183 +0184 % update ruleGenes and their indices +0185 ruleGenes = unique(regexp(updatedRule,'[^&|\(\) ]+','match'),'stable'); +0186 [~,geneInd] = ismember(ruleGenes,genes); +0187 +0188 elseif ~contains(rule,'|') % rule contains enzyme complex +0189 scoreMethod = complexScoring; +0190 updatedRule = rule; +0191 else +0192 error('This function cannot handle rules with both "OR" and "AND" expressions.'); +0193 end +0194 +0195 % score rule +0196 switch lower(scoreMethod) +0197 case 'min' +0198 rScore = min(gScores(geneInd),[],'omitnan'); +0199 case 'max' +0200 rScore = max(gScores(geneInd),[],'omitnan'); +0201 case 'median' +0202 rScore = median(gScores(geneInd),'omitnan'); +0203 case 'average' +0204 rScore = mean(gScores(geneInd),'omitnan'); +0205 end 0206 -0207 -0208 function updatedRule = processComplexRule(rule,genes,gScores,isozymeScoring,complexScoring) -0209 % Update reactions containing both AND and OR expressions via recursive descent. -0210 % -0211 % Walks the boolean parse tree: OR nodes (isozymes) prune negative-score -0212 % branches while keeping at least one; AND nodes (complexes) are scored as -0213 % a unit and may be removed at the enclosing OR level. Nested isozyme groups -0214 % within a complex are simplified in place. -0215 updatedRule = evalGPR(rule, genes, gScores, isozymeScoring, complexScoring); -0216 end -0217 -0218 -0219 -0220 function [rule, score] = evalGPR(expr, genes, gScores, isozymeScoring, complexScoring) -0221 % Recursively evaluate and simplify a GPR expression. -0222 % NaN-scored genes/complexes are not pruned (treated as non-negative). -0223 -0224 expr = strtrim(expr); -0225 -0226 % Strip redundant outer parentheses -0227 while numel(expr) >= 2 && expr(1) == '(' -0228 closePos = findMatchingClose(expr, 1); -0229 if closePos == numel(expr) -0230 expr = strtrim(expr(2:end-1)); -0231 else -0232 break -0233 end -0234 end -0235 -0236 % OR split: isozyme level -0237 orTerms = splitDepth0(expr, '|'); -0238 if numel(orTerms) > 1 -0239 termScores = nan(1, numel(orTerms)); -0240 termRules = cell(1, numel(orTerms)); -0241 for k = 1:numel(orTerms) -0242 [termRules{k}, termScores(k)] = evalGPR(orTerms{k}, genes, gScores, isozymeScoring, complexScoring); -0243 end -0244 % NaN-scored terms are kept; negatives pruned when alternatives exist -0245 negIdx = termScores < 0; % NaN -> false, so NaN terms are retained -0246 if all(negIdx) -0247 % All terms negative: keep the least-negative (deterministic) -0248 [~, ord] = sort(termScores, 'descend'); -0249 keepIdx = ord(1); -0250 else -0251 keepIdx = find(~negIdx); -0252 end -0253 termRules = termRules(keepIdx); -0254 termScores = termScores(keepIdx); -0255 rule = joinOR(termRules); -0256 score = applyScoring(termScores, isozymeScoring); -0257 return -0258 end -0259 -0260 % AND split: complex level -0261 andTerms = splitDepth0(expr, '&'); -0262 if numel(andTerms) > 1 -0263 termScores = nan(1, numel(andTerms)); -0264 termRules = cell(1, numel(andTerms)); -0265 for k = 1:numel(andTerms) -0266 [termRules{k}, termScores(k)] = evalGPR(andTerms{k}, genes, gScores, isozymeScoring, complexScoring); -0267 end -0268 score = applyScoring(termScores, complexScoring); -0269 rule = joinAND(termRules); -0270 return -0271 end -0272 -0273 % Leaf: single gene -0274 geneIdx = find(strcmp(genes, expr), 1); -0275 if ~isempty(geneIdx) -0276 score = gScores(geneIdx); -0277 else -0278 score = NaN; -0279 end -0280 rule = expr; -0281 end -0282 -0283 -0284 -0285 function terms = splitDepth0(expr, sep) -0286 % Split expr on ' sep ' at bracket depth 0. -0287 terms = {}; -0288 depth = 0; -0289 start = 1; -0290 n = numel(expr); -0291 i = 1; -0292 while i <= n -0293 c = expr(i); -0294 if c == '(' -0295 depth = depth + 1; -0296 i = i + 1; -0297 elseif c == ')' -0298 depth = depth - 1; +0207 end +0208 +0209 +0210 +0211 function updatedRule = processComplexRule(rule,genes,gScores,isozymeScoring,complexScoring) +0212 % Update reactions containing both AND and OR expressions via recursive descent. +0213 % +0214 % Walks the boolean parse tree: OR nodes (isozymes) prune negative-score +0215 % branches while keeping at least one; AND nodes (complexes) are scored as +0216 % a unit and may be removed at the enclosing OR level. Nested isozyme groups +0217 % within a complex are simplified in place. +0218 updatedRule = evalGPR(rule, genes, gScores, isozymeScoring, complexScoring); +0219 end +0220 +0221 +0222 +0223 function [rule, score] = evalGPR(expr, genes, gScores, isozymeScoring, complexScoring) +0224 % Recursively evaluate and simplify a GPR expression. +0225 % NaN-scored genes/complexes are not pruned (treated as non-negative). +0226 +0227 expr = strtrim(expr); +0228 +0229 % Strip redundant outer parentheses +0230 while numel(expr) >= 2 && expr(1) == '(' +0231 closePos = findMatchingClose(expr, 1); +0232 if closePos == numel(expr) +0233 expr = strtrim(expr(2:end-1)); +0234 else +0235 break +0236 end +0237 end +0238 +0239 % OR split: isozyme level +0240 orTerms = splitDepth0(expr, '|'); +0241 if numel(orTerms) > 1 +0242 termScores = nan(1, numel(orTerms)); +0243 termRules = cell(1, numel(orTerms)); +0244 for k = 1:numel(orTerms) +0245 [termRules{k}, termScores(k)] = evalGPR(orTerms{k}, genes, gScores, isozymeScoring, complexScoring); +0246 end +0247 % NaN-scored terms are kept; negatives pruned when alternatives exist +0248 negIdx = termScores < 0; % NaN -> false, so NaN terms are retained +0249 if all(negIdx) +0250 % All terms negative: keep the least-negative (deterministic) +0251 [~, ord] = sort(termScores, 'descend'); +0252 keepIdx = ord(1); +0253 else +0254 keepIdx = find(~negIdx); +0255 end +0256 termRules = termRules(keepIdx); +0257 termScores = termScores(keepIdx); +0258 rule = joinOR(termRules); +0259 score = applyScoring(termScores, isozymeScoring); +0260 return +0261 end +0262 +0263 % AND split: complex level +0264 andTerms = splitDepth0(expr, '&'); +0265 if numel(andTerms) > 1 +0266 termScores = nan(1, numel(andTerms)); +0267 termRules = cell(1, numel(andTerms)); +0268 for k = 1:numel(andTerms) +0269 [termRules{k}, termScores(k)] = evalGPR(andTerms{k}, genes, gScores, isozymeScoring, complexScoring); +0270 end +0271 score = applyScoring(termScores, complexScoring); +0272 rule = joinAND(termRules); +0273 return +0274 end +0275 +0276 % Leaf: single gene +0277 geneIdx = find(strcmp(genes, expr), 1); +0278 if ~isempty(geneIdx) +0279 score = gScores(geneIdx); +0280 else +0281 score = NaN; +0282 end +0283 rule = expr; +0284 end +0285 +0286 +0287 +0288 function terms = splitDepth0(expr, sep) +0289 % Split expr on ' sep ' at bracket depth 0. +0290 terms = {}; +0291 depth = 0; +0292 start = 1; +0293 n = numel(expr); +0294 i = 1; +0295 while i <= n +0296 c = expr(i); +0297 if c == '(' +0298 depth = depth + 1; 0299 i = i + 1; -0300 elseif c == sep && depth == 0 && i >= 2 && i <= n-1 && expr(i-1) == ' ' && expr(i+1) == ' ' -0301 terms{end+1} = strtrim(expr(start:i-2)); %#ok<AGROW> -0302 start = i + 2; -0303 i = i + 2; -0304 else -0305 i = i + 1; -0306 end -0307 end -0308 terms{end+1} = strtrim(expr(start:end)); -0309 end -0310 -0311 -0312 -0313 function pos = findMatchingClose(expr, openPos) -0314 % Return position of ')' matching '(' at openPos. -0315 depth = 0; -0316 for i = openPos:numel(expr) -0317 if expr(i) == '('; depth = depth + 1; -0318 elseif expr(i) == ')'; depth = depth - 1; -0319 if depth == 0; pos = i; return; end -0320 end -0321 end -0322 pos = numel(expr); -0323 end -0324 -0325 -0326 -0327 function rule = joinOR(rules) -0328 % Join rules with ' | '; wrap AND-containing subterms in parens. -0329 n = numel(rules); -0330 if n > 1 -0331 for k = 1:n -0332 if contains(rules{k}, '&') -0333 rules{k} = ['(' rules{k} ')']; -0334 end -0335 end -0336 end -0337 rule = strjoin(rules, ' | '); -0338 end -0339 -0340 -0341 -0342 function rule = joinAND(rules) -0343 % Join rules with ' & '; wrap OR-containing subterms in parens. -0344 n = numel(rules); -0345 if n > 1 -0346 for k = 1:n -0347 if contains(rules{k}, '|') -0348 rules{k} = ['(' rules{k} ')']; -0349 end -0350 end -0351 end -0352 rule = strjoin(rules, ' & '); -0353 end -0354 -0355 -0356 -0357 function s = applyScoring(scores, method) -0358 switch lower(method) -0359 case 'min'; s = min(scores, [], 'omitnan'); -0360 case 'max'; s = max(scores, [], 'omitnan'); -0361 case 'median'; s = median(scores, 'omitnan'); -0362 case 'average'; s = mean(scores, 'omitnan'); -0363 otherwise; s = max(scores, [], 'omitnan'); -0364 end -0365 end -0366 -0367 -0368

    +0300 elseif c == ')' +0301 depth = depth - 1; +0302 i = i + 1; +0303 elseif c == sep && depth == 0 && i >= 2 && i <= n-1 && expr(i-1) == ' ' && expr(i+1) == ' ' +0304 terms{end+1} = strtrim(expr(start:i-2)); %#ok<AGROW> +0305 start = i + 2; +0306 i = i + 2; +0307 else +0308 i = i + 1; +0309 end +0310 end +0311 terms{end+1} = strtrim(expr(start:end)); +0312 end +0313 +0314 +0315 +0316 function pos = findMatchingClose(expr, openPos) +0317 % Return position of ')' matching '(' at openPos. +0318 depth = 0; +0319 for i = openPos:numel(expr) +0320 if expr(i) == '('; depth = depth + 1; +0321 elseif expr(i) == ')'; depth = depth - 1; +0322 if depth == 0; pos = i; return; end +0323 end +0324 end +0325 pos = numel(expr); +0326 end +0327 +0328 +0329 +0330 function rule = joinOR(rules) +0331 % Join rules with ' | '; wrap AND-containing subterms in parens. +0332 n = numel(rules); +0333 if n > 1 +0334 for k = 1:n +0335 if contains(rules{k}, '&') +0336 rules{k} = ['(' rules{k} ')']; +0337 end +0338 end +0339 end +0340 rule = strjoin(rules, ' | '); +0341 end +0342 +0343 +0344 +0345 function rule = joinAND(rules) +0346 % Join rules with ' & '; wrap OR-containing subterms in parens. +0347 n = numel(rules); +0348 if n > 1 +0349 for k = 1:n +0350 if contains(rules{k}, '|') +0351 rules{k} = ['(' rules{k} ')']; +0352 end +0353 end +0354 end +0355 rule = strjoin(rules, ' & '); +0356 end +0357 +0358 +0359 +0360 function s = applyScoring(scores, method) +0361 switch lower(method) +0362 case 'min'; s = min(scores, [], 'omitnan'); +0363 case 'max'; s = max(scores, [], 'omitnan'); +0364 case 'median'; s = median(scores, 'omitnan'); +0365 case 'average'; s = mean(scores, 'omitnan'); +0366 otherwise; s = max(scores, [], 'omitnan'); +0367 end +0368 end +0369 +0370 +0371

    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/manipulation/removeMets.html b/docs/html/manipulation/removeMets.html index ef308511..1cf98de4 100644 --- a/docs/html/manipulation/removeMets.html +++ b/docs/html/manipulation/removeMets.html @@ -41,16 +41,17 @@

    DESCRIPTION ^SOURCE CODE ^% Name-Value Arguments 0014 % -------------------- 0015 % isNames : logical -0016 % true if the supplied mets represent metabolite names (as opposed to -0017 % IDs). This is a way to delete metabolites in several compartments at -0018 % once without knowing the exact IDs. This only works if metsToRemove -0019 % is a cell array (default false). +0016 % true if the supplied mets are metabolite names, false if they are IDs. +0017 % All metabolites with a listed name are deleted, which removes +0018 % metabolites in several compartments at once. Requires metsToRemove to +0019 % be a cell array (default false). 0020 % removeUnusedRxns : logical -0021 % remove reactions that are no longer in use (default false). +0021 % remove reactions that involve no metabolite (default false). 0022 % removeUnusedGenes : logical -0023 % remove genes that are no longer in use (default false). -0024 % removeUnusedComps : logical -0025 % remove compartments that are no longer in use (default false). -0026 % -0027 % Returns -0028 % ------- -0029 % reducedModel : struct -0030 % an updated model structure. -0031 % -0032 % Examples -0033 % -------- -0034 % reducedModel = removeMets(model, metsToRemove, isNames, ... -0035 % removeUnusedRxns, removeUnusedGenes, removeUnusedComps); -0036 if ~islogical(metsToRemove) && ~isnumeric(metsToRemove) -0037 metsToRemove=convertCharArray(metsToRemove); -0038 end -0039 -0040 p=parseRAVENargs(varargin, {'isNames',false; 'removeUnusedRxns',false; 'removeUnusedGenes',false; 'removeUnusedComps',false}); -0041 isNames=p.isNames; -0042 removeUnusedRxns=p.removeUnusedRxns; -0043 removeUnusedGenes=p.removeUnusedGenes; -0044 removeUnusedComps=p.removeUnusedComps; -0045 -0046 %Check that metsToRemove is a cell array -0047 if isNames==true && ~iscell(metsToRemove) -0048 error('Must supply a cell array of strings if isNames=true'); -0049 end -0050 -0051 reducedModel=model; -0052 -0053 if isNames==false -0054 indexesToDelete=getIndexes(model,metsToRemove,'mets'); -0055 else -0056 indexesToDelete=[]; -0057 for i=1:numel(metsToRemove) -0058 indexesToDelete=[indexesToDelete;find(strcmp(metsToRemove(i),model.metNames))]; -0059 end -0060 end -0061 -0062 %Remove metabolites -0063 if ~isempty(indexesToDelete) -0064 reducedModel.mets(indexesToDelete)=[]; -0065 reducedModel.S(indexesToDelete,:)=[]; -0066 if isfield(reducedModel,'b') -0067 reducedModel.b(indexesToDelete,:)=[]; -0068 end -0069 if isfield(reducedModel,'metNames') -0070 reducedModel.metNames(indexesToDelete)=[]; -0071 end -0072 if isfield(reducedModel,'metComps') -0073 reducedModel.metComps(indexesToDelete)=[]; -0074 end -0075 if isfield(reducedModel,'inchis') -0076 reducedModel.inchis(indexesToDelete)=[]; -0077 end -0078 if isfield(reducedModel,'metSmiles') -0079 reducedModel.metSmiles(indexesToDelete)=[]; -0080 end -0081 if isfield(reducedModel,'metFormulas') -0082 reducedModel.metFormulas(indexesToDelete)=[]; -0083 end -0084 if isfield(reducedModel,'metMiriams') -0085 reducedModel.metMiriams(indexesToDelete)=[]; -0086 end -0087 if isfield(reducedModel,'unconstrained') -0088 reducedModel.unconstrained(indexesToDelete)=[]; -0089 end -0090 if isfield(reducedModel,'metFrom') -0091 reducedModel.metFrom(indexesToDelete)=[]; -0092 end -0093 if isfield(reducedModel,'metCharges') -0094 reducedModel.metCharges(indexesToDelete)=[]; -0095 end -0096 if isfield(reducedModel,'metDeltaG') -0097 reducedModel.metDeltaG(indexesToDelete)=[]; -0098 end -0099 if isfield(reducedModel,'metNotes') -0100 reducedModel.metNotes(indexesToDelete)=[]; -0101 end -0102 end -0103 -0104 %Remove unused reactions -0105 if removeUnusedRxns==true -0106 %Get unused reactions -0107 [~, a]=find(reducedModel.S); -0108 rxnsToRemove=1:numel(reducedModel.rxns); -0109 rxnsToRemove(a)=[]; -0110 reducedModel=removeReactions(reducedModel,rxnsToRemove,false,removeUnusedGenes); -0111 end -0112 -0113 %Remove unused compartments -0114 if removeUnusedComps==true -0115 oldComps=reducedModel.comps; -0116 I=ismember(1:numel(oldComps),reducedModel.metComps); -0117 if ~all(I) -0118 reducedModel.comps(~I)=[]; -0119 reducedModel.compNames(~I)=[]; -0120 if isfield(reducedModel,'compOutside') -0121 reducedModel.compOutside(~I)=[]; -0122 end -0123 if isfield(reducedModel,'compMiriams') -0124 reducedModel.compMiriams(~I)=[]; -0125 end -0126 [~, J]=ismember(oldComps(reducedModel.metComps),reducedModel.comps); -0127 reducedModel.metComps=J; -0128 end -0129 end -0130 end

    +0023 % remove genes that no remaining reaction uses. Takes effect only when +0024 % removeUnusedRxns is true (default false). +0025 % removeUnusedComps : logical +0026 % remove compartments that contain no metabolite (default false). +0027 % +0028 % Returns +0029 % ------- +0030 % reducedModel : struct +0031 % an updated model structure. +0032 % +0033 % Examples +0034 % -------- +0035 % reducedModel = removeMets(model, metsToRemove, isNames, ... +0036 % removeUnusedRxns, removeUnusedGenes, removeUnusedComps); +0037 if ~islogical(metsToRemove) && ~isnumeric(metsToRemove) +0038 metsToRemove=convertCharArray(metsToRemove); +0039 end +0040 +0041 p=parseRAVENargs(varargin, {'isNames',false; 'removeUnusedRxns',false; 'removeUnusedGenes',false; 'removeUnusedComps',false}); +0042 isNames=p.isNames; +0043 removeUnusedRxns=p.removeUnusedRxns; +0044 removeUnusedGenes=p.removeUnusedGenes; +0045 removeUnusedComps=p.removeUnusedComps; +0046 +0047 %Check that metsToRemove is a cell array +0048 if isNames==true && ~iscell(metsToRemove) +0049 error('Must supply a cell array of strings if isNames=true'); +0050 end +0051 +0052 reducedModel=model; +0053 +0054 if isNames==false +0055 indexesToDelete=getIndexes(model,metsToRemove,'mets'); +0056 else +0057 indexesToDelete=[]; +0058 for i=1:numel(metsToRemove) +0059 indexesToDelete=[indexesToDelete;find(strcmp(metsToRemove(i),model.metNames))]; +0060 end +0061 end +0062 +0063 %Remove metabolites +0064 if ~isempty(indexesToDelete) +0065 reducedModel.mets(indexesToDelete)=[]; +0066 reducedModel.S(indexesToDelete,:)=[]; +0067 if isfield(reducedModel,'b') +0068 reducedModel.b(indexesToDelete,:)=[]; +0069 end +0070 if isfield(reducedModel,'metNames') +0071 reducedModel.metNames(indexesToDelete)=[]; +0072 end +0073 if isfield(reducedModel,'metComps') +0074 reducedModel.metComps(indexesToDelete)=[]; +0075 end +0076 if isfield(reducedModel,'inchis') +0077 reducedModel.inchis(indexesToDelete)=[]; +0078 end +0079 if isfield(reducedModel,'metSmiles') +0080 reducedModel.metSmiles(indexesToDelete)=[]; +0081 end +0082 if isfield(reducedModel,'metFormulas') +0083 reducedModel.metFormulas(indexesToDelete)=[]; +0084 end +0085 if isfield(reducedModel,'metMiriams') +0086 reducedModel.metMiriams(indexesToDelete)=[]; +0087 end +0088 if isfield(reducedModel,'unconstrained') +0089 reducedModel.unconstrained(indexesToDelete)=[]; +0090 end +0091 if isfield(reducedModel,'metFrom') +0092 reducedModel.metFrom(indexesToDelete)=[]; +0093 end +0094 if isfield(reducedModel,'metCharges') +0095 reducedModel.metCharges(indexesToDelete)=[]; +0096 end +0097 if isfield(reducedModel,'metDeltaG') +0098 reducedModel.metDeltaG(indexesToDelete)=[]; +0099 end +0100 if isfield(reducedModel,'metNotes') +0101 reducedModel.metNotes(indexesToDelete)=[]; +0102 end +0103 end +0104 +0105 %Remove unused reactions +0106 if removeUnusedRxns==true +0107 %Get unused reactions +0108 [~, a]=find(reducedModel.S); +0109 rxnsToRemove=1:numel(reducedModel.rxns); +0110 rxnsToRemove(a)=[]; +0111 reducedModel=removeReactions(reducedModel,rxnsToRemove,false,removeUnusedGenes); +0112 end +0113 +0114 %Remove unused compartments +0115 if removeUnusedComps==true +0116 oldComps=reducedModel.comps; +0117 I=ismember(1:numel(oldComps),reducedModel.metComps); +0118 if ~all(I) +0119 reducedModel.comps(~I)=[]; +0120 reducedModel.compNames(~I)=[]; +0121 if isfield(reducedModel,'compOutside') +0122 reducedModel.compOutside(~I)=[]; +0123 end +0124 if isfield(reducedModel,'compMiriams') +0125 reducedModel.compMiriams(~I)=[]; +0126 end +0127 [~, J]=ismember(oldComps(reducedModel.metComps),reducedModel.comps); +0128 reducedModel.metComps=J; +0129 end +0130 end +0131 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/manipulation/removeReactions.html b/docs/html/manipulation/removeReactions.html index 69edfa9b..1791ca47 100644 --- a/docs/html/manipulation/removeReactions.html +++ b/docs/html/manipulation/removeReactions.html @@ -41,11 +41,12 @@

    DESCRIPTION ^CROSS-REFERENCE INFORMATION ^
 <li><a href=removeMets removeMets Delete a set of metabolites from a model. This function is called by:
      -
    • changeRxns changeRxns Modify the equations of reactions in a model.
    • contractModel contractModel Contract a model by grouping all identical reactions.
    • copyToComps copyToComps Copy reactions to new compartment(s).
    • deleteUnusedGenes deleteUnusedGenes Delete all genes not associated to any reaction.
    • mergeCompartments mergeCompartments Merge all compartments in a model.
    • removeBadRxns removeBadRxns Remove reactions that enable production/consumption from nothing.
    • removeGenes removeGenes Delete a set of genes from a model.
    • removeMets removeMets Delete a set of metabolites from a model.
    • simplifyModel simplifyModel Simplify a model by deleting reactions and metabolites.
    +
  • changeRxns changeRxns Modify the equations of reactions in a model.
  • contractModel contractModel Contract a model by merging identical reactions.
  • copyToComps copyToComps Copy reactions to new compartment(s).
  • deleteUnusedGenes deleteUnusedGenes Delete all genes not associated to any reaction.
  • mergeCompartments mergeCompartments Merge all compartments in a model.
  • removeBadRxns removeBadRxns Remove reactions that enable production or consumption from nothing.
  • removeGenes removeGenes Delete a set of genes from a model.
  • removeMets removeMets Delete a set of metabolites from a model.
  • simplifyModel simplifyModel Simplify a model by deleting reactions and metabolites.
  • @@ -85,83 +86,84 @@

    SOURCE CODE ^% Name-Value Arguments 0014 % -------------------- 0015 % removeUnusedMets : logical -0016 % remove metabolites that are no longer in use (default false). +0016 % remove metabolites that no remaining reaction uses (default false). 0017 % removeUnusedGenes : logical -0018 % remove genes that are no longer in use (default false). +0018 % remove genes that no remaining reaction uses (default false). 0019 % removeUnusedComps : logical -0020 % remove compartments that are no longer in use (default false). -0021 % -0022 % Returns -0023 % ------- -0024 % reducedModel : struct -0025 % an updated model structure. -0026 % -0027 % Examples -0028 % -------- -0029 % reducedModel = removeReactions(model, rxnsToRemove, removeUnusedMets, ... -0030 % removeUnusedGenes, removeUnusedComps); -0031 -0032 p=parseRAVENargs(varargin, {'removeUnusedMets',false; 'removeUnusedGenes',false; 'removeUnusedComps',false}); -0033 removeUnusedMets=p.removeUnusedMets; -0034 removeUnusedGenes=p.removeUnusedGenes; -0035 removeUnusedComps=p.removeUnusedComps; -0036 if ~islogical(rxnsToRemove) && ~isnumeric(rxnsToRemove) -0037 rxnsToRemove=convertCharArray(rxnsToRemove); -0038 end -0039 -0040 reducedModel=model; -0041 reg=ravenModelFields(); -0042 -0043 if ~isempty(rxnsToRemove) || removeUnusedMets || removeUnusedGenes -0044 indexesToDelete=getIndexes(model,rxnsToRemove,'rxns'); -0045 -0046 %Remove reactions -0047 if ~isempty(indexesToDelete) -0048 rxnReg=reg(strcmp({reg.type},'rxn')); -0049 for ri_=1:numel(rxnReg) -0050 fname=rxnReg(ri_).name; -0051 if isfield(reducedModel,fname) -0052 reducedModel.(fname)(indexesToDelete)=[]; -0053 end -0054 end -0055 if isfield(reducedModel,'S') -0056 reducedModel.S(:,indexesToDelete)=[]; -0057 end -0058 if isfield(reducedModel,'rxnGeneMat') -0059 reducedModel.rxnGeneMat(indexesToDelete,:)=[]; -0060 end -0061 end -0062 -0063 %Remove unused metabolites -0064 if removeUnusedMets==true -0065 if isfield(reducedModel,'S') -0066 [usedMets, ~]=find(reducedModel.S); -0067 unUsedMets=true(numel(reducedModel.mets),1); -0068 unUsedMets(usedMets)=false; -0069 reducedModel=removeMets(reducedModel,unUsedMets,false,false,false,removeUnusedComps); -0070 end -0071 end -0072 -0073 %Remove unused genes -0074 if removeUnusedGenes==true && isfield(reducedModel,'rxnGeneMat') -0075 %Find all genes that are not used -0076 [~, b]=find(reducedModel.rxnGeneMat); -0077 toKeep=false(numel(reducedModel.genes),1); -0078 toKeep(b)=true; -0079 -0080 geneReg=reg(strcmp({reg.type},'gene')); -0081 for ri_=1:numel(geneReg) -0082 fname=geneReg(ri_).name; -0083 if isfield(reducedModel,fname) -0084 reducedModel.(fname)=reducedModel.(fname)(toKeep); -0085 end -0086 end -0087 reducedModel.rxnGeneMat=reducedModel.rxnGeneMat(:,toKeep); -0088 end -0089 else -0090 reducedModel=model; -0091 end -0092 end +0020 % remove compartments that contain no metabolite. Takes effect only when +0021 % removeUnusedMets is true (default false). +0022 % +0023 % Returns +0024 % ------- +0025 % reducedModel : struct +0026 % an updated model structure. +0027 % +0028 % Examples +0029 % -------- +0030 % reducedModel = removeReactions(model, rxnsToRemove, removeUnusedMets, ... +0031 % removeUnusedGenes, removeUnusedComps); +0032 +0033 p=parseRAVENargs(varargin, {'removeUnusedMets',false; 'removeUnusedGenes',false; 'removeUnusedComps',false}); +0034 removeUnusedMets=p.removeUnusedMets; +0035 removeUnusedGenes=p.removeUnusedGenes; +0036 removeUnusedComps=p.removeUnusedComps; +0037 if ~islogical(rxnsToRemove) && ~isnumeric(rxnsToRemove) +0038 rxnsToRemove=convertCharArray(rxnsToRemove); +0039 end +0040 +0041 reducedModel=model; +0042 reg=ravenModelFields(); +0043 +0044 if ~isempty(rxnsToRemove) || removeUnusedMets || removeUnusedGenes +0045 indexesToDelete=getIndexes(model,rxnsToRemove,'rxns'); +0046 +0047 %Remove reactions +0048 if ~isempty(indexesToDelete) +0049 rxnReg=reg(strcmp({reg.type},'rxn')); +0050 for ri_=1:numel(rxnReg) +0051 fname=rxnReg(ri_).name; +0052 if isfield(reducedModel,fname) +0053 reducedModel.(fname)(indexesToDelete)=[]; +0054 end +0055 end +0056 if isfield(reducedModel,'S') +0057 reducedModel.S(:,indexesToDelete)=[]; +0058 end +0059 if isfield(reducedModel,'rxnGeneMat') +0060 reducedModel.rxnGeneMat(indexesToDelete,:)=[]; +0061 end +0062 end +0063 +0064 %Remove unused metabolites +0065 if removeUnusedMets==true +0066 if isfield(reducedModel,'S') +0067 [usedMets, ~]=find(reducedModel.S); +0068 unUsedMets=true(numel(reducedModel.mets),1); +0069 unUsedMets(usedMets)=false; +0070 reducedModel=removeMets(reducedModel,unUsedMets,false,false,false,removeUnusedComps); +0071 end +0072 end +0073 +0074 %Remove unused genes +0075 if removeUnusedGenes==true && isfield(reducedModel,'rxnGeneMat') +0076 %Find all genes that are not used +0077 [~, b]=find(reducedModel.rxnGeneMat); +0078 toKeep=false(numel(reducedModel.genes),1); +0079 toKeep(b)=true; +0080 +0081 geneReg=reg(strcmp({reg.type},'gene')); +0082 for ri_=1:numel(geneReg) +0083 fname=geneReg(ri_).name; +0084 if isfield(reducedModel,fname) +0085 reducedModel.(fname)=reducedModel.(fname)(toKeep); +0086 end +0087 end +0088 reducedModel.rxnGeneMat=reducedModel.rxnGeneMat(:,toKeep); +0089 end +0090 else +0091 reducedModel=model; +0092 end +0093 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/manipulation/replaceMets.html b/docs/html/manipulation/replaceMets.html index a441b72f..094b1e43 100644 --- a/docs/html/manipulation/replaceMets.html +++ b/docs/html/manipulation/replaceMets.html @@ -29,20 +29,24 @@

    SYNOPSIS ^DESCRIPTION ^

     replaceMets  Replace a metabolite with another already in the model.
     
    - Replaces metabolite names and annotation with a replacement metabolite
    - that is already in the model. If this results in duplicate metabolites,
    - the replacement metabolite will be kept, while the S matrix is updated to
    - use the replacement metabolite instead. At the end, contractModel is run
    - to remove any duplicate reactions that might have occurred.
    + Replaces the name and annotation (formula, miriams, charge, deltaG, InChI
    + and SMILES) of a metabolite with those of a replacement metabolite that is
    + already in the model. If this results in duplicate metabolites, the S matrix
    + is updated to use one of them. With identifiers true the replacement
    + metabolite is kept; by name, the metabolite with the lowest index among those
    + of the same name and compartment is kept. At the end, contractModel is run to remove
    + duplicate reactions that this creates.
     
      Parameters
      ----------
      model : struct
          a model structure.
      metabolite : char
    -     string with name of metabolite to be replaced.
    +     name of the metabolite to be replaced (its identifier if identifiers is
    +     true).
      replacement : char
    -     string with name of replacement metabolite.
    +     name of the replacement metabolite (its identifier if identifiers is
    +     true).
     
      Name-Value Arguments
      --------------------
    @@ -50,17 +54,17 @@ 

    DESCRIPTION ^DESCRIPTION ^CROSS-REFERENCE INFORMATION ^

    This function calls:
      -
    • contractModel contractModel Contract a model by grouping all identical reactions.
    +
  • contractModel contractModel Contract a model by merging identical reactions.
  • This function is called by:
    @@ -89,194 +93,198 @@

    SOURCE CODE ^
    0001 function [model, removedRxns, idxDuplRxns]=replaceMets(model,metabolite,replacement,varargin)
     0002 % replaceMets  Replace a metabolite with another already in the model.
     0003 %
    -0004 % Replaces metabolite names and annotation with a replacement metabolite
    -0005 % that is already in the model. If this results in duplicate metabolites,
    -0006 % the replacement metabolite will be kept, while the S matrix is updated to
    -0007 % use the replacement metabolite instead. At the end, contractModel is run
    -0008 % to remove any duplicate reactions that might have occurred.
    -0009 %
    -0010 % Parameters
    -0011 % ----------
    -0012 % model : struct
    -0013 %     a model structure.
    -0014 % metabolite : char
    -0015 %     string with name of metabolite to be replaced.
    -0016 % replacement : char
    -0017 %     string with name of replacement metabolite.
    -0018 %
    -0019 % Name-Value Arguments
    -0020 % --------------------
    -0021 % verbose : logical
    -0022 %     whether to print the ids of reactions that involve the replaced
    -0023 %     metabolite (default false).
    -0024 % identifiers : logical
    -0025 %     true if 'metabolite' and 'replacement' refer to metabolite
    -0026 %     identifiers instead of metabolite names (default false).
    -0027 %
    -0028 % Returns
    -0029 % -------
    -0030 % model : struct
    -0031 %     model structure with selected metabolites replaced.
    -0032 % removedRxns : cell
    -0033 %     identifiers of duplicate reactions that were removed.
    -0034 % idxDuplRxns : double
    -0035 %     index of removedRxns in original model.
    -0036 %
    -0037 % Examples
    -0038 % --------
    -0039 %     [model, removedRxns, idxDuplRxns] = replaceMets(model, metabolite, ...
    -0040 %         replacement, verbose);
    -0041 %
    -0042 % Notes
    -0043 % -----
    -0044 % This function is useful when the model contains both 'oxygen' and 'o2' as
    -0045 % metabolite names. If 'oxygen' and 'o2' are identifiers instead, then the
    -0046 % 'identifiers' flag should be set to true.
    -0047 
    -0048 metabolite=char(metabolite);
    -0049 replacement=char(replacement);
    -0050 
    -0051 p=parseRAVENargs(varargin, {'verbose',false; 'identifiers',false});
    -0052 verbose=p.verbose;
    -0053 if isempty(verbose)
    -0054     verbose=false;
    -0055 end
    -0056 identifiers=p.identifiers;
    -0057 
    -0058 % Find occurence of replacement metabolites. Annotation will be taken from
    -0059 % first metabolite found.
    -0060 if identifiers
    -0061     repIdx = find(strcmp(replacement,model.mets));
    -0062 else
    -0063     repIdx = find(strcmp(replacement,model.metNames));
    -0064 end
    -0065 if isempty(repIdx)
    -0066     error('The replacement metabolite cannot be found in the model.');
    -0067 end
    -0068 % Captured by id, not position: idxDelete below removes rows, which would
    -0069 % otherwise shift repIdx out from under the metabolite it is meant to name
    -0070 repMetIds = model.mets(repIdx);
    -0071 
    -0072 % Change name and information from metabolite to replacement metabolite
    -0073 if identifiers
    -0074     metIdx = find(strcmp(metabolite,model.mets));
    -0075 else
    -0076     metIdx = find(strcmp(metabolite,model.metNames));
    -0077 end
    -0078 if isempty(metIdx)
    -0079     error('The to-be-replaced metabolite cannot be found in the model.');
    -0080 end
    -0081 
    -0082 % Either of the two rows can be the one that survives the merge: with identifiers the
    -0083 % replacement stays, by name the replaced metabolite is renamed and stays while the
    -0084 % replacement's row is deleted below. Both ids are kept so that contractModel is
    -0085 % restricted to the metabolite that is left.
    -0086 repMetIds = [repMetIds; model.mets(metIdx)];
    -0087 
    -0088 rxnsWithMet = find(model.S(metIdx,:));
    -0089 if verbose==true
    -0090     fprintf('\n\nThe following reactions contain the to-be-replaced metabolite as reactant:\n')
    -0091     %Reaction ids are arbitrary text and may contain "%"; print as
    -0092     %literal data rather than as an fprintf format string.
    -0093     fprintf('%s\n', strjoin(model.rxns(rxnsWithMet),newline))
    -0094 end
    -0095 
    -0096 model.metNames(metIdx) = model.metNames(repIdx(1));
    -0097 if isfield(model,'metFormulas')
    -0098     model.metFormulas(metIdx) = model.metFormulas(repIdx(1));
    -0099 end
    -0100 if isfield(model,'metMiriams')
    -0101     model.metMiriams(metIdx) = model.metMiriams(repIdx(1));
    -0102 end
    -0103 if isfield(model,'metCharges')
    -0104     model.metCharges(metIdx) = model.metCharges(repIdx(1));
    -0105 end
    -0106 if isfield(model,'metDeltaG')
    -0107     model.metDeltaG(metIdx) = model.metDeltaG(repIdx(1));
    -0108 end
    -0109 if isfield(model,'inchis')
    -0110     model.inchis(metIdx) = model.inchis(repIdx(1));
    -0111 end
    -0112 if isfield(model,'metSmiles')
    -0113     model.metSmiles(metIdx) = model.metSmiles(repIdx(1));
    -0114 end
    -0115 
    -0116 idxDelete=[];
    -0117 if identifiers
    -0118     originalStoch = model.S(metIdx,rxnsWithMet);
    -0119     % Add rather than overwrite: a reaction where the replacement
    -0120     % metabolite is already itself a participant must keep that
    -0121     % contribution, not have it clobbered by the replaced metabolite's
    -0122     model.S(repIdx,rxnsWithMet) = model.S(repIdx,rxnsWithMet) + originalStoch;
    -0123     model.S(metIdx,rxnsWithMet) = 0;
    -0124     idxDelete = metIdx;
    -0125 else
    -0126     % Run through replacement metabolites and their compartments. If any of the
    -0127     % to-be-replaced metabolites is already present (checked by
    -0128     % metaboliteName[compartment], then the replacement metabolite is kept and
    -0129     % the to-be-replace metabolite ID deleted.
    -0130     
    -0131     % Build list of metaboliteName[compartment]
    -0132     metCompsN =cellstr(num2str(model.metComps));
    -0133     map = containers.Map(cellstr(num2str(transpose(1:length(model.comps)))),model.comps);
    -0134     metCompsN = map.values(metCompsN);
    -0135     metCompsN = strcat(lower(model.metNames),'[',metCompsN,']');
    -0136     
    -0137     for i = 1:length(repIdx)
    -0138         metCompsNidx=find(strcmp(metCompsN(repIdx(i)), metCompsN));
    -0139         if length(metCompsNidx)>1
    -0140             for j = 2:length(metCompsNidx)
    -0141                 model.S(metCompsNidx(1),:) = model.S(metCompsNidx(1),:) + model.S(metCompsNidx(j),:);
    -0142                 idxDelete=[idxDelete; metCompsNidx(j)]; % Make list of metabolite IDs to delete
    -0143             end
    -0144         end
    -0145     end
    -0146 end
    -0147 
    -0148 if ~isempty(idxDelete)
    -0149     model.S(idxDelete,:) =[];
    -0150     model.mets(idxDelete) = [];
    -0151     model.metNames(idxDelete) = [];
    -0152     model.metComps(idxDelete) = [];
    -0153     model.b(idxDelete,:) = [];
    -0154     if isfield(model,'metFormulas')
    -0155         model.metFormulas(idxDelete) = [];
    -0156     end
    -0157     if isfield(model,'unconstrained')
    -0158         model.unconstrained(idxDelete) = [];
    -0159     end
    -0160     if isfield(model,'metMiriams')
    -0161         model.metMiriams(idxDelete) = [];
    -0162     end
    -0163     if isfield(model,'metCharges')
    -0164         model.metCharges(idxDelete) = [];
    -0165     end
    -0166     if isfield(model,'metDeltaG')
    -0167         model.metDeltaG(idxDelete) = [];
    -0168     end
    -0169     if isfield(model,'inchis')
    -0170         model.inchis(idxDelete) = [];
    -0171     end
    -0172     if isfield(model,'metSmiles')
    -0173         model.metSmiles(idxDelete) = [];
    -0174     end
    -0175     if isfield(model,'metFrom')
    -0176         model.metFrom(idxDelete) = [];
    -0177     end
    -0178 end
    -0179 
    -0180 % This could now have created duplicate reactions. Contract model.
    -0181 % repIdx may point past the end, or at the wrong row, once idxDelete has
    -0182 % shifted things above; re-resolve the metabolite that is left by id. An empty
    -0183 % list must not reach contractModel, which would then check every reaction.
    -0184 % distReverse is passed by name rather than positionally as [], which
    -0185 % parseRAVENargs would take literally instead of falling back to its
    -0186 % documented default of true.
    -0187 repIdx = find(ismember(model.mets, repMetIds));
    -0188 if ~isempty(repIdx)
    -0189     model=contractModel(model,'mets',repIdx);
    -0190 end
    -0191 end

    +0004 % Replaces the name and annotation (formula, miriams, charge, deltaG, InChI +0005 % and SMILES) of a metabolite with those of a replacement metabolite that is +0006 % already in the model. If this results in duplicate metabolites, the S matrix +0007 % is updated to use one of them. With identifiers true the replacement +0008 % metabolite is kept; by name, the metabolite with the lowest index among those +0009 % of the same name and compartment is kept. At the end, contractModel is run to remove +0010 % duplicate reactions that this creates. +0011 % +0012 % Parameters +0013 % ---------- +0014 % model : struct +0015 % a model structure. +0016 % metabolite : char +0017 % name of the metabolite to be replaced (its identifier if identifiers is +0018 % true). +0019 % replacement : char +0020 % name of the replacement metabolite (its identifier if identifiers is +0021 % true). +0022 % +0023 % Name-Value Arguments +0024 % -------------------- +0025 % verbose : logical +0026 % whether to print the ids of reactions that involve the replaced +0027 % metabolite (default false). +0028 % identifiers : logical +0029 % true if 'metabolite' and 'replacement' are metabolite identifiers, +0030 % false if they are metabolite names (default false). +0031 % +0032 % Returns +0033 % ------- +0034 % model : struct +0035 % model structure with the selected metabolites replaced. +0036 % removedRxns : cell +0037 % not assigned by the function; requesting this output raises an error. +0038 % idxDuplRxns : double +0039 % not assigned by the function; requesting this output raises an error. +0040 % +0041 % Examples +0042 % -------- +0043 % [model, removedRxns, idxDuplRxns] = replaceMets(model, metabolite, ... +0044 % replacement, verbose); +0045 % +0046 % Notes +0047 % ----- +0048 % The function applies to a model that contains both 'oxygen' and 'o2' as +0049 % metabolite names. If 'oxygen' and 'o2' are identifiers, 'identifiers' is +0050 % set to true. +0051 +0052 metabolite=char(metabolite); +0053 replacement=char(replacement); +0054 +0055 p=parseRAVENargs(varargin, {'verbose',false; 'identifiers',false}); +0056 verbose=p.verbose; +0057 if isempty(verbose) +0058 verbose=false; +0059 end +0060 identifiers=p.identifiers; +0061 +0062 % Find occurence of replacement metabolites. Annotation will be taken from +0063 % first metabolite found. +0064 if identifiers +0065 repIdx = find(strcmp(replacement,model.mets)); +0066 else +0067 repIdx = find(strcmp(replacement,model.metNames)); +0068 end +0069 if isempty(repIdx) +0070 error('The replacement metabolite cannot be found in the model.'); +0071 end +0072 % Captured by id, not position: idxDelete below removes rows, which would +0073 % otherwise shift repIdx out from under the metabolite it is meant to name +0074 repMetIds = model.mets(repIdx); +0075 +0076 % Change name and information from metabolite to replacement metabolite +0077 if identifiers +0078 metIdx = find(strcmp(metabolite,model.mets)); +0079 else +0080 metIdx = find(strcmp(metabolite,model.metNames)); +0081 end +0082 if isempty(metIdx) +0083 error('The to-be-replaced metabolite cannot be found in the model.'); +0084 end +0085 +0086 % Either of the two rows can be the one that survives the merge: with identifiers the +0087 % replacement stays, by name the replaced metabolite is renamed and stays while the +0088 % replacement's row is deleted below. Both ids are kept so that contractModel is +0089 % restricted to the metabolite that is left. +0090 repMetIds = [repMetIds; model.mets(metIdx)]; +0091 +0092 rxnsWithMet = find(model.S(metIdx,:)); +0093 if verbose==true +0094 fprintf('\n\nThe following reactions contain the to-be-replaced metabolite as reactant:\n') +0095 %Reaction ids are arbitrary text and may contain "%"; print as +0096 %literal data rather than as an fprintf format string. +0097 fprintf('%s\n', strjoin(model.rxns(rxnsWithMet),newline)) +0098 end +0099 +0100 model.metNames(metIdx) = model.metNames(repIdx(1)); +0101 if isfield(model,'metFormulas') +0102 model.metFormulas(metIdx) = model.metFormulas(repIdx(1)); +0103 end +0104 if isfield(model,'metMiriams') +0105 model.metMiriams(metIdx) = model.metMiriams(repIdx(1)); +0106 end +0107 if isfield(model,'metCharges') +0108 model.metCharges(metIdx) = model.metCharges(repIdx(1)); +0109 end +0110 if isfield(model,'metDeltaG') +0111 model.metDeltaG(metIdx) = model.metDeltaG(repIdx(1)); +0112 end +0113 if isfield(model,'inchis') +0114 model.inchis(metIdx) = model.inchis(repIdx(1)); +0115 end +0116 if isfield(model,'metSmiles') +0117 model.metSmiles(metIdx) = model.metSmiles(repIdx(1)); +0118 end +0119 +0120 idxDelete=[]; +0121 if identifiers +0122 originalStoch = model.S(metIdx,rxnsWithMet); +0123 % Add rather than overwrite: a reaction where the replacement +0124 % metabolite is already itself a participant must keep that +0125 % contribution, not have it clobbered by the replaced metabolite's +0126 model.S(repIdx,rxnsWithMet) = model.S(repIdx,rxnsWithMet) + originalStoch; +0127 model.S(metIdx,rxnsWithMet) = 0; +0128 idxDelete = metIdx; +0129 else +0130 % Run through replacement metabolites and their compartments. If any of the +0131 % to-be-replaced metabolites is already present (checked by +0132 % metaboliteName[compartment], then the replacement metabolite is kept and +0133 % the to-be-replace metabolite ID deleted. +0134 +0135 % Build list of metaboliteName[compartment] +0136 metCompsN =cellstr(num2str(model.metComps)); +0137 map = containers.Map(cellstr(num2str(transpose(1:length(model.comps)))),model.comps); +0138 metCompsN = map.values(metCompsN); +0139 metCompsN = strcat(lower(model.metNames),'[',metCompsN,']'); +0140 +0141 for i = 1:length(repIdx) +0142 metCompsNidx=find(strcmp(metCompsN(repIdx(i)), metCompsN)); +0143 if length(metCompsNidx)>1 +0144 for j = 2:length(metCompsNidx) +0145 model.S(metCompsNidx(1),:) = model.S(metCompsNidx(1),:) + model.S(metCompsNidx(j),:); +0146 idxDelete=[idxDelete; metCompsNidx(j)]; % Make list of metabolite IDs to delete +0147 end +0148 end +0149 end +0150 end +0151 +0152 if ~isempty(idxDelete) +0153 model.S(idxDelete,:) =[]; +0154 model.mets(idxDelete) = []; +0155 model.metNames(idxDelete) = []; +0156 model.metComps(idxDelete) = []; +0157 model.b(idxDelete,:) = []; +0158 if isfield(model,'metFormulas') +0159 model.metFormulas(idxDelete) = []; +0160 end +0161 if isfield(model,'unconstrained') +0162 model.unconstrained(idxDelete) = []; +0163 end +0164 if isfield(model,'metMiriams') +0165 model.metMiriams(idxDelete) = []; +0166 end +0167 if isfield(model,'metCharges') +0168 model.metCharges(idxDelete) = []; +0169 end +0170 if isfield(model,'metDeltaG') +0171 model.metDeltaG(idxDelete) = []; +0172 end +0173 if isfield(model,'inchis') +0174 model.inchis(idxDelete) = []; +0175 end +0176 if isfield(model,'metSmiles') +0177 model.metSmiles(idxDelete) = []; +0178 end +0179 if isfield(model,'metFrom') +0180 model.metFrom(idxDelete) = []; +0181 end +0182 end +0183 +0184 % This could now have created duplicate reactions. Contract model. +0185 % repIdx may point past the end, or at the wrong row, once idxDelete has +0186 % shifted things above; re-resolve the metabolite that is left by id. An empty +0187 % list must not reach contractModel, which would then check every reaction. +0188 % distReverse is passed by name rather than positionally as [], which +0189 % parseRAVENargs would take literally instead of falling back to its +0190 % documented default of true. +0191 repIdx = find(ismember(model.mets, repMetIds)); +0192 if ~isempty(repIdx) +0193 model=contractModel(model,'mets',repIdx); +0194 end +0195 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/manipulation/reverseRxns.html b/docs/html/manipulation/reverseRxns.html index 82aaf3d2..c2dc6c95 100644 --- a/docs/html/manipulation/reverseRxns.html +++ b/docs/html/manipulation/reverseRxns.html @@ -4,7 +4,7 @@ Description of reverseRxns - + @@ -21,18 +21,35 @@

    reverseRxns

    PURPOSE ^

    -
    reverseRxns
    +
    reverseRxns Reverse the direction of reactions in a model.

    SYNOPSIS ^

    function model=reverseRxns(model, rxns)

    DESCRIPTION ^

    -
     reverseRxns
    - Reverses the given reactions in place: negates their stoichiometry and
    - objective coefficient, and swaps/negates their lower and upper bounds,
    - so flux in the new positive direction is what was previously negative flux.
    - model     the model to change (input/output)
    - rxns      the rxns to reverse
    +
     reverseRxns  Reverse the direction of reactions in a model.
    +
    + Negates the stoichiometric coefficients and the objective coefficient of
    + the reactions, and sets the lower bound to the negated upper bound and the
    + upper bound to the negated lower bound. A flux in the new positive
    + direction equals a flux in the original negative direction. Reaction IDs
    + in rxns that are not in the model are ignored.
    +
    + Parameters
    + ----------
    + model : struct
    +     a model structure.
    + rxns : cell
    +     IDs of the reactions to reverse.
    +
    + Returns
    + -------
    + model : struct
    +     an updated model structure.
    +
    + Examples
    + --------
    +     model = reverseRxns(model, rxns);

    CROSS-REFERENCE INFORMATION ^

    @@ -48,25 +65,42 @@

    CROSS-REFERENCE INFORMATION ^
 
 <h2><a name=SOURCE CODE ^

    0001 function model=reverseRxns(model, rxns)
    -0002 % reverseRxns
    -0003 % Reverses the given reactions in place: negates their stoichiometry and
    -0004 % objective coefficient, and swaps/negates their lower and upper bounds,
    -0005 % so flux in the new positive direction is what was previously negative flux.
    -0006 % model     the model to change (input/output)
    -0007 % rxns      the rxns to reverse
    -0008 
    -0009 rxnInd = find(ismember(model.rxns,rxns));
    -0010 
    -0011 %reverse the reactions
    -0012 model.S(:,rxnInd) = model.S(:,rxnInd)*-1;
    -0013 %swap the bounds
    -0014 ub = model.ub(rxnInd);
    -0015 model.ub(rxnInd) = -model.lb(rxnInd);
    -0016 model.lb(rxnInd) = -ub;
    -0017 %flip the objective
    -0018 model.c(rxnInd) = -model.c(rxnInd);
    -0019 
    -0020 end
    +0002 % reverseRxns Reverse the direction of reactions in a model. +0003 % +0004 % Negates the stoichiometric coefficients and the objective coefficient of +0005 % the reactions, and sets the lower bound to the negated upper bound and the +0006 % upper bound to the negated lower bound. A flux in the new positive +0007 % direction equals a flux in the original negative direction. Reaction IDs +0008 % in rxns that are not in the model are ignored. +0009 % +0010 % Parameters +0011 % ---------- +0012 % model : struct +0013 % a model structure. +0014 % rxns : cell +0015 % IDs of the reactions to reverse. +0016 % +0017 % Returns +0018 % ------- +0019 % model : struct +0020 % an updated model structure. +0021 % +0022 % Examples +0023 % -------- +0024 % model = reverseRxns(model, rxns); +0025 +0026 rxnInd = find(ismember(model.rxns,rxns)); +0027 +0028 %reverse the reactions +0029 model.S(:,rxnInd) = model.S(:,rxnInd)*-1; +0030 %swap the bounds +0031 ub = model.ub(rxnInd); +0032 model.ub(rxnInd) = -model.lb(rxnInd); +0033 model.lb(rxnInd) = -ub; +0034 %flip the objective +0035 model.c(rxnInd) = -model.c(rxnInd); +0036 +0037 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/manipulation/setExchangeBounds.html b/docs/html/manipulation/setExchangeBounds.html index be306766..83cbac29 100644 --- a/docs/html/manipulation/setExchangeBounds.html +++ b/docs/html/manipulation/setExchangeBounds.html @@ -38,31 +38,34 @@

    DESCRIPTION ^DESCRIPTION ^DESCRIPTION ^CROSS-REFERENCE INFORMATION ^

    @@ -107,226 +110,229 @@

    SOURCE CODE ^% -------------------- 0011 % mets : cell or double 0012 % a cell array of metabolite names (case insensitive) or metabolite -0013 % IDs, or a vector of metabolite indices (default all exchanged +0013 % IDs, or a vector of indices in model.mets (default all exchanged 0014 % metabolites). 0015 % lb : double -0016 % lower bound of exchange flux. Can be either a vector of bounds -0017 % corresponding to each of the provided metabolites, or a single value -0018 % that will be applied to all (default model.annotation.defaultLB if it -0019 % exists, otherwise -1000). +0016 % lower bound of exchange flux. Either a vector of bounds corresponding +0017 % to each of the provided metabolites, or a single value that is applied +0018 % to all (default model.annotation.defaultLB if it exists, otherwise +0019 % -1000). Without mets, only a single value is accepted. 0020 % ub : double -0021 % upper bound of exchange flux. Can be either a vector of bounds -0022 % corresponding to each of the provided metabolites, or a single value -0023 % that will be applied to all (default model.annotation.defaultUB if it -0024 % exists, otherwise 1000). +0021 % upper bound of exchange flux. Either a vector of bounds corresponding +0022 % to each of the provided metabolites, or a single value that is applied +0023 % to all (default model.annotation.defaultUB if it exists, otherwise +0024 % 1000). Without mets, only a single value is accepted. 0025 % closeOthers : logical -0026 % close exchange reactions for all other exchanged metabolites not -0027 % present in the provided list. This will prevent IMPORT of the -0028 % metabolites, but their EXPORT will not be modified (default true). -0029 % mediaOnly : logical -0030 % only consider exchange reactions involving exchange to or from the -0031 % extracellular (media) compartment. Reactions such as "sink" reactions -0032 % that exchange metabolites directly with an intracellular compartment -0033 % will therefore be ignored even though "getExchangeRxns" identifies -0034 % such reactions as exchange reactions. The function will attempt to -0035 % identify the extracellular compartment by the "compNames" field, and -0036 % also requires the "metComps" field to be present, otherwise the -0037 % mediaOnly flag will be ignored (default false). -0038 % -0039 % Returns -0040 % ------- -0041 % exchModel : struct -0042 % a model structure with updated exchange flux bounds for the provided -0043 % set of metabolites. -0044 % unusedMets : cell -0045 % metabolites provided by the user that were not used because they are -0046 % not involved in any exchange reactions in the model. -0047 % -0048 % Examples -0049 % -------- -0050 % exchModel = setExchangeBounds(model, mets, lb, ub, closeOthers, ... -0051 % mediaOnly); -0052 % -0053 % Notes -0054 % ----- -0055 % Exchange reactions involving more than one metabolite will be ignored. -0056 -0057 -0058 % handle input arguments -0059 p=parseRAVENargs(varargin, {'mets',[]; 'lb',[]; 'ub',[]; 'closeOthers',true; 'mediaOnly',false}); -0060 mets=p.mets; -0061 if ~isempty(mets) && ~islogical(mets) && ~isnumeric(mets) -0062 mets=convertCharArray(mets); -0063 end -0064 -0065 lb=p.lb; -0066 if isempty(lb) -0067 if isfield(model,'annotation') && isfield(model.annotation,'defaultLB') -0068 lb = model.annotation.defaultLB; -0069 else -0070 lb = -1000; -0071 end -0072 end -0073 -0074 ub=p.ub; -0075 if isempty(ub) -0076 if isfield(model,'annotation') && isfield(model.annotation,'defaultUB') -0077 ub = model.annotation.defaultUB; -0078 else -0079 ub = 1000; -0080 end -0081 end -0082 -0083 closeOthers=p.closeOthers; -0084 if isempty(closeOthers) -0085 closeOthers = true; -0086 end -0087 -0088 mediaOnly=p.mediaOnly; -0089 if mediaOnly -0090 if ~all(isfield(model,{'compNames','metComps'})) -0091 error('mediaOnly option requires the "compNames" and "metComps" model fields.'); -0092 end -0093 end -0094 -0095 -0096 % for models with an "unconstrained" field, generate a version of the model -0097 % where all the unconstrained metabolites have stoichiometric coeffs set to -0098 % zero, but remain in the model to avoid index changes -0099 model_temp = model; -0100 if isfield(model,'unconstrained') -0101 model_temp.S(model_temp.unconstrained == 1,:) = 0; -0102 end -0103 -0104 % find exchange rxns, ignoring those involving more than one metabolite -0105 [~,exchRxnInd] = getExchangeRxns(model); -0106 exchRxnInd(sum(model_temp.S(:,exchRxnInd) ~= 0,1) > 1) = []; -0107 -0108 % find all exchanged metabolites -0109 exchMetInd = find(any(model_temp.S(:,exchRxnInd) ~= 0,2)); +0026 % close the exchange reactions of all exchanged metabolites that are not +0027 % in the provided list. This prevents import of these metabolites and +0028 % leaves their export unchanged. Set to false, with a warning, when the +0029 % exchange reactions are not all written in the same direction +0030 % (default true). +0031 % mediaOnly : logical +0032 % only consider exchange reactions involving exchange to or from the +0033 % extracellular (media) compartment. Reactions such as "sink" reactions +0034 % that exchange metabolites directly with an intracellular compartment +0035 % are therefore ignored, although "getExchangeRxns" identifies such +0036 % reactions as exchange reactions. The extracellular compartment is the +0037 % one named "extracellular" in the "compNames" field (case insensitive). +0038 % The "compNames" and "metComps" fields are required; an error is thrown +0039 % if either is missing or if no compartment has this name +0040 % (default false). +0041 % +0042 % Returns +0043 % ------- +0044 % exchModel : struct +0045 % a model structure with updated exchange flux bounds for the provided +0046 % set of metabolites. +0047 % unusedMets : cell +0048 % metabolites in mets that were not used because they are not involved +0049 % in any of the considered exchange reactions in the model. +0050 % +0051 % Examples +0052 % -------- +0053 % exchModel = setExchangeBounds(model, mets, lb, ub, closeOthers, ... +0054 % mediaOnly); +0055 % +0056 % Notes +0057 % ----- +0058 % Exchange reactions involving more than one metabolite are ignored. +0059 +0060 +0061 % handle input arguments +0062 p=parseRAVENargs(varargin, {'mets',[]; 'lb',[]; 'ub',[]; 'closeOthers',true; 'mediaOnly',false}); +0063 mets=p.mets; +0064 if ~isempty(mets) && ~islogical(mets) && ~isnumeric(mets) +0065 mets=convertCharArray(mets); +0066 end +0067 +0068 lb=p.lb; +0069 if isempty(lb) +0070 if isfield(model,'annotation') && isfield(model.annotation,'defaultLB') +0071 lb = model.annotation.defaultLB; +0072 else +0073 lb = -1000; +0074 end +0075 end +0076 +0077 ub=p.ub; +0078 if isempty(ub) +0079 if isfield(model,'annotation') && isfield(model.annotation,'defaultUB') +0080 ub = model.annotation.defaultUB; +0081 else +0082 ub = 1000; +0083 end +0084 end +0085 +0086 closeOthers=p.closeOthers; +0087 if isempty(closeOthers) +0088 closeOthers = true; +0089 end +0090 +0091 mediaOnly=p.mediaOnly; +0092 if mediaOnly +0093 if ~all(isfield(model,{'compNames','metComps'})) +0094 error('mediaOnly option requires the "compNames" and "metComps" model fields.'); +0095 end +0096 end +0097 +0098 +0099 % for models with an "unconstrained" field, generate a version of the model +0100 % where all the unconstrained metabolites have stoichiometric coeffs set to +0101 % zero, but remain in the model to avoid index changes +0102 model_temp = model; +0103 if isfield(model,'unconstrained') +0104 model_temp.S(model_temp.unconstrained == 1,:) = 0; +0105 end +0106 +0107 % find exchange rxns, ignoring those involving more than one metabolite +0108 [~,exchRxnInd] = getExchangeRxns(model); +0109 exchRxnInd(sum(model_temp.S(:,exchRxnInd) ~= 0,1) > 1) = []; 0110 -0111 if ( mediaOnly ) -0112 % ignore exchanged metabolites in non-extracellular compartments and -0113 % any exchange reactions involving these metabolites -0114 [~,extCompInd] = ismember('extracellular',lower(model.compNames)); -0115 if extCompInd > 0 -0116 ignoreMet = (model.metComps(exchMetInd) ~= extCompInd); -0117 else -0118 error('Could not find any compartments named "extracellular".'); -0119 end -0120 ignoreRxn = any(model.S(exchMetInd(ignoreMet),exchRxnInd) ~= 0,1); -0121 exchMetInd(ignoreMet) = []; -0122 exchRxnInd(ignoreRxn) = []; -0123 end -0124 -0125 % Check that all exchange reactions are formulated in the same direction. -0126 % If not, this means that negative flux indicates import for some exchange -0127 % reactions, but indicates export for others. Therefore, the LB and UB -0128 % would need to be specified differently depending on the exchange reaction -0129 % direction, which is error-prone. -0130 if all(all(model.S(exchMetInd,exchRxnInd) <= 0)) -0131 importDir = 'backward'; -0132 elseif all(all(model.S(exchMetInd,exchRxnInd) >= 0)) -0133 importDir = 'forward'; -0134 else -0135 fprintf('WARNING: Some exchange reactions differ in direction, and therefore have opposite meanings of LB and UB.\n'); -0136 if closeOthers -0137 fprintf(' Therefore, the "closeOthers" option will be set to FALSE.\n'); -0138 end -0139 closeOthers = false; -0140 end -0141 -0142 % prepare exchanged metabolites and bounds -0143 if ~isempty(mets) -0144 -0145 % prepare lb and ub variables -0146 if numel(lb) == 1 -0147 lb = lb*ones(size(mets)); -0148 elseif numel(lb) ~= numel(mets) -0149 error('lb must be a single value or a vector of equal length as mets'); -0150 end -0151 if numel(ub) == 1 -0152 ub = ub*ones(size(mets)); -0153 elseif numel(ub) ~= numel(mets) -0154 error('ub must be a single value or a vector of equal length as mets'); -0155 end -0156 -0157 % map provided mets to exchanged metabolites -0158 if isnumeric(mets) -0159 % mets are provided as indices -0160 [isExch,metInd] = ismember(mets,exchMetInd); -0161 elseif sum(ismember(lower(mets),lower(model.metNames))) > sum(ismember(mets,model.mets)) -0162 % assume that mets are provided as names -0163 [isExch,metInd] = ismember(lower(mets),lower(model.metNames(exchMetInd))); -0164 else -0165 % assume that mets are provided as met IDs -0166 [isExch,metInd] = ismember(mets,model.mets(exchMetInd)); -0167 end -0168 -0169 % get exchanged met indices and determine unused (non-exchanged) mets -0170 unusedMets = mets(~isExch); -0171 exchMetInd = exchMetInd(metInd(isExch)); -0172 -0173 % update bound vectors -0174 lb = lb(isExch); -0175 ub = ub(isExch); -0176 -0177 else -0178 % if no mets were provided, use all exchanged mets -0179 if numel(lb) > 1 || numel(ub) > 1 -0180 error('Only one upper and one lower bound may be provide if metabolites are not specified.'); -0181 else -0182 lb = lb*ones(size(exchMetInd)); -0183 ub = ub*ones(size(exchMetInd)); -0184 end -0185 unusedMets = {}; -0186 end -0187 -0188 % check that at least one exchanged metabolite matches -0189 if isempty(exchMetInd) -0190 fprintf('None of the provided metabolites were found in any exchange reactions.\n'); -0191 exchModel = model; -0192 return -0193 end -0194 -0195 % determine which metabolite is exchanged in each exchange reaction -0196 [metInd,rxnInd] = find(model_temp.S(exchMetInd,exchRxnInd) ~= 0); +0111 % find all exchanged metabolites +0112 exchMetInd = find(any(model_temp.S(:,exchRxnInd) ~= 0,2)); +0113 +0114 if ( mediaOnly ) +0115 % ignore exchanged metabolites in non-extracellular compartments and +0116 % any exchange reactions involving these metabolites +0117 [~,extCompInd] = ismember('extracellular',lower(model.compNames)); +0118 if extCompInd > 0 +0119 ignoreMet = (model.metComps(exchMetInd) ~= extCompInd); +0120 else +0121 error('Could not find any compartments named "extracellular".'); +0122 end +0123 ignoreRxn = any(model.S(exchMetInd(ignoreMet),exchRxnInd) ~= 0,1); +0124 exchMetInd(ignoreMet) = []; +0125 exchRxnInd(ignoreRxn) = []; +0126 end +0127 +0128 % Check that all exchange reactions are formulated in the same direction. +0129 % If not, this means that negative flux indicates import for some exchange +0130 % reactions, but indicates export for others. Therefore, the LB and UB +0131 % would need to be specified differently depending on the exchange reaction +0132 % direction, which is error-prone. +0133 if all(all(model.S(exchMetInd,exchRxnInd) <= 0)) +0134 importDir = 'backward'; +0135 elseif all(all(model.S(exchMetInd,exchRxnInd) >= 0)) +0136 importDir = 'forward'; +0137 else +0138 fprintf('WARNING: Some exchange reactions differ in direction, and therefore have opposite meanings of LB and UB.\n'); +0139 if closeOthers +0140 fprintf(' Therefore, the "closeOthers" option will be set to FALSE.\n'); +0141 end +0142 closeOthers = false; +0143 end +0144 +0145 % prepare exchanged metabolites and bounds +0146 if ~isempty(mets) +0147 +0148 % prepare lb and ub variables +0149 if numel(lb) == 1 +0150 lb = lb*ones(size(mets)); +0151 elseif numel(lb) ~= numel(mets) +0152 error('lb must be a single value or a vector of equal length as mets'); +0153 end +0154 if numel(ub) == 1 +0155 ub = ub*ones(size(mets)); +0156 elseif numel(ub) ~= numel(mets) +0157 error('ub must be a single value or a vector of equal length as mets'); +0158 end +0159 +0160 % map provided mets to exchanged metabolites +0161 if isnumeric(mets) +0162 % mets are provided as indices +0163 [isExch,metInd] = ismember(mets,exchMetInd); +0164 elseif sum(ismember(lower(mets),lower(model.metNames))) > sum(ismember(mets,model.mets)) +0165 % assume that mets are provided as names +0166 [isExch,metInd] = ismember(lower(mets),lower(model.metNames(exchMetInd))); +0167 else +0168 % assume that mets are provided as met IDs +0169 [isExch,metInd] = ismember(mets,model.mets(exchMetInd)); +0170 end +0171 +0172 % get exchanged met indices and determine unused (non-exchanged) mets +0173 unusedMets = mets(~isExch); +0174 exchMetInd = exchMetInd(metInd(isExch)); +0175 +0176 % update bound vectors +0177 lb = lb(isExch); +0178 ub = ub(isExch); +0179 +0180 else +0181 % if no mets were provided, use all exchanged mets +0182 if numel(lb) > 1 || numel(ub) > 1 +0183 error('Only one upper and one lower bound may be provide if metabolites are not specified.'); +0184 else +0185 lb = lb*ones(size(exchMetInd)); +0186 ub = ub*ones(size(exchMetInd)); +0187 end +0188 unusedMets = {}; +0189 end +0190 +0191 % check that at least one exchanged metabolite matches +0192 if isempty(exchMetInd) +0193 fprintf('None of the provided metabolites were found in any exchange reactions.\n'); +0194 exchModel = model; +0195 return +0196 end 0197 -0198 % check for any metabolites that are exchanged in more than one reaction. -0199 % metInd holds one entry per (metabolite,reaction) pair, so a metabolite -0200 % involved in more than one exchange reaction is a repeated *value* in -0201 % metInd, not a position past numel(unique(metInd)) -- comparing those two -0202 % index spaces directly (as a previous version of this check did) compares -0203 % positions in the unique list against indices into metInd itself. -0204 [uniqueMetInd,~,ic] = unique(metInd); -0205 counts = accumarray(ic,1); -0206 multiMetInd = exchMetInd(uniqueMetInd(counts>1)); -0207 if ~isempty(multiMetInd) -0208 fprintf('WARNING: The following metabolites are involved in more than one exchange reaction:\n'); -0209 fprintf('\t%s\n',model.metNames{multiMetInd(1:min(numel(multiMetInd),10))}); -0210 if numel(multiMetInd) > 10 -0211 fprintf('\t... and %u more.\n',numel(multiMetInd)-10); -0212 end -0213 end -0214 -0215 % set exchange reaction bounds -0216 model = setParam(model,'lb',exchRxnInd(rxnInd),lb(metInd)); -0217 model = setParam(model,'ub',exchRxnInd(rxnInd),ub(metInd)); -0218 -0219 if closeOthers -0220 % constrain import of all other exchange reactions to zero -0221 constrainInd = setdiff(exchRxnInd,exchRxnInd(rxnInd)); -0222 if strcmp(importDir,'backward') -0223 model = setParam(model,'lb',constrainInd,0); -0224 else -0225 model = setParam(model,'ub',constrainInd,0); -0226 end -0227 end -0228 -0229 % assign output model -0230 exchModel = model; +0198 % determine which metabolite is exchanged in each exchange reaction +0199 [metInd,rxnInd] = find(model_temp.S(exchMetInd,exchRxnInd) ~= 0); +0200 +0201 % check for any metabolites that are exchanged in more than one reaction. +0202 % metInd holds one entry per (metabolite,reaction) pair, so a metabolite +0203 % involved in more than one exchange reaction is a repeated *value* in +0204 % metInd, not a position past numel(unique(metInd)) -- comparing those two +0205 % index spaces directly (as a previous version of this check did) compares +0206 % positions in the unique list against indices into metInd itself. +0207 [uniqueMetInd,~,ic] = unique(metInd); +0208 counts = accumarray(ic,1); +0209 multiMetInd = exchMetInd(uniqueMetInd(counts>1)); +0210 if ~isempty(multiMetInd) +0211 fprintf('WARNING: The following metabolites are involved in more than one exchange reaction:\n'); +0212 fprintf('\t%s\n',model.metNames{multiMetInd(1:min(numel(multiMetInd),10))}); +0213 if numel(multiMetInd) > 10 +0214 fprintf('\t... and %u more.\n',numel(multiMetInd)-10); +0215 end +0216 end +0217 +0218 % set exchange reaction bounds +0219 model = setParam(model,'lb',exchRxnInd(rxnInd),lb(metInd)); +0220 model = setParam(model,'ub',exchRxnInd(rxnInd),ub(metInd)); +0221 +0222 if closeOthers +0223 % constrain import of all other exchange reactions to zero +0224 constrainInd = setdiff(exchRxnInd,exchRxnInd(rxnInd)); +0225 if strcmp(importDir,'backward') +0226 model = setParam(model,'lb',constrainInd,0); +0227 else +0228 model = setParam(model,'ub',constrainInd,0); +0229 end +0230 end 0231 -0232 +0232 % assign output model +0233 exchModel = model; +0234 +0235
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/manipulation/setParam.html b/docs/html/manipulation/setParam.html index d4a0487b..67264fca 100644 --- a/docs/html/manipulation/setParam.html +++ b/docs/html/manipulation/setParam.html @@ -34,23 +34,31 @@

    DESCRIPTION ^SOURCE CODE ^% model : struct 0007 % a model structure. 0008 % paramType : char -0009 % the type of parameter to set: -0010 % -0011 % - "lb" : lower bound. -0012 % - "ub" : upper bound. -0013 % - "eq" : both upper and lower bound (equality constraint). -0014 % - "obj" : objective coefficient. -0015 % - "rev" : reversibility (only changes the model.rev fields, does not -0016 % affect model.lb and model.ub). -0017 % - "var" : variance around measured bound. -0018 % - "unc" : unconstrained, set lower and upper bound to the default -0019 % values (-1000 and 1000, or any other values that are defined in -0020 % model.annotation.defaultLB and .defaultUB). -0021 % rxnList : cell or double -0022 % a cell array of reaction IDs or a vector with their corresponding -0023 % indexes. -0024 % params : double -0025 % a vector of the corresponding values. -0026 % var : double, optional -0027 % percentage of variance around measured value, if "var" is set as -0028 % paramType. Defining "var" as 5 results in lb and ub at 97.5% and -0029 % 102.5% of the provided params value (if params value is negative, -0030 % then lb and ub are 102.5% and 97.5%). -0031 % -0032 % Returns -0033 % ------- -0034 % model : struct -0035 % an updated model structure. -0036 % -0037 % Examples -0038 % -------- -0039 % model = setParam(model, paramType, rxnList, params, var); -0040 -0041 paramType=convertCharArray(paramType); -0042 if ~any(strcmpi(paramType,{'lb','ub','eq','obj','rev','var','unc'})) -0043 EM=['Incorrect parameter type: "' paramType{1} '"']; -0044 error('RAVEN:badInput', '%s', EM); -0045 end -0046 if isnumeric(rxnList) || islogical(rxnList) -0047 rxnList=model.rxns(rxnList); -0048 else -0049 rxnList=convertCharArray(rxnList); -0050 end -0051 if isempty(rxnList) -0052 return; +0009 % the type of parameter to set. The value is matched case-insensitively, +0010 % except for the reset of the other objective coefficients under "obj" +0011 % (below): +0012 % +0013 % - "lb" : lower bound. +0014 % - "ub" : upper bound. +0015 % - "eq" : both upper and lower bound (equality constraint). +0016 % - "obj" : objective coefficient. The objective coefficients of all +0017 % other reactions are set to 0 only when every element of paramType +0018 % contains "obj" in lower case; with another capitalisation, or when +0019 % paramType also holds other types, the other coefficients keep their +0020 % values. +0021 % - "rev" : reversibility (only changes the model.rev field, does not +0022 % affect model.lb and model.ub). A non-zero value sets the reaction +0023 % reversible. +0024 % - "var" : variance around measured bound. +0025 % - "unc" : unconstrained, sets the lower and upper bound to +0026 % model.annotation.defaultLB and model.annotation.defaultUB if these +0027 % are defined, otherwise to -1000 and 1000. +0028 % rxnList : cell or double +0029 % a cell array of reaction IDs or a vector with their corresponding +0030 % indexes. IDs that are not in the model are ignored with a warning. +0031 % params : double +0032 % a vector of the corresponding values, or a single value that is +0033 % applied to all reactions in rxnList. +0034 % var : double, optional +0035 % percentage of variance around measured value, if "var" is set as +0036 % paramType. Defining "var" as 5 results in lb and ub at 97.5% and +0037 % 102.5% of the provided params value (if params value is negative, +0038 % then lb and ub are 102.5% and 97.5%). +0039 % +0040 % Returns +0041 % ------- +0042 % model : struct +0043 % an updated model structure. +0044 % +0045 % Examples +0046 % -------- +0047 % model = setParam(model, paramType, rxnList, params, var); +0048 +0049 paramType=convertCharArray(paramType); +0050 if ~any(strcmpi(paramType,{'lb','ub','eq','obj','rev','var','unc'})) +0051 EM=['Incorrect parameter type: "' paramType{1} '"']; +0052 error('RAVEN:badInput', '%s', EM); 0053 end -0054 -0055 %Allow to set several parameters to the same value -0056 if numel(rxnList)~=numel(params) && numel(params)~=1 -0057 EM='The number of parameter values and the number of reactions must be the same'; -0058 error('RAVEN:badInput', '%s', EM); -0059 end -0060 -0061 if length(rxnList)>1 -0062 if length(paramType)==1 -0063 paramType(1:length(rxnList))=paramType; -0064 end -0065 if length(params)==1 -0066 params(1:length(rxnList))=params; -0067 end -0068 end -0069 -0070 %Find the indexes for the reactions in rxnList. Do not use getIndexes -0071 %as we do not want to throw errors if matches fail -0072 indexes=zeros(numel(rxnList),1); -0073 -0074 [Lia,Locb] = ismember(rxnList,model.rxns); -0075 indexes(Lia) = Locb; -0076 if any(~Lia) -0077 params(~Lia)=[]; -0078 indexes(~Lia)=[]; -0079 paramType(~Lia)=[]; -0080 warning('RAVEN:warning', '%s', ravenList('Reactions not present in model, will be ignored:', rxnList(~Lia))); -0081 end -0082 -0083 %Change the parameters -0084 if ~isempty(indexes) -0085 if contains(paramType,'obj') -0086 model.c=zeros(numel(model.c),1); % parameter is changed, not added -0087 end -0088 for j=1:length(paramType) -0089 if strcmpi(paramType{j},'eq') -0090 model.lb(indexes(j))=params(j); -0091 model.ub(indexes(j))=params(j); -0092 end -0093 if strcmpi(paramType{j},'lb') -0094 model.lb(indexes(j))=params(j); -0095 end -0096 if strcmpi(paramType{j},'ub') -0097 model.ub(indexes(j))=params(j); -0098 end -0099 if strcmpi(paramType{j},'obj') -0100 model.c(indexes(j))=params(j); -0101 end -0102 if strcmpi(paramType{j},'rev') -0103 %Non-zero values are interpreted as reversible -0104 model.rev(indexes(j))=params(j)~=0; -0105 end -0106 if strcmpi(paramType{j},'var') -0107 if params(j) < 0 -0108 model.lb(indexes(j)) = params(j) * (1+var/200); -0109 model.ub(indexes(j)) = params(j) * (1-var/200); -0110 else -0111 model.lb(indexes(j)) = params(j) * (1-var/200); -0112 model.ub(indexes(j)) = params(j) * (1+var/200); -0113 end -0114 end -0115 if strcmpi(paramType{j},'unc') -0116 if isfield(model,'annotation') && isfield(model.annotation,'defaultLB') -0117 lb = model.annotation.defaultLB; +0054 if isnumeric(rxnList) || islogical(rxnList) +0055 rxnList=model.rxns(rxnList); +0056 else +0057 rxnList=convertCharArray(rxnList); +0058 end +0059 if isempty(rxnList) +0060 return; +0061 end +0062 +0063 %Allow to set several parameters to the same value +0064 if numel(rxnList)~=numel(params) && numel(params)~=1 +0065 EM='The number of parameter values and the number of reactions must be the same'; +0066 error('RAVEN:badInput', '%s', EM); +0067 end +0068 +0069 if length(rxnList)>1 +0070 if length(paramType)==1 +0071 paramType(1:length(rxnList))=paramType; +0072 end +0073 if length(params)==1 +0074 params(1:length(rxnList))=params; +0075 end +0076 end +0077 +0078 %Find the indexes for the reactions in rxnList. Do not use getIndexes +0079 %as we do not want to throw errors if matches fail +0080 indexes=zeros(numel(rxnList),1); +0081 +0082 [Lia,Locb] = ismember(rxnList,model.rxns); +0083 indexes(Lia) = Locb; +0084 if any(~Lia) +0085 params(~Lia)=[]; +0086 indexes(~Lia)=[]; +0087 paramType(~Lia)=[]; +0088 warning('RAVEN:warning', '%s', ravenList('Reactions not present in model, will be ignored:', rxnList(~Lia))); +0089 end +0090 +0091 %Change the parameters +0092 if ~isempty(indexes) +0093 if contains(paramType,'obj') +0094 model.c=zeros(numel(model.c),1); % parameter is changed, not added +0095 end +0096 for j=1:length(paramType) +0097 if strcmpi(paramType{j},'eq') +0098 model.lb(indexes(j))=params(j); +0099 model.ub(indexes(j))=params(j); +0100 end +0101 if strcmpi(paramType{j},'lb') +0102 model.lb(indexes(j))=params(j); +0103 end +0104 if strcmpi(paramType{j},'ub') +0105 model.ub(indexes(j))=params(j); +0106 end +0107 if strcmpi(paramType{j},'obj') +0108 model.c(indexes(j))=params(j); +0109 end +0110 if strcmpi(paramType{j},'rev') +0111 %Non-zero values are interpreted as reversible +0112 model.rev(indexes(j))=params(j)~=0; +0113 end +0114 if strcmpi(paramType{j},'var') +0115 if params(j) < 0 +0116 model.lb(indexes(j)) = params(j) * (1+var/200); +0117 model.ub(indexes(j)) = params(j) * (1-var/200); 0118 else -0119 lb = -1000; -0120 end -0121 if isfield(model,'annotation') && isfield(model.annotation,'defaultUB') -0122 ub = model.annotation.defaultUB; -0123 else -0124 ub = 1000; -0125 end -0126 model.lb(indexes(j)) = lb; -0127 model.ub(indexes(j)) = ub; -0128 end -0129 end -0130 end -0131 if any(ismember(paramType,{'lb','ub','unc'})) -0132 invalidBound = model.lb(indexes) > model.ub(indexes); -0133 if any(invalidBound) -0134 error(['Invalid set of bounds for reaction(s): ', strjoin(model.rxns(indexes(invalidBound)),', '), '.']) -0135 end -0136 end +0119 model.lb(indexes(j)) = params(j) * (1-var/200); +0120 model.ub(indexes(j)) = params(j) * (1+var/200); +0121 end +0122 end +0123 if strcmpi(paramType{j},'unc') +0124 if isfield(model,'annotation') && isfield(model.annotation,'defaultLB') +0125 lb = model.annotation.defaultLB; +0126 else +0127 lb = -1000; +0128 end +0129 if isfield(model,'annotation') && isfield(model.annotation,'defaultUB') +0130 ub = model.annotation.defaultUB; +0131 else +0132 ub = 1000; +0133 end +0134 model.lb(indexes(j)) = lb; +0135 model.ub(indexes(j)) = ub; +0136 end +0137 end +0138 end +0139 if any(ismember(paramType,{'lb','ub','unc'})) +0140 invalidBound = model.lb(indexes) > model.ub(indexes); +0141 if any(invalidBound) +0142 error(['Invalid set of bounds for reaction(s): ', strjoin(model.rxns(indexes(invalidBound)),', '), '.']) +0143 end +0144 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/manipulation/simplifyModel.html b/docs/html/manipulation/simplifyModel.html index 063951a2..d2f17e55 100644 --- a/docs/html/manipulation/simplifyModel.html +++ b/docs/html/manipulation/simplifyModel.html @@ -29,9 +29,8 @@

    SYNOPSIS ^DESCRIPTION ^

     simplifyModel  Simplify a model by deleting reactions and metabolites.
     
    - This function is for reducing the model size by removing reactions and
    - associated metabolites that cannot carry flux. It can also be used for
    - identifying different types of gaps.
    + Deletes reactions and metabolites that cannot carry flux. The deleted
    + reactions and metabolites identify different types of gaps.
     
      Parameters
      ----------
    @@ -41,27 +40,37 @@ 

    DESCRIPTION ^DESCRIPTION ^CROSS-REFERENCE INFORMATION ^

    This function calls:
      -
    • contractModel contractModel Contract a model by grouping all identical reactions.
    • convertToIrrev convertToIrrev Convert a model to irreversible form.
    • removeMets removeMets Delete a set of metabolites from a model.
    • removeReactions removeReactions Delete a set of reactions from a model.
    +
  • contractModel contractModel Contract a model by merging identical reactions.
  • convertToIrrev convertToIrrev Convert a model to irreversible form.
  • removeMets removeMets Delete a set of metabolites from a model.
  • removeReactions removeReactions Delete a set of reactions from a model.
  • This function is called by:
    @@ -99,337 +108,346 @@

    SOURCE CODE ^
    0001 function [reducedModel, deletedReactions, deletedMetabolites]=simplifyModel(model,varargin)
     0002 % simplifyModel  Simplify a model by deleting reactions and metabolites.
     0003 %
    -0004 % This function is for reducing the model size by removing reactions and
    -0005 % associated metabolites that cannot carry flux. It can also be used for
    -0006 % identifying different types of gaps.
    -0007 %
    -0008 % Parameters
    -0009 % ----------
    -0010 % model : struct
    -0011 %     a model structure.
    -0012 %
    -0013 % Name-Value Arguments
    -0014 % --------------------
    -0015 % deleteUnconstrained : logical
    -0016 %     delete metabolites marked as unconstrained (default true).
    +0004 % Deletes reactions and metabolites that cannot carry flux. The deleted
    +0005 % reactions and metabolites identify different types of gaps.
    +0006 %
    +0007 % Parameters
    +0008 % ----------
    +0009 % model : struct
    +0010 %     a model structure.
    +0011 %
    +0012 % Name-Value Arguments
    +0013 % --------------------
    +0014 % deleteUnconstrained : logical
    +0015 %     delete metabolites marked as unconstrained in model.unconstrained, and
    +0016 %     the compartments that are then left without metabolites (default true).
     0017 % deleteDuplicates : logical
    -0018 %     delete all but one of duplicate reactions (default false).
    -0019 % deleteZeroInterval : logical
    -0020 %     delete reactions that are constrained to zero flux (default false).
    -0021 % deleteInaccessible : logical
    -0022 %     delete dead end reactions (default false).
    -0023 % deleteMinMax : logical
    -0024 %     delete reactions that cannot carry a flux by trying to
    -0025 %     minimize/maximize the flux through that reaction. May be time
    -0026 %     consuming (default false).
    -0027 % groupLinear : logical
    -0028 %     group linearly dependent pathways (default false).
    -0029 % constrainReversible : logical
    -0030 %     check if there are reversible reactions which can only carry flux in
    -0031 %     one direction, and if so constrain them to be irreversible. This
    -0032 %     tends to allow for more reactions grouped when using groupLinear
    -0033 %     (default false).
    -0034 % reservedRxns : cell
    -0035 %     cell array with reaction IDs that are not allowed to be removed
    -0036 %     (default none).
    -0037 % suppressWarnings : logical
    -0038 %     true if warnings should be suppressed (default false).
    -0039 %
    -0040 % Returns
    -0041 % -------
    -0042 % reducedModel : struct
    -0043 %     an updated model structure.
    -0044 % deletedReactions : cell
    -0045 %     a cell array with the IDs of all deleted reactions.
    -0046 % deletedMetabolites : cell
    -0047 %     a cell array with the IDs of all deleted metabolites.
    +0018 %     delete all but the first occurrence of duplicate reactions, as found by
    +0019 %     contractModel. Duplicate reactions have the same stoichiometry and
    +0020 %     reversibility. The kept reaction takes the minimum lower bound, the
    +0021 %     maximum upper bound and the maximum objective coefficient of the
    +0022 %     duplicates, with a warning when these differ (default false).
    +0023 % deleteZeroInterval : logical
    +0024 %     delete reactions that are constrained to zero flux (default false).
    +0025 % deleteInaccessible : logical
    +0026 %     delete dead end reactions (default false).
    +0027 % deleteMinMax : logical
    +0028 %     delete reactions that cannot carry a flux, as determined by haveFlux
    +0029 %     under the bounds of the model. The run time grows with the number of
    +0030 %     reactions, since haveFlux solves linear programs (default false).
    +0031 % groupLinear : logical
    +0032 %     group linearly dependent pathways. The grouped model is irreversible,
    +0033 %     and all gene information (genes, rxnGeneMat, grRules and the
    +0034 %     gene-associated fields) is deleted (default false).
    +0035 % constrainReversible : logical
    +0036 %     check if there are reversible reactions which can only carry flux in
    +0037 %     one direction, and if so constrain them to be irreversible. A reversible
    +0038 %     reaction that is constrained to one direction is not split into two
    +0039 %     reactions when groupLinear converts the model to irreversible, so the
    +0040 %     metabolites that it connects can meet the condition for grouping (one
    +0041 %     producing and one consuming reaction) (default false).
    +0042 % reservedRxns : cell
    +0043 %     cell array with reaction IDs that are not deleted by
    +0044 %     deleteZeroInterval, deleteInaccessible and deleteMinMax and not merged
    +0045 %     by groupLinear (default none).
    +0046 % suppressWarnings : logical
    +0047 %     true if warnings should be suppressed (default false).
     0048 %
    -0049 % Examples
    -0050 % --------
    -0051 %     [reducedModel, deletedReactions, deletedMetabolites] = ...
    -0052 %         simplifyModel(model, deleteUnconstrained, deleteDuplicates, ...
    -0053 %         deleteZeroInterval, deleteInaccessible, deleteMinMax, ...
    -0054 %         groupLinear, constrainReversible, reservedRxns, suppressWarnings);
    -0055 
    -0056 p=parseRAVENargs(varargin, {'deleteUnconstrained',true; 'deleteDuplicates',false; 'deleteZeroInterval',false; 'deleteInaccessible',false; 'deleteMinMax',false; 'groupLinear',false; 'constrainReversible',false; 'reservedRxns',[]; 'suppressWarnings',false});
    -0057 deleteUnconstrained=p.deleteUnconstrained;
    -0058 deleteDuplicates=p.deleteDuplicates;
    -0059 deleteZeroInterval=p.deleteZeroInterval;
    -0060 deleteInaccessible=p.deleteInaccessible;
    -0061 deleteMinMax=p.deleteMinMax;
    -0062 groupLinear=p.groupLinear;
    -0063 constrainReversible=p.constrainReversible;
    -0064 reservedRxns=p.reservedRxns;
    -0065 if ~isempty(reservedRxns)
    -0066     reservedRxns=convertCharArray(reservedRxns);
    -0067 end
    -0068 suppressWarnings=p.suppressWarnings;
    -0069 
    -0070 reducedModel=model;
    -0071 deletedReactions={};
    -0072 deletedMetabolites={};
    -0073 
    -0074 if deleteUnconstrained==true
    -0075     if isfield(reducedModel,'unconstrained')
    -0076         %Remove unbalanced metabolites
    -0077         deletedMetabolites=reducedModel.mets(reducedModel.unconstrained~=0);
    -0078         reducedModel=removeMets(reducedModel,reducedModel.unconstrained~=0,false,false,false,true);
    -0079         reducedModel=rmfield(reducedModel,'unconstrained');
    -0080     end
    -0081 end
    +0049 % Returns
    +0050 % -------
    +0051 % reducedModel : struct
    +0052 %     an updated model structure.
    +0053 % deletedReactions : cell
    +0054 %     a cell array with the IDs of all deleted reactions.
    +0055 % deletedMetabolites : cell
    +0056 %     a cell array with the IDs of all deleted metabolites.
    +0057 %
    +0058 % Examples
    +0059 % --------
    +0060 %     [reducedModel, deletedReactions, deletedMetabolites] = ...
    +0061 %         simplifyModel(model, deleteUnconstrained, deleteDuplicates, ...
    +0062 %         deleteZeroInterval, deleteInaccessible, deleteMinMax, ...
    +0063 %         groupLinear, constrainReversible, reservedRxns, suppressWarnings);
    +0064 
    +0065 p=parseRAVENargs(varargin, {'deleteUnconstrained',true; 'deleteDuplicates',false; 'deleteZeroInterval',false; 'deleteInaccessible',false; 'deleteMinMax',false; 'groupLinear',false; 'constrainReversible',false; 'reservedRxns',[]; 'suppressWarnings',false});
    +0066 deleteUnconstrained=p.deleteUnconstrained;
    +0067 deleteDuplicates=p.deleteDuplicates;
    +0068 deleteZeroInterval=p.deleteZeroInterval;
    +0069 deleteInaccessible=p.deleteInaccessible;
    +0070 deleteMinMax=p.deleteMinMax;
    +0071 groupLinear=p.groupLinear;
    +0072 constrainReversible=p.constrainReversible;
    +0073 reservedRxns=p.reservedRxns;
    +0074 if ~isempty(reservedRxns)
    +0075     reservedRxns=convertCharArray(reservedRxns);
    +0076 end
    +0077 suppressWarnings=p.suppressWarnings;
    +0078 
    +0079 reducedModel=model;
    +0080 deletedReactions={};
    +0081 deletedMetabolites={};
     0082 
    -0083 if deleteDuplicates==true
    -0084     %Delete all but the last occurrence of duplicate reactions. The
    -0085     %reactions must have the same bounds, reversibility, and objective
    -0086     %coefficient to be regarded as duplicate
    -0087     [reducedModel, rxnsToDelete, ~]=contractModel(reducedModel);
    -0088     deletedReactions=[deletedReactions; rxnsToDelete];
    -0089 end
    -0090 
    -0091 if deleteZeroInterval==true
    -0092     %Find all reactions where both LB and UB are 0
    -0093     zeroIntervalReactions=and(reducedModel.lb==0,reducedModel.ub==0);
    -0094     
    -0095     rxnsToDelete=setdiff(reducedModel.rxns(zeroIntervalReactions),reservedRxns);
    -0096     deletedReactions=[deletedReactions; rxnsToDelete];
    -0097     
    -0098     %Remove reactions
    -0099     reducedModel=removeReactions(reducedModel,rxnsToDelete);
    -0100     
    -0101     %Find metabolites that no longer are used and delete them
    -0102     notInUse=orphanedMets(reducedModel);
    -0103     deletedMetabolites=[deletedMetabolites;reducedModel.mets(notInUse)];
    -0104     
    -0105     %Remove metabolites
    -0106     reducedModel=removeMets(reducedModel,notInUse);
    -0107 end
    -0108 
    -0109 if deleteInaccessible==true
    -0110     %Print a warning if exchange metabolites have not been deleted yet. This
    -0111     %often means that the only allowed products are the metabolites that
    -0112     %are taken up be the system
    -0113     if isfield(reducedModel,'unconstrained') && suppressWarnings==false
    -0114         EM='Removing dead-end reactions before removing exchange metabolites';
    -0115         warning('RAVEN:warning', '%s', EM);
    -0116     end
    -0117     
    -0118     while true
    -0119         %Find all metabolites that are only reactants or only products.
    -0120         %Delete those metabolites and reactions. This is done until no more
    -0121         %such reactions are found
    -0122         
    -0123         %Construct a stoichiometric matrix where all reactions are written
    -0124         %as reversible
    -0125         
    -0126         %Add fake exchange reactions if model.b~=0
    -0127         in=any(reducedModel.b<0,2);
    -0128         out=any(reducedModel.b>0,2);
    -0129         I=speye(numel(reducedModel.mets));
    -0130         revS=[reducedModel.S,reducedModel.S(:,reducedModel.lb<0)*-1 I(:,in) I(:,out)*-1];
    +0083 if deleteUnconstrained==true
    +0084     if isfield(reducedModel,'unconstrained')
    +0085         %Remove unbalanced metabolites
    +0086         deletedMetabolites=reducedModel.mets(reducedModel.unconstrained~=0);
    +0087         reducedModel=removeMets(reducedModel,reducedModel.unconstrained~=0,false,false,false,true);
    +0088         reducedModel=rmfield(reducedModel,'unconstrained');
    +0089     end
    +0090 end
    +0091 
    +0092 if deleteDuplicates==true
    +0093     %Delete all but the last occurrence of duplicate reactions. The
    +0094     %reactions must have the same bounds, reversibility, and objective
    +0095     %coefficient to be regarded as duplicate
    +0096     [reducedModel, rxnsToDelete, ~]=contractModel(reducedModel);
    +0097     deletedReactions=[deletedReactions; rxnsToDelete];
    +0098 end
    +0099 
    +0100 if deleteZeroInterval==true
    +0101     %Find all reactions where both LB and UB are 0
    +0102     zeroIntervalReactions=and(reducedModel.lb==0,reducedModel.ub==0);
    +0103     
    +0104     rxnsToDelete=setdiff(reducedModel.rxns(zeroIntervalReactions),reservedRxns);
    +0105     deletedReactions=[deletedReactions; rxnsToDelete];
    +0106     
    +0107     %Remove reactions
    +0108     reducedModel=removeReactions(reducedModel,rxnsToDelete);
    +0109     
    +0110     %Find metabolites that no longer are used and delete them
    +0111     notInUse=orphanedMets(reducedModel);
    +0112     deletedMetabolites=[deletedMetabolites;reducedModel.mets(notInUse)];
    +0113     
    +0114     %Remove metabolites
    +0115     reducedModel=removeMets(reducedModel,notInUse);
    +0116 end
    +0117 
    +0118 if deleteInaccessible==true
    +0119     %Print a warning if exchange metabolites have not been deleted yet. This
    +0120     %often means that the only allowed products are the metabolites that
    +0121     %are taken up be the system
    +0122     if isfield(reducedModel,'unconstrained') && suppressWarnings==false
    +0123         EM='Removing dead-end reactions before removing exchange metabolites';
    +0124         warning('RAVEN:warning', '%s', EM);
    +0125     end
    +0126     
    +0127     while true
    +0128         %Find all metabolites that are only reactants or only products.
    +0129         %Delete those metabolites and reactions. This is done until no more
    +0130         %such reactions are found
     0131         
    -0132         metUsage=sum(abs(revS')>0);
    -0133         onlyProducts=sum(revS'>0) == metUsage;
    -0134         onlyReactants=sum(revS'<0) == metUsage;
    -0135         
    -0136         %Also remove metabolites that only participate in one reversible
    -0137         %reaction Do not remove the ones in one reversible reaction if is
    -0138         %also has a non-zero coefficient in model.b
    -0139         notInUse=onlyProducts | onlyReactants | (sum(abs(reducedModel.S')>0)<=1 & (~in & ~out)');
    -0140         deletedRxn=false;
    -0141         if any(notInUse)
    -0142             %Find their corresponding reactions
    -0143             rxnsNotInUse=sum(abs(reducedModel.S(notInUse,:))>0,1)>0;
    -0144             rxnsToDelete=setdiff(reducedModel.rxns(rxnsNotInUse),reservedRxns);
    -0145             deletedReactions=[deletedReactions; rxnsToDelete];
    -0146             
    -0147             %Remove reactions
    -0148             reducedModel=removeReactions(reducedModel,rxnsToDelete);
    -0149             
    -0150             %Remove metabolites. Recalculate since it could be that some
    -0151             %cannot be deleted due to reserved rxns
    -0152             notInUse=orphanedMets(reducedModel);
    -0153             deletedMetabolites=[deletedMetabolites; reducedModel.mets(notInUse)];
    -0154             reducedModel=removeMets(reducedModel,notInUse);
    +0132         %Construct a stoichiometric matrix where all reactions are written
    +0133         %as reversible
    +0134         
    +0135         %Add fake exchange reactions if model.b~=0
    +0136         in=any(reducedModel.b<0,2);
    +0137         out=any(reducedModel.b>0,2);
    +0138         I=speye(numel(reducedModel.mets));
    +0139         revS=[reducedModel.S,reducedModel.S(:,reducedModel.lb<0)*-1 I(:,in) I(:,out)*-1];
    +0140         
    +0141         metUsage=sum(abs(revS')>0);
    +0142         onlyProducts=sum(revS'>0) == metUsage;
    +0143         onlyReactants=sum(revS'<0) == metUsage;
    +0144         
    +0145         %Also remove metabolites that only participate in one reversible
    +0146         %reaction Do not remove the ones in one reversible reaction if is
    +0147         %also has a non-zero coefficient in model.b
    +0148         notInUse=onlyProducts | onlyReactants | (sum(abs(reducedModel.S')>0)<=1 & (~in & ~out)');
    +0149         deletedRxn=false;
    +0150         if any(notInUse)
    +0151             %Find their corresponding reactions
    +0152             rxnsNotInUse=sum(abs(reducedModel.S(notInUse,:))>0,1)>0;
    +0153             rxnsToDelete=setdiff(reducedModel.rxns(rxnsNotInUse),reservedRxns);
    +0154             deletedReactions=[deletedReactions; rxnsToDelete];
     0155             
    -0156             %It could happen that it just finds reserved reactions and gets
    -0157             %stuck
    -0158             if ~isempty(rxnsToDelete)
    -0159                 deletedRxn=true;
    -0160             end
    -0161         else
    -0162             break;
    -0163         end
    -0164         if deletedRxn==false
    -0165             break;
    -0166         end
    -0167     end
    -0168 end
    -0169 
    -0170 if deleteMinMax==true
    -0171     %Get reactions that cannot carry fluxes. This should be done
    -0172     %algebraically if possible
    -0173     I=~haveFlux(reducedModel);
    -0174     
    -0175     %Remove reactions
    -0176     rxnsToDelete=setdiff(reducedModel.rxns(I),reservedRxns);
    -0177     deletedReactions=[deletedReactions; rxnsToDelete];
    -0178     reducedModel=removeReactions(reducedModel,rxnsToDelete);
    -0179     
    -0180     %Remove metabolites
    -0181     notInUse=orphanedMets(reducedModel);
    -0182     deletedMetabolites=[deletedMetabolites; reducedModel.mets(notInUse)];
    -0183     reducedModel=removeMets(reducedModel,notInUse);
    -0184 end
    -0185 
    -0186 if constrainReversible==true
    -0187     revs=find(reducedModel.rev);
    -0188     %getAllowedBounds treats an empty reaction list as all reactions
    -0189     if ~isempty(revs)
    -0190         [I,J]=getAllowedBounds(reducedModel,revs);
    -0191     
    -0192         I=abs(I);
    -0193         J=abs(J);
    -0194     
    -0195         %Get the "small" values
    -0196         K=I<10^-10;
    -0197         L=J<10^-10;
    -0198     
    -0199         %Keep the values where only one direction is zero (small)
    -0200         I(K==L)=[];
    -0201         J(K==L)=[];
    -0202         revs(K==L)=[];
    +0156             %Remove reactions
    +0157             reducedModel=removeReactions(reducedModel,rxnsToDelete);
    +0158             
    +0159             %Remove metabolites. Recalculate since it could be that some
    +0160             %cannot be deleted due to reserved rxns
    +0161             notInUse=orphanedMets(reducedModel);
    +0162             deletedMetabolites=[deletedMetabolites; reducedModel.mets(notInUse)];
    +0163             reducedModel=removeMets(reducedModel,notInUse);
    +0164             
    +0165             %It could happen that it just finds reserved reactions and gets
    +0166             %stuck
    +0167             if ~isempty(rxnsToDelete)
    +0168                 deletedRxn=true;
    +0169             end
    +0170         else
    +0171             break;
    +0172         end
    +0173         if deletedRxn==false
    +0174             break;
    +0175         end
    +0176     end
    +0177 end
    +0178 
    +0179 if deleteMinMax==true
    +0180     %Get reactions that cannot carry fluxes. This should be done
    +0181     %algebraically if possible
    +0182     I=~haveFlux(reducedModel);
    +0183     
    +0184     %Remove reactions
    +0185     rxnsToDelete=setdiff(reducedModel.rxns(I),reservedRxns);
    +0186     deletedReactions=[deletedReactions; rxnsToDelete];
    +0187     reducedModel=removeReactions(reducedModel,rxnsToDelete);
    +0188     
    +0189     %Remove metabolites
    +0190     notInUse=orphanedMets(reducedModel);
    +0191     deletedMetabolites=[deletedMetabolites; reducedModel.mets(notInUse)];
    +0192     reducedModel=removeMets(reducedModel,notInUse);
    +0193 end
    +0194 
    +0195 if constrainReversible==true
    +0196     revs=find(reducedModel.rev);
    +0197     %getAllowedBounds treats an empty reaction list as all reactions
    +0198     if ~isempty(revs)
    +0199         [I,J]=getAllowedBounds(reducedModel,revs);
    +0200     
    +0201         I=abs(I);
    +0202         J=abs(J);
     0203     
    -0204         %Change the reversibility of the remaining reactions
    -0205         reducedModel.rev(revs(J>10^-10))=0; %Ignore reverse direction
    -0206         reducedModel.lb(revs(J>10^-10))=0;
    +0204         %Get the "small" values
    +0205         K=I<10^-10;
    +0206         L=J<10^-10;
     0207     
    -0208         toSwitch=revs(I>10^-10);
    -0209         reducedModel.rev(toSwitch)=0; %Change directionality
    -0210         reducedModel.ub(toSwitch)=reducedModel.lb(toSwitch)*-1;
    -0211         reducedModel.lb(toSwitch)=0;
    -0212         reducedModel.S(:,toSwitch)=reducedModel.S(:,toSwitch).*-1;
    -0213         reducedModel.c(toSwitch)=reducedModel.c(toSwitch)*-1;
    -0214     end
    -0215 end
    -0216 if groupLinear==true
    -0217     if  suppressWarnings==false
    -0218         fprintf('NOTE: You have chosen to group linear reactions. This option does not keep track of gene/reaction associations when reactions are merged. Deleting all gene information\n');
    -0219     end
    -0220     
    -0221     reducedModel.genes={};
    -0222     reducedModel.rxnGeneMat=sparse(numel(reducedModel.rxns),0);
    -0223     reducedModel.grRules(:)={''};
    -0224     
    -0225     if isfield(reducedModel,'geneShortNames')
    -0226         reducedModel.geneShortNames={};
    -0227     end
    -0228     if isfield(reducedModel,'proteins')
    -0229         reducedModel.proteins={};
    -0230     end
    -0231     if isfield(reducedModel,'geneMiriams')
    -0232         reducedModel.geneMiriams={};
    -0233     end
    -0234     if isfield(reducedModel,'geneComps')
    -0235         reducedModel.geneComps=[];
    +0208         %Keep the values where only one direction is zero (small)
    +0209         I(K==L)=[];
    +0210         J(K==L)=[];
    +0211         revs(K==L)=[];
    +0212     
    +0213         %Change the reversibility of the remaining reactions
    +0214         reducedModel.rev(revs(J>10^-10))=0; %Ignore reverse direction
    +0215         reducedModel.lb(revs(J>10^-10))=0;
    +0216     
    +0217         toSwitch=revs(I>10^-10);
    +0218         reducedModel.rev(toSwitch)=0; %Change directionality
    +0219         reducedModel.ub(toSwitch)=reducedModel.lb(toSwitch)*-1;
    +0220         reducedModel.lb(toSwitch)=0;
    +0221         reducedModel.S(:,toSwitch)=reducedModel.S(:,toSwitch).*-1;
    +0222         reducedModel.c(toSwitch)=reducedModel.c(toSwitch)*-1;
    +0223     end
    +0224 end
    +0225 if groupLinear==true
    +0226     if  suppressWarnings==false
    +0227         fprintf('NOTE: You have chosen to group linear reactions. This option does not keep track of gene/reaction associations when reactions are merged. Deleting all gene information\n');
    +0228     end
    +0229     
    +0230     reducedModel.genes={};
    +0231     reducedModel.rxnGeneMat=sparse(numel(reducedModel.rxns),0);
    +0232     reducedModel.grRules(:)={''};
    +0233     
    +0234     if isfield(reducedModel,'geneShortNames')
    +0235         reducedModel.geneShortNames={};
     0236     end
    -0237     
    -0238     %Convert the model to irreversible
    -0239     irrevModel=convertToIrrev(reducedModel);
    -0240     
    -0241     %Loop through and iteratively group linear reactions
    -0242     while 1
    -0243         %Get the banned reaction indexes. Note that the indexes will change
    -0244         %in each iteration, but the names will not as they will not be merged
    -0245         %with any other reaction
    -0246         bannedIndexes=getIndexes(irrevModel,reservedRxns,'rxns');
    -0247         
    -0248         %Select all metabolites that are only present as reactants/products
    -0249         %in one reaction
    -0250         singleNegative=find(sum(irrevModel.S'<0)==1);
    -0251         singlePositive=find(sum(irrevModel.S'>0)==1);
    -0252         
    -0253         %Retrieve the common metabolites
    -0254         common=intersect(singleNegative,singlePositive);
    -0255         
    -0256         mergedSome=false;
    -0257         
    -0258         %Loop through each of them and see if the reactions should be
    -0259         %merged
    -0260         for i=1:numel(common)
    -0261             involvedRxns=find(irrevModel.S(common(i),:));
    -0262             
    -0263             %Check so that one or both of the reactions have not been merged
    -0264             %already
    -0265             if numel(involvedRxns)==2 && isempty(intersect(bannedIndexes,involvedRxns))
    -0266                 %Calculate how many times the second reaction has to be
    -0267                 %multiplied before being merged with the first
    -0268                 stoichRatio=abs(irrevModel.S(common(i),involvedRxns(1))/irrevModel.S(common(i),involvedRxns(2)));
    -0269                 
    -0270                 %Add the second to the first
    -0271                 irrevModel.S(:,involvedRxns(1))=irrevModel.S(:,involvedRxns(1))+irrevModel.S(:,involvedRxns(2))*stoichRatio;
    -0272                 
    -0273                 %Clear the second reaction
    -0274                 irrevModel.S(:,involvedRxns(2))=0;
    -0275                 
    -0276                 %This is to prevent numerical issues. It should be 0
    -0277                 %already
    -0278                 irrevModel.S(common(i),involvedRxns(1))=0;
    -0279                 
    -0280                 %At this point the second reaction is certain to be deleted
    -0281                 %in a later step and can therefore be ignored
    -0282                 
    -0283                 %Recalculate the bounds for the new reaction. This can be
    -0284                 %problematic since the scale of the bounds may change
    -0285                 %dramatically. Let the most constraining reaction determine
    -0286                 %the new bound
    -0287                 lb1=irrevModel.lb(involvedRxns(1));
    -0288                 lb2=irrevModel.lb(involvedRxns(2));
    -0289                 ub1=irrevModel.ub(involvedRxns(1));
    -0290                 ub2=irrevModel.ub(involvedRxns(2));
    +0237     if isfield(reducedModel,'proteins')
    +0238         reducedModel.proteins={};
    +0239     end
    +0240     if isfield(reducedModel,'geneMiriams')
    +0241         reducedModel.geneMiriams={};
    +0242     end
    +0243     if isfield(reducedModel,'geneComps')
    +0244         reducedModel.geneComps=[];
    +0245     end
    +0246     
    +0247     %Convert the model to irreversible
    +0248     irrevModel=convertToIrrev(reducedModel);
    +0249     
    +0250     %Loop through and iteratively group linear reactions
    +0251     while 1
    +0252         %Get the banned reaction indexes. Note that the indexes will change
    +0253         %in each iteration, but the names will not as they will not be merged
    +0254         %with any other reaction
    +0255         bannedIndexes=getIndexes(irrevModel,reservedRxns,'rxns');
    +0256         
    +0257         %Select all metabolites that are only present as reactants/products
    +0258         %in one reaction
    +0259         singleNegative=find(sum(irrevModel.S'<0)==1);
    +0260         singlePositive=find(sum(irrevModel.S'>0)==1);
    +0261         
    +0262         %Retrieve the common metabolites
    +0263         common=intersect(singleNegative,singlePositive);
    +0264         
    +0265         mergedSome=false;
    +0266         
    +0267         %Loop through each of them and see if the reactions should be
    +0268         %merged
    +0269         for i=1:numel(common)
    +0270             involvedRxns=find(irrevModel.S(common(i),:));
    +0271             
    +0272             %Check so that one or both of the reactions have not been merged
    +0273             %already
    +0274             if numel(involvedRxns)==2 && isempty(intersect(bannedIndexes,involvedRxns))
    +0275                 %Calculate how many times the second reaction has to be
    +0276                 %multiplied before being merged with the first
    +0277                 stoichRatio=abs(irrevModel.S(common(i),involvedRxns(1))/irrevModel.S(common(i),involvedRxns(2)));
    +0278                 
    +0279                 %Add the second to the first
    +0280                 irrevModel.S(:,involvedRxns(1))=irrevModel.S(:,involvedRxns(1))+irrevModel.S(:,involvedRxns(2))*stoichRatio;
    +0281                 
    +0282                 %Clear the second reaction
    +0283                 irrevModel.S(:,involvedRxns(2))=0;
    +0284                 
    +0285                 %This is to prevent numerical issues. It should be 0
    +0286                 %already
    +0287                 irrevModel.S(common(i),involvedRxns(1))=0;
    +0288                 
    +0289                 %At this point the second reaction is certain to be deleted
    +0290                 %in a later step and can therefore be ignored
     0291                 
    -0292                 if lb2~=-inf
    -0293                     irrevModel.lb(involvedRxns(1))=max(lb1,lb2/stoichRatio);
    -0294                 end
    -0295                 if ub2~=inf
    -0296                     irrevModel.ub(involvedRxns(1))=min(ub1,ub2/stoichRatio);
    -0297                 end
    -0298                 
    -0299                 %Then recalculate the objective coefficient. The resulting
    -0300                 %coefficient is the weighted sum of the previous
    -0301                 irrevModel.c(involvedRxns(1))=irrevModel.c(involvedRxns(1))+irrevModel.c(involvedRxns(2))*stoichRatio;
    -0302                 
    -0303                 %Iterate again
    -0304                 mergedSome=true;
    -0305             end
    -0306         end
    -0307         
    -0308         %All possible reactions merged
    -0309         if mergedSome==false
    -0310             break;
    -0311         end
    -0312         
    -0313         %Now delete all reactions that involve no metabolites
    -0314         I=find(sum(irrevModel.S~=0)==0);
    -0315         
    -0316         %Remove reactions
    -0317         irrevModel=removeReactions(irrevModel,I);
    -0318         
    -0319         %Remove metabolites
    -0320         notInUse=orphanedMets(irrevModel);
    -0321         irrevModel=removeMets(irrevModel,notInUse);
    -0322     end
    -0323     
    -0324     reducedModel=irrevModel;
    -0325 end
    -0326 end
    -0327 
    -0328 function I=orphanedMets(model)
    -0329 %Metabolites that take part in no reaction, and can therefore be removed
    -0330 %without changing what the model can do. Metabolites with a non-zero
    -0331 %model.b are kept: b is a boundary condition on the metabolite, so removing
    -0332 %the row would drop that constraint along with it.
    -0333 I=sum(model.S~=0,2)==0 & ~any(model.b~=0,2);
    -0334 end

    +0292 %Recalculate the bounds for the new reaction. This can be +0293 %problematic since the scale of the bounds may change +0294 %dramatically. Let the most constraining reaction determine +0295 %the new bound +0296 lb1=irrevModel.lb(involvedRxns(1)); +0297 lb2=irrevModel.lb(involvedRxns(2)); +0298 ub1=irrevModel.ub(involvedRxns(1)); +0299 ub2=irrevModel.ub(involvedRxns(2)); +0300 +0301 if lb2~=-inf +0302 irrevModel.lb(involvedRxns(1))=max(lb1,lb2/stoichRatio); +0303 end +0304 if ub2~=inf +0305 irrevModel.ub(involvedRxns(1))=min(ub1,ub2/stoichRatio); +0306 end +0307 +0308 %Then recalculate the objective coefficient. The resulting +0309 %coefficient is the weighted sum of the previous +0310 irrevModel.c(involvedRxns(1))=irrevModel.c(involvedRxns(1))+irrevModel.c(involvedRxns(2))*stoichRatio; +0311 +0312 %Iterate again +0313 mergedSome=true; +0314 end +0315 end +0316 +0317 %All possible reactions merged +0318 if mergedSome==false +0319 break; +0320 end +0321 +0322 %Now delete all reactions that involve no metabolites +0323 I=find(sum(irrevModel.S~=0)==0); +0324 +0325 %Remove reactions +0326 irrevModel=removeReactions(irrevModel,I); +0327 +0328 %Remove metabolites +0329 notInUse=orphanedMets(irrevModel); +0330 irrevModel=removeMets(irrevModel,notInUse); +0331 end +0332 +0333 reducedModel=irrevModel; +0334 end +0335 end +0336 +0337 function I=orphanedMets(model) +0338 %Metabolites that take part in no reaction, and can therefore be removed +0339 %without changing what the model can do. Metabolites with a non-zero +0340 %model.b are kept: b is a boundary condition on the metabolite, so removing +0341 %the row would drop that constraint along with it. +0342 I=sum(model.S~=0,2)==0 & ~any(model.b~=0,2); +0343 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/manipulation/sortIdentifiers.html b/docs/html/manipulation/sortIdentifiers.html index 8571978d..a27c6bea 100644 --- a/docs/html/manipulation/sortIdentifiers.html +++ b/docs/html/manipulation/sortIdentifiers.html @@ -29,8 +29,8 @@

    SYNOPSIS ^DESCRIPTION ^

     sortIdentifiers  Sort model identifiers alphabetically.
     
    - Sort reactions, metabolites, genes and compartments alphabetically by
    - their identifier.
    + Sorts reactions, metabolites, genes (if the genes field exists) and
    + compartments alphabetically by their identifier.
     
      Parameters
      ----------
    @@ -62,8 +62,8 @@ 

    SOURCE CODE ^
    0001 function newModel = sortIdentifiers(model)
     0002 % sortIdentifiers  Sort model identifiers alphabetically.
     0003 %
    -0004 % Sort reactions, metabolites, genes and compartments alphabetically by
    -0005 % their identifier.
    +0004 % Sorts reactions, metabolites, genes (if the genes field exists) and
    +0005 % compartments alphabetically by their identifier.
     0006 %
     0007 % Parameters
     0008 % ----------
    diff --git a/docs/html/manipulation/sortModel.html b/docs/html/manipulation/sortModel.html
    index 42401098..352468ae 100644
    --- a/docs/html/manipulation/sortModel.html
    +++ b/docs/html/manipulation/sortModel.html
    @@ -4,7 +4,7 @@
     
       Description of sortModel
       
    -  
    +  
       
       
       
    @@ -21,16 +21,17 @@ 

    sortModel

    PURPOSE ^

    -
    sortModel Standardize a model's internal ordering: can orient
    +
    sortModel Sort the metabolites and reactions of a model.

    SYNOPSIS ^

    function model=sortModel(model,varargin)

    DESCRIPTION ^

    -
     sortModel  Standardize a model's internal ordering: can orient
    - reversible reactions consistently; can also reorder metabolites within
    - reaction equations and/or reactions within each subsystem, depending on
    - which options are enabled.
    +
     sortModel  Sort the metabolites and reactions of a model.
    +
    + Depending on the options, the function sorts the metabolites by name and
    + compartment, orients reversible reactions consistently, and reorders the
    + reactions within each subsystem.
     
      Parameters
      ----------
    @@ -40,21 +41,25 @@ 

    DESCRIPTION ^CROSS-REFERENCE INFORMATION ^
 <li><a href=permuteModel permuteModel Change the order of the reactions or metabolites in a model. This function is called by:
      -
    • contractModel contractModel Contract a model by grouping all identical reactions.
    +
  • contractModel contractModel Contract a model by merging identical reactions.
  • SOURCE CODE ^

    0001 function model=sortModel(model,varargin)
    -0002 % sortModel  Standardize a model's internal ordering: can orient
    -0003 % reversible reactions consistently; can also reorder metabolites within
    -0004 % reaction equations and/or reactions within each subsystem, depending on
    -0005 % which options are enabled.
    -0006 %
    -0007 % Parameters
    -0008 % ----------
    -0009 % model : struct
    -0010 %     a model structure.
    -0011 %
    -0012 % Name-Value Arguments
    -0013 % --------------------
    -0014 % sortReversible : logical
    -0015 %     sorts the reversible reactions so that the metabolite that is first
    -0016 %     in lexicographical order is a reactant (default false).
    -0017 % sortMetName : logical
    -0018 %     sort the metabolite names in the equation, also uses compartment
    -0019 %     abbreviation (default false).
    -0020 % sortReactionOrder : logical
    -0021 %     sorts the reaction order within each subsystem so that reactions
    -0022 %     consuming some metabolite come after reactions producing it. This
    -0023 %     overrides the sortReversible option and reactions are sorted so that
    -0024 %     the production direction matches the consumption direction (default
    -0025 %     false).
    -0026 % seed : double
    -0027 %     seed for the random number generator used by the sortReactionOrder
    -0028 %     local search, so its result is reproducible across runs (default:
    -0029 %     current rng state is left untouched).
    -0030 %
    -0031 % Returns
    -0032 % -------
    -0033 % model : struct
    -0034 %     an updated model structure.
    +0002 % sortModel  Sort the metabolites and reactions of a model.
    +0003 %
    +0004 % Depending on the options, the function sorts the metabolites by name and
    +0005 % compartment, orients reversible reactions consistently, and reorders the
    +0006 % reactions within each subsystem.
    +0007 %
    +0008 % Parameters
    +0009 % ----------
    +0010 % model : struct
    +0011 %     a model structure.
    +0012 %
    +0013 % Name-Value Arguments
    +0014 % --------------------
    +0015 % sortReversible : logical
    +0016 %     orient each reversible reaction by changing the sign of its
    +0017 %     stoichiometric coefficients. The metabolite that is first in
    +0018 %     lexicographical order (metabolite name followed by compartment ID)
    +0019 %     becomes a reactant. Not applied when sortReactionOrder is true
    +0020 %     (default false).
    +0021 % sortMetName : logical
    +0022 %     sort the metabolites of the model by metabolite name followed by
    +0023 %     compartment ID (default false).
    +0024 % sortReactionOrder : logical
    +0025 %     reorder the reactions within each subsystem, such that reactions
    +0026 %     consuming a metabolite come after reactions producing it. The order is
    +0027 %     found by a random local search that also changes the direction of
    +0028 %     reversible reactions to match production and consumption direction.
    +0029 %     Requires the subSystems field. Subsystems with fewer than 2 or more
    +0030 %     than 250 reactions are not reordered (default false).
    +0031 % seed : double
    +0032 %     seed for the random number generator used by the sortReactionOrder
    +0033 %     local search, which makes its result reproducible across runs
    +0034 %     (default: the current rng state is not changed).
     0035 %
    -0036 % Examples
    -0037 % --------
    -0038 %     model = sortModel(model, sortReversible, sortMetName, sortReactionOrder);
    -0039 
    -0040 p=parseRAVENargs(varargin, {'sortReversible',false; 'sortMetName',false; 'sortReactionOrder',false; 'seed',[]});
    -0041 if ~isempty(p.seed)
    -0042     rng(p.seed);
    -0043 end
    -0044 sortReversible=p.sortReversible;
    -0045 sortMetName=p.sortMetName;
    -0046 sortReactionOrder=p.sortReactionOrder;
    -0047 
    -0048 if sortMetName==true
    -0049     %Assuming that metComps are the indexes.
    -0050     [~, metIndexes]=sort(strcat(model.metNames,'[',model.comps(model.metComps),']'));
    -0051     model=permuteModel(model,metIndexes,'mets');
    -0052 end
    -0053 
    -0054 if sortReversible==true && sortReactionOrder==false
    -0055     revIndexes=find(model.rev);
    -0056 
    -0057     for i=1:numel(revIndexes)
    -0058         %Create a cell array with all the metabolite names
    -0059         mets=find(model.S(:,revIndexes(i)));
    -0060         metNames=strcat(model.metNames(mets),model.comps(model.metComps(mets)));
    -0061         
    -0062         if iscellstr(metNames)
    -0063             [~, indexes]=sort(metNames);
    -0064             
    -0065             if model.S(mets(indexes(1)),revIndexes(i))>0
    -0066                 model.S(:,revIndexes(i))=model.S(:,revIndexes(i))*-1;
    -0067             end
    -0068         end
    -0069     end
    -0070 end
    -0071 
    -0072 if sortReactionOrder==true
    -0073     if ~isfield(model,'subSystems')
    -0074         EM='The model must contain a subSystems field in order to sort reaction order';
    -0075         error('RAVEN:badInput', '%s', EM);
    -0076     end
    -0077     
    -0078     subsystemsUnique='';
    -0079     subsystemsConcatenated='';
    -0080     for i=1:numel(model.subSystems)
    -0081         if ~iscell(model.subSystems{i})
    -0082             model.subSystems{i} = {model.subSystems{i}};
    -0083         end    
    -0084         subsystemsConcatenated{i,1}=strjoin(model.subSystems{i,1},';');
    -0085         if ~isempty(model.subSystems{i,1})
    -0086             for j=1:numel(model.subSystems{i,1})
    -0087                 subsystemsUnique{numel(subsystemsUnique)+1,1}=model.subSystems{i,1}{1,j};
    -0088             end
    -0089         end
    -0090     end
    -0091     subsystemsUnique=unique(subsystemsUnique);
    -0092     for i=1:numel(subsystemsUnique)
    -0093         %Get all reactions for that subsystem. The subsystem name is
    -0094         %escaped (it is arbitrary model text, not a regex pattern), and
    -0095         %the match is anchored to a whole ';'-delimited entry, since a
    -0096         %plain substring search would also match e.g. "Glycolysis /
    -0097         %Gluconeogenesis" when looking for "Glycolysis".
    -0098         pattern=['(^|;)' regexptranslate('escape',subsystemsUnique{i}) '(;|$)'];
    -0099         rxns=find(~cellfun(@isempty,regexp(subsystemsConcatenated,pattern)));
    -0100         
    -0101         %Temporarily ignore large subsystems because of inefficient
    -0102         %implementation
    -0103         if numel(rxns)<2 || numel(rxns)>250
    -0104             continue;
    -0105         end
    -0106         
    -0107         nRxns=numel(rxns);
    -0108         revRxns=rxns(model.rev(rxns)~=0);
    -0109 
    -0110         %This variable will hold the current reversibility directions of
    -0111         %the reversible reactions. 1 means the same direction as in the
    -0112         %original model and -1 means the opposite direction.
    -0113         oldRev=ones(numel(revRxns),1);
    +0036 % Returns
    +0037 % -------
    +0038 % model : struct
    +0039 %     an updated model structure.
    +0040 %
    +0041 % Examples
    +0042 % --------
    +0043 %     model = sortModel(model, sortReversible, sortMetName, sortReactionOrder);
    +0044 
    +0045 p=parseRAVENargs(varargin, {'sortReversible',false; 'sortMetName',false; 'sortReactionOrder',false; 'seed',[]});
    +0046 if ~isempty(p.seed)
    +0047     rng(p.seed);
    +0048 end
    +0049 sortReversible=p.sortReversible;
    +0050 sortMetName=p.sortMetName;
    +0051 sortReactionOrder=p.sortReactionOrder;
    +0052 
    +0053 if sortMetName==true
    +0054     %Assuming that metComps are the indexes.
    +0055     [~, metIndexes]=sort(strcat(model.metNames,'[',model.comps(model.metComps),']'));
    +0056     model=permuteModel(model,metIndexes,'mets');
    +0057 end
    +0058 
    +0059 if sortReversible==true && sortReactionOrder==false
    +0060     revIndexes=find(model.rev);
    +0061 
    +0062     for i=1:numel(revIndexes)
    +0063         %Create a cell array with all the metabolite names
    +0064         mets=find(model.S(:,revIndexes(i)));
    +0065         metNames=strcat(model.metNames(mets),model.comps(model.metComps(mets)));
    +0066         
    +0067         if iscellstr(metNames)
    +0068             [~, indexes]=sort(metNames);
    +0069             
    +0070             if model.S(mets(indexes(1)),revIndexes(i))>0
    +0071                 model.S(:,revIndexes(i))=model.S(:,revIndexes(i))*-1;
    +0072             end
    +0073         end
    +0074     end
    +0075 end
    +0076 
    +0077 if sortReactionOrder==true
    +0078     if ~isfield(model,'subSystems')
    +0079         EM='The model must contain a subSystems field in order to sort reaction order';
    +0080         error('RAVEN:badInput', '%s', EM);
    +0081     end
    +0082     
    +0083     subsystemsUnique='';
    +0084     subsystemsConcatenated='';
    +0085     for i=1:numel(model.subSystems)
    +0086         if ~iscell(model.subSystems{i})
    +0087             model.subSystems{i} = {model.subSystems{i}};
    +0088         end    
    +0089         subsystemsConcatenated{i,1}=strjoin(model.subSystems{i,1},';');
    +0090         if ~isempty(model.subSystems{i,1})
    +0091             for j=1:numel(model.subSystems{i,1})
    +0092                 subsystemsUnique{numel(subsystemsUnique)+1,1}=model.subSystems{i,1}{1,j};
    +0093             end
    +0094         end
    +0095     end
    +0096     subsystemsUnique=unique(subsystemsUnique);
    +0097     for i=1:numel(subsystemsUnique)
    +0098         %Get all reactions for that subsystem. The subsystem name is
    +0099         %escaped (it is arbitrary model text, not a regex pattern), and
    +0100         %the match is anchored to a whole ';'-delimited entry, since a
    +0101         %plain substring search would also match e.g. "Glycolysis /
    +0102         %Gluconeogenesis" when looking for "Glycolysis".
    +0103         pattern=['(^|;)' regexptranslate('escape',subsystemsUnique{i}) '(;|$)'];
    +0104         rxns=find(~cellfun(@isempty,regexp(subsystemsConcatenated,pattern)));
    +0105         
    +0106         %Temporarily ignore large subsystems because of inefficient
    +0107         %implementation
    +0108         if numel(rxns)<2 || numel(rxns)>250
    +0109             continue;
    +0110         end
    +0111         
    +0112         nRxns=numel(rxns);
    +0113         revRxns=rxns(model.rev(rxns)~=0);
     0114 
    -0115         %Work on just this subsystem's own (mets x nRxns) columns rather
    -0116         %than a full copy of model.S every iteration below: the score
    -0117         %only ever depends on these columns, whose involved-metabolite
    -0118         %rows and reversible-reaction positions are fixed for the whole
    -0119         %optimization (only their order/sign changes), so both can be
    -0120         %precomputed once instead of per iteration.
    -0121         subS=model.S(:,rxns);
    -0122         subS=subS(any(subS,2),:);
    -0123         [~,revLocalIdx]=ismember(revRxns,rxns);
    -0124 
    -0125         %The problem could be solved analytically, but a simple random
    -0126         %method is used instead. Two reactions are chosen randomly and
    -0127         %their positions are switched. A score is calculated based on the
    -0128         %number of metabolites that are produced before they are consumed.
    -0129         %If the perturbed model has a better or equal score than the
    -0130         %original the reaction order is switched. If no increase in score
    -0131         %has been seen after 1000*rxnsInSubsystem then the optimization is
    -0132         %terminated
    -0133 
    -0134         rxnOrder=1:nRxns;
    -0135         oldScore=-inf;
    -0136         counter=0;
    -0137         firstIter=true;
    -0138         while 1==1
    -0139             counter=counter+1;
    -0140             if counter==100*nRxns
    -0141                 break;
    -0142             end
    -0143 
    -0144             newRxnOrder=rxnOrder;
    -0145             rev=oldRev;
    -0146 
    -0147             if firstIter==false
    -0148                 y=randperm(nRxns,2);
    -0149 
    -0150                 newRxnOrder(y(1))=rxnOrder(y(2));
    -0151                 newRxnOrder(y(2))=rxnOrder(y(1));
    -0152 
    -0153                 %With a 50% chance, also switch the reversibility of one of
    -0154                 %the reactions
    -0155                 if rand()>0.5 && numel(rev)>1
    -0156                     n=randperm(numel(rev),1);
    -0157                     rev(n)=rev(n)*-1;
    -0158                 end
    -0159             end
    -0160             firstIter=false;
    -0161 
    -0162             tempS=subS;
    -0163 
    -0164             %Switch the directionalities
    -0165             for j=1:numel(rev)
    -0166                 if rev(j)==-1
    -0167                     tempS(:,revLocalIdx(j))=tempS(:,revLocalIdx(j)).*-1;
    -0168                 end
    -0169             end
    -0170 
    -0171             %Get the metabolites that are involved and when they are
    -0172             %produced/consumed
    -0173             s=tempS(:,newRxnOrder);
    -0174 
    -0175             %Add so that all mets are produced and consumed in the end
    -0176             s=[s ones(size(s,1),1) ones(size(s,1),1)*-1];
    -0177             
    -0178             %For each metabolite, find the reaction where it is first
    -0179             %produced and the reaction where it is first consumed
    -0180             s1=s>0;
    -0181             r1=arrayfun(@(x) find(s1(x,:),1,'first'),1:size(s1,1));
    -0182             s2=s<0;
    -0183             r2=arrayfun(@(x) find(s2(x,:),1,'first'),1:size(s2,1));
    -0184             
    -0185             score=sum(r1<r2);
    -0186             
    -0187             if score>=oldScore
    -0188                 if score>oldScore
    -0189                     counter=0;
    -0190                 end
    -0191                 oldScore=score;
    -0192                 oldRev=rev;
    -0193                 rxnOrder=newRxnOrder;
    -0194             end
    -0195         end
    -0196         
    -0197         %Update the model for this subsystem
    -0198         for j=1:numel(oldRev)
    -0199             if oldRev(j)==-1
    -0200                 model.S(:,revRxns(j))=model.S(:,revRxns(j)).*-1;
    -0201             end
    -0202         end
    -0203         order=1:numel(model.rxns);
    -0204         order(rxns)=rxns(rxnOrder);
    -0205         model=permuteModel(model, order, 'rxns');
    -0206     end
    -0207 end
    -0208 end
    +0115 %This variable will hold the current reversibility directions of +0116 %the reversible reactions. 1 means the same direction as in the +0117 %original model and -1 means the opposite direction. +0118 oldRev=ones(numel(revRxns),1); +0119 +0120 %Work on just this subsystem's own (mets x nRxns) columns rather +0121 %than a full copy of model.S every iteration below: the score +0122 %only ever depends on these columns, whose involved-metabolite +0123 %rows and reversible-reaction positions are fixed for the whole +0124 %optimization (only their order/sign changes), so both can be +0125 %precomputed once instead of per iteration. +0126 subS=model.S(:,rxns); +0127 subS=subS(any(subS,2),:); +0128 [~,revLocalIdx]=ismember(revRxns,rxns); +0129 +0130 %The problem could be solved analytically, but a simple random +0131 %method is used instead. Two reactions are chosen randomly and +0132 %their positions are switched. A score is calculated based on the +0133 %number of metabolites that are produced before they are consumed. +0134 %If the perturbed model has a better or equal score than the +0135 %original the reaction order is switched. If no increase in score +0136 %has been seen after 1000*rxnsInSubsystem then the optimization is +0137 %terminated +0138 +0139 rxnOrder=1:nRxns; +0140 oldScore=-inf; +0141 counter=0; +0142 firstIter=true; +0143 while 1==1 +0144 counter=counter+1; +0145 if counter==100*nRxns +0146 break; +0147 end +0148 +0149 newRxnOrder=rxnOrder; +0150 rev=oldRev; +0151 +0152 if firstIter==false +0153 y=randperm(nRxns,2); +0154 +0155 newRxnOrder(y(1))=rxnOrder(y(2)); +0156 newRxnOrder(y(2))=rxnOrder(y(1)); +0157 +0158 %With a 50% chance, also switch the reversibility of one of +0159 %the reactions +0160 if rand()>0.5 && numel(rev)>1 +0161 n=randperm(numel(rev),1); +0162 rev(n)=rev(n)*-1; +0163 end +0164 end +0165 firstIter=false; +0166 +0167 tempS=subS; +0168 +0169 %Switch the directionalities +0170 for j=1:numel(rev) +0171 if rev(j)==-1 +0172 tempS(:,revLocalIdx(j))=tempS(:,revLocalIdx(j)).*-1; +0173 end +0174 end +0175 +0176 %Get the metabolites that are involved and when they are +0177 %produced/consumed +0178 s=tempS(:,newRxnOrder); +0179 +0180 %Add so that all mets are produced and consumed in the end +0181 s=[s ones(size(s,1),1) ones(size(s,1),1)*-1]; +0182 +0183 %For each metabolite, find the reaction where it is first +0184 %produced and the reaction where it is first consumed +0185 s1=s>0; +0186 r1=arrayfun(@(x) find(s1(x,:),1,'first'),1:size(s1,1)); +0187 s2=s<0; +0188 r2=arrayfun(@(x) find(s2(x,:),1,'first'),1:size(s2,1)); +0189 +0190 score=sum(r1<r2); +0191 +0192 if score>=oldScore +0193 if score>oldScore +0194 counter=0; +0195 end +0196 oldScore=score; +0197 oldRev=rev; +0198 rxnOrder=newRxnOrder; +0199 end +0200 end +0201 +0202 %Update the model for this subsystem +0203 for j=1:numel(oldRev) +0204 if oldRev(j)==-1 +0205 model.S(:,revRxns(j))=model.S(:,revRxns(j)).*-1; +0206 end +0207 end +0208 order=1:numel(model.rxns); +0209 order(rxns)=rxns(rxnOrder); +0210 model=permuteModel(model, order, 'rxns'); +0211 end +0212 end +0213 end

    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/manipulation/standardizeGrRules.html b/docs/html/manipulation/standardizeGrRules.html index d7c9dc3a..fb21e618 100644 --- a/docs/html/manipulation/standardizeGrRules.html +++ b/docs/html/manipulation/standardizeGrRules.html @@ -4,7 +4,7 @@ Description of standardizeGrRules - + @@ -21,21 +21,25 @@

    standardizeGrRules

    PURPOSE ^

    -
    standardizeGrRules Standardize gene-reaction rules in a model.
    +
    standardizeGrRules Standardise gene-reaction rules in a model.

    SYNOPSIS ^

    function [grRules,rxnGeneMat,indexes2check] = standardizeGrRules(model,varargin)

    DESCRIPTION ^

    -
     standardizeGrRules  Standardize gene-reaction rules in a model.
    +
     standardizeGrRules  Standardise gene-reaction rules in a model.
     
    - The grRules are standardized according to the following:
    + The grRules are standardised according to the following:
     
      - No overall containing brackets
    - - Just enzyme complexes are enclosed into brackets
    - - " and " and " or " strings are strictly set to lowercase
    + - Only enzyme complexes are enclosed in brackets
    + - " and " and " or " strings are set to lowercase
     
    - A rxnGeneMat matrix consistent with the standardized grRules is created.
    + A rxnGeneMat matrix consistent with the standardised grRules is created.
    + grRules that are not in disjunctive normal form (an "or" nested inside an
    + "and") or that cannot be parsed are not standardised, apart from the
    + removal of double spaces, spaces inside brackets and uppercase operators.
    + The function throws an error if the model has no grRules field.
     
      Parameters
      ----------
    @@ -45,21 +49,21 @@ 

    DESCRIPTION ^CROSS-REFERENCE INFORMATION ^
 <li><a href=findPotentialErrors findPotentialErrors Identify reactions with non-DNF GPR rules. This function is called by:
      -
    • addRxns addRxns Add reactions to a model.
    • changeGrRules changeGrRules Change multiple grRules at the same time.
    • contractModel contractModel Contract a model by grouping all identical reactions.
    • expandModel expandModel Expand reactions that use several gene associations.
    • mergeModels mergeModels Merge models into one model structure.
    • removeGenes removeGenes Delete a set of genes from a model.
    +
  • addRxns addRxns Add reactions to a model.
  • changeGrRules changeGrRules Change the grRules of multiple reactions.
  • contractModel contractModel Contract a model by merging identical reactions.
  • expandModel expandModel Expand reactions that use several gene associations.
  • mergeModels mergeModels Merge models into one model structure.
  • removeGenes removeGenes Delete a set of genes from a model.
  • SUBFUNCTIONS ^

    @@ -86,194 +90,198 @@

    SUBFUNCTIONS ^SOURCE CODE ^

    0001 function [grRules,rxnGeneMat,indexes2check] = standardizeGrRules(model,varargin)
    -0002 % standardizeGrRules  Standardize gene-reaction rules in a model.
    +0002 % standardizeGrRules  Standardise gene-reaction rules in a model.
     0003 %
    -0004 % The grRules are standardized according to the following:
    +0004 % The grRules are standardised according to the following:
     0005 %
     0006 % - No overall containing brackets
    -0007 % - Just enzyme complexes are enclosed into brackets
    -0008 % - " and " and " or " strings are strictly set to lowercase
    +0007 % - Only enzyme complexes are enclosed in brackets
    +0008 % - " and " and " or " strings are set to lowercase
     0009 %
    -0010 % A rxnGeneMat matrix consistent with the standardized grRules is created.
    -0011 %
    -0012 % Parameters
    -0013 % ----------
    -0014 % model : struct
    -0015 %     a model structure.
    -0016 %
    -0017 % Name-Value Arguments
    -0018 % --------------------
    -0019 % embedded : logical
    -0020 %     if true, suppresses the console warning about potentially
    -0021 %     problematic grRules relationships, for use when this function is
    -0022 %     called inside of another RAVEN function that handles reporting
    -0023 %     itself (default false).
    -0024 %
    -0025 % Returns
    -0026 % -------
    -0027 % grRules : cell
    -0028 %     [nRxns x 1] cell array with the standardized grRules.
    -0029 % rxnGeneMat : double
    -0030 %     [nRxns x nGenes] sparse matrix consistent with the standardized
    -0031 %     grRules.
    -0032 % indexes2check : double
    -0033 %     indices of the grRules with potentially problematic relationships
    -0034 %     that should be checked manually.
    -0035 %
    -0036 % Examples
    -0037 % --------
    -0038 %     [grRules, rxnGeneMat, indexes2check] = standardizeGrRules(model, embedded);
    +0010 % A rxnGeneMat matrix consistent with the standardised grRules is created.
    +0011 % grRules that are not in disjunctive normal form (an "or" nested inside an
    +0012 % "and") or that cannot be parsed are not standardised, apart from the
    +0013 % removal of double spaces, spaces inside brackets and uppercase operators.
    +0014 % The function throws an error if the model has no grRules field.
    +0015 %
    +0016 % Parameters
    +0017 % ----------
    +0018 % model : struct
    +0019 %     a model structure.
    +0020 %
    +0021 % Name-Value Arguments
    +0022 % --------------------
    +0023 % embedded : logical
    +0024 %     if true, suppresses the console warning about grRules that are not in
    +0025 %     disjunctive normal form or cannot be parsed, for use when this
    +0026 %     function is called inside another RAVEN function that handles
    +0027 %     reporting itself (default false).
    +0028 %
    +0029 % Returns
    +0030 % -------
    +0031 % grRules : cell
    +0032 %     [nRxns x 1] cell array with the standardised grRules.
    +0033 % rxnGeneMat : double
    +0034 %     [nRxns x nGenes] sparse matrix consistent with the standardised
    +0035 %     grRules.
    +0036 % indexes2check : double
    +0037 %     indices of the grRules that are not in disjunctive normal form or
    +0038 %     cannot be parsed. These grRules need a manual check.
     0039 %
    -0040 % Notes
    -0041 % -----
    -0042 % If used in a model reconstruction or modification pipeline, run this
    -0043 % function first.
    -0044 
    -0045 %Preallocate fields
    -0046 n          = length(model.rxns);
    -0047 [g,~]      = size(model.genes);
    -0048 rxnGeneMat = sparse(n,g);
    -0049 grRules    = cell(n,1);
    -0050 genes      = model.genes;
    -0051 p=parseRAVENargs(varargin, {'embedded',false});
    -0052 embedded=p.embedded;
    -0053 
    -0054 %Gene id -> index lookup, built once rather than linearly scanning all of
    -0055 %model.genes (via strcmpi) for every gene token of every reaction, which
    -0056 %is quadratic in genome-scale models.
    -0057 if isempty(genes)
    -0058     geneMap = containers.Map('KeyType','char','ValueType','double');
    -0059 else
    -0060     geneMap = containers.Map(lower(genes),num2cell(1:numel(genes)));
    -0061 end
    -0062 
    -0063 if isfield(model,'grRules')
    -0064     originalGrRules=model.grRules; 
    -0065     originalGrRules=grRulesPreparation(originalGrRules);
    -0066     %Search for potential logical errors in the grRules field
    -0067     tmpModel.rxns=model.rxns; tmpModel.grRules=originalGrRules;
    -0068     fpeIssues=findPotentialErrors(tmpModel);
    -0069     if isempty(fpeIssues)
    -0070         indexes2check=[];
    -0071     else
    -0072         indexes2check=vertcat(fpeIssues.index);
    -0073     end
    -0074     if ~isempty(indexes2check) && ~embedded
    -0075         %Escaped: rxn id and grRule are arbitrary model text and may
    -0076         %contain "%", which sprintf/warning would otherwise misread as a
    -0077         %format directive and truncate the rest of the message.
    -0078         esc=@(s) strrep(s,'%','%%');
    -0079         STR=['Potentially problematic ") AND (", ") AND" or "AND ("relat' ...
    -0080              'ionships found in\n\n'];
    -0081         for fpeI=1:numel(fpeIssues)
    -0082             STR=[STR '  - grRule #' esc(fpeIssues(fpeI).rxn) ': ' esc(fpeIssues(fpeI).grRule) '\n']; %#ok<AGROW>
    -0083         end
    -0084         STR=[STR '\n This kind of relationships should only be present ' ...
    -0085              'in reactions catalysed by complexes of isoenzymes e.g.\n\n' ...
    -0086              '  - (G1 or G2) and (G3 or G4)\n\n For these cases modify ' ...
    -0087              'the grRules manually, writing all the possible combinations ' ...
    -0088              'e.g.\n\n  - (G1 and G3) or (G1 and G4) or (G2 and G3) or ' ...
    -0089              '(G2 and G4)\n\n For other cases modify the correspondent ' ...
    -0090              'grRules avoiding:\n\n  1) Overall container brackets, e.g.' ...
    -0091              '\n        "(G1 and G2)" should be "G1 and G2"\n\n  2) ' ...
    -0092              'Single unit enzymes enclosed into brackets, e.g.\n        ' ...
    -0093              '"(G1)" should be "G1"\n\n  3) The use of uppercases for ' ...
    -0094              'logical operators, e.g.\n        "G1 OR G2" should be "G1 ' ...
    -0095              'or G2"\n\n  4) Unbalanced brackets, e.g.\n        "((G1 ' ...
    -0096              'and G2) or G3" should be "(G1 and G2) or G3"\n'];
    -0097         %warning() re-parses its argument as a format string too, so the
    -0098         %already-resolved text must be passed via '%s', not directly.
    -0099         warning('%s', sprintf(STR))
    -0100     end
    -0101     
    -0102     for i=1:length(originalGrRules)
    -0103         originalSTR = originalGrRules{i};
    -0104         grRules{i,:}  = originalSTR;
    -0105         %Non-empty grRules are splitted in all their different isoenzymes
    -0106         genesSets   = getSimpleGeneSets(originalSTR);
    -0107         rxnGeneMat  = modifyRxnGeneMat(genesSets,geneMap,rxnGeneMat,i);
    -0108         %standardize the non-conflicting grRules
    -0109         if ~ismember(i,indexes2check)
    -0110             newSTR = [];
    -0111             if ~isempty(genesSets)
    -0112                 for j=1:length(genesSets)
    -0113                     simpleSet  = genesSets{j};
    -0114                     %Enclose simpleSet in brackets
    -0115                     if length(genesSets)>1
    -0116                         if ~isempty(strfind(simpleSet,' and '))
    -0117                             simpleSet = horzcat('(',simpleSet,')');
    -0118                         end
    -0119                     end
    -0120                     %Separate genesSets in the substring (in case of
    -0121                     %isoenzymes)
    -0122                     if j<length(genesSets)
    -0123                         newSTR = [newSTR, simpleSet, ' or '];
    -0124                         %Add the last simpleSet
    -0125                     else
    -0126                         newSTR = [newSTR, simpleSet];
    -0127                     end
    -0128                 end
    -0129                 %Update grRule
    -0130                 grRules{i} = char(newSTR);
    -0131             end
    -0132         end
    -0133     end
    -0134 else
    -0135     error('The model does not have a grRules field.')
    -0136 end
    -0137 
    -0138 end
    -0139 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    -0140 %Function that gets a cell array with all the simple geneSets in a given
    -0141 %grRule string
    -0142 function genesSets = getSimpleGeneSets(originalSTR)
    -0143 genesSets  = [];
    -0144 if ~isempty(originalSTR)
    -0145     originalSTR = strtrim(originalSTR);
    -0146     %Remove all brackets
    -0147     originalSTR = strrep(originalSTR,'(','');
    -0148     originalSTR = strrep(originalSTR,')','');
    -0149     %Split all the different genesSets
    -0150     genesSets  = transpose(strsplit(originalSTR,' or '));
    -0151 end
    -0152 end
    -0153 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    -0154 %Function that gets a cell array of simple genes sets (single genes or
    -0155 %enzyme complexes) associated with the i-th reaction and modifies the
    -0156 %correspondent row in the rxnGeneMat accordingly.
    -0157 function rxnGeneMat = modifyRxnGeneMat(genesSets,geneMap,rxnGeneMat,i)
    -0158 
    -0159 if ~isempty(genesSets)
    -0160     for j=1:length(genesSets)
    -0161         simpleSet  = genesSets{j};
    -0162         %        rxnGeneMat = modifyRxnGeneMat(simpleSet,model.genes,rxnGeneMat,i);
    -0163         %Get individual genes
    -0164         STR   = strrep(simpleSet,') and (',' and ');
    -0165         genes = strsplit(STR,' ');
    -0166         for k=1:length(genes)
    -0167             if ~strcmpi(genes(k),' and ')
    -0168                 geneKey = lower(genes{k});
    -0169                 if isKey(geneMap,geneKey)
    -0170                     rxnGeneMat(i,geneMap(geneKey)) = 1;
    -0171                     %else
    -0172                     %In this case the gene should be added to the
    -0173                     %genes field (and to all of its dependencies)
    -0174                 end
    -0175             end
    -0176         end
    -0177     end
    -0178 end
    -0179 end
    -0180 
    -0181 function grRules = grRulesPreparation(grRules)
    -0182 %Remove unnecessary blanks
    -0183 grRules=strrep(grRules,'  ',' ');
    -0184 grRules=strrep(grRules,'( ','(');
    -0185 grRules=strrep(grRules,' )',')');
    -0186 % Make sure that AND and OR strings are in lowercase
    -0187 grRules=strrep(grRules,' AND ',' and ');
    -0188 grRules=strrep(grRules,' OR ',' or ');
    -0189 end
    +0040 % Examples +0041 % -------- +0042 % [grRules, rxnGeneMat, indexes2check] = standardizeGrRules(model, embedded); +0043 % +0044 % Notes +0045 % ----- +0046 % If used in a model reconstruction or modification pipeline, run this +0047 % function first. +0048 +0049 %Preallocate fields +0050 n = length(model.rxns); +0051 [g,~] = size(model.genes); +0052 rxnGeneMat = sparse(n,g); +0053 grRules = cell(n,1); +0054 genes = model.genes; +0055 p=parseRAVENargs(varargin, {'embedded',false}); +0056 embedded=p.embedded; +0057 +0058 %Gene id -> index lookup, built once rather than linearly scanning all of +0059 %model.genes (via strcmpi) for every gene token of every reaction, which +0060 %is quadratic in genome-scale models. +0061 if isempty(genes) +0062 geneMap = containers.Map('KeyType','char','ValueType','double'); +0063 else +0064 geneMap = containers.Map(lower(genes),num2cell(1:numel(genes))); +0065 end +0066 +0067 if isfield(model,'grRules') +0068 originalGrRules=model.grRules; +0069 originalGrRules=grRulesPreparation(originalGrRules); +0070 %Search for potential logical errors in the grRules field +0071 tmpModel.rxns=model.rxns; tmpModel.grRules=originalGrRules; +0072 fpeIssues=findPotentialErrors(tmpModel); +0073 if isempty(fpeIssues) +0074 indexes2check=[]; +0075 else +0076 indexes2check=vertcat(fpeIssues.index); +0077 end +0078 if ~isempty(indexes2check) && ~embedded +0079 %Escaped: rxn id and grRule are arbitrary model text and may +0080 %contain "%", which sprintf/warning would otherwise misread as a +0081 %format directive and truncate the rest of the message. +0082 esc=@(s) strrep(s,'%','%%'); +0083 STR=['Potentially problematic ") AND (", ") AND" or "AND ("relat' ... +0084 'ionships found in\n\n']; +0085 for fpeI=1:numel(fpeIssues) +0086 STR=[STR ' - grRule #' esc(fpeIssues(fpeI).rxn) ': ' esc(fpeIssues(fpeI).grRule) '\n']; %#ok<AGROW> +0087 end +0088 STR=[STR '\n This kind of relationships should only be present ' ... +0089 'in reactions catalysed by complexes of isoenzymes e.g.\n\n' ... +0090 ' - (G1 or G2) and (G3 or G4)\n\n For these cases modify ' ... +0091 'the grRules manually, writing all the possible combinations ' ... +0092 'e.g.\n\n - (G1 and G3) or (G1 and G4) or (G2 and G3) or ' ... +0093 '(G2 and G4)\n\n For other cases modify the correspondent ' ... +0094 'grRules avoiding:\n\n 1) Overall container brackets, e.g.' ... +0095 '\n "(G1 and G2)" should be "G1 and G2"\n\n 2) ' ... +0096 'Single unit enzymes enclosed into brackets, e.g.\n ' ... +0097 '"(G1)" should be "G1"\n\n 3) The use of uppercases for ' ... +0098 'logical operators, e.g.\n "G1 OR G2" should be "G1 ' ... +0099 'or G2"\n\n 4) Unbalanced brackets, e.g.\n "((G1 ' ... +0100 'and G2) or G3" should be "(G1 and G2) or G3"\n']; +0101 %warning() re-parses its argument as a format string too, so the +0102 %already-resolved text must be passed via '%s', not directly. +0103 warning('%s', sprintf(STR)) +0104 end +0105 +0106 for i=1:length(originalGrRules) +0107 originalSTR = originalGrRules{i}; +0108 grRules{i,:} = originalSTR; +0109 %Non-empty grRules are splitted in all their different isoenzymes +0110 genesSets = getSimpleGeneSets(originalSTR); +0111 rxnGeneMat = modifyRxnGeneMat(genesSets,geneMap,rxnGeneMat,i); +0112 %standardize the non-conflicting grRules +0113 if ~ismember(i,indexes2check) +0114 newSTR = []; +0115 if ~isempty(genesSets) +0116 for j=1:length(genesSets) +0117 simpleSet = genesSets{j}; +0118 %Enclose simpleSet in brackets +0119 if length(genesSets)>1 +0120 if ~isempty(strfind(simpleSet,' and ')) +0121 simpleSet = horzcat('(',simpleSet,')'); +0122 end +0123 end +0124 %Separate genesSets in the substring (in case of +0125 %isoenzymes) +0126 if j<length(genesSets) +0127 newSTR = [newSTR, simpleSet, ' or ']; +0128 %Add the last simpleSet +0129 else +0130 newSTR = [newSTR, simpleSet]; +0131 end +0132 end +0133 %Update grRule +0134 grRules{i} = char(newSTR); +0135 end +0136 end +0137 end +0138 else +0139 error('The model does not have a grRules field.') +0140 end +0141 +0142 end +0143 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +0144 %Function that gets a cell array with all the simple geneSets in a given +0145 %grRule string +0146 function genesSets = getSimpleGeneSets(originalSTR) +0147 genesSets = []; +0148 if ~isempty(originalSTR) +0149 originalSTR = strtrim(originalSTR); +0150 %Remove all brackets +0151 originalSTR = strrep(originalSTR,'(',''); +0152 originalSTR = strrep(originalSTR,')',''); +0153 %Split all the different genesSets +0154 genesSets = transpose(strsplit(originalSTR,' or ')); +0155 end +0156 end +0157 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +0158 %Function that gets a cell array of simple genes sets (single genes or +0159 %enzyme complexes) associated with the i-th reaction and modifies the +0160 %correspondent row in the rxnGeneMat accordingly. +0161 function rxnGeneMat = modifyRxnGeneMat(genesSets,geneMap,rxnGeneMat,i) +0162 +0163 if ~isempty(genesSets) +0164 for j=1:length(genesSets) +0165 simpleSet = genesSets{j}; +0166 % rxnGeneMat = modifyRxnGeneMat(simpleSet,model.genes,rxnGeneMat,i); +0167 %Get individual genes +0168 STR = strrep(simpleSet,') and (',' and '); +0169 genes = strsplit(STR,' '); +0170 for k=1:length(genes) +0171 if ~strcmpi(genes(k),' and ') +0172 geneKey = lower(genes{k}); +0173 if isKey(geneMap,geneKey) +0174 rxnGeneMat(i,geneMap(geneKey)) = 1; +0175 %else +0176 %In this case the gene should be added to the +0177 %genes field (and to all of its dependencies) +0178 end +0179 end +0180 end +0181 end +0182 end +0183 end +0184 +0185 function grRules = grRulesPreparation(grRules) +0186 %Remove unnecessary blanks +0187 grRules=strrep(grRules,' ',' '); +0188 grRules=strrep(grRules,'( ','('); +0189 grRules=strrep(grRules,' )',')'); +0190 % Make sure that AND and OR strings are in lowercase +0191 grRules=strrep(grRules,' AND ',' and '); +0192 grRules=strrep(grRules,' OR ',' or '); +0193 end

    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/queries/buildEquation.html b/docs/html/queries/buildEquation.html index 9bc9baaf..9a198ea0 100644 --- a/docs/html/queries/buildEquation.html +++ b/docs/html/queries/buildEquation.html @@ -34,14 +34,17 @@

    DESCRIPTION ^SOURCE CODE ^% mets : cell 0007 % metabolites involved in the reaction. 0008 % stoichCoeffs : double -0009 % vector with corresponding stoichiometric coeffs. -0010 % isrev : logical -0011 % indicates if the reaction is reversible or not. -0012 % -0013 % Returns -0014 % ------- -0015 % equationString : char -0016 % equation as a string. -0017 % -0018 % Examples -0019 % -------- -0020 % equationString = buildEquation(mets, stoichCoeffs, isrev); -0021 -0022 mets=convertCharArray(mets); -0023 if ~isnumeric(stoichCoeffs) -0024 EM = 'stoichCoeffs must be a numeric vector'; -0025 error('RAVEN:badInput', '%s', EM); -0026 elseif ~islogical(isrev) -0027 EM = 'isrev must be a logical'; +0009 % vector with the corresponding stoichiometric coefficients, of the same +0010 % length as mets. Negative coefficients are reactants and positive +0011 % coefficients are products. +0012 % isrev : logical +0013 % true if the reaction is reversible (written with "<=>"), false if it +0014 % is irreversible (written with "=>"). +0015 % +0016 % Returns +0017 % ------- +0018 % equationString : char +0019 % the equation as a string. +0020 % +0021 % Examples +0022 % -------- +0023 % equationString = buildEquation(mets, stoichCoeffs, isrev); +0024 +0025 mets=convertCharArray(mets); +0026 if ~isnumeric(stoichCoeffs) +0027 EM = 'stoichCoeffs must be a numeric vector'; 0028 error('RAVEN:badInput', '%s', EM); -0029 elseif length(mets) ~= length(stoichCoeffs) -0030 EM = 'lengths of mets and stoichCoeffs should be the same'; +0029 elseif ~islogical(isrev) +0030 EM = 'isrev must be a logical'; 0031 error('RAVEN:badInput', '%s', EM); -0032 end -0033 -0034 %Reactant half: -0035 reactants = mets(stoichCoeffs<0); -0036 reactantsCoeffs = abs(stoichCoeffs(stoichCoeffs<0)); -0037 reactantsEqn = concatenateEquation(reactants,reactantsCoeffs); -0038 -0039 %Product half: -0040 products = mets(stoichCoeffs>0); -0041 productsCoeffs = abs(stoichCoeffs(stoichCoeffs>0)); -0042 productsEqn = concatenateEquation(products,productsCoeffs); -0043 -0044 %Full equation: -0045 if isrev -0046 equationString = [reactantsEqn ' <=> ' productsEqn]; -0047 else -0048 equationString = [reactantsEqn ' => ' productsEqn]; -0049 end -0050 -0051 end -0052 -0053 function eqn = concatenateEquation(mets,stoichCoeffs) -0054 %This function concatenates metabolites and stoich. coefficients to form -0055 %either the left or right side of the rxn equation -0056 eqn = ''; -0057 for i = 1:length(stoichCoeffs) -0058 if i == 1 -0059 plusString=''; -0060 else -0061 plusString=' + '; -0062 end -0063 stoich = stoichCoeffs(i); -0064 if stoich == 1 -0065 stoich = ''; -0066 else -0067 stoich = [num2str(stoich) ' ']; -0068 end -0069 eqn = [eqn plusString stoich mets{i}]; -0070 end -0071 end

    +0032 elseif length(mets) ~= length(stoichCoeffs) +0033 EM = 'lengths of mets and stoichCoeffs should be the same'; +0034 error('RAVEN:badInput', '%s', EM); +0035 end +0036 +0037 %Reactant half: +0038 reactants = mets(stoichCoeffs<0); +0039 reactantsCoeffs = abs(stoichCoeffs(stoichCoeffs<0)); +0040 reactantsEqn = concatenateEquation(reactants,reactantsCoeffs); +0041 +0042 %Product half: +0043 products = mets(stoichCoeffs>0); +0044 productsCoeffs = abs(stoichCoeffs(stoichCoeffs>0)); +0045 productsEqn = concatenateEquation(products,productsCoeffs); +0046 +0047 %Full equation: +0048 if isrev +0049 equationString = [reactantsEqn ' <=> ' productsEqn]; +0050 else +0051 equationString = [reactantsEqn ' => ' productsEqn]; +0052 end +0053 +0054 end +0055 +0056 function eqn = concatenateEquation(mets,stoichCoeffs) +0057 %This function concatenates metabolites and stoich. coefficients to form +0058 %either the left or right side of the rxn equation +0059 eqn = ''; +0060 for i = 1:length(stoichCoeffs) +0061 if i == 1 +0062 plusString=''; +0063 else +0064 plusString=' + '; +0065 end +0066 stoich = stoichCoeffs(i); +0067 if stoich == 1 +0068 stoich = ''; +0069 else +0070 stoich = [num2str(stoich) ' ']; +0071 end +0072 eqn = [eqn plusString stoich mets{i}]; +0073 end +0074 end

    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/queries/checkModelStruct.html b/docs/html/queries/checkModelStruct.html index 524309b2..ebf79191 100644 --- a/docs/html/queries/checkModelStruct.html +++ b/docs/html/queries/checkModelStruct.html @@ -4,7 +4,7 @@ Description of checkModelStruct - + @@ -21,13 +21,13 @@

    checkModelStruct

    PURPOSE ^

    -
    checkModelStruct Perform a number of checks to ensure a model structure is ok.
    +
    checkModelStruct Check a model structure for errors and inconsistencies.

    SYNOPSIS ^

    function issues=checkModelStruct(model,varargin)

    DESCRIPTION ^

    -
     checkModelStruct  Perform a number of checks to ensure a model structure is ok.
    +
     checkModelStruct  Check a model structure for errors and inconsistencies.
     
      Parameters
      ----------
    @@ -38,32 +38,33 @@ 

    DESCRIPTION ^SUBFUNCTIONS ^SOURCE CODE ^

    0001 function issues=checkModelStruct(model,varargin)
    -0002 % checkModelStruct  Perform a number of checks to ensure a model structure is ok.
    +0002 % checkModelStruct  Check a model structure for errors and inconsistencies.
     0003 %
     0004 % Parameters
     0005 % ----------
    @@ -98,565 +99,566 @@ 

    SOURCE CODE ^% -------------------- 0011 % throwErrors : logical 0012 % true if structural problems (wrong types, missing required fields, -0013 % duplicate IDs, etc.) should throw an error. Advisory notices such as +0013 % duplicate IDs and other problems of structure) should throw an error. Advisory notices such as 0014 % unused elements and bound inconsistencies are always reported as 0015 % warnings regardless of this flag (default true). 0016 % trimWarnings : logical -0017 % true if only a maximum of 10 items should be displayed in a given -0018 % warning or error (default true). +0017 % true if a given warning or error lists at most 10 items, the last of +0018 % which reports the number of remaining items (default true). 0019 % 0020 % Returns 0021 % ------- 0022 % issues : struct -0023 % When called with an output argument, returns a struct array with one -0024 % element per finding (does not throw or print) instead of the default -0025 % print/throw behaviour. Fields: -0026 % -0027 % - category : char — type of issue: "missing_field", "wrong_type", -0028 % "empty_id", "duplicate", "invalid_id", "invalid_bounds", -0029 % "unused", "objective", "gpr", "invalid_formula", -0030 % "cross_reference", or "other". -0031 % - target : char — the specific item involved (field name, reaction -0032 % ID, metabolite ID, etc.), or "" when not applicable. -0033 % - message : char — the full diagnostic text. -0034 % -0035 % Notes -0036 % ----- -0037 % This is typically performed after importing or constructing a model, or -0038 % before attempting to export a model to SBML format. -0039 % -0040 % Examples -0041 % -------- -0042 % checkModelStruct(model); -0043 % checkModelStruct(model, "throwErrors", false); -0044 % issues = checkModelStruct(model); -0045 -0046 p=parseRAVENargs(varargin, {'throwErrors',true; 'trimWarnings',true}); -0047 throwErrors=p.throwErrors; -0048 trimWarnings=p.trimWarnings; -0049 -0050 collecting = nargout > 0; -0051 issues = struct('category',{},'target',{},'message',{}); -0052 -0053 function reportIssue(severity, msg, items) -0054 % Report one issue: accumulate in collect mode, or warn/error. -0055 % severity: "error" | "warning" -0056 % When items is supplied (3 args) but is empty, silently do nothing. -0057 listProvided = nargin >= 3; -0058 if nargin < 3; items = {}; end -0059 if ~isempty(items) -0060 items = convertCharArray(items); -0061 end -0062 if listProvided && isempty(items); return; end -0063 if trimWarnings && numel(items) > 10 -0064 items{10} = sprintf('...and %d more', numel(items)-9); -0065 items(11:end) = []; -0066 end -0067 cat = issueCategory(msg); -0068 if collecting -0069 if isempty(items) -0070 issues(end+1,1) = struct('category',cat,'target','','message',msg); -0071 else -0072 for ki_ = 1:numel(items) -0073 issues(end+1,1) = struct('category',cat,'target',items{ki_},'message',msg); %#ok<AGROW> -0074 end -0075 end -0076 return -0077 end -0078 isErr = strcmp(severity,'error') && throwErrors; -0079 if isErr -0080 if isempty(items) -0081 error('RAVEN:modelError', '%s', msg); -0082 else -0083 error('RAVEN:modelError', '%s', ravenList(msg, items, false)); -0084 end -0085 else -0086 if isempty(items) -0087 warning('RAVEN:modelError', '%s', msg); -0088 else -0089 warning('RAVEN:modelError', '%s', ravenList(msg, items, false)); -0090 end -0091 end -0092 end -0093 -0094 %Missing elements — checked inline so missing fields do not cause cascading -0095 %errors in the rest of the body -0096 fields={'id';'name';'rxns';'mets';'S';'lb';'ub';'rev';'c';'b';'comps';'metComps'}; -0097 missingField=false; -0098 for i=1:numel(fields) -0099 if ~isfield(model,fields{i}) -0100 EM=['The model is missing the "' fields{i} '" field']; -0101 reportIssue('error',EM); -0102 missingField=true; -0103 end -0104 end -0105 if missingField -0106 return % missing required fields would cause access errors below; this -0107 % must fire whenever a field is missing, not only when -0108 % collecting -- throwErrors=false otherwise warns here and -0109 % then crashes on the very field it just warned about -0110 end -0111 -0112 %Type check -0113 if ~ischar(model.id) -0114 EM='The "id" field must be a string'; -0115 reportIssue('error',EM); -0116 end -0117 if ~ischar(model.name) -0118 EM='The "name" field must be a string'; -0119 reportIssue('error',EM); -0120 end -0121 if ~iscellstr(model.rxns) -0122 EM='The "rxns" field must be a cell array of strings'; -0123 reportIssue('error',EM); -0124 end -0125 if ~iscellstr(model.mets) -0126 EM='The "mets" field must be a cell array of strings'; -0127 reportIssue('error',EM); -0128 end -0129 if ~isnumeric(model.S) -0130 EM='The "S" field must be of type "double"'; -0131 reportIssue('error',EM); -0132 end -0133 if ~isnumeric(model.lb) -0134 EM='The "lb" field must be of type "double"'; -0135 reportIssue('error',EM); -0136 end -0137 if ~isnumeric(model.ub) -0138 EM='The "ub" field must be of type "double"'; -0139 reportIssue('error',EM); -0140 end -0141 if ~isnumeric(model.rev) -0142 EM='The "rev" field must be of type "double"'; -0143 reportIssue('error',EM); -0144 end -0145 if ~isnumeric(model.c) -0146 EM='The "c" field must be of type "double"'; -0147 reportIssue('error',EM); -0148 end -0149 if ~isnumeric(model.b) -0150 EM='The "b" field must be of type "double"'; -0151 reportIssue('error',EM); -0152 end -0153 if ~iscellstr(model.comps) -0154 EM='The "comps" field must be a cell array of strings'; -0155 reportIssue('error',EM); -0156 end -0157 if ~isnumeric(model.metComps) -0158 EM='The "metComps" field must be of type "double"'; -0159 reportIssue('error',EM); -0160 end -0161 if isfield(model,'compNames') -0162 if ~iscellstr(model.compNames) -0163 EM='The "compNames" field must be a cell array of strings'; -0164 reportIssue('error',EM); -0165 end -0166 end -0167 if isfield(model,'compOutside') -0168 if ~iscellstr(model.compOutside) -0169 EM='The "compOutside" field must be a cell array of strings'; -0170 reportIssue('error',EM); -0171 end -0172 end -0173 if isfield(model,'rxnNames') -0174 if ~iscellstr(model.rxnNames) -0175 EM='The "rxnNames" field must be a cell array of strings'; -0176 reportIssue('error',EM); -0177 end -0178 end -0179 if isfield(model,'metNames') -0180 if ~iscellstr(model.metNames) -0181 EM='The "metNames" field must be a cell array of strings'; -0182 reportIssue('error',EM); -0183 end -0184 end -0185 if isfield(model,'genes') -0186 if ~iscellstr(model.genes) -0187 EM='The "genes" field must be a cell array of strings'; -0188 reportIssue('error',EM); -0189 end -0190 end -0191 if isfield(model,'rxnGeneMat') -0192 if ~isnumeric(model.rxnGeneMat) -0193 EM='The "rxnGeneMat" field must be of type "double"'; -0194 reportIssue('error',EM); -0195 end -0196 end -0197 if isfield(model,'grRules') -0198 if ~iscellstr(model.grRules) -0199 EM='The "grRules" field must be a cell array of strings'; -0200 reportIssue('error',EM); -0201 end -0202 if ~isfield(model,'genes') -0203 EM='If "grRules" field exists, the model should also contain a "genes" field'; -0204 reportIssue('error',EM); -0205 else -0206 %Erroneous grRules that start/end with OR/AND -0207 EM='The following reaction(s) have grRules that start or end with ''OR'' or ''AND'':'; -0208 reportIssue('error',EM,model.rxns(startsWith(model.grRules,{'or ','and '}) | endsWith(model.grRules,{' or',' and'}))); -0209 %grRules that are not in genes field -0210 geneList = getGenesFromGrRules(model.grRules); -0211 geneList = setdiff(unique(geneList),model.genes); -0212 if ~isempty(geneList) -0213 problemGrRules = model.rxns(contains(model.grRules,geneList)); -0214 problemGrRules = strjoin(problemGrRules(:),'; '); -0215 EM=['The reaction(s) "' problemGrRules '" contain the following genes in its "grRules" field, but these are not in the "genes" field:']; -0216 reportIssue('error',EM,geneList); -0217 end -0218 end -0219 end -0220 if isfield(model,'rxnComps') -0221 if ~isnumeric(model.rxnComps) -0222 EM='The "rxnComps" field must be of type "double"'; -0223 reportIssue('error',EM); -0224 end -0225 end -0226 if isfield(model,'inchis') -0227 if ~iscellstr(model.inchis) -0228 EM='The "inchis" field must be a cell array of strings'; -0229 reportIssue('error',EM); -0230 end -0231 end -0232 if isfield(model,'metSmiles') -0233 if ~iscellstr(model.metSmiles) -0234 EM='The "metSmiles" field must be a cell array of strings'; -0235 reportIssue('error',EM); -0236 end -0237 end -0238 if isfield(model,'metFormulas') -0239 if ~iscellstr(model.metFormulas) -0240 EM='The "metFormulas" field must be a cell array of strings'; -0241 reportIssue('error',EM); -0242 end -0243 end -0244 if isfield(model,'metCharges') -0245 if ~isnumeric(model.metCharges) -0246 EM='The "metCharges" field must be a double'; -0247 reportIssue('error',EM); -0248 end -0249 end -0250 if isfield(model,'metDeltaG') -0251 if ~isnumeric(model.metDeltaG) -0252 EM='The "metDeltaG" field must be a double'; -0253 reportIssue('error',EM); -0254 end -0255 end -0256 if isfield(model,'subSystems') -0257 isNested = any(cellfun(@(x) iscell(x), model.subSystems)); -0258 isCellStr = any(cellfun(@(x) ischar(x), model.subSystems)); -0259 if ~xor(isNested,isCellStr) -0260 EM='The "subSystems" field must be a cell array of chars, *or* a cell array of cell arrays of chars'; -0261 reportIssue('error',EM); -0262 end -0263 end -0264 if isfield(model,'eccodes') -0265 if ~iscellstr(model.eccodes) -0266 EM='The "eccodes" field must be a cell array of strings'; -0267 reportIssue('error',EM); -0268 end -0269 end -0270 if isfield(model,'unconstrained') -0271 if ~isnumeric(model.unconstrained) -0272 EM='The "unconstrained" field must be of type "double"'; -0273 reportIssue('error',EM); -0274 end -0275 end -0276 if isfield(model,'rxnNotes') -0277 if ~iscellstr(model.rxnNotes) -0278 EM='The "rxnNotes" field must be a cell array of strings'; -0279 reportIssue('error',EM); -0280 end -0281 end -0282 if isfield(model,'rxnReferences') -0283 if ~iscellstr(model.rxnReferences) -0284 EM='The "rxnReferences" field must be a cell array of strings'; -0285 reportIssue('error',EM); -0286 end -0287 end -0288 if isfield(model,'rxnConfidenceScores') -0289 if ~isnumeric(model.rxnConfidenceScores) -0290 EM='The "rxnConfidenceScores" field must be a double'; -0291 reportIssue('error',EM); -0292 end -0293 end -0294 if isfield(model,'rxnDeltaG') -0295 if ~isnumeric(model.rxnDeltaG) -0296 EM='The "rxnDeltaG" field must be a double'; -0297 reportIssue('error',EM); -0298 end -0299 end -0300 -0301 %Empty strings -0302 if isempty(model.id) -0303 EM='The "id" field cannot be empty'; -0304 reportIssue('error',EM); -0305 end -0306 if any(cellfun(@isempty,model.rxns)) -0307 EM='The model contains empty reaction IDs'; -0308 reportIssue('error',EM); -0309 end -0310 if any(cellfun(@isempty,model.mets)) -0311 EM='The model contains empty metabolite IDs'; -0312 reportIssue('error',EM); -0313 end -0314 if any(cellfun(@isempty,model.comps)) -0315 EM='The model contains empty compartment IDs'; -0316 reportIssue('error',EM); -0317 end -0318 if isfield(model,'metNames') -0319 EM='The following metabolites have empty names:'; -0320 reportIssue('error',EM,model.mets(cellfun(@isempty,model.metNames))); -0321 end -0322 -0323 if isfield(model,'genes') -0324 if any(cellfun(@isempty,model.genes)) -0325 EM='The model contains empty gene IDs'; -0326 reportIssue('error',EM); -0327 end -0328 end -0329 -0330 %Validate format of ids -0331 fields = {'rxns';'mets';'comps';'genes'}; -0332 fieldNames = {'reaction';'metabolite';'compartment';'gene'}; -0333 fieldPrefix = {'R_';'M_';'C_';'G_'}; -0334 for i=1:numel(fields) -0335 try -0336 numIDs = ~startsWith(model.(fields{i}),regexpPattern('^[a-zA-Z_]')); -0337 catch -0338 numIDs = []; -0339 end -0340 if any(numIDs) -0341 EM = ['The following ' fieldNames{i} ' identifiers do not start '... -0342 'with a letter or _ (conflicting with SBML specifications). '... -0343 'This does not impact RAVEN functionality, but be aware that '... -0344 'exportModel will automatically add ' fieldPrefix{i} ... -0345 ' prefixes to all ' fieldNames{i} ' identifiers:']; -0346 reportIssue('warning',EM,{model.(fields{i}){numIDs}}); -0347 end -0348 end -0349 -0350 %Duplicates -0351 EM='The following reaction IDs are duplicates:'; -0352 reportIssue('error',EM,model.rxns(duplicates(model.rxns))); -0353 EM='The following metabolite IDs are duplicates:'; -0354 reportIssue('error',EM,model.mets(duplicates(model.mets))); -0355 EM='The following compartment IDs are duplicates:'; -0356 reportIssue('error',EM,model.comps(duplicates(model.comps))); -0357 if isfield(model,'genes') -0358 EM='The following genes are duplicates:'; -0359 reportIssue('error',EM,model.genes(duplicates(model.genes))); -0360 end -0361 if isfield(model,'metNames') -0362 metInComp=strcat(model.metNames,'[',model.comps(model.metComps),']'); -0363 EM='The following metabolites already exist in the same compartment:'; -0364 reportIssue('error',EM,metInComp(duplicates(metInComp))); -0365 end -0366 -0367 %Elements never used (always reported as warnings) -0368 EM='The following reactions are empty (no involved metabolites):'; -0369 reportIssue('warning',EM,model.rxns(~any(model.S,1))); -0370 EM='The following metabolites are never used in a reaction:'; -0371 reportIssue('warning',EM,model.mets(~any(model.S,2))); -0372 if isfield(model,'genes') -0373 EM='The following genes are not associated to a reaction:'; -0374 reportIssue('warning',EM,model.genes(~any(model.rxnGeneMat,1))); -0375 end -0376 I=true(numel(model.comps),1); -0377 I(model.metComps)=false; -0378 EM='The following compartments contain no metabolites:'; -0379 reportIssue('warning',EM,model.comps(I)); -0380 -0381 %Contradicting bounds -0382 EM='The following reactions have contradicting bounds (lower bound is higher than upper bound):'; -0383 reportIssue('error',EM,model.rxns(model.lb>model.ub)); -0384 EM='The following reactions have lower and upper bounds that indicate reversibility, but are indicated as irreversible in model.rev:'; -0385 reportIssue('warning',EM,model.rxns(model.lb < 0 & model.ub > 0 & model.rev==0)); -0386 -0387 %Multiple or no objective functions not allowed in SBML L3V1 FBCv2 -0388 if numel(find(model.c))>1 -0389 EM='Multiple objective functions found. This might be intended, but results in FBCv2 non-compliant SBML file when exported'; -0390 reportIssue('warning',EM,model.rxns(find(model.c))); -0391 elseif ~any(model.c) -0392 EM='No objective function found. This might be intended, but results in FBCv2 non-compliant SBML file when exported'; -0393 reportIssue('warning',EM); -0394 end -0395 -0396 %Mapping of compartments -0397 if isfield(model,'compOutside') -0398 EM='The following compartments are in "compOutside" but not in "comps":'; -0399 reportIssue('error',EM,setdiff(model.compOutside,[{''};model.comps])); -0400 end -0401 -0402 %Met names which start with number -0403 if isfield(model,'metNames') -0404 I=false(numel(model.metNames),1); -0405 for i=1:numel(model.metNames) -0406 index=strfind(model.metNames{i},' '); -0407 if any(index) -0408 if ~isnan(str2double(model.metNames{i}(1:index(1)-1))) -0409 I(i)=true; -0410 end -0411 end -0412 end -0413 EM='The following metabolite names begin with a number directly followed by space, which could potentially cause problems:'; -0414 reportIssue('warning',EM,model.metNames(I)); -0415 end -0416 -0417 %Non-parseable composition -0418 if isfield(model,'metFormulas') -0419 [~, ~, exitFlag]=parseFormulas(model.metFormulas,true,false); -0420 EM='The composition for the following metabolites could not be parsed:'; -0421 reportIssue('warning',EM,model.mets(exitFlag==-1)); -0422 end -0423 -0424 %Check if there are metabolites with different names but the same MIRIAM -0425 %codes -0426 if isfield(model,'metMiriams') -0427 miriams=containers.Map(); -0428 for i=1:numel(model.mets) -0429 if ~isempty(model.metMiriams{i}) -0430 %Loop through and add for each miriam -0431 for j=1:numel(model.metMiriams{i}.name) -0432 %Get existing metabolite indexes -0433 current=strcat(model.metMiriams{i}.name{j},'/',model.metMiriams{i}.value{j}); -0434 if isKey(miriams,current) -0435 existing=miriams(current); -0436 else -0437 existing=[]; -0438 end -0439 miriams(current)=[existing;i]; -0440 end -0441 end -0442 end -0443 -0444 %Get all keys -0445 allMiriams=keys(miriams); -0446 -0447 hasMultiple=false(numel(allMiriams),1); -0448 for i=1:numel(allMiriams) -0449 if numel(miriams(allMiriams{i}))>1 -0450 %Check if they all have the same name -0451 if numel(unique(model.metNames(miriams(allMiriams{i}))))>1 -0452 % regexp returns [] (not a false-like 0) when there is no -0453 % match, and ~[] is [] too, so the negation itself is -0454 % never true: use isempty to actually test for "no match" -0455 if isempty(regexp(allMiriams{i},'^sbo\/SBO:','once')) % SBO terms are expected to be multiple -0456 hasMultiple(i)=true; -0457 end -0458 end -0459 end -0460 end -0461 -0462 %Print output -0463 EM='The following MIRIAM strings are associated to more than one unique metabolite name:'; -0464 reportIssue('warning',EM,allMiriams(hasMultiple)); -0465 end -0466 -0467 %Check if there are metabolites with different names but the same InChI -0468 %codes -0469 if isfield(model,'inchis') -0470 inchis=containers.Map(); -0471 for i=1:numel(model.mets) -0472 if ~isempty(model.inchis{i}) -0473 %Get existing metabolite indexes -0474 if isKey(inchis,model.inchis{i}) -0475 existing=inchis(model.inchis{i}); -0476 else -0477 existing=[]; -0478 end -0479 inchis(model.inchis{i})=[existing;i]; -0480 end -0481 end -0482 -0483 %Get all keys -0484 allInchis=keys(inchis); -0485 -0486 hasMultiple=false(numel(allInchis),1); -0487 for i=1:numel(allInchis) -0488 if numel(inchis(allInchis{i}))>1 -0489 %Check if they all have the same name -0490 if numel(unique(model.metNames(inchis(allInchis{i}))))>1 -0491 hasMultiple(i)=true; -0492 end -0493 end -0494 end -0495 -0496 %Print output -0497 EM='The following InChI strings are associated to more than one unique metabolite name:'; -0498 reportIssue('warning',EM,allInchis(hasMultiple)); -0499 end -0500 -0501 % %Check if there are metabolites with different names but the same SMILES -0502 % if isfield(model,"metSmiles") -0503 % metSmiles=containers.Map(); -0504 % for i=1:numel(model.mets) -0505 % if ~isempty(model.metSmiles{i}) -0506 % %Get existing metabolite indexes -0507 % if isKey(metSmiles,model.metSmiles{i}) -0508 % existing=metSmiles(model.metSmiles{i}); -0509 % else -0510 % existing=[]; -0511 % end -0512 % metSmiles(model.metSmiles{i})=[existing;i]; -0513 % end -0514 % end -0515 % -0516 % %Get all keys -0517 % allmetSmiles=keys(metSmiles); -0518 % -0519 % hasMultiple=false(numel(metSmiles),1); -0520 % for i=1:numel(metSmiles) -0521 % if numel(metSmiles(metSmiles{i}))>1 -0522 % %Check if they all have the same name -0523 % if numel(unique(model.metNames(metSmiles(allmetSmiles{i}))))>1 -0524 % hasMultiple(i)=true; -0525 % end -0526 % end -0527 % end -0528 % -0529 % %Print output -0530 % EM="The following metSmiles strings are associated to more than one unique metabolite name:"; -0531 % reportIssue("warning",EM,allmetSmiles(hasMultiple)); -0532 % end -0533 end -0534 -0535 function I=duplicates(strings) -0536 I=false(numel(strings),1); -0537 [J, K]=unique(strings); -0538 if numel(J)~=numel(strings) -0539 L=1:numel(strings); -0540 L(K)=[]; -0541 I(L)=true; -0542 end +0023 % When called with an output argument, the function returns a struct +0024 % array with one element per finding and neither throws nor prints. The +0025 % default behaviour without an output argument is to print warnings and +0026 % throw errors. Fields: +0027 % +0028 % - category (char): type of issue, one of "missing_field", +0029 % "wrong_type", "empty_id", "duplicate", "invalid_id", +0030 % "invalid_bounds", "unused", "objective", "gpr", "invalid_formula", +0031 % "cross_reference" or "other". +0032 % - target (char): the item involved (field name, reaction ID, +0033 % metabolite ID or another item), or "" when not applicable. +0034 % - message (char): the full diagnostic text. +0035 % +0036 % Notes +0037 % ----- +0038 % The check is intended for use after importing or constructing a model and +0039 % before exporting a model to SBML format. +0040 % +0041 % Examples +0042 % -------- +0043 % checkModelStruct(model); +0044 % checkModelStruct(model, "throwErrors", false); +0045 % issues = checkModelStruct(model); +0046 +0047 p=parseRAVENargs(varargin, {'throwErrors',true; 'trimWarnings',true}); +0048 throwErrors=p.throwErrors; +0049 trimWarnings=p.trimWarnings; +0050 +0051 collecting = nargout > 0; +0052 issues = struct('category',{},'target',{},'message',{}); +0053 +0054 function reportIssue(severity, msg, items) +0055 % Report one issue: accumulate in collect mode, or warn/error. +0056 % severity: "error" | "warning" +0057 % When items is supplied (3 args) but is empty, silently do nothing. +0058 listProvided = nargin >= 3; +0059 if nargin < 3; items = {}; end +0060 if ~isempty(items) +0061 items = convertCharArray(items); +0062 end +0063 if listProvided && isempty(items); return; end +0064 if trimWarnings && numel(items) > 10 +0065 items{10} = sprintf('...and %d more', numel(items)-9); +0066 items(11:end) = []; +0067 end +0068 cat = issueCategory(msg); +0069 if collecting +0070 if isempty(items) +0071 issues(end+1,1) = struct('category',cat,'target','','message',msg); +0072 else +0073 for ki_ = 1:numel(items) +0074 issues(end+1,1) = struct('category',cat,'target',items{ki_},'message',msg); %#ok<AGROW> +0075 end +0076 end +0077 return +0078 end +0079 isErr = strcmp(severity,'error') && throwErrors; +0080 if isErr +0081 if isempty(items) +0082 error('RAVEN:modelError', '%s', msg); +0083 else +0084 error('RAVEN:modelError', '%s', ravenList(msg, items, false)); +0085 end +0086 else +0087 if isempty(items) +0088 warning('RAVEN:modelError', '%s', msg); +0089 else +0090 warning('RAVEN:modelError', '%s', ravenList(msg, items, false)); +0091 end +0092 end +0093 end +0094 +0095 %Missing elements — checked inline so missing fields do not cause cascading +0096 %errors in the rest of the body +0097 fields={'id';'name';'rxns';'mets';'S';'lb';'ub';'rev';'c';'b';'comps';'metComps'}; +0098 missingField=false; +0099 for i=1:numel(fields) +0100 if ~isfield(model,fields{i}) +0101 EM=['The model is missing the "' fields{i} '" field']; +0102 reportIssue('error',EM); +0103 missingField=true; +0104 end +0105 end +0106 if missingField +0107 return % missing required fields would cause access errors below; this +0108 % must fire whenever a field is missing, not only when +0109 % collecting -- throwErrors=false otherwise warns here and +0110 % then crashes on the very field it just warned about +0111 end +0112 +0113 %Type check +0114 if ~ischar(model.id) +0115 EM='The "id" field must be a string'; +0116 reportIssue('error',EM); +0117 end +0118 if ~ischar(model.name) +0119 EM='The "name" field must be a string'; +0120 reportIssue('error',EM); +0121 end +0122 if ~iscellstr(model.rxns) +0123 EM='The "rxns" field must be a cell array of strings'; +0124 reportIssue('error',EM); +0125 end +0126 if ~iscellstr(model.mets) +0127 EM='The "mets" field must be a cell array of strings'; +0128 reportIssue('error',EM); +0129 end +0130 if ~isnumeric(model.S) +0131 EM='The "S" field must be of type "double"'; +0132 reportIssue('error',EM); +0133 end +0134 if ~isnumeric(model.lb) +0135 EM='The "lb" field must be of type "double"'; +0136 reportIssue('error',EM); +0137 end +0138 if ~isnumeric(model.ub) +0139 EM='The "ub" field must be of type "double"'; +0140 reportIssue('error',EM); +0141 end +0142 if ~isnumeric(model.rev) +0143 EM='The "rev" field must be of type "double"'; +0144 reportIssue('error',EM); +0145 end +0146 if ~isnumeric(model.c) +0147 EM='The "c" field must be of type "double"'; +0148 reportIssue('error',EM); +0149 end +0150 if ~isnumeric(model.b) +0151 EM='The "b" field must be of type "double"'; +0152 reportIssue('error',EM); +0153 end +0154 if ~iscellstr(model.comps) +0155 EM='The "comps" field must be a cell array of strings'; +0156 reportIssue('error',EM); +0157 end +0158 if ~isnumeric(model.metComps) +0159 EM='The "metComps" field must be of type "double"'; +0160 reportIssue('error',EM); +0161 end +0162 if isfield(model,'compNames') +0163 if ~iscellstr(model.compNames) +0164 EM='The "compNames" field must be a cell array of strings'; +0165 reportIssue('error',EM); +0166 end +0167 end +0168 if isfield(model,'compOutside') +0169 if ~iscellstr(model.compOutside) +0170 EM='The "compOutside" field must be a cell array of strings'; +0171 reportIssue('error',EM); +0172 end +0173 end +0174 if isfield(model,'rxnNames') +0175 if ~iscellstr(model.rxnNames) +0176 EM='The "rxnNames" field must be a cell array of strings'; +0177 reportIssue('error',EM); +0178 end +0179 end +0180 if isfield(model,'metNames') +0181 if ~iscellstr(model.metNames) +0182 EM='The "metNames" field must be a cell array of strings'; +0183 reportIssue('error',EM); +0184 end +0185 end +0186 if isfield(model,'genes') +0187 if ~iscellstr(model.genes) +0188 EM='The "genes" field must be a cell array of strings'; +0189 reportIssue('error',EM); +0190 end +0191 end +0192 if isfield(model,'rxnGeneMat') +0193 if ~isnumeric(model.rxnGeneMat) +0194 EM='The "rxnGeneMat" field must be of type "double"'; +0195 reportIssue('error',EM); +0196 end +0197 end +0198 if isfield(model,'grRules') +0199 if ~iscellstr(model.grRules) +0200 EM='The "grRules" field must be a cell array of strings'; +0201 reportIssue('error',EM); +0202 end +0203 if ~isfield(model,'genes') +0204 EM='If "grRules" field exists, the model should also contain a "genes" field'; +0205 reportIssue('error',EM); +0206 else +0207 %Erroneous grRules that start/end with OR/AND +0208 EM='The following reaction(s) have grRules that start or end with ''OR'' or ''AND'':'; +0209 reportIssue('error',EM,model.rxns(startsWith(model.grRules,{'or ','and '}) | endsWith(model.grRules,{' or',' and'}))); +0210 %grRules that are not in genes field +0211 geneList = getGenesFromGrRules(model.grRules); +0212 geneList = setdiff(unique(geneList),model.genes); +0213 if ~isempty(geneList) +0214 problemGrRules = model.rxns(contains(model.grRules,geneList)); +0215 problemGrRules = strjoin(problemGrRules(:),'; '); +0216 EM=['The reaction(s) "' problemGrRules '" contain the following genes in its "grRules" field, but these are not in the "genes" field:']; +0217 reportIssue('error',EM,geneList); +0218 end +0219 end +0220 end +0221 if isfield(model,'rxnComps') +0222 if ~isnumeric(model.rxnComps) +0223 EM='The "rxnComps" field must be of type "double"'; +0224 reportIssue('error',EM); +0225 end +0226 end +0227 if isfield(model,'inchis') +0228 if ~iscellstr(model.inchis) +0229 EM='The "inchis" field must be a cell array of strings'; +0230 reportIssue('error',EM); +0231 end +0232 end +0233 if isfield(model,'metSmiles') +0234 if ~iscellstr(model.metSmiles) +0235 EM='The "metSmiles" field must be a cell array of strings'; +0236 reportIssue('error',EM); +0237 end +0238 end +0239 if isfield(model,'metFormulas') +0240 if ~iscellstr(model.metFormulas) +0241 EM='The "metFormulas" field must be a cell array of strings'; +0242 reportIssue('error',EM); +0243 end +0244 end +0245 if isfield(model,'metCharges') +0246 if ~isnumeric(model.metCharges) +0247 EM='The "metCharges" field must be a double'; +0248 reportIssue('error',EM); +0249 end +0250 end +0251 if isfield(model,'metDeltaG') +0252 if ~isnumeric(model.metDeltaG) +0253 EM='The "metDeltaG" field must be a double'; +0254 reportIssue('error',EM); +0255 end +0256 end +0257 if isfield(model,'subSystems') +0258 isNested = any(cellfun(@(x) iscell(x), model.subSystems)); +0259 isCellStr = any(cellfun(@(x) ischar(x), model.subSystems)); +0260 if ~xor(isNested,isCellStr) +0261 EM='The "subSystems" field must be a cell array of chars, *or* a cell array of cell arrays of chars'; +0262 reportIssue('error',EM); +0263 end +0264 end +0265 if isfield(model,'eccodes') +0266 if ~iscellstr(model.eccodes) +0267 EM='The "eccodes" field must be a cell array of strings'; +0268 reportIssue('error',EM); +0269 end +0270 end +0271 if isfield(model,'unconstrained') +0272 if ~isnumeric(model.unconstrained) +0273 EM='The "unconstrained" field must be of type "double"'; +0274 reportIssue('error',EM); +0275 end +0276 end +0277 if isfield(model,'rxnNotes') +0278 if ~iscellstr(model.rxnNotes) +0279 EM='The "rxnNotes" field must be a cell array of strings'; +0280 reportIssue('error',EM); +0281 end +0282 end +0283 if isfield(model,'rxnReferences') +0284 if ~iscellstr(model.rxnReferences) +0285 EM='The "rxnReferences" field must be a cell array of strings'; +0286 reportIssue('error',EM); +0287 end +0288 end +0289 if isfield(model,'rxnConfidenceScores') +0290 if ~isnumeric(model.rxnConfidenceScores) +0291 EM='The "rxnConfidenceScores" field must be a double'; +0292 reportIssue('error',EM); +0293 end +0294 end +0295 if isfield(model,'rxnDeltaG') +0296 if ~isnumeric(model.rxnDeltaG) +0297 EM='The "rxnDeltaG" field must be a double'; +0298 reportIssue('error',EM); +0299 end +0300 end +0301 +0302 %Empty strings +0303 if isempty(model.id) +0304 EM='The "id" field cannot be empty'; +0305 reportIssue('error',EM); +0306 end +0307 if any(cellfun(@isempty,model.rxns)) +0308 EM='The model contains empty reaction IDs'; +0309 reportIssue('error',EM); +0310 end +0311 if any(cellfun(@isempty,model.mets)) +0312 EM='The model contains empty metabolite IDs'; +0313 reportIssue('error',EM); +0314 end +0315 if any(cellfun(@isempty,model.comps)) +0316 EM='The model contains empty compartment IDs'; +0317 reportIssue('error',EM); +0318 end +0319 if isfield(model,'metNames') +0320 EM='The following metabolites have empty names:'; +0321 reportIssue('error',EM,model.mets(cellfun(@isempty,model.metNames))); +0322 end +0323 +0324 if isfield(model,'genes') +0325 if any(cellfun(@isempty,model.genes)) +0326 EM='The model contains empty gene IDs'; +0327 reportIssue('error',EM); +0328 end +0329 end +0330 +0331 %Validate format of ids +0332 fields = {'rxns';'mets';'comps';'genes'}; +0333 fieldNames = {'reaction';'metabolite';'compartment';'gene'}; +0334 fieldPrefix = {'R_';'M_';'C_';'G_'}; +0335 for i=1:numel(fields) +0336 try +0337 numIDs = ~startsWith(model.(fields{i}),regexpPattern('^[a-zA-Z_]')); +0338 catch +0339 numIDs = []; +0340 end +0341 if any(numIDs) +0342 EM = ['The following ' fieldNames{i} ' identifiers do not start '... +0343 'with a letter or _ (conflicting with SBML specifications). '... +0344 'This does not impact RAVEN functionality, but be aware that '... +0345 'exportModel will automatically add ' fieldPrefix{i} ... +0346 ' prefixes to all ' fieldNames{i} ' identifiers:']; +0347 reportIssue('warning',EM,{model.(fields{i}){numIDs}}); +0348 end +0349 end +0350 +0351 %Duplicates +0352 EM='The following reaction IDs are duplicates:'; +0353 reportIssue('error',EM,model.rxns(duplicates(model.rxns))); +0354 EM='The following metabolite IDs are duplicates:'; +0355 reportIssue('error',EM,model.mets(duplicates(model.mets))); +0356 EM='The following compartment IDs are duplicates:'; +0357 reportIssue('error',EM,model.comps(duplicates(model.comps))); +0358 if isfield(model,'genes') +0359 EM='The following genes are duplicates:'; +0360 reportIssue('error',EM,model.genes(duplicates(model.genes))); +0361 end +0362 if isfield(model,'metNames') +0363 metInComp=strcat(model.metNames,'[',model.comps(model.metComps),']'); +0364 EM='The following metabolites already exist in the same compartment:'; +0365 reportIssue('error',EM,metInComp(duplicates(metInComp))); +0366 end +0367 +0368 %Elements never used (always reported as warnings) +0369 EM='The following reactions are empty (no involved metabolites):'; +0370 reportIssue('warning',EM,model.rxns(~any(model.S,1))); +0371 EM='The following metabolites are never used in a reaction:'; +0372 reportIssue('warning',EM,model.mets(~any(model.S,2))); +0373 if isfield(model,'genes') +0374 EM='The following genes are not associated to a reaction:'; +0375 reportIssue('warning',EM,model.genes(~any(model.rxnGeneMat,1))); +0376 end +0377 I=true(numel(model.comps),1); +0378 I(model.metComps)=false; +0379 EM='The following compartments contain no metabolites:'; +0380 reportIssue('warning',EM,model.comps(I)); +0381 +0382 %Contradicting bounds +0383 EM='The following reactions have contradicting bounds (lower bound is higher than upper bound):'; +0384 reportIssue('error',EM,model.rxns(model.lb>model.ub)); +0385 EM='The following reactions have lower and upper bounds that indicate reversibility, but are indicated as irreversible in model.rev:'; +0386 reportIssue('warning',EM,model.rxns(model.lb < 0 & model.ub > 0 & model.rev==0)); +0387 +0388 %Multiple or no objective functions not allowed in SBML L3V1 FBCv2 +0389 if numel(find(model.c))>1 +0390 EM='Multiple objective functions found. This might be intended, but results in FBCv2 non-compliant SBML file when exported'; +0391 reportIssue('warning',EM,model.rxns(find(model.c))); +0392 elseif ~any(model.c) +0393 EM='No objective function found. This might be intended, but results in FBCv2 non-compliant SBML file when exported'; +0394 reportIssue('warning',EM); +0395 end +0396 +0397 %Mapping of compartments +0398 if isfield(model,'compOutside') +0399 EM='The following compartments are in "compOutside" but not in "comps":'; +0400 reportIssue('error',EM,setdiff(model.compOutside,[{''};model.comps])); +0401 end +0402 +0403 %Met names which start with number +0404 if isfield(model,'metNames') +0405 I=false(numel(model.metNames),1); +0406 for i=1:numel(model.metNames) +0407 index=strfind(model.metNames{i},' '); +0408 if any(index) +0409 if ~isnan(str2double(model.metNames{i}(1:index(1)-1))) +0410 I(i)=true; +0411 end +0412 end +0413 end +0414 EM='The following metabolite names begin with a number directly followed by space, which could potentially cause problems:'; +0415 reportIssue('warning',EM,model.metNames(I)); +0416 end +0417 +0418 %Non-parseable composition +0419 if isfield(model,'metFormulas') +0420 [~, ~, exitFlag]=parseFormulas(model.metFormulas,true,false); +0421 EM='The composition for the following metabolites could not be parsed:'; +0422 reportIssue('warning',EM,model.mets(exitFlag==-1)); +0423 end +0424 +0425 %Check if there are metabolites with different names but the same MIRIAM +0426 %codes +0427 if isfield(model,'metMiriams') +0428 miriams=containers.Map(); +0429 for i=1:numel(model.mets) +0430 if ~isempty(model.metMiriams{i}) +0431 %Loop through and add for each miriam +0432 for j=1:numel(model.metMiriams{i}.name) +0433 %Get existing metabolite indexes +0434 current=strcat(model.metMiriams{i}.name{j},'/',model.metMiriams{i}.value{j}); +0435 if isKey(miriams,current) +0436 existing=miriams(current); +0437 else +0438 existing=[]; +0439 end +0440 miriams(current)=[existing;i]; +0441 end +0442 end +0443 end +0444 +0445 %Get all keys +0446 allMiriams=keys(miriams); +0447 +0448 hasMultiple=false(numel(allMiriams),1); +0449 for i=1:numel(allMiriams) +0450 if numel(miriams(allMiriams{i}))>1 +0451 %Check if they all have the same name +0452 if numel(unique(model.metNames(miriams(allMiriams{i}))))>1 +0453 % regexp returns [] (not a false-like 0) when there is no +0454 % match, and ~[] is [] too, so the negation itself is +0455 % never true: use isempty to actually test for "no match" +0456 if isempty(regexp(allMiriams{i},'^sbo\/SBO:','once')) % SBO terms are expected to be multiple +0457 hasMultiple(i)=true; +0458 end +0459 end +0460 end +0461 end +0462 +0463 %Print output +0464 EM='The following MIRIAM strings are associated to more than one unique metabolite name:'; +0465 reportIssue('warning',EM,allMiriams(hasMultiple)); +0466 end +0467 +0468 %Check if there are metabolites with different names but the same InChI +0469 %codes +0470 if isfield(model,'inchis') +0471 inchis=containers.Map(); +0472 for i=1:numel(model.mets) +0473 if ~isempty(model.inchis{i}) +0474 %Get existing metabolite indexes +0475 if isKey(inchis,model.inchis{i}) +0476 existing=inchis(model.inchis{i}); +0477 else +0478 existing=[]; +0479 end +0480 inchis(model.inchis{i})=[existing;i]; +0481 end +0482 end +0483 +0484 %Get all keys +0485 allInchis=keys(inchis); +0486 +0487 hasMultiple=false(numel(allInchis),1); +0488 for i=1:numel(allInchis) +0489 if numel(inchis(allInchis{i}))>1 +0490 %Check if they all have the same name +0491 if numel(unique(model.metNames(inchis(allInchis{i}))))>1 +0492 hasMultiple(i)=true; +0493 end +0494 end +0495 end +0496 +0497 %Print output +0498 EM='The following InChI strings are associated to more than one unique metabolite name:'; +0499 reportIssue('warning',EM,allInchis(hasMultiple)); +0500 end +0501 +0502 % %Check if there are metabolites with different names but the same SMILES +0503 % if isfield(model,"metSmiles") +0504 % metSmiles=containers.Map(); +0505 % for i=1:numel(model.mets) +0506 % if ~isempty(model.metSmiles{i}) +0507 % %Get existing metabolite indexes +0508 % if isKey(metSmiles,model.metSmiles{i}) +0509 % existing=metSmiles(model.metSmiles{i}); +0510 % else +0511 % existing=[]; +0512 % end +0513 % metSmiles(model.metSmiles{i})=[existing;i]; +0514 % end +0515 % end +0516 % +0517 % %Get all keys +0518 % allmetSmiles=keys(metSmiles); +0519 % +0520 % hasMultiple=false(numel(metSmiles),1); +0521 % for i=1:numel(metSmiles) +0522 % if numel(metSmiles(metSmiles{i}))>1 +0523 % %Check if they all have the same name +0524 % if numel(unique(model.metNames(metSmiles(allmetSmiles{i}))))>1 +0525 % hasMultiple(i)=true; +0526 % end +0527 % end +0528 % end +0529 % +0530 % %Print output +0531 % EM="The following metSmiles strings are associated to more than one unique metabolite name:"; +0532 % reportIssue("warning",EM,allmetSmiles(hasMultiple)); +0533 % end +0534 end +0535 +0536 function I=duplicates(strings) +0537 I=false(numel(strings),1); +0538 [J, K]=unique(strings); +0539 if numel(J)~=numel(strings) +0540 L=1:numel(strings); +0541 L(K)=[]; +0542 I(L)=true; 0543 end -0544 -0545 function cat=issueCategory(msg) -0546 if contains(msg,'missing') -0547 cat='missing_field'; -0548 elseif contains(msg,'must be') || contains(msg,'should') -0549 cat='wrong_type'; -0550 elseif contains(msg,'empty') -0551 cat='empty_id'; -0552 elseif contains(msg,'duplicate') || contains(msg,'already exist in the same') -0553 cat='duplicate'; -0554 elseif contains(msg,'bound') -0555 cat='invalid_bounds'; -0556 elseif contains(msg,'SBML') || contains(msg,'identifier') || contains(msg,'do not start with') -0557 cat='invalid_id'; -0558 elseif contains(msg,'never used') || contains(msg,'not associated') || contains(msg,'contain no metabolites') || contains(msg,'are empty (no') -0559 cat='unused'; -0560 elseif contains(msg,'objective') -0561 cat='objective'; -0562 elseif contains(msg,'grRule') || contains(msg,'start or end with') || contains(msg,'"genes"') -0563 cat='gpr'; -0564 elseif contains(msg,'could not be parsed') || contains(msg,'composition') -0565 cat='invalid_formula'; -0566 elseif contains(msg,'MIRIAM') || contains(msg,'InChI') || contains(msg,'SMILES') -0567 cat='cross_reference'; -0568 else -0569 cat='other'; -0570 end -0571 end

    +0544 end +0545 +0546 function cat=issueCategory(msg) +0547 if contains(msg,'missing') +0548 cat='missing_field'; +0549 elseif contains(msg,'must be') || contains(msg,'should') +0550 cat='wrong_type'; +0551 elseif contains(msg,'empty') +0552 cat='empty_id'; +0553 elseif contains(msg,'duplicate') || contains(msg,'already exist in the same') +0554 cat='duplicate'; +0555 elseif contains(msg,'bound') +0556 cat='invalid_bounds'; +0557 elseif contains(msg,'SBML') || contains(msg,'identifier') || contains(msg,'do not start with') +0558 cat='invalid_id'; +0559 elseif contains(msg,'never used') || contains(msg,'not associated') || contains(msg,'contain no metabolites') || contains(msg,'are empty (no') +0560 cat='unused'; +0561 elseif contains(msg,'objective') +0562 cat='objective'; +0563 elseif contains(msg,'grRule') || contains(msg,'start or end with') || contains(msg,'"genes"') +0564 cat='gpr'; +0565 elseif contains(msg,'could not be parsed') || contains(msg,'composition') +0566 cat='invalid_formula'; +0567 elseif contains(msg,'MIRIAM') || contains(msg,'InChI') || contains(msg,'SMILES') +0568 cat='cross_reference'; +0569 else +0570 cat='other'; +0571 end +0572 end

    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/queries/constructEquations.html b/docs/html/queries/constructEquations.html index e407d806..ebf8a9d4 100644 --- a/docs/html/queries/constructEquations.html +++ b/docs/html/queries/constructEquations.html @@ -46,21 +46,23 @@

    DESCRIPTION ^DESCRIPTION ^CROSS-REFERENCE INFORMATION ^

    @@ -80,7 +82,7 @@

    CROSS-REFERENCE INFORMATION ^
 <li><a href=buildEquation buildEquation Construct single equation string for a given reaction.
  • getIndexes getIndexes Retrieve the indexes for a list of reactions or metabolites.
  • This function is called by:
      -
    • printFluxes printFluxes Print reactions and fluxes to the screen or to a file.
    • printModelStats printModelStats Print some statistics about a model to the screen.
    +
  • printFluxes printFluxes Print reactions and fluxes to the screen or to a file.
  • printModelStats printModelStats Print statistics about a model to the screen.
  • @@ -106,89 +108,91 @@

    SOURCE CODE ^% sort reversible reactions so that the metabolite that is first in the 0019 % lexicographic order is a reactant (default false). 0020 % sortMetNames : logical -0021 % sort the metabolite names in the equation. Uses compartment even if -0022 % useComps is false (default false). +0021 % sort the metabolite names in the equation. The compartment is included +0022 % in the sorting even if useComps is false (default false). 0023 % useMetID : logical -0024 % use metabolite ID in generated equations (default false). -0025 % useFormula : logical -0026 % use metabolite formula in generated equations (default false). -0027 % useRevField : logical -0028 % use the model.rev field to indicate reaction reversibility, -0029 % alternatively this is determined from the model.ub and model.lb fields -0030 % (default true). -0031 % -0032 % Returns -0033 % ------- -0034 % equationStrings : cell -0035 % a cell array with equations. -0036 % -0037 % Examples -0038 % -------- -0039 % equationStrings = constructEquations(model, rxns, useComps, ... -0040 % sortRevRxns, sortMetNames, useMetID, useFormula, useRevField); -0041 % -0042 % Notes -0043 % ----- -0044 % If useRevField is false, then reactions should be organized in their -0045 % forward direction (e.g. ub = 1000 and lb = -1000/0) for the reversibility -0046 % to be correctly determined. -0047 -0048 p=parseRAVENargs(varargin, {'rxns',[]; 'useComps',true; 'sortRevRxns',false; 'sortMetNames',false; 'useMetID',false; 'useFormula',false; 'useRevField',true}); -0049 rxns=p.rxns; -0050 if isempty(rxns) -0051 rxns=model.rxns; -0052 elseif ~islogical(rxns) && ~isnumeric(rxns) -0053 rxns=convertCharArray(rxns); -0054 end -0055 useComps=p.useComps; -0056 sortRevRxns=p.sortRevRxns; -0057 sortMetNames=p.sortMetNames; -0058 useMetID=p.useMetID; -0059 useFormula=p.useFormula; -0060 useRevField=p.useRevField; -0061 -0062 %Sort reversible equations -0063 if sortRevRxns==true -0064 model=sortModel(model,true); -0065 end -0066 -0067 %Sort metabolite names, including compartment -0068 if sortMetNames==true -0069 model=sortModel(model,false,true); -0070 end -0071 -0072 Rindexes=getIndexes(model,rxns,'rxns'); +0024 % use metabolite ID in generated equations. Cannot be true together with +0025 % useFormula (default false). +0026 % useFormula : logical +0027 % use metabolite formula in generated equations. Cannot be true together +0028 % with useMetID (default false). +0029 % useRevField : logical +0030 % use the model.rev field to indicate reaction reversibility. When +0031 % false, a reaction is reversible if model.lb < 0 and model.ub > 0 +0032 % (default true). +0033 % +0034 % Returns +0035 % ------- +0036 % equationStrings : cell +0037 % a cell array with the equation of each reaction. +0038 % +0039 % Examples +0040 % -------- +0041 % equationStrings = constructEquations(model, rxns, useComps, ... +0042 % sortRevRxns, sortMetNames, useMetID, useFormula, useRevField); +0043 % +0044 % Notes +0045 % ----- +0046 % If useRevField is false, the reactions have to be written in their forward +0047 % direction (for example ub = 1000 and lb = -1000 or 0) for the reversibility +0048 % to be determined correctly. +0049 +0050 p=parseRAVENargs(varargin, {'rxns',[]; 'useComps',true; 'sortRevRxns',false; 'sortMetNames',false; 'useMetID',false; 'useFormula',false; 'useRevField',true}); +0051 rxns=p.rxns; +0052 if isempty(rxns) +0053 rxns=model.rxns; +0054 elseif ~islogical(rxns) && ~isnumeric(rxns) +0055 rxns=convertCharArray(rxns); +0056 end +0057 useComps=p.useComps; +0058 sortRevRxns=p.sortRevRxns; +0059 sortMetNames=p.sortMetNames; +0060 useMetID=p.useMetID; +0061 useFormula=p.useFormula; +0062 useRevField=p.useRevField; +0063 +0064 %Sort reversible equations +0065 if sortRevRxns==true +0066 model=sortModel(model,true); +0067 end +0068 +0069 %Sort metabolite names, including compartment +0070 if sortMetNames==true +0071 model=sortModel(model,false,true); +0072 end 0073 -0074 equationStrings=cell(numel(Rindexes),1); +0074 Rindexes=getIndexes(model,rxns,'rxns'); 0075 -0076 for i=1:numel(Rindexes) -0077 Mindexes=find(model.S(:,Rindexes(i))); -0078 %Define metabolites by id, formula or name, and with or without compartment: -0079 if useMetID==true && useFormula==false -0080 mets = model.mets(Mindexes); -0081 elseif useMetID==false && useFormula==true -0082 mets = strcat('[',model.metFormulas(Mindexes),']'); -0083 elseif useMetID==true && useFormula==true -0084 error('Arguments useMetID and useFormula cannot be both TRUE!'); -0085 else -0086 mets = model.metNames(Mindexes); -0087 end -0088 if useComps==true -0089 comps = model.comps(model.metComps(Mindexes)); -0090 mets = strcat(mets,'[',comps,']'); -0091 end -0092 %Define stoich coeffs and reversibility: -0093 stoichCoeffs = model.S(Mindexes,Rindexes(i)); -0094 if useRevField == true -0095 isrev = model.rev(Rindexes(i))==1; -0096 else -0097 isrev = model.lb(Rindexes(i))<0 & model.ub(Rindexes(i))>0; -0098 end -0099 -0100 %Construct equation: -0101 equationStrings{i} = buildEquation(mets,stoichCoeffs,isrev); -0102 end -0103 end

    +0076 equationStrings=cell(numel(Rindexes),1); +0077 +0078 for i=1:numel(Rindexes) +0079 Mindexes=find(model.S(:,Rindexes(i))); +0080 %Define metabolites by id, formula or name, and with or without compartment: +0081 if useMetID==true && useFormula==false +0082 mets = model.mets(Mindexes); +0083 elseif useMetID==false && useFormula==true +0084 mets = strcat('[',model.metFormulas(Mindexes),']'); +0085 elseif useMetID==true && useFormula==true +0086 error('Arguments useMetID and useFormula cannot be both TRUE!'); +0087 else +0088 mets = model.metNames(Mindexes); +0089 end +0090 if useComps==true +0091 comps = model.comps(model.metComps(Mindexes)); +0092 mets = strcat(mets,'[',comps,']'); +0093 end +0094 %Define stoich coeffs and reversibility: +0095 stoichCoeffs = model.S(Mindexes,Rindexes(i)); +0096 if useRevField == true +0097 isrev = model.rev(Rindexes(i))==1; +0098 else +0099 isrev = model.lb(Rindexes(i))<0 & model.ub(Rindexes(i))>0; +0100 end +0101 +0102 %Construct equation: +0103 equationStrings{i} = buildEquation(mets,stoichCoeffs,isrev); +0104 end +0105 end


    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/queries/constructS.html b/docs/html/queries/constructS.html index fc5846b1..9b3a8e0f 100644 --- a/docs/html/queries/constructS.html +++ b/docs/html/queries/constructS.html @@ -32,32 +32,34 @@

    DESCRIPTION ^SOURCE CODE ^% Parameters 0005 % ---------- 0006 % equations : cell -0007 % cell array of equations on the form "A + 2 B <=> 3 C", where <=> +0007 % cell array of equations in the form "A + 2 B <=> 3 C", where <=> 0008 % indicates reversible and => irreversible reactions. 0009 % 0010 % Name-Value Arguments 0011 % -------------------- 0012 % mets : cell 0013 % cell array of metabolites. All metabolites in the equations must be -0014 % present in the list (default generated from the equations). -0015 % rxns : cell -0016 % cell array of reaction ids. This is only used for printing reaction ids -0017 % instead of equations in warnings/errors (default []). -0018 % -0019 % Returns -0020 % ------- -0021 % S : double -0022 % the resulting stoichiometric matrix. -0023 % mets : cell -0024 % cell array with metabolites that corresponds to the order in the S -0025 % matrix. -0026 % badRxns : logical -0027 % boolean vector with the reactions that have one or more metabolites as -0028 % both substrate and product. An example would be the phosphotransferase -0029 % ATP + ADP <=> ADP + ATP. In the stoichiometric matrix this equals to an -0030 % empty reaction which can be problematic. -0031 % reversible : logical -0032 % boolean vector with true if the equation is reversible. -0033 % -0034 % Examples -0035 % -------- -0036 % [S, mets, badRxns, reversible] = constructS(equations, mets); -0037 -0038 equations=convertCharArray(equations); -0039 p=parseRAVENargs(varargin, {'mets',[]; 'rxns',[]}); -0040 mets=p.mets; -0041 rxns=p.rxns; -0042 if ~isempty(mets) -0043 mets=convertCharArray(mets); -0044 end -0045 if ~isempty(rxns) -0046 rxns=convertCharArray(rxns); -0047 end -0048 -0049 badRxns=false(numel(equations),1); +0014 % present in the list, otherwise an error is thrown (default generated +0015 % from the equations). +0016 % rxns : cell +0017 % cell array of reaction ids. Used to print reaction ids in error +0018 % messages in place of equations (default []). +0019 % +0020 % Returns +0021 % ------- +0022 % S : double +0023 % the resulting stoichiometric matrix. +0024 % mets : cell +0025 % cell array with the metabolites in the order of the rows of the S +0026 % matrix. +0027 % badRxns : logical +0028 % logical vector with true for the reactions in which one or more +0029 % metabolites occur more than once, for example as both substrate and +0030 % product. An example is the phosphotransferase reaction +0031 % ATP + ADP <=> ADP + ATP, which gives an empty column in the +0032 % stoichiometric matrix. +0033 % reversible : logical +0034 % logical vector with true if the equation is reversible. +0035 % +0036 % Examples +0037 % -------- +0038 % [S, mets, badRxns, reversible] = constructS(equations, mets); +0039 +0040 equations=convertCharArray(equations); +0041 p=parseRAVENargs(varargin, {'mets',[]; 'rxns',[]}); +0042 mets=p.mets; +0043 rxns=p.rxns; +0044 if ~isempty(mets) +0045 mets=convertCharArray(mets); +0046 end +0047 if ~isempty(rxns) +0048 rxns=convertCharArray(rxns); +0049 end 0050 -0051 %Check that no equations are too short to have reversibility data -0052 I=cellfun(@numel,equations); -0053 I=find(I<4,1); -0054 if any(I) -0055 if isempty(rxns) -0056 EM=['The following equation does not have reversibility data: ' equations{I} ]; -0057 error('RAVEN:badInput', '%s', EM); -0058 else -0059 EM=['The reaction ' rxns{I} ' does not have reversibility data']; -0060 error('RAVEN:badInput', '%s', EM); -0061 end -0062 end -0063 -0064 %Makes life a little easier -0065 equations=strtrim(equations); -0066 equations=fixEquations(equations); -0067 -0068 %A supplied list is authoritative, so an entry in it can be matched in full -0069 %before the leading number of "2 oxoglutarate" is read as a coefficient. A -0070 %list derived from the equations cannot disambiguate that. -0071 metsSupplied=~isempty(mets); -0072 if isempty(mets) -0073 mets=parseRxnEqu(equations); -0074 end -0075 -0076 %Get which reactions are reversible -0077 reversible=cellfun(@any,strfind(equations,' <=> ')); -0078 -0079 %Make them all reversible. This is not all that neat, but nevermind -0080 equations=strrep(equations,' => ',' <=> '); -0081 -0082 %Replace the the plus signs with some weird character that will be used for -0083 %parsing -0084 equations=strrep(equations,' + ', '€'); -0085 -0086 %Keep track of coefficients to be added to S-matrix -0087 metsToS = cell(100000,1); -0088 rxnsToS = zeros(100000,1); -0089 coefToS = zeros(100000,1); -0090 newEntry = 1; -0091 %Loop through the equations and add the info to the S matrix -0092 for i=1:numel(equations) -0093 %Start by finding the position of the (=> or <=>) -0094 arrowIndex=strfind(equations{i},' <=> '); -0095 -0096 if numel(arrowIndex)~=1 -0097 if isempty(rxns) -0098 EM=['The following equation does not have reversibility data: ' equations{i} ]; -0099 error('RAVEN:badInput', '%s', EM); -0100 else -0101 EM=['The reaction ' rxns{i} ' does not have reversibility data']; -0102 error('RAVEN:badInput', '%s', EM); -0103 end -0104 end -0105 -0106 reactants=regexp(equations{i}(1:arrowIndex-1),'€','split'); -0107 products=regexp(equations{i}(arrowIndex+5:end),'€','split'); -0108 -0109 %If the splitting character is at the end (if exchange rxns), then an -0110 %empty string will exist together with the real ones. Remove it -0111 reactants(cellfun(@isempty,reactants))=[]; -0112 products(cellfun(@isempty,products))=[]; -0113 -0114 %A vector where an element is -1 is the corresponding metabolite is a -0115 %reactant and 1 if it is a product -0116 multiplyWith=[ones(numel(reactants),1)*-1; ones(numel(products),1)]; -0117 -0118 metabolites=strtrim([reactants products]); +0051 badRxns=false(numel(equations),1); +0052 +0053 %Check that no equations are too short to have reversibility data +0054 I=cellfun(@numel,equations); +0055 I=find(I<4,1); +0056 if any(I) +0057 if isempty(rxns) +0058 EM=['The following equation does not have reversibility data: ' equations{I} ]; +0059 error('RAVEN:badInput', '%s', EM); +0060 else +0061 EM=['The reaction ' rxns{I} ' does not have reversibility data']; +0062 error('RAVEN:badInput', '%s', EM); +0063 end +0064 end +0065 +0066 %Makes life a little easier +0067 equations=strtrim(equations); +0068 equations=fixEquations(equations); +0069 +0070 %A supplied list is authoritative, so an entry in it can be matched in full +0071 %before the leading number of "2 oxoglutarate" is read as a coefficient. A +0072 %list derived from the equations cannot disambiguate that. +0073 metsSupplied=~isempty(mets); +0074 if isempty(mets) +0075 mets=parseRxnEqu(equations); +0076 end +0077 +0078 %Get which reactions are reversible +0079 reversible=cellfun(@any,strfind(equations,' <=> ')); +0080 +0081 %Make them all reversible. This is not all that neat, but nevermind +0082 equations=strrep(equations,' => ',' <=> '); +0083 +0084 %Replace the the plus signs with some weird character that will be used for +0085 %parsing +0086 equations=strrep(equations,' + ', '€'); +0087 +0088 %Keep track of coefficients to be added to S-matrix +0089 metsToS = cell(100000,1); +0090 rxnsToS = zeros(100000,1); +0091 coefToS = zeros(100000,1); +0092 newEntry = 1; +0093 %Loop through the equations and add the info to the S matrix +0094 for i=1:numel(equations) +0095 %Start by finding the position of the (=> or <=>) +0096 arrowIndex=strfind(equations{i},' <=> '); +0097 +0098 if numel(arrowIndex)~=1 +0099 if isempty(rxns) +0100 EM=['The following equation does not have reversibility data: ' equations{i} ]; +0101 error('RAVEN:badInput', '%s', EM); +0102 else +0103 EM=['The reaction ' rxns{i} ' does not have reversibility data']; +0104 error('RAVEN:badInput', '%s', EM); +0105 end +0106 end +0107 +0108 reactants=regexp(equations{i}(1:arrowIndex-1),'€','split'); +0109 products=regexp(equations{i}(arrowIndex+5:end),'€','split'); +0110 +0111 %If the splitting character is at the end (if exchange rxns), then an +0112 %empty string will exist together with the real ones. Remove it +0113 reactants(cellfun(@isempty,reactants))=[]; +0114 products(cellfun(@isempty,products))=[]; +0115 +0116 %A vector where an element is -1 is the corresponding metabolite is a +0117 %reactant and 1 if it is a product +0118 multiplyWith=[ones(numel(reactants),1)*-1; ones(numel(products),1)]; 0119 -0120 %Now loop through the reactants and see if the metabolite has a -0121 %coefficient (it will look as "number name") -0122 for j=1:numel(metabolites) -0123 space=strfind(metabolites{j},' '); -0124 -0125 if isempty(space) -0126 %No coefficient -0127 coeff=1; -0128 name=metabolites{j}; -0129 elseif metsSupplied && any(strcmp(strtrim(metabolites{j}),mets)) -0130 %The whole entry is a known metabolite, so its leading number is -0131 %part of its name ("2 oxoglutarate") rather than a coefficient -0132 coeff=1; -0133 name=strtrim(metabolites{j}); -0134 else -0135 coeff=str2double(metabolites{j}(1:space(1))); -0136 -0137 %If it was not a coefficiant -0138 if isnan(coeff) -0139 coeff=1; -0140 name=strtrim(metabolites{j}); -0141 else -0142 name=strtrim(metabolites{j}(space(1)+1:end)); -0143 end -0144 end -0145 -0146 %Find the name in the mets list [a b]=ismember(name,mets); -0147 metsToS{newEntry}=name; -0148 rxnsToS(newEntry)=i; -0149 coefToS(newEntry)=coeff*multiplyWith(j); -0150 newEntry=newEntry+1; -0151 end -0152 end -0153 %Remove unused fields -0154 metsToS(newEntry:end)=[]; -0155 rxnsToS(newEntry:end)=[]; -0156 coefToS(newEntry:end)=[]; -0157 -0158 %Match to mets array -0159 [metsPresent,metsLoc]=ismember(metsToS,mets); -0160 -0161 %Find badRxns -0162 [~,I]=unique([rxnsToS,metsLoc],'rows','stable'); -0163 x=1:length(rxnsToS); -0164 x(I)=[]; -0165 x=unique(rxnsToS(x)); -0166 badRxns(x)=true; -0167 -0168 if any(~metsPresent) -0169 if isempty(rxns) -0170 EM=['Could not find the following metabolites in the metabolite list: ',... -0171 strjoin(unique(metsToS(~metsPresent)),', ')]; -0172 error('RAVEN:badInput','%s',EM) -0173 else -0174 %Escaped: metabolite/reaction names are arbitrary model text and -0175 %may contain "%", which the sprintf below (needed to interpret -0176 %the template's own "\n") would otherwise misread as a format -0177 %directive and truncate the rest of the message. -0178 esc=@(c) strrep(c,'%','%%'); -0179 missingMet = find(~metsPresent); -0180 missingMet = strcat(esc(metsToS(missingMet)),' (reaction:',esc(rxns(rxnsToS(missingMet))),')\n'); -0181 missingMet = strjoin(missingMet,''); -0182 EM=sprintf(['Could not find the following metabolites (reaction indicated) in the metabolite list: \n' ... -0183 missingMet]); -0184 error('RAVEN:badInput','%s',EM) -0185 end -0186 end -0187 %sparse() adds up repeated (metabolite,reaction) entries, which is what a -0188 %metabolite occurring more than once in one equation means: "2 H2O + H2O =>" -0189 %is a coefficient of 3, and a metabolite on both sides cancels to an empty -0190 %column, as badRxns above reports. Assigning into a preallocated matrix -0191 %instead kept only the last occurrence, and materialised the whole matrix -0192 %densely on the way. -0193 S=sparse(metsLoc,rxnsToS,coefToS,numel(mets),numel(equations)); -0194 end -0195 -0196 function equ=fixEquations(equ) -0197 %If the equation starts with "=>" or "<=>" then add a space again. This is -0198 %an alternative way to represent uptake reactions. The opposite way for -0199 %producing reactions -0200 equ=equ(:); -0201 for i=1:numel(equ) -0202 if strcmp(equ{i}(1:2),'=>') || strcmp(equ{i}(1:3),'<=>') -0203 equ{i}=[' ' equ{i}]; -0204 else -0205 if strcmp(equ{i}(end-1:end),'=>') || strcmp(equ{i}(end-2:end),'<=>') -0206 equ{i}=[equ{i} ' ']; -0207 end -0208 end -0209 end -0210 end +0120 metabolites=strtrim([reactants products]); +0121 +0122 %Now loop through the reactants and see if the metabolite has a +0123 %coefficient (it will look as "number name") +0124 for j=1:numel(metabolites) +0125 space=strfind(metabolites{j},' '); +0126 +0127 if isempty(space) +0128 %No coefficient +0129 coeff=1; +0130 name=metabolites{j}; +0131 elseif metsSupplied && any(strcmp(strtrim(metabolites{j}),mets)) +0132 %The whole entry is a known metabolite, so its leading number is +0133 %part of its name ("2 oxoglutarate") rather than a coefficient +0134 coeff=1; +0135 name=strtrim(metabolites{j}); +0136 else +0137 coeff=str2double(metabolites{j}(1:space(1))); +0138 +0139 %If it was not a coefficiant +0140 if isnan(coeff) +0141 coeff=1; +0142 name=strtrim(metabolites{j}); +0143 else +0144 name=strtrim(metabolites{j}(space(1)+1:end)); +0145 end +0146 end +0147 +0148 %Find the name in the mets list [a b]=ismember(name,mets); +0149 metsToS{newEntry}=name; +0150 rxnsToS(newEntry)=i; +0151 coefToS(newEntry)=coeff*multiplyWith(j); +0152 newEntry=newEntry+1; +0153 end +0154 end +0155 %Remove unused fields +0156 metsToS(newEntry:end)=[]; +0157 rxnsToS(newEntry:end)=[]; +0158 coefToS(newEntry:end)=[]; +0159 +0160 %Match to mets array +0161 [metsPresent,metsLoc]=ismember(metsToS,mets); +0162 +0163 %Find badRxns +0164 [~,I]=unique([rxnsToS,metsLoc],'rows','stable'); +0165 x=1:length(rxnsToS); +0166 x(I)=[]; +0167 x=unique(rxnsToS(x)); +0168 badRxns(x)=true; +0169 +0170 if any(~metsPresent) +0171 if isempty(rxns) +0172 EM=['Could not find the following metabolites in the metabolite list: ',... +0173 strjoin(unique(metsToS(~metsPresent)),', ')]; +0174 error('RAVEN:badInput','%s',EM) +0175 else +0176 %Escaped: metabolite/reaction names are arbitrary model text and +0177 %may contain "%", which the sprintf below (needed to interpret +0178 %the template's own "\n") would otherwise misread as a format +0179 %directive and truncate the rest of the message. +0180 esc=@(c) strrep(c,'%','%%'); +0181 missingMet = find(~metsPresent); +0182 missingMet = strcat(esc(metsToS(missingMet)),' (reaction:',esc(rxns(rxnsToS(missingMet))),')\n'); +0183 missingMet = strjoin(missingMet,''); +0184 EM=sprintf(['Could not find the following metabolites (reaction indicated) in the metabolite list: \n' ... +0185 missingMet]); +0186 error('RAVEN:badInput','%s',EM) +0187 end +0188 end +0189 %sparse() adds up repeated (metabolite,reaction) entries, which is what a +0190 %metabolite occurring more than once in one equation means: "2 H2O + H2O =>" +0191 %is a coefficient of 3, and a metabolite on both sides cancels to an empty +0192 %column, as badRxns above reports. Assigning into a preallocated matrix +0193 %instead kept only the last occurrence, and materialised the whole matrix +0194 %densely on the way. +0195 S=sparse(metsLoc,rxnsToS,coefToS,numel(mets),numel(equations)); +0196 end +0197 +0198 function equ=fixEquations(equ) +0199 %If the equation starts with "=>" or "<=>" then add a space again. This is +0200 %an alternative way to represent uptake reactions. The opposite way for +0201 %producing reactions +0202 equ=equ(:); +0203 for i=1:numel(equ) +0204 if strcmp(equ{i}(1:2),'=>') || strcmp(equ{i}(1:3),'<=>') +0205 equ{i}=[' ' equ{i}]; +0206 else +0207 if strcmp(equ{i}(end-1:end),'=>') || strcmp(equ{i}(end-2:end),'<=>') +0208 equ{i}=[equ{i} ' ']; +0209 end +0210 end +0211 end +0212 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/queries/getAllRxnsFromGenes.html b/docs/html/queries/getAllRxnsFromGenes.html index 28bc1c72..e7bc5f44 100644 --- a/docs/html/queries/getAllRxnsFromGenes.html +++ b/docs/html/queries/getAllRxnsFromGenes.html @@ -30,7 +30,8 @@

    DESCRIPTION ^
     getAllRxnsFromGenes  Find all reactions annotated by the genes of a set.
     
      Given a list of reactions, this function finds the associated genes in
    - the model and returns all reactions that are annotated by these genes.
    + the model and returns all reactions that are annotated by these genes,
    + including the given reactions.
     
      Parameters
      ----------
    @@ -43,9 +44,9 @@ 

    DESCRIPTION ^SOURCE CODE ^% getAllRxnsFromGenes Find all reactions annotated by the genes of a set. 0003 % 0004 % Given a list of reactions, this function finds the associated genes in -0005 % the model and returns all reactions that are annotated by these genes. -0006 % -0007 % Parameters -0008 % ---------- -0009 % model : struct -0010 % a model structure. -0011 % rxns : cell or logical or double -0012 % either a cell array of IDs, a logical vector with the same number of -0013 % elements as reactions in the model, or a vector of indexes. -0014 % -0015 % Returns -0016 % ------- -0017 % allRxns : cell or logical or double -0018 % either a cell array of IDs, a logical vector with the same number of -0019 % elements as reactions in the model, or a vector of indexes, -0020 % dependent on the format of rxns. -0021 % -0022 % Examples -0023 % -------- -0024 % allRxns = getAllRxnsFromGenes(model, rxns); -0025 -0026 if ~islogical(rxns) && ~isnumeric(rxns) -0027 rxns=convertCharArray(rxns); -0028 end -0029 -0030 rxnIdx=getIndexes(model,rxns,'rxns'); -0031 [~, geneIdx]=find(model.rxnGeneMat(rxnIdx,:)); -0032 [allRxns, ~]=find(model.rxnGeneMat(:,geneIdx)); -0033 if iscell(rxns) -0034 allRxns=unique([model.rxns(allRxns);model.rxns(rxnIdx)]); -0035 elseif isnumeric(rxns) || islogical(rxns) -0036 allRxns=unique([allRxns;rxnIdx]); -0037 if islogical(rxns) -0038 temp=false(numel(model.rxns),1); -0039 temp(allRxns)=true; -0040 allRxns=temp; -0041 end -0042 end -0043 end

    +0005 % the model and returns all reactions that are annotated by these genes, +0006 % including the given reactions. +0007 % +0008 % Parameters +0009 % ---------- +0010 % model : struct +0011 % a model structure. +0012 % rxns : cell or logical or double +0013 % either a cell array of IDs, a logical vector with the same number of +0014 % elements as reactions in the model, or a vector of indexes. +0015 % +0016 % Returns +0017 % ------- +0018 % allRxns : cell or logical or double +0019 % the reactions in the same format as rxns: a cell array of IDs (unique +0020 % and sorted), a logical vector with the same number of elements as +0021 % reactions in the model, or a vector of indexes (unique and sorted). +0022 % +0023 % Examples +0024 % -------- +0025 % allRxns = getAllRxnsFromGenes(model, rxns); +0026 +0027 if ~islogical(rxns) && ~isnumeric(rxns) +0028 rxns=convertCharArray(rxns); +0029 end +0030 +0031 rxnIdx=getIndexes(model,rxns,'rxns'); +0032 [~, geneIdx]=find(model.rxnGeneMat(rxnIdx,:)); +0033 [allRxns, ~]=find(model.rxnGeneMat(:,geneIdx)); +0034 if iscell(rxns) +0035 allRxns=unique([model.rxns(allRxns);model.rxns(rxnIdx)]); +0036 elseif isnumeric(rxns) || islogical(rxns) +0037 allRxns=unique([allRxns;rxnIdx]); +0038 if islogical(rxns) +0039 temp=false(numel(model.rxns),1); +0040 temp(allRxns)=true; +0041 allRxns=temp; +0042 end +0043 end +0044 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/queries/getElementalBalance.html b/docs/html/queries/getElementalBalance.html index 3b57ae20..e71bc47c 100644 --- a/docs/html/queries/getElementalBalance.html +++ b/docs/html/queries/getElementalBalance.html @@ -29,7 +29,10 @@

    SYNOPSIS ^DESCRIPTION ^

     getElementalBalance  Check whether the reactions of a model are balanced.
     
    - Checks a model to see if the reactions are elementally balanced.
    + Checks whether the reactions are elementally balanced, using the
    + metFormulas field or, when that is absent, the inchis field. An error is
    + thrown if the model has neither field. The charge balance is reported
    + separately.
     
      Parameters
      ----------
    @@ -41,13 +44,13 @@ 

    DESCRIPTION ^DESCRIPTION ^CROSS-REFERENCE INFORMATION ^
 <li><a href=getIndexes getIndexes Retrieve the indexes for a list of reactions or metabolites.
  • parseFormulas parseFormulas Get the elemental composition from formulas.
  • This function is called by:
      -
    • printFluxes printFluxes Print reactions and fluxes to the screen or to a file.
    • printModelStats printModelStats Print some statistics about a model to the screen.
    +
  • printFluxes printFluxes Print reactions and fluxes to the screen or to a file.
  • printModelStats printModelStats Print statistics about a model to the screen.
  • @@ -91,191 +95,195 @@

    SOURCE CODE ^
    0001 function balanceStructure=getElementalBalance(model,varargin)
     0002 % getElementalBalance  Check whether the reactions of a model are balanced.
     0003 %
    -0004 % Checks a model to see if the reactions are elementally balanced.
    -0005 %
    -0006 % Parameters
    -0007 % ----------
    -0008 % model : struct
    -0009 %     a model structure.
    -0010 %
    -0011 % Name-Value Arguments
    -0012 % --------------------
    -0013 % rxns : cell or logical or double
    -0014 %     either a cell array of reaction IDs, a logical vector with the same
    -0015 %     number of elements as reactions in the model, or a vector of
    -0016 %     indexes. Only these reactions will be checked (default model.rxns).
    -0017 % printUnbalanced : logical
    -0018 %     print warnings about the reactions that were unbalanced
    -0019 %     (default false).
    -0020 % printUnparsable : logical
    -0021 %     print warnings about the reactions that cannot be parsed
    +0004 % Checks whether the reactions are elementally balanced, using the
    +0005 % metFormulas field or, when that is absent, the inchis field. An error is
    +0006 % thrown if the model has neither field. The charge balance is reported
    +0007 % separately.
    +0008 %
    +0009 % Parameters
    +0010 % ----------
    +0011 % model : struct
    +0012 %     a model structure.
    +0013 %
    +0014 % Name-Value Arguments
    +0015 % --------------------
    +0016 % rxns : cell or logical or double
    +0017 %     either a cell array of reaction IDs, a logical vector with the same
    +0018 %     number of elements as reactions in the model, or a vector of
    +0019 %     indexes. Only these reactions are checked (default model.rxns).
    +0020 % printUnbalanced : logical
    +0021 %     print warnings about the reactions that are unbalanced
     0022 %     (default false).
    -0023 %
    -0024 % Returns
    -0025 % -------
    -0026 % balanceStructure : struct
    -0027 %     elemental balance structure with fields:
    -0028 %
    -0029 %     - balanceStatus : 1 if the reaction is balanced, 0 if it is
    -0030 %       unbalanced, -1 if it could not be balanced due to missing
    -0031 %       information, -2 if it could not be balanced due to an error.
    -0032 %       Elemental only; charge is reported separately below
    -0033 %     - chargeStatus : 1 if the reaction is charge balanced, 0 if it is
    -0034 %       not, -1 if any participating metabolite has no charge (or the
    -0035 %       model has no metCharges field), in which case the charge balance
    -0036 %       is unknown rather than zero
    -0037 %     - chargeResidual : the sum of charges over the reaction, NaN where
    -0038 %       chargeStatus is -1
    -0039 %     - elements : struct with fields abbrevs (cell array with
    -0040 %       abbreviations for all used elements) and names (cell array with
    -0041 %       the names for all used elements)
    -0042 %     - leftComp : MxN matrix with the sum of coefficients for each of the
    -0043 %       elements (N) for the left side of the reactions (M)
    -0044 %     - rightComp : the corresponding matrix for the right side
    -0045 %
    -0046 % Examples
    -0047 % --------
    -0048 %     balanceStructure = getElementalBalance(model, rxns, printUnbalanced, printUnparsable);
    -0049 
    -0050 p=parseRAVENargs(varargin, {'rxns',[]; 'printUnbalanced',false; 'printUnparsable',false});
    -0051 rxns=p.rxns;
    -0052 if ~isempty(rxns) && ~islogical(rxns) && ~isnumeric(rxns)
    -0053     rxns=convertCharArray(rxns);
    -0054 end
    -0055 printUnbalanced=p.printUnbalanced;
    -0056 printUnparsable=p.printUnparsable;
    -0057 
    -0058 if ~isempty(rxns)
    -0059     indexes=~getIndexes(model,rxns,'rxns',true);
    -0060     model=removeReactions(model,indexes,true);
    -0061 end
    -0062 
    -0063 balanceStructure.balanceStatus=nan(numel(model.rxns),1);
    -0064 
    -0065 %Get the formulas
    -0066 if isfield(model,'metFormulas')
    -0067     [balanceStructure.elements, useMat, exitFlag]=parseFormulas(model.metFormulas, true);
    -0068 else
    -0069     if isfield(model,'inchis')
    -0070         [balanceStructure.elements, useMat, exitFlag]=parseFormulas(model.inchis, true,true);
    -0071     else
    -0072         EM='The model must contain either the "metFormulas" or the "inchis" field in order to test for elemental balancing';
    -0073         error('RAVEN:badInput', '%s', EM);
    -0074     end
    -0075 end
    -0076 
    -0077 balanceStructure.leftComp=zeros(numel(model.rxns),numel(balanceStructure.elements.names));
    -0078 balanceStructure.rightComp=balanceStructure.leftComp;
    -0079 
    -0080 %Look at the right and left side of the reactions separately
    -0081 S{1}=model.S;
    -0082 S{2}=model.S;
    -0083 S{1}(S{1}>0)=0; %Left side
    -0084 S{2}(S{2}<0)=0; %Right side
    -0085 S{1}=abs(S{1}); %Both should have positive values
    -0086 
    -0087 %First do it for left side and then for right
    -0088 for i=1:2
    -0089     for j=1:numel(model.rxns)
    -0090         %Get the balance status of the involved mets
    -0091         I=exitFlag(S{i}(:,j)~=0);
    -0092         if any(I==-1)
    -0093             balanceStructure.balanceStatus(j)=-2;
    -0094         end
    -0095         if any(I==0)
    -0096             %Do not change a more serious error to a less serious one
    -0097             balanceStructure.balanceStatus(j)=min(-1,balanceStructure.balanceStatus(j));
    +0023 % printUnparsable : logical
    +0024 %     print warnings about the reactions that cannot be balanced because of
    +0025 %     missing information or parsing errors (default false).
    +0026 %
    +0027 % Returns
    +0028 % -------
    +0029 % balanceStructure : struct
    +0030 %     elemental balance structure with fields:
    +0031 %
    +0032 %     - balanceStatus : 1 if the reaction is balanced, 0 if it is
    +0033 %       unbalanced, -1 if it could not be balanced due to missing
    +0034 %       information (including a reaction without metabolites), -2 if it
    +0035 %       could not be balanced because a formula could not be parsed.
    +0036 %       Elemental only; charge is reported in chargeStatus
    +0037 %     - chargeStatus : 1 if the reaction is charge balanced, 0 if it is
    +0038 %       not, -1 if any participating metabolite has no charge, the reaction
    +0039 %       has no metabolites, or the model has no metCharges field; the
    +0040 %       charge balance is then unknown
    +0041 %     - chargeResidual : the sum of charges over the reaction, NaN where
    +0042 %       chargeStatus is -1
    +0043 %     - elements : struct with fields abbrevs (cell array with
    +0044 %       abbreviations for all used elements) and names (cell array with
    +0045 %       the names for all used elements)
    +0046 %     - leftComp : MxN matrix with the sum of coefficients for each of the
    +0047 %       elements (N) for the left side of the reactions (M)
    +0048 %     - rightComp : the corresponding matrix for the right side
    +0049 %
    +0050 % Examples
    +0051 % --------
    +0052 %     balanceStructure = getElementalBalance(model, rxns, printUnbalanced, printUnparsable);
    +0053 
    +0054 p=parseRAVENargs(varargin, {'rxns',[]; 'printUnbalanced',false; 'printUnparsable',false});
    +0055 rxns=p.rxns;
    +0056 if ~isempty(rxns) && ~islogical(rxns) && ~isnumeric(rxns)
    +0057     rxns=convertCharArray(rxns);
    +0058 end
    +0059 printUnbalanced=p.printUnbalanced;
    +0060 printUnparsable=p.printUnparsable;
    +0061 
    +0062 if ~isempty(rxns)
    +0063     indexes=~getIndexes(model,rxns,'rxns',true);
    +0064     model=removeReactions(model,indexes,true);
    +0065 end
    +0066 
    +0067 balanceStructure.balanceStatus=nan(numel(model.rxns),1);
    +0068 
    +0069 %Get the formulas
    +0070 if isfield(model,'metFormulas')
    +0071     [balanceStructure.elements, useMat, exitFlag]=parseFormulas(model.metFormulas, true);
    +0072 else
    +0073     if isfield(model,'inchis')
    +0074         [balanceStructure.elements, useMat, exitFlag]=parseFormulas(model.inchis, true,true);
    +0075     else
    +0076         EM='The model must contain either the "metFormulas" or the "inchis" field in order to test for elemental balancing';
    +0077         error('RAVEN:badInput', '%s', EM);
    +0078     end
    +0079 end
    +0080 
    +0081 balanceStructure.leftComp=zeros(numel(model.rxns),numel(balanceStructure.elements.names));
    +0082 balanceStructure.rightComp=balanceStructure.leftComp;
    +0083 
    +0084 %Look at the right and left side of the reactions separately
    +0085 S{1}=model.S;
    +0086 S{2}=model.S;
    +0087 S{1}(S{1}>0)=0; %Left side
    +0088 S{2}(S{2}<0)=0; %Right side
    +0089 S{1}=abs(S{1}); %Both should have positive values
    +0090 
    +0091 %First do it for left side and then for right
    +0092 for i=1:2
    +0093     for j=1:numel(model.rxns)
    +0094         %Get the balance status of the involved mets
    +0095         I=exitFlag(S{i}(:,j)~=0);
    +0096         if any(I==-1)
    +0097             balanceStructure.balanceStatus(j)=-2;
     0098         end
    -0099     end
    -0100 end
    -0101 %Elemental composition per reaction, vectorized as a single matrix
    -0102 %product per side instead of a per-reaction, per-element loop (which is
    -0103 %quadratic in a genome-scale model's reaction and element counts).
    -0104 balanceStructure.leftComp=full(S{1}'*useMat);
    -0105 balanceStructure.rightComp=full(S{2}'*useMat);
    -0106 
    -0107 %Now compare the left and right sides to find which are unbalanced. This is
    -0108 %done even if the reaction as a whole could not be balanced
    -0109 total=abs(balanceStructure.rightComp-balanceStructure.leftComp)>10^-8; %To deal with roundoff error
    +0099         if any(I==0)
    +0100             %Do not change a more serious error to a less serious one
    +0101             balanceStructure.balanceStatus(j)=min(-1,balanceStructure.balanceStatus(j));
    +0102         end
    +0103     end
    +0104 end
    +0105 %Elemental composition per reaction, vectorized as a single matrix
    +0106 %product per side instead of a per-reaction, per-element loop (which is
    +0107 %quadratic in a genome-scale model's reaction and element counts).
    +0108 balanceStructure.leftComp=full(S{1}'*useMat);
    +0109 balanceStructure.rightComp=full(S{2}'*useMat);
     0110 
    -0111 %Get which reactions are unbalanced. Do not change an error to just
    -0112 %unbalanced
    -0113 balanceStructure.balanceStatus(any(total,2))=min(balanceStructure.balanceStatus(any(total,2)),0);
    +0111 %Now compare the left and right sides to find which are unbalanced. This is
    +0112 %done even if the reaction as a whole could not be balanced
    +0113 total=abs(balanceStructure.rightComp-balanceStructure.leftComp)>10^-8; %To deal with roundoff error
     0114 
    -0115 %Reactions with no metabolites are not balanced (empty S column skips both loops above)
    -0116 emptyRxns = full(sum(model.S ~= 0, 1).' == 0);
    -0117 balanceStructure.balanceStatus(emptyRxns) = min(-1, balanceStructure.balanceStatus(emptyRxns));
    +0115 %Get which reactions are unbalanced. Do not change an error to just
    +0116 %unbalanced
    +0117 balanceStructure.balanceStatus(any(total,2))=min(balanceStructure.balanceStatus(any(total,2)),0);
     0118 
    -0119 %The remaining ones are all balanced
    -0120 balanceStructure.balanceStatus(isnan(balanceStructure.balanceStatus))=1;
    -0121 
    -0122 %Charge balance. This is reported separately from balanceStatus, which
    -0123 %callers such as removeBadRxns and printModelStats read as a purely
    -0124 %elemental verdict.
    -0125 balanceStructure.chargeStatus=zeros(numel(model.rxns),1);
    -0126 balanceStructure.chargeResidual=nan(numel(model.rxns),1);
    -0127 if ~isfield(model,'metCharges')
    -0128     balanceStructure.chargeStatus(:)=-1;
    -0129 else
    -0130     for j=1:numel(model.rxns)
    -0131         %Only the participating metabolites may be touched: S is sparse and
    -0132         %0*NaN is NaN, so a single unset charge anywhere in the model would
    -0133         %otherwise poison every reaction. Summing with 'omitnan' instead
    -0134         %would be worse, silently reporting an unknown residual as 0.
    -0135         idx=find(model.S(:,j));
    -0136         if isempty(idx) || any(isnan(model.metCharges(idx)))
    -0137             balanceStructure.chargeStatus(j)=-1;
    -0138         else
    -0139             balanceStructure.chargeResidual(j)=sum(full(model.S(idx,j)).*model.metCharges(idx));
    -0140             if abs(balanceStructure.chargeResidual(j))>10^-8 %Roundoff error
    -0141                 balanceStructure.chargeStatus(j)=0;
    -0142             else
    -0143                 balanceStructure.chargeStatus(j)=1;
    -0144             end
    -0145         end
    -0146     end
    -0147 end
    -0148 
    -0149 %Print warnings
    -0150 toPrint=[];
    -0151 if printUnbalanced==true
    -0152     toPrint=[toPrint;find(balanceStructure.balanceStatus==0)];
    -0153 end
    -0154 if printUnparsable==true
    -0155     toPrint=[toPrint;find(balanceStructure.balanceStatus<0)];
    -0156 end
    -0157 
    -0158 toPrint=sort(toPrint);
    -0159 for i=1:numel(toPrint)
    -0160     if balanceStructure.balanceStatus(toPrint(i))<0
    -0161         if balanceStructure.balanceStatus(toPrint(i))==-1
    -0162             EM=['The reaction ' model.rxns{toPrint(i)} ' could not be balanced due to missing information'];
    -0163             warning('RAVEN:warning', '%s', EM);
    -0164         else
    -0165             EM=['The reaction ' model.rxns{toPrint(i)} ' could not be balanced due to a parsing error'];
    -0166             warning('RAVEN:warning', '%s', EM);
    -0167         end
    -0168     else
    -0169         %Find the compounds that are not balanced
    -0170         notBalanced=find(total(toPrint(i),:));
    -0171         for j=1:numel(notBalanced)
    -0172             EM=['The reaction ' model.rxns{toPrint(i)} ' is not balanced with respect to ' balanceStructure.elements.names{notBalanced(j)}];
    -0173             warning('RAVEN:warning', '%s', EM);
    -0174         end
    -0175     end
    -0176 end
    -0177 
    -0178 % Re-order the structure entries so they are consistent with the ordering of
    -0179 % the input reaction indexes
    -0180 if ~isempty(rxns)
    -0181     rxns = getIndexes(model,rxns,'rxns');
    -0182     [~,i] = sort(rxns);
    -0183     balanceStructure.balanceStatus(i) = balanceStructure.balanceStatus;
    -0184     balanceStructure.chargeStatus(i) = balanceStructure.chargeStatus;
    -0185     balanceStructure.chargeResidual(i) = balanceStructure.chargeResidual;
    -0186     balanceStructure.leftComp(i,:) = balanceStructure.leftComp;
    -0187     balanceStructure.rightComp(i,:) = balanceStructure.rightComp;
    -0188 end

    +0119 %Reactions with no metabolites are not balanced (empty S column skips both loops above) +0120 emptyRxns = full(sum(model.S ~= 0, 1).' == 0); +0121 balanceStructure.balanceStatus(emptyRxns) = min(-1, balanceStructure.balanceStatus(emptyRxns)); +0122 +0123 %The remaining ones are all balanced +0124 balanceStructure.balanceStatus(isnan(balanceStructure.balanceStatus))=1; +0125 +0126 %Charge balance. This is reported separately from balanceStatus, which +0127 %callers such as removeBadRxns and printModelStats read as a purely +0128 %elemental verdict. +0129 balanceStructure.chargeStatus=zeros(numel(model.rxns),1); +0130 balanceStructure.chargeResidual=nan(numel(model.rxns),1); +0131 if ~isfield(model,'metCharges') +0132 balanceStructure.chargeStatus(:)=-1; +0133 else +0134 for j=1:numel(model.rxns) +0135 %Only the participating metabolites may be touched: S is sparse and +0136 %0*NaN is NaN, so a single unset charge anywhere in the model would +0137 %otherwise poison every reaction. Summing with 'omitnan' instead +0138 %would be worse, silently reporting an unknown residual as 0. +0139 idx=find(model.S(:,j)); +0140 if isempty(idx) || any(isnan(model.metCharges(idx))) +0141 balanceStructure.chargeStatus(j)=-1; +0142 else +0143 balanceStructure.chargeResidual(j)=sum(full(model.S(idx,j)).*model.metCharges(idx)); +0144 if abs(balanceStructure.chargeResidual(j))>10^-8 %Roundoff error +0145 balanceStructure.chargeStatus(j)=0; +0146 else +0147 balanceStructure.chargeStatus(j)=1; +0148 end +0149 end +0150 end +0151 end +0152 +0153 %Print warnings +0154 toPrint=[]; +0155 if printUnbalanced==true +0156 toPrint=[toPrint;find(balanceStructure.balanceStatus==0)]; +0157 end +0158 if printUnparsable==true +0159 toPrint=[toPrint;find(balanceStructure.balanceStatus<0)]; +0160 end +0161 +0162 toPrint=sort(toPrint); +0163 for i=1:numel(toPrint) +0164 if balanceStructure.balanceStatus(toPrint(i))<0 +0165 if balanceStructure.balanceStatus(toPrint(i))==-1 +0166 EM=['The reaction ' model.rxns{toPrint(i)} ' could not be balanced due to missing information']; +0167 warning('RAVEN:warning', '%s', EM); +0168 else +0169 EM=['The reaction ' model.rxns{toPrint(i)} ' could not be balanced due to a parsing error']; +0170 warning('RAVEN:warning', '%s', EM); +0171 end +0172 else +0173 %Find the compounds that are not balanced +0174 notBalanced=find(total(toPrint(i),:)); +0175 for j=1:numel(notBalanced) +0176 EM=['The reaction ' model.rxns{toPrint(i)} ' is not balanced with respect to ' balanceStructure.elements.names{notBalanced(j)}]; +0177 warning('RAVEN:warning', '%s', EM); +0178 end +0179 end +0180 end +0181 +0182 % Re-order the structure entries so they are consistent with the ordering of +0183 % the input reaction indexes +0184 if ~isempty(rxns) +0185 rxns = getIndexes(model,rxns,'rxns'); +0186 [~,i] = sort(rxns); +0187 balanceStructure.balanceStatus(i) = balanceStructure.balanceStatus; +0188 balanceStructure.chargeStatus(i) = balanceStructure.chargeStatus; +0189 balanceStructure.chargeResidual(i) = balanceStructure.chargeResidual; +0190 balanceStructure.leftComp(i,:) = balanceStructure.leftComp; +0191 balanceStructure.rightComp(i,:) = balanceStructure.rightComp; +0192 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/queries/getExchangeRxns.html b/docs/html/queries/getExchangeRxns.html index e8b8859a..cbcbe4ed 100644 --- a/docs/html/queries/getExchangeRxns.html +++ b/docs/html/queries/getExchangeRxns.html @@ -30,7 +30,9 @@

    DESCRIPTION ^
     getExchangeRxns  Retrieve the exchange reactions from a model.
     
      Exchange reactions are identified by having either no substrates or no
    - products.
    + products. In a model with metabolites marked in the unconstrained field,
    + the exchange reactions are the reactions that involve such a metabolite
    + (as substrate for 'in', as product for 'out').
     
      Parameters
      ----------
    @@ -67,8 +69,9 @@ 

    DESCRIPTION ^CROSS-REFERENCE INFORMATION ^
 </ul>
 This function is called by:
 <ul style= -
  • getTransportRxns getTransportRxns Retrieve the transport reactions from a model.
  • modelSummary modelSummary Print a concise overview of a RAVEN model and its flux state.
  • printFluxes printFluxes Print reactions and fluxes to the screen or to a file.
  • +
  • getTransportRxns getTransportRxns Retrieve the transport reactions from a model.
  • modelSummary modelSummary Print an overview of a RAVEN model and its flux state.
  • printFluxes printFluxes Print reactions and fluxes to the screen or to a file.
  • @@ -96,115 +99,118 @@

    SOURCE CODE ^% getExchangeRxns Retrieve the exchange reactions from a model. 0003 % 0004 % Exchange reactions are identified by having either no substrates or no -0005 % products. -0006 % -0007 % Parameters -0008 % ---------- -0009 % model : struct -0010 % a model structure. -0011 % -0012 % Name-Value Arguments -0013 % -------------------- -0014 % reactionType : char -0015 % which exchange reactions should be returned (default 'all'): -0016 % -0017 % - 'all' : all reactions, irrespective of reaction bounds -0018 % - 'uptake' : reactions with bounds that imply that only uptake is -0019 % allowed. Reaction direction, upper and lower bounds are all -0020 % considered -0021 % - 'excrete' : reactions with bounds that imply that only excretion is -0022 % allowed. Reaction direction, upper and lower bounds are all -0023 % considered -0024 % - 'reverse' : reactions with non-zero upper and lower bounds that -0025 % imply that both uptake and excretion are allowed -0026 % - 'blocked' : reactions that have zero upper and lower bounds, not -0027 % allowing any flux -0028 % - 'in' : reactions where the boundary metabolite is the substrate of -0029 % the reaction; a positive flux value would imply uptake, but -0030 % reaction bounds are not considered -0031 % - 'out' : reactions where the boundary metabolite is the product of -0032 % the reaction; a negative flux value would imply uptake, but -0033 % reaction bounds are not considered -0034 % -0035 % Returns -0036 % ------- -0037 % exchangeRxns : cell -0038 % cell array with the IDs of the exchange reactions. -0039 % exchangeRxnsIndexes : double -0040 % vector with the indexes of the exchange reactions. -0041 % exchangedMets : double -0042 % vector with the metabolite index for each exchange reaction (one -0043 % metabolite per exchange reaction). Only computed when requested. -0044 % -0045 % Notes -0046 % ----- -0047 % The union of 'in' and 'out' equals 'all'. Also, the union of 'uptake', -0048 % 'excrete', 'reverse' and 'blocked' equals 'all'. -0049 % -0050 % Examples -0051 % -------- -0052 % [exchangeRxns, exchangeRxnsIndexes] = getExchangeRxns(model, reactionType); -0053 -0054 p=parseRAVENargs(varargin, {'reactionType','all'}); -0055 reactionType=char(p.reactionType); +0005 % products. In a model with metabolites marked in the unconstrained field, +0006 % the exchange reactions are the reactions that involve such a metabolite +0007 % (as substrate for 'in', as product for 'out'). +0008 % +0009 % Parameters +0010 % ---------- +0011 % model : struct +0012 % a model structure. +0013 % +0014 % Name-Value Arguments +0015 % -------------------- +0016 % reactionType : char +0017 % which exchange reactions should be returned (default 'all'): +0018 % +0019 % - 'all' : all reactions, irrespective of reaction bounds +0020 % - 'uptake' : reactions with bounds that imply that only uptake is +0021 % allowed. Reaction direction, upper and lower bounds are all +0022 % considered +0023 % - 'excrete' : reactions with bounds that imply that only excretion is +0024 % allowed. Reaction direction, upper and lower bounds are all +0025 % considered +0026 % - 'reverse' : reactions with non-zero upper and lower bounds that +0027 % imply that both uptake and excretion are allowed +0028 % - 'blocked' : reactions that have zero upper and lower bounds, not +0029 % allowing any flux +0030 % - 'in' : reactions where the boundary metabolite is the substrate of +0031 % the reaction; a positive flux value would imply uptake, but +0032 % reaction bounds are not considered +0033 % - 'out' : reactions where the boundary metabolite is the product of +0034 % the reaction; a negative flux value would imply uptake, but +0035 % reaction bounds are not considered +0036 % +0037 % Returns +0038 % ------- +0039 % exchangeRxns : cell +0040 % cell array with the IDs of the exchange reactions. +0041 % exchangeRxnsIndexes : double +0042 % vector with the indexes of the exchange reactions. +0043 % exchangedMets : double +0044 % vector with the index of the first metabolite with a non-zero +0045 % coefficient in each exchange reaction (0 if there is none). Only +0046 % computed when requested. +0047 % +0048 % Notes +0049 % ----- +0050 % The union of 'in' and 'out' equals 'all'. Also, the union of 'uptake', +0051 % 'excrete', 'reverse' and 'blocked' equals 'all'. +0052 % +0053 % Examples +0054 % -------- +0055 % [exchangeRxns, exchangeRxnsIndexes] = getExchangeRxns(model, reactionType); 0056 -0057 % Find exchange reactions. Both branches must produce a column of reaction -0058 % indexes: every use below (concatenation, bound lookups, model.rxns -0059 % indexing) reads them as indexes, not as a mask over the reactions. -0060 if isfield(model, 'unconstrained') && any(model.unconstrained~=0) -0061 % With boundary metabolites present, an exchange reaction is one that -0062 % involves a boundary metabolite. Producing the boundary metabolite is -0063 % what leaves the reaction without a product once the boundary -0064 % metabolite is removed, which is the definition the branch below uses. -0065 boundaryMets = model.unconstrained~=0; -0066 [~, I]=find(model.S(boundaryMets,:)>0); -0067 hasNoProd = unique(I(:)); -0068 [~, I]=find(model.S(boundaryMets,:)<0); -0069 hasNoSubs = unique(I(:)); -0070 else -0071 hasNoProd = transpose(find(sum(model.S>0)==0)); -0072 hasNoSubs = transpose(find(sum(model.S<0)==0)); -0073 end -0074 allExch = [hasNoProd; hasNoSubs]; -0075 -0076 switch reactionType -0077 case {'both','all'} % For legacy reasons, 'both' is also allowed -0078 exchangeRxnsIndexes = allExch; -0079 case 'in' -0080 exchangeRxnsIndexes = hasNoSubs; -0081 case 'out' -0082 exchangeRxnsIndexes = hasNoProd; -0083 case 'blocked' -0084 exchangeRxnsIndexes = allExch(model.lb(allExch) == 0 & model.ub(allExch) == 0); -0085 case 'reverse' -0086 exchangeRxnsIndexes = allExch(model.lb(allExch) < 0 & model.ub(allExch) > 0); -0087 case 'uptake' -0088 -0089 exchangeRxnsIndexes = allExch([(model.lb(hasNoProd) < 0 & model.ub(hasNoProd) <= 0); ... -0090 (model.lb(hasNoSubs) >= 0 & model.ub(hasNoSubs) > 0)]); -0091 case 'excrete' -0092 exchangeRxnsIndexes = allExch([(model.lb(hasNoProd) >= 0 & model.ub(hasNoProd) > 0); ... -0093 (model.lb(hasNoSubs) < 0 & model.ub(hasNoSubs) <= 0)]); -0094 otherwise -0095 error('Invalid reactionType specified') -0096 end -0097 % unique rather than sort: a reaction that involves two boundary metabolites, -0098 % and an empty reaction (no substrates and no products), each appear in both -0099 % halves of allExch and would otherwise be reported twice. -0100 exchangeRxnsIndexes = unique(exchangeRxnsIndexes); -0101 exchangeRxns = model.rxns(exchangeRxnsIndexes); -0102 -0103 if nargout > 2 -0104 nExch=numel(exchangeRxnsIndexes); -0105 exchangedMets=zeros(nExch,1); -0106 for ei=1:nExch -0107 k=find(model.S(:,exchangeRxnsIndexes(ei)),1); -0108 if ~isempty(k) -0109 exchangedMets(ei)=k; -0110 end -0111 end -0112 end -0113 end

    +0057 p=parseRAVENargs(varargin, {'reactionType','all'}); +0058 reactionType=char(p.reactionType); +0059 +0060 % Find exchange reactions. Both branches must produce a column of reaction +0061 % indexes: every use below (concatenation, bound lookups, model.rxns +0062 % indexing) reads them as indexes, not as a mask over the reactions. +0063 if isfield(model, 'unconstrained') && any(model.unconstrained~=0) +0064 % With boundary metabolites present, an exchange reaction is one that +0065 % involves a boundary metabolite. Producing the boundary metabolite is +0066 % what leaves the reaction without a product once the boundary +0067 % metabolite is removed, which is the definition the branch below uses. +0068 boundaryMets = model.unconstrained~=0; +0069 [~, I]=find(model.S(boundaryMets,:)>0); +0070 hasNoProd = unique(I(:)); +0071 [~, I]=find(model.S(boundaryMets,:)<0); +0072 hasNoSubs = unique(I(:)); +0073 else +0074 hasNoProd = transpose(find(sum(model.S>0)==0)); +0075 hasNoSubs = transpose(find(sum(model.S<0)==0)); +0076 end +0077 allExch = [hasNoProd; hasNoSubs]; +0078 +0079 switch reactionType +0080 case {'both','all'} % For legacy reasons, 'both' is also allowed +0081 exchangeRxnsIndexes = allExch; +0082 case 'in' +0083 exchangeRxnsIndexes = hasNoSubs; +0084 case 'out' +0085 exchangeRxnsIndexes = hasNoProd; +0086 case 'blocked' +0087 exchangeRxnsIndexes = allExch(model.lb(allExch) == 0 & model.ub(allExch) == 0); +0088 case 'reverse' +0089 exchangeRxnsIndexes = allExch(model.lb(allExch) < 0 & model.ub(allExch) > 0); +0090 case 'uptake' +0091 +0092 exchangeRxnsIndexes = allExch([(model.lb(hasNoProd) < 0 & model.ub(hasNoProd) <= 0); ... +0093 (model.lb(hasNoSubs) >= 0 & model.ub(hasNoSubs) > 0)]); +0094 case 'excrete' +0095 exchangeRxnsIndexes = allExch([(model.lb(hasNoProd) >= 0 & model.ub(hasNoProd) > 0); ... +0096 (model.lb(hasNoSubs) < 0 & model.ub(hasNoSubs) <= 0)]); +0097 otherwise +0098 error('Invalid reactionType specified') +0099 end +0100 % unique rather than sort: a reaction that involves two boundary metabolites, +0101 % and an empty reaction (no substrates and no products), each appear in both +0102 % halves of allExch and would otherwise be reported twice. +0103 exchangeRxnsIndexes = unique(exchangeRxnsIndexes); +0104 exchangeRxns = model.rxns(exchangeRxnsIndexes); +0105 +0106 if nargout > 2 +0107 nExch=numel(exchangeRxnsIndexes); +0108 exchangedMets=zeros(nExch,1); +0109 for ei=1:nExch +0110 k=find(model.S(:,exchangeRxnsIndexes(ei)),1); +0111 if ~isempty(k) +0112 exchangedMets(ei)=k; +0113 end +0114 end +0115 end +0116 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/queries/getGenesFromGrRules.html b/docs/html/queries/getGenesFromGrRules.html index 374f2b27..281dedf2 100644 --- a/docs/html/queries/getGenesFromGrRules.html +++ b/docs/html/queries/getGenesFromGrRules.html @@ -33,25 +33,25 @@

    DESCRIPTION ^CROSS-REFERENCE INFORMATION ^
 </ul>
 This function is called by:
 <ul style= -
  • checkModelStruct checkModelStruct Perform a number of checks to ensure a model structure is ok.
  • +
  • checkModelStruct checkModelStruct Check a model structure for errors and inconsistencies.
  • @@ -77,25 +77,25 @@

    SOURCE CODE ^% ---------- 0006 % grRules : cell 0007 % a cell array of model grRules, from which a list of genes is to be -0008 % extracted. NOTE: Boolean operators can be text ("and", "or") or -0009 % symbolic ("&", "|"), but there must be a space between operators and -0010 % gene names/IDs. -0011 % -0012 % Name-Value Arguments -0013 % -------------------- -0014 % originalGenes : cell -0015 % the original gene list from the model. If supplied, the genes parsed -0016 % from grRules must match it exactly (as a set), an error is thrown -0017 % otherwise, and the returned genes list follows originalGenes' order. -0018 % -0019 % Returns -0020 % ------- -0021 % genes : cell -0022 % a unique list of all gene IDs that appear in grRules. +0008 % extracted. Boolean operators can be text ("and", "or") or symbolic +0009 % ("&", "|"), with a space between operators and gene names or IDs. +0010 % +0011 % Name-Value Arguments +0012 % -------------------- +0013 % originalGenes : cell +0014 % the original gene list from the model. If supplied, the genes parsed +0015 % from grRules must match it as a set, otherwise an error is thrown, and +0016 % the returned genes list follows the order of originalGenes. +0017 % +0018 % Returns +0019 % ------- +0020 % genes : cell +0021 % a unique, sorted list of all gene IDs that appear in grRules (in the +0022 % order of originalGenes when supplied). 0023 % rxnGeneMat : double -0024 % (optional) a binary matrix indicating which genes participate in each -0025 % reaction, where rows correspond to reactions (entries in grRules) and -0026 % columns correspond to genes. +0024 % (optional) a sparse binary matrix indicating which genes participate in +0025 % each reaction, where rows correspond to reactions (entries in grRules) +0026 % and columns correspond to genes. Only computed when requested. 0027 % 0028 % Examples 0029 % -------- diff --git a/docs/html/queries/getIndexes.html b/docs/html/queries/getIndexes.html index 6fb99f30..6d651b27 100644 --- a/docs/html/queries/getIndexes.html +++ b/docs/html/queries/getIndexes.html @@ -35,15 +35,19 @@

    DESCRIPTION ^DESCRIPTION ^SOURCE CODE ^% a model structure. 0008 % objects : cell or logical or double 0009 % either a cell array of IDs, a logical vector with the same number of -0010 % elements as metabolites in the model, or a vector of indexes. -0011 % type : char -0012 % 'rxns', 'mets', or 'genes' depending on what to retrieve. 'metnames' -0013 % queries metabolite names, while 'metcomps' allows providing specific -0014 % metabolites and their compartments in the format metaboliteName[comp]. -0015 % An error is raised if such a name matches no metabolite, or more than -0016 % one. -0017 % If a model.ec structure exists (GECKO 3), then also 'ecenzymes', -0018 % 'ecrxns' and 'ecgenes' are allowed. -0019 % -0020 % Name-Value Arguments -0021 % -------------------- -0022 % returnLogical : logical -0023 % sets whether to return a logical array or an array with the indexes -0024 % (default false). -0025 % -0026 % Returns -0027 % ------- -0028 % indexes : logical or double -0029 % can be a logical array or a double array depending on the value of -0030 % returnLogical. -0031 % -0032 % Notes -0033 % ----- -0034 % If 'ecenzymes', 'ecrxns' or 'ecgenes' are used with a GECKO 3 model, then -0035 % the indexes are from the model.ec.enzymes, model.ec.rxns or model.ec.genes -0036 % fields, respectively. -0037 % -0038 % Examples -0039 % -------- -0040 % indexes = getIndexes(model, objects, type, returnLogical); -0041 -0042 p=parseRAVENargs(varargin, {'returnLogical', false}); -0043 returnLogical=p.returnLogical; -0044 -0045 if ~islogical(objects) && ~isnumeric(objects) -0046 objects=convertCharArray(objects); -0047 end -0048 type=char(type); -0049 -0050 indexes=[]; -0051 -0052 switch type -0053 case 'rxns' -0054 searchIn=model.rxns; -0055 case 'mets' -0056 searchIn=model.mets; -0057 case 'genes' -0058 searchIn=model.genes; -0059 case 'metnames' -0060 searchIn=model.metNames; -0061 case 'metcomps' -0062 % If provided as metaboliteName[comp], then index search is quite -0063 % different from general approach, and therefore coded separately. -0064 mets=regexprep(objects,'(^.+)\[(.+)\]$','$1'); -0065 comps=regexprep(objects,'(^.+)\[(.+)\]$','$2'); -0066 indexes=zeros(1,numel(objects)); -0067 for i=1:numel(objects) -0068 index=find(strcmp(mets(i),model.metNames)); -0069 index=index(strcmp(comps(i),model.comps(model.metComps(index)))); -0070 if isscalar(index) -0071 indexes(i)=index; -0072 elseif isempty(index) -0073 error(['Could not find object ''' objects{i} ''' in the model']); -0074 else -0075 error(['''' objects{i} ''' is ambiguous: it matches the ' ... -0076 'metabolites ' strjoin(model.mets(index),', ') ... -0077 '. Use metabolite ids (type ''mets'') instead.']); -0078 end -0079 end -0080 indexes=indexes(:); -0081 if returnLogical==true -0082 tempIndexes=false(numel(model.mets),1); -0083 tempIndexes(indexes)=true; -0084 indexes=tempIndexes; -0085 end -0086 return %None of the remaining function needs to run if metcomps -0087 case 'ecrxns' -0088 if ~isfield(model,'ec') -0089 error('Type %s cannot be used if no model.ec structure is present.',type) -0090 end -0091 searchIn=model.ec.rxns; -0092 case 'ecenzymes' -0093 if ~isfield(model,'ec') -0094 error('Type %s cannot be used if no model.ec structure is present.',type) -0095 end -0096 searchIn=model.ec.enzymes; -0097 case 'ecgenes' -0098 if ~isfield(model,'ec') -0099 error('Type %s cannot be used if no model.ec structure is present.',type) -0100 end -0101 searchIn=model.ec.genes; -0102 otherwise -0103 if contains(objects,{'rxns','mets','metnames','metcomps','genes','ecgenes','ecenzymes','ecrxns'}) -0104 error('The second and third parameter provided to run getIndexes are likely switched. Note that the second parameter is the object to find the index for, while the third parameter is the object type (''rxns'', ''mets'', ''metnames'', ''metcomps'', ''genes'', ''ecgenes'', ''ecenzymes'' or ''ecrxns'').') -0105 else -0106 error('Incorrect value of the ''type'' parameter. Allowed values are ''rxns'', ''mets'', ''metnames'', ''metcomps'', ''genes'', ''ecgenes'', ''ecenzymes'' or ''ecrxns''.'); -0107 end -0108 end -0109 -0110 if iscell(objects) -0111 if strcmpi(type,'metnames') -0112 % metnames allows multiple matches per name (same metabolite in different -0113 % compartments), so return a cell array of index vectors. -0114 for i=1:numel(objects) -0115 indexes{i}=find(strcmp(objects(i),searchIn)); -0116 end -0117 else -0118 % Build a hash map once (O(m)) for O(1) per-object lookup instead of -0119 % O(m) strcmp per object. -0120 searchMap = containers.Map(searchIn, 1:numel(searchIn)); -0121 for i=1:numel(objects) -0122 if isKey(searchMap, objects{i}) -0123 indexes(i) = searchMap(objects{i}); -0124 else -0125 error(['Could not find object ''' objects{i} ''' in the model']); -0126 end -0127 end -0128 end -0129 else -0130 %Now it's either a logical mask or an array with indexes. We want indexes. -0131 if islogical(objects) -0132 indexes=find(objects); -0133 else -0134 indexes=objects; -0135 end -0136 end -0137 -0138 if returnLogical==true -0139 tempIndexes=false(numel(searchIn),1); -0140 tempIndexes(indexes)=true; -0141 indexes=tempIndexes; -0142 end -0143 -0144 indexes=indexes(:); -0145 if iscell(indexes) && length(indexes)==1 -0146 indexes=indexes{1}; -0147 end -0148 end +0010 % elements as objects of the given type in the model, or a vector of +0011 % indexes. An error is raised if an ID is not found in the model, except +0012 % for type 'metnames', where a name that is not found gives an empty +0013 % index vector. +0014 % type : char +0015 % 'rxns', 'mets' or 'genes', depending on what to retrieve. 'metnames' +0016 % queries metabolite names and returns, for each name, the indexes of all +0017 % metabolites with that name. 'metcomps' queries specific +0018 % metabolites and their compartments in the format metaboliteName[comp]. +0019 % An error is raised if such a name matches no metabolite, or more than +0020 % one. +0021 % If a model.ec structure exists (GECKO 3), 'ecenzymes', 'ecrxns' and +0022 % 'ecgenes' are also allowed. +0023 % +0024 % Name-Value Arguments +0025 % -------------------- +0026 % returnLogical : logical +0027 % sets whether to return a logical array or an array with the indexes +0028 % (default false). +0029 % +0030 % Returns +0031 % ------- +0032 % indexes : logical or double +0033 % a logical array if returnLogical is true, otherwise a double array. For +0034 % 'metnames' with several names, a cell array of double vectors. +0035 % +0036 % Notes +0037 % ----- +0038 % If 'ecenzymes', 'ecrxns' or 'ecgenes' are used with a GECKO 3 model, then +0039 % the indexes are from the model.ec.enzymes, model.ec.rxns or model.ec.genes +0040 % fields, respectively. +0041 % +0042 % Examples +0043 % -------- +0044 % indexes = getIndexes(model, objects, type, returnLogical); +0045 +0046 p=parseRAVENargs(varargin, {'returnLogical', false}); +0047 returnLogical=p.returnLogical; +0048 +0049 if ~islogical(objects) && ~isnumeric(objects) +0050 objects=convertCharArray(objects); +0051 end +0052 type=char(type); +0053 +0054 indexes=[]; +0055 +0056 switch type +0057 case 'rxns' +0058 searchIn=model.rxns; +0059 case 'mets' +0060 searchIn=model.mets; +0061 case 'genes' +0062 searchIn=model.genes; +0063 case 'metnames' +0064 searchIn=model.metNames; +0065 case 'metcomps' +0066 % If provided as metaboliteName[comp], then index search is quite +0067 % different from general approach, and therefore coded separately. +0068 mets=regexprep(objects,'(^.+)\[(.+)\]$','$1'); +0069 comps=regexprep(objects,'(^.+)\[(.+)\]$','$2'); +0070 indexes=zeros(1,numel(objects)); +0071 for i=1:numel(objects) +0072 index=find(strcmp(mets(i),model.metNames)); +0073 index=index(strcmp(comps(i),model.comps(model.metComps(index)))); +0074 if isscalar(index) +0075 indexes(i)=index; +0076 elseif isempty(index) +0077 error(['Could not find object ''' objects{i} ''' in the model']); +0078 else +0079 error(['''' objects{i} ''' is ambiguous: it matches the ' ... +0080 'metabolites ' strjoin(model.mets(index),', ') ... +0081 '. Use metabolite ids (type ''mets'') instead.']); +0082 end +0083 end +0084 indexes=indexes(:); +0085 if returnLogical==true +0086 tempIndexes=false(numel(model.mets),1); +0087 tempIndexes(indexes)=true; +0088 indexes=tempIndexes; +0089 end +0090 return %None of the remaining function needs to run if metcomps +0091 case 'ecrxns' +0092 if ~isfield(model,'ec') +0093 error('Type %s cannot be used if no model.ec structure is present.',type) +0094 end +0095 searchIn=model.ec.rxns; +0096 case 'ecenzymes' +0097 if ~isfield(model,'ec') +0098 error('Type %s cannot be used if no model.ec structure is present.',type) +0099 end +0100 searchIn=model.ec.enzymes; +0101 case 'ecgenes' +0102 if ~isfield(model,'ec') +0103 error('Type %s cannot be used if no model.ec structure is present.',type) +0104 end +0105 searchIn=model.ec.genes; +0106 otherwise +0107 if contains(objects,{'rxns','mets','metnames','metcomps','genes','ecgenes','ecenzymes','ecrxns'}) +0108 error('The second and third parameter provided to run getIndexes are likely switched. Note that the second parameter is the object to find the index for, while the third parameter is the object type (''rxns'', ''mets'', ''metnames'', ''metcomps'', ''genes'', ''ecgenes'', ''ecenzymes'' or ''ecrxns'').') +0109 else +0110 error('Incorrect value of the ''type'' parameter. Allowed values are ''rxns'', ''mets'', ''metnames'', ''metcomps'', ''genes'', ''ecgenes'', ''ecenzymes'' or ''ecrxns''.'); +0111 end +0112 end +0113 +0114 if iscell(objects) +0115 if strcmpi(type,'metnames') +0116 % metnames allows multiple matches per name (same metabolite in different +0117 % compartments), so return a cell array of index vectors. +0118 for i=1:numel(objects) +0119 indexes{i}=find(strcmp(objects(i),searchIn)); +0120 end +0121 else +0122 % Build a hash map once (O(m)) for O(1) per-object lookup instead of +0123 % O(m) strcmp per object. +0124 searchMap = containers.Map(searchIn, 1:numel(searchIn)); +0125 for i=1:numel(objects) +0126 if isKey(searchMap, objects{i}) +0127 indexes(i) = searchMap(objects{i}); +0128 else +0129 error(['Could not find object ''' objects{i} ''' in the model']); +0130 end +0131 end +0132 end +0133 else +0134 %Now it's either a logical mask or an array with indexes. We want indexes. +0135 if islogical(objects) +0136 indexes=find(objects); +0137 else +0138 indexes=objects; +0139 end +0140 end +0141 +0142 if returnLogical==true +0143 tempIndexes=false(numel(searchIn),1); +0144 tempIndexes(indexes)=true; +0145 indexes=tempIndexes; +0146 end +0147 +0148 indexes=indexes(:); +0149 if iscell(indexes) && length(indexes)==1 +0150 indexes=indexes{1}; +0151 end +0152 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/queries/getRxnsInComp.html b/docs/html/queries/getRxnsInComp.html index 1bb1f51f..7b17daae 100644 --- a/docs/html/queries/getRxnsInComp.html +++ b/docs/html/queries/getRxnsInComp.html @@ -34,20 +34,21 @@

    DESCRIPTION ^SOURCE CODE ^% model : struct 0007 % a model structure. 0008 % comp : char -0009 % string with the compartment id. -0010 % -0011 % Name-Value Arguments -0012 % -------------------- -0013 % includePartial : logical -0014 % true if reactions with metabolites in several compartments (normally -0015 % transport reactions) should be included (default false). -0016 % -0017 % Returns -0018 % ------- -0019 % I : double -0020 % boolean vector of the reactions. -0021 % rxnNames : cell -0022 % the names of the reactions. -0023 % -0024 % Examples -0025 % -------- -0026 % [I, rxnNames] = getRxnsInComp(model, comp, includePartial); -0027 -0028 comp=char(comp); -0029 p=parseRAVENargs(varargin, {'includePartial',false}); -0030 includePartial=p.includePartial; -0031 -0032 J=find(ismember(upper(model.comps),upper(comp))); -0033 -0034 if numel(J)~=1 -0035 EM=['No unique match to compartment "' comp{1} '"']; -0036 error('RAVEN:badInput', '%s', EM); -0037 end -0038 -0039 K=model.metComps==J; %Get all metabolites in the compartment -0040 -0041 S=model.S~=0; -0042 -0043 %Find the reactions which involve any of the mets -0044 [~, I]=find(S(K,:)); -0045 I=unique(I); -0046 -0047 %Then remove the ones which also include metabolites in other comps -0048 if includePartial==false -0049 I=I(sum(S(:,I))==sum(S(K,I))); -0050 end -0051 rxnNames=model.rxnNames(I); -0052 end +0009 % the compartment id (case insensitive). An error is thrown if the id +0010 % does not match exactly one compartment. +0011 % +0012 % Name-Value Arguments +0013 % -------------------- +0014 % includePartial : logical +0015 % true if reactions with metabolites in several compartments (for +0016 % example transport reactions) are included (default false). +0017 % +0018 % Returns +0019 % ------- +0020 % I : double +0021 % indexes of the reactions in the compartment. +0022 % rxnNames : cell +0023 % the names of these reactions. +0024 % +0025 % Examples +0026 % -------- +0027 % [I, rxnNames] = getRxnsInComp(model, comp, includePartial); +0028 +0029 comp=char(comp); +0030 p=parseRAVENargs(varargin, {'includePartial',false}); +0031 includePartial=p.includePartial; +0032 +0033 J=find(ismember(upper(model.comps),upper(comp))); +0034 +0035 if numel(J)~=1 +0036 EM=['No unique match to compartment "' comp{1} '"']; +0037 error('RAVEN:badInput', '%s', EM); +0038 end +0039 +0040 K=model.metComps==J; %Get all metabolites in the compartment +0041 +0042 S=model.S~=0; +0043 +0044 %Find the reactions which involve any of the mets +0045 [~, I]=find(S(K,:)); +0046 I=unique(I); +0047 +0048 %Then remove the ones which also include metabolites in other comps +0049 if includePartial==false +0050 I=I(sum(S(:,I))==sum(S(K,I))); +0051 end +0052 rxnNames=model.rxnNames(I); +0053 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/queries/getTransportRxns.html b/docs/html/queries/getTransportRxns.html index 2d619104..74422859 100644 --- a/docs/html/queries/getTransportRxns.html +++ b/docs/html/queries/getTransportRxns.html @@ -47,7 +47,8 @@

    DESCRIPTION ^CROSS-REFERENCE INFORMATION ^

    @@ -83,28 +84,29 @@

    SOURCE CODE ^% Notes 0020 % ----- 0021 % Transport reactions are defined as reactions involving (at least) one -0022 % metabolite name in more than one compartment. -0023 -0024 transportRxns=false(numel(model.rxns),1); -0025 -0026 %An exchange reaction against an explicit boundary metabolite (as -0027 %closeModel creates) copies the real metabolite's own name onto its -0028 %boundary counterpart, so it has exactly the same "same name, different -0029 %compartment" shape as a genuine transport reaction. Exclude it, using -0030 %getExchangeRxns' own definition rather than re-deriving one here. -0031 [~,exchIdx]=getExchangeRxns(model,'all'); -0032 isExch=false(numel(model.rxns),1); -0033 isExch(exchIdx)=true; -0034 -0035 for i=1:numel(model.rxns) -0036 if isExch(i) -0037 continue -0038 end -0039 %Get the involved metabolites in each reaction -0040 mets=model.metNames(model.S(:,i)~=0); -0041 transportRxns(i)=numel(mets)~=numel(unique(mets)); -0042 end -0043 end +0022 % metabolite name in more than one compartment. Exchange reactions, as +0023 % identified by getExchangeRxns, are not transport reactions. +0024 +0025 transportRxns=false(numel(model.rxns),1); +0026 +0027 %An exchange reaction against an explicit boundary metabolite (as +0028 %closeModel creates) copies the real metabolite's own name onto its +0029 %boundary counterpart, so it has exactly the same "same name, different +0030 %compartment" shape as a genuine transport reaction. Exclude it, using +0031 %getExchangeRxns' own definition rather than re-deriving one here. +0032 [~,exchIdx]=getExchangeRxns(model,'all'); +0033 isExch=false(numel(model.rxns),1); +0034 isExch(exchIdx)=true; +0035 +0036 for i=1:numel(model.rxns) +0037 if isExch(i) +0038 continue +0039 end +0040 %Get the involved metabolites in each reaction +0041 mets=model.metNames(model.S(:,i)~=0); +0042 transportRxns(i)=numel(mets)~=numel(unique(mets)); +0043 end +0044 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/queries/index.html b/docs/html/queries/index.html index 1042551c..2bf32072 100644 --- a/docs/html/queries/index.html +++ b/docs/html/queries/index.html @@ -19,7 +19,7 @@

    Index for queries

    Matlab files in this directory:

    -
     buildEquationbuildEquation Construct single equation string for a given reaction.
     checkModelStructcheckModelStruct Perform a number of checks to ensure a model structure is ok.
     constructEquationsconstructEquations Construct equation strings for reactions.
     constructSconstructS Construct a stoichiometric matrix from a cell array of equations.
     getAllRxnsFromGenesgetAllRxnsFromGenes Find all reactions annotated by the genes of a set.
     getElementalBalancegetElementalBalance Check whether the reactions of a model are balanced.
     getExchangeRxnsgetExchangeRxns Retrieve the exchange reactions from a model.
     getGenesFromGrRulesgetGenesFromGrRules Extract gene list and rxnGeneMat from grRules array.
     getIndexesgetIndexes Retrieve the indexes for a list of reactions or metabolites.
     getRxnsInCompgetRxnsInComp Get the reactions in a specified compartment.
     getTransportRxnsgetTransportRxns Retrieve the transport reactions from a model.
     modelSummarymodelSummary Print a concise overview of a RAVEN model and its flux state.
     parseFormulasparseFormulas Get the elemental composition from formulas.
     parseRxnEquparseRxnEqu Get all metabolite names from a cell array of equations.
     printFluxesprintFluxes Print reactions and fluxes to the screen or to a file.
     printModelStatsprintModelStats Print some statistics about a model to the screen.
    + buildEquationbuildEquation Construct single equation string for a given reaction.  checkModelStructcheckModelStruct Check a model structure for errors and inconsistencies.  constructEquationsconstructEquations Construct equation strings for reactions.  constructSconstructS Construct a stoichiometric matrix from a cell array of equations.  getAllRxnsFromGenesgetAllRxnsFromGenes Find all reactions annotated by the genes of a set.  getElementalBalancegetElementalBalance Check whether the reactions of a model are balanced.  getExchangeRxnsgetExchangeRxns Retrieve the exchange reactions from a model.  getGenesFromGrRulesgetGenesFromGrRules Extract gene list and rxnGeneMat from grRules array.  getIndexesgetIndexes Retrieve the indexes for a list of reactions or metabolites.  getRxnsInCompgetRxnsInComp Get the reactions in a specified compartment.  getTransportRxnsgetTransportRxns Retrieve the transport reactions from a model.  modelSummarymodelSummary Print an overview of a RAVEN model and its flux state.  parseFormulasparseFormulas Get the elemental composition from formulas.  parseRxnEquparseRxnEqu Get all metabolite names from a cell array of equations.  printFluxesprintFluxes Print reactions and fluxes to the screen or to a file.  printModelStatsprintModelStats Print statistics about a model to the screen. diff --git a/docs/html/queries/modelSummary.html b/docs/html/queries/modelSummary.html index 6dd7c296..0a6e0a0a 100644 --- a/docs/html/queries/modelSummary.html +++ b/docs/html/queries/modelSummary.html @@ -4,7 +4,7 @@ Description of modelSummary - + @@ -21,17 +21,17 @@

    modelSummary

    PURPOSE ^

    -
    modelSummary Print a concise overview of a RAVEN model and its flux state.
    +
    modelSummary Print an overview of a RAVEN model and its flux state.

    SYNOPSIS ^

    function modelSummary(model, varargin)

    DESCRIPTION ^

    -
     modelSummary  Print a concise overview of a RAVEN model and its flux state.
    +
     modelSummary  Print an overview of a RAVEN model and its flux state.
     
    - Prints model dimensions, the objective function, and — when a flux vector
    - is supplied — the objective value plus all net exchange fluxes partitioned
    - into uptake and secretion.
    + Prints the model dimensions and the objective function. When a flux vector
    + is supplied, it also prints the objective value and the net exchange
    + fluxes, partitioned into uptake and secretion.
     
      Parameters
      ----------
    @@ -41,12 +41,14 @@ 

    DESCRIPTION ^DESCRIPTION ^printModelStats : the structural view instead. It breaks the counts down per - compartment, adds unique EC codes, biochemically unique reactions and - unique metabolite names, and optionally reports dead ends and elemental - imbalances. It knows nothing about fluxes or the objective. - printFluxes : the exchange fluxes on their own, with more control over - formatting and filtering than the summary here offers.

    + printModelStats : model statistics. It breaks the counts down per + compartment, adds the number of unique EC codes, biochemically unique + reactions and unique metabolite names, and optionally reports dead ends + and elemental imbalances. It prints no fluxes or objective. + printFluxes : prints the fluxes of reactions with options for filtering + and for the format of the output.

    CROSS-REFERENCE INFORMATION ^

    @@ -79,11 +81,11 @@

    SUBFUNCTIONS ^SOURCE CODE ^

    0001 function modelSummary(model, varargin)
    -0002 % modelSummary  Print a concise overview of a RAVEN model and its flux state.
    +0002 % modelSummary  Print an overview of a RAVEN model and its flux state.
     0003 %
    -0004 % Prints model dimensions, the objective function, and — when a flux vector
    -0005 % is supplied — the objective value plus all net exchange fluxes partitioned
    -0006 % into uptake and secretion.
    +0004 % Prints the model dimensions and the objective function. When a flux vector
    +0005 % is supplied, it also prints the objective value and the net exchange
    +0006 % fluxes, partitioned into uptake and secretion.
     0007 %
     0008 % Parameters
     0009 % ----------
    @@ -93,136 +95,138 @@ 

    SOURCE CODE ^% Name-Value Arguments 0014 % -------------------- 0015 % fluxes : double -0016 % flux vector (length = numel(model.rxns)). When provided, shows the -0017 % objective value and the exchange-flux summary (default []). -0018 % cutoff : double -0019 % minimum absolute net exchange flux to include (default 1e-8). -0020 % nMax : double -0021 % maximum exchange reactions displayed per direction (default 20). -0022 % -0023 % Examples -0024 % -------- -0025 % modelSummary(model) -0026 % modelSummary(model, 'fluxes', sol.x) -0027 % modelSummary(model, 'fluxes', sol.x, 'nMax', 10) -0028 % -0029 % See also -0030 % -------- -0031 % printModelStats : the structural view instead. It breaks the counts down per -0032 % compartment, adds unique EC codes, biochemically unique reactions and -0033 % unique metabolite names, and optionally reports dead ends and elemental -0034 % imbalances. It knows nothing about fluxes or the objective. -0035 % printFluxes : the exchange fluxes on their own, with more control over -0036 % formatting and filtering than the summary here offers. -0037 -0038 p = parseRAVENargs(varargin, {'fluxes',[]; 'cutoff',1e-8; 'nMax',20}); -0039 fluxes = p.fluxes; -0040 cutoff = p.cutoff; -0041 nMax = p.nMax; -0042 -0043 nRxns = numel(model.rxns); -0044 nMets = numel(model.mets); -0045 nGenes = 0; if isfield(model,'genes'), nGenes = numel(model.genes); end -0046 nComps = 0; if isfield(model,'comps'), nComps = numel(model.comps); end -0047 -0048 W = 62; -0049 bar = repmat('=', 1, W); -0050 sep = repmat('-', 1, W); -0051 -0052 fprintf('%s\n', bar); -0053 modelID = ''; if isfield(model,'id') && ~isempty(model.id), modelID = model.id; end -0054 if isfield(model,'name') && ~isempty(model.name) -0055 fprintf(' %s (%s)\n', model.name, modelID); -0056 elseif ~isempty(modelID) -0057 fprintf(' %s\n', modelID); -0058 else -0059 fprintf(' (unnamed model)\n'); -0060 end -0061 fprintf('%s\n', bar); -0062 fprintf(' %-15s %5d %-15s %5d\n', 'Reactions:', nRxns, 'Metabolites:', nMets); -0063 fprintf(' %-15s %5d %-15s %5d\n', 'Genes:', nGenes,'Compartments:', nComps); -0064 fprintf('%s\n', sep); -0065 -0066 % Objective -0067 objIdx = find(model.c ~= 0); -0068 if isempty(objIdx) -0069 fprintf(' Objective: (none)\n'); -0070 else -0071 fprintf(' Objective:\n'); -0072 for i = 1:numel(objIdx) -0073 j = objIdx(i); -0074 rName = ''; -0075 if isfield(model,'rxnNames') && j <= numel(model.rxnNames) && ~isempty(model.rxnNames{j}) -0076 rName = [' (' model.rxnNames{j} ')']; -0077 end -0078 fprintf(' %+.4g \xD7 %s%s\n', model.c(j), model.rxns{j}, rName); -0079 end -0080 end -0081 -0082 if isempty(fluxes) -0083 fprintf('%s\n', bar); -0084 return -0085 end -0086 -0087 objVal = model.c' * fluxes; -0088 fprintf(' Objective value: %.6g\n', objVal); -0089 fprintf('%s\n', sep); -0090 -0091 % Classify exchange fluxes -0092 [~, exchIdx] = getExchangeRxns(model); -0093 -0094 upRows = {}; % {metLabel, rxnID, absFlux} -0095 outRows = {}; -0096 -0097 for i = 1:numel(exchIdx) -0098 f = fluxes(exchIdx(i)); -0099 if abs(f) < cutoff, continue; end -0100 col = model.S(:, exchIdx(i)); -0101 m = find(col, 1); % exchange reactions have exactly one metabolite -0102 if isempty(m), continue; end -0103 netProd = full(col(m)) * f; % positive = metabolite secreted; full() avoids sparse scalar in cell -0104 mLabel = metLabel_(model, m); -0105 rID = model.rxns{exchIdx(i)}; -0106 if netProd > 0 -0107 outRows{end+1} = {mLabel, rID, netProd}; %#ok<AGROW> -0108 else -0109 upRows{end+1} = {mLabel, rID, -netProd}; %#ok<AGROW> -0110 end -0111 end -0112 -0113 printSection_(' UPTAKE', upRows, nMax, W); -0114 printSection_(' SECRETION', outRows, nMax, W); -0115 fprintf('%s\n', bar); -0116 end -0117 -0118 %-------------------------------------------------------------------------- -0119 function printSection_(title, rows, nMax, W) -0120 fprintf('\n%s\n', title); -0121 fprintf(' %-26s %-20s %10s\n', 'Metabolite', 'Reaction', 'Flux'); -0122 fprintf(' %s\n', repmat('-', 1, W-2)); -0123 if isempty(rows) -0124 fprintf(' (none)\n'); -0125 return -0126 end -0127 vals = cellfun(@(r) r{3}, rows); -0128 [~, si] = sort(vals, 'descend'); -0129 rows = rows(si); -0130 for i = 1:min(numel(rows), nMax) -0131 fprintf(' %-26s %-20s %10.4g\n', rows{i}{1}, rows{i}{2}, rows{i}{3}); -0132 end -0133 if numel(rows) > nMax -0134 fprintf(' ... (%d more, use nMax to show all)\n', numel(rows) - nMax); -0135 end -0136 end -0137 -0138 function s = metLabel_(model, metIdx) -0139 if isfield(model,'metNames') && ~isempty(model.metNames{metIdx}) -0140 s = model.metNames{metIdx}; -0141 else -0142 s = model.mets{metIdx}; -0143 end -0144 if numel(s) > 26, s = [s(1:23) '...']; end -0145 end

    +0016 % flux vector with numel(model.rxns) elements. When provided, the +0017 % objective value and the exchange-flux summary are printed +0018 % (default []). +0019 % cutoff : double +0020 % minimum absolute net exchange flux to include (default 1e-8). +0021 % nMax : double +0022 % maximum number of exchange reactions displayed per direction, in +0023 % descending order of absolute flux (default 20). +0024 % +0025 % Examples +0026 % -------- +0027 % modelSummary(model) +0028 % modelSummary(model, 'fluxes', sol.x) +0029 % modelSummary(model, 'fluxes', sol.x, 'nMax', 10) +0030 % +0031 % See also +0032 % -------- +0033 % printModelStats : model statistics. It breaks the counts down per +0034 % compartment, adds the number of unique EC codes, biochemically unique +0035 % reactions and unique metabolite names, and optionally reports dead ends +0036 % and elemental imbalances. It prints no fluxes or objective. +0037 % printFluxes : prints the fluxes of reactions with options for filtering +0038 % and for the format of the output. +0039 +0040 p = parseRAVENargs(varargin, {'fluxes',[]; 'cutoff',1e-8; 'nMax',20}); +0041 fluxes = p.fluxes; +0042 cutoff = p.cutoff; +0043 nMax = p.nMax; +0044 +0045 nRxns = numel(model.rxns); +0046 nMets = numel(model.mets); +0047 nGenes = 0; if isfield(model,'genes'), nGenes = numel(model.genes); end +0048 nComps = 0; if isfield(model,'comps'), nComps = numel(model.comps); end +0049 +0050 W = 62; +0051 bar = repmat('=', 1, W); +0052 sep = repmat('-', 1, W); +0053 +0054 fprintf('%s\n', bar); +0055 modelID = ''; if isfield(model,'id') && ~isempty(model.id), modelID = model.id; end +0056 if isfield(model,'name') && ~isempty(model.name) +0057 fprintf(' %s (%s)\n', model.name, modelID); +0058 elseif ~isempty(modelID) +0059 fprintf(' %s\n', modelID); +0060 else +0061 fprintf(' (unnamed model)\n'); +0062 end +0063 fprintf('%s\n', bar); +0064 fprintf(' %-15s %5d %-15s %5d\n', 'Reactions:', nRxns, 'Metabolites:', nMets); +0065 fprintf(' %-15s %5d %-15s %5d\n', 'Genes:', nGenes,'Compartments:', nComps); +0066 fprintf('%s\n', sep); +0067 +0068 % Objective +0069 objIdx = find(model.c ~= 0); +0070 if isempty(objIdx) +0071 fprintf(' Objective: (none)\n'); +0072 else +0073 fprintf(' Objective:\n'); +0074 for i = 1:numel(objIdx) +0075 j = objIdx(i); +0076 rName = ''; +0077 if isfield(model,'rxnNames') && j <= numel(model.rxnNames) && ~isempty(model.rxnNames{j}) +0078 rName = [' (' model.rxnNames{j} ')']; +0079 end +0080 fprintf(' %+.4g \xD7 %s%s\n', model.c(j), model.rxns{j}, rName); +0081 end +0082 end +0083 +0084 if isempty(fluxes) +0085 fprintf('%s\n', bar); +0086 return +0087 end +0088 +0089 objVal = model.c' * fluxes; +0090 fprintf(' Objective value: %.6g\n', objVal); +0091 fprintf('%s\n', sep); +0092 +0093 % Classify exchange fluxes +0094 [~, exchIdx] = getExchangeRxns(model); +0095 +0096 upRows = {}; % {metLabel, rxnID, absFlux} +0097 outRows = {}; +0098 +0099 for i = 1:numel(exchIdx) +0100 f = fluxes(exchIdx(i)); +0101 if abs(f) < cutoff, continue; end +0102 col = model.S(:, exchIdx(i)); +0103 m = find(col, 1); % exchange reactions have exactly one metabolite +0104 if isempty(m), continue; end +0105 netProd = full(col(m)) * f; % positive = metabolite secreted; full() avoids sparse scalar in cell +0106 mLabel = metLabel_(model, m); +0107 rID = model.rxns{exchIdx(i)}; +0108 if netProd > 0 +0109 outRows{end+1} = {mLabel, rID, netProd}; %#ok<AGROW> +0110 else +0111 upRows{end+1} = {mLabel, rID, -netProd}; %#ok<AGROW> +0112 end +0113 end +0114 +0115 printSection_(' UPTAKE', upRows, nMax, W); +0116 printSection_(' SECRETION', outRows, nMax, W); +0117 fprintf('%s\n', bar); +0118 end +0119 +0120 %-------------------------------------------------------------------------- +0121 function printSection_(title, rows, nMax, W) +0122 fprintf('\n%s\n', title); +0123 fprintf(' %-26s %-20s %10s\n', 'Metabolite', 'Reaction', 'Flux'); +0124 fprintf(' %s\n', repmat('-', 1, W-2)); +0125 if isempty(rows) +0126 fprintf(' (none)\n'); +0127 return +0128 end +0129 vals = cellfun(@(r) r{3}, rows); +0130 [~, si] = sort(vals, 'descend'); +0131 rows = rows(si); +0132 for i = 1:min(numel(rows), nMax) +0133 fprintf(' %-26s %-20s %10.4g\n', rows{i}{1}, rows{i}{2}, rows{i}{3}); +0134 end +0135 if numel(rows) > nMax +0136 fprintf(' ... (%d more, use nMax to show all)\n', numel(rows) - nMax); +0137 end +0138 end +0139 +0140 function s = metLabel_(model, metIdx) +0141 if isfield(model,'metNames') && ~isempty(model.metNames{metIdx}) +0142 s = model.metNames{metIdx}; +0143 else +0144 s = model.mets{metIdx}; +0145 end +0146 if numel(s) > 26, s = [s(1:23) '...']; end +0147 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/queries/parseFormulas.html b/docs/html/queries/parseFormulas.html index cdc4ec9c..2c421212 100644 --- a/docs/html/queries/parseFormulas.html +++ b/docs/html/queries/parseFormulas.html @@ -37,15 +37,16 @@

    DESCRIPTION ^DESCRIPTION ^DESCRIPTION ^CROSS-REFERENCE INFORMATION ^
 </ul>
 This function is called by:
 <ul style= -
  • checkModelStruct checkModelStruct Perform a number of checks to ensure a model structure is ok.
  • getElementalBalance getElementalBalance Check whether the reactions of a model are balanced.
  • +
  • checkModelStruct checkModelStruct Check a model structure for errors and inconsistencies.
  • getElementalBalance getElementalBalance Check whether the reactions of a model are balanced.
  • @@ -98,261 +102,265 @@

    SOURCE CODE ^% Name-Value Arguments 0010 % -------------------- 0011 % noPolymers : logical -0012 % assume that all polymers consist of one element. Corresponds to -0013 % counting everything between (...)n as n being equal to one. Only one -0014 % set of parentheses is allowed. If this is false then polymers are -0015 % returned as "Could not parse formula" (default false). +0012 % treat polymers as a single unit: everything between (...)n is counted +0013 % with n equal to one. Only one set of parentheses is allowed. If this is +0014 % false, polymers get exitFlag -1, "Could not parse formula" +0015 % (default false). 0016 % isInchi : logical 0017 % true if the formulas are in the InChI format (default false). 0018 % ignoreRX : logical -0019 % ignore R-groups and bound protein. This can be useful since they are -0020 % often used only as intermediates (default false). -0021 % -0022 % Returns -0023 % ------- -0024 % elements : struct -0025 % struct with fields: -0026 % -0027 % - abbrevs : cell array with abbreviations for all used elements -0028 % - names : cell array with the names for all used elements -0029 % useMat : double -0030 % MxN matrix with the number of atoms for each formula (M) and each -0031 % element (N). -0032 % exitFlag : double -0033 % array with the exit flags: -0034 % -0035 % - 1 : successful parsing -0036 % - 0 : no formula found -0037 % - -1 : could not parse formula -0038 % MW : double -0039 % predicted molecular weight (g/mol). This is only returned for -0040 % formulas which can be successfully parsed, and its calculation -0041 % doesn't affect the exitFlag variable. NaN is returned if the weight -0042 % couldn't be calculated. -0043 % -0044 % Examples -0045 % -------- -0046 % [elements, useMat, exitFlag, MW] = ... -0047 % parseFormulas(formulas, noPolymers, isInchi, ignoreRX); -0048 -0049 p=parseRAVENargs(varargin, {'noPolymers',false; 'isInchi',false; 'ignoreRX',false}); -0050 noPolymers=p.noPolymers; -0051 isInchi=p.isInchi; -0052 ignoreRX=p.ignoreRX; -0053 -0054 elements.abbrevs={'C', 'N', 'O', 'S', 'P', 'H', 'He', 'Li', 'Be', 'B', 'F', 'Ne', 'Na', 'Mg', 'Al',... -0055 'Si', 'Cl', 'Ar', 'K', 'Ca', 'Sc', 'Ti', 'V', 'Cr', 'Mn', 'Fe', 'Co', 'Ni',... -0056 'Cu', 'Zn', 'Ga', 'Ge', 'As', 'Se', 'Br', 'Kr', 'Rb', 'Sr', 'Y', 'Zr', 'Nb', 'Mo', 'Tc',... -0057 'Ru', 'Rh', 'Pd', 'Ag', 'Cd', 'In', 'Sn', 'Sb', 'Te', 'I', 'Xe', 'Cs', 'Ba', 'La', 'Ce',... -0058 'Pr', 'Nd', 'Pm', 'Sm', 'Eu', 'Gd', 'Tb', 'Dy', 'Ho', 'Er', 'Tm', 'Yb', 'Lu', 'Hf', 'Ta',... -0059 'W', 'Re', 'Os', 'Ir', 'Pt', 'Au', 'Hg', 'Tl', 'Pb', 'Bi', 'Po', 'At', 'Rn', 'Fr', 'Ra',... -0060 'Ac', 'Th', 'Pa', 'U', 'Np', 'Pu', 'Am', 'Cm', 'Bk', 'Cf', 'Es', 'Fm', 'Md', 'No', 'Lr',... -0061 'Rf', 'Db', 'Sg', 'Bh', 'Hs', 'Mt', 'Ds', 'Rg', 'Cn', 'R', 'X'}'; -0062 elements.names={'carbon', 'nitrogen', 'oxygen', 'sulfur', 'phosphorus', 'hydrogen', 'helium', 'lithium', 'beryllium', 'boron',... -0063 'fluorine', 'neon', 'sodium', 'magnesium', 'aluminum,', 'silicon',... -0064 'chlorine', 'argon', 'potassium', 'calcium', 'scandium', 'titanium', 'vanadium',... -0065 'chromium', 'manganese', 'iron', 'cobalt', 'nickel', 'copper', 'zinc', 'gallium', 'germanium',... -0066 'arsenic', 'selenium', 'bromine', 'krypton', 'rubidium', 'strontium', 'yttrium', 'zirconium',... -0067 'niobium', 'molybdenum', 'technetium', 'ruthenium', 'rhodium', 'palladium', 'silver', 'cadmium',... -0068 'indium', 'tin', 'antimony', 'tellurium', 'iodine', 'xenon', 'cesium', 'barium', 'lanthanum',... -0069 'cerium', 'praseodymium', 'neodymium', 'promethium', 'samarium', 'europium', 'gadolinium',... -0070 'terbium', 'dysprosium', 'holmium', 'erbium', 'thulium', 'ytterbium', 'lutetium', 'hafnium',... -0071 'tantalum', 'tungsten', 'rhenium', 'osmium', 'iridium', 'platinum', 'gold', 'mercury',... -0072 'thallium', 'lead', 'bismuth', 'polonium', 'astatine', 'radon', 'francium', 'radium',... -0073 'actinium', 'thorium', 'protactinium', 'uranium', 'neptunium', 'plutonium', 'americium',... -0074 'curium', 'berkelium', 'californium', 'einsteinium', 'fermium', 'mendelevium', 'nobelium',... -0075 'lawrencium', 'rutherfordium', 'dubnium', 'seaborgium', 'bohrium', 'hassium', 'meitnerium',... -0076 'darmstadtium', 'roentgenium', 'copernicium', 'generic group', 'bound protein'}'; -0077 -0078 EWs=[12.0107 14.0067 15.9994 32.065 30.973762 1.00794 4.002602 6.941 9.012182 10.811 18.9984032 ... -0079 20.1797 22.98976928 24.305 26.9815386 28.0855 35.453 39.948 39.0983 40.078 44.955912 47.867 50.9415 ... -0080 51.9961 54.938045 55.845 58.933195 58.6934 63.546 65.39 69.723 72.64 74.9216 78.96 79.904 83.798 ... -0081 85.4678 87.62 88.90585 91.224 92.906 95.94 97.9072 101.07 102.905 106.42 107.8682 112.411 114.818 ... -0082 118.71 121.76 127.6 126.904 131.293 132.9054519 137.327 138.90547 140.116 140.90765 144.242 144.9127 ... -0083 150.36 151.964 157.25 158.92535 162.5 164.93 167.259 168.93421 173.04 174.967 178.49 180.94788 183.84 ... -0084 186.207 190.23 192.217 195.084 196.966569 200.59 204.3833 207.2 208.9804 208.9824 209.9871 222.0176 ... -0085 223.0197 226.0254 227.0277 232.03806 231.03588 238.02891 237.0482 244.0642 243.0614 247.0704 247.0703 ... -0086 251.0796 252.083 257.0951 258.0984 259.101 262.1097 261.1088 262 266 264 277 268 271 272 nan nan nan]'; -0087 -0088 %Set the EWs of these groups to 0 -0089 if ignoreRX==true -0090 EWs(end-1:end)=0; -0091 end -0092 -0093 useMat=zeros(numel(formulas),numel(elements.abbrevs)); -0094 -0095 exitFlag=zeros(numel(formulas),1); +0019 % set the atomic weight of R-groups and bound protein to 0, which allows +0020 % MW to be calculated for formulas that contain them. The atoms are still +0021 % counted in useMat (default false). +0022 % +0023 % Returns +0024 % ------- +0025 % elements : struct +0026 % struct with fields: +0027 % +0028 % - abbrevs : cell array with abbreviations for all used elements +0029 % - names : cell array with the names for all used elements +0030 % +0031 % Elements that occur in none of the formulas are not included. +0032 % useMat : double +0033 % MxN matrix with the number of atoms for each formula (M) and each +0034 % element (N). +0035 % exitFlag : double +0036 % array with the exit flags: +0037 % +0038 % - 1 : successful parsing +0039 % - 0 : no formula found +0040 % - -1 : could not parse formula +0041 % MW : double +0042 % predicted molecular weight (g/mol). This is only calculated when +0043 % requested, and only for formulas which can be successfully parsed; +0044 % its calculation does not affect the exitFlag variable. NaN is returned +0045 % if the weight cannot be calculated, for example for formulas that +0046 % contain R-groups or bound protein while ignoreRX is false. +0047 % +0048 % Examples +0049 % -------- +0050 % [elements, useMat, exitFlag, MW] = ... +0051 % parseFormulas(formulas, noPolymers, isInchi, ignoreRX); +0052 +0053 p=parseRAVENargs(varargin, {'noPolymers',false; 'isInchi',false; 'ignoreRX',false}); +0054 noPolymers=p.noPolymers; +0055 isInchi=p.isInchi; +0056 ignoreRX=p.ignoreRX; +0057 +0058 elements.abbrevs={'C', 'N', 'O', 'S', 'P', 'H', 'He', 'Li', 'Be', 'B', 'F', 'Ne', 'Na', 'Mg', 'Al',... +0059 'Si', 'Cl', 'Ar', 'K', 'Ca', 'Sc', 'Ti', 'V', 'Cr', 'Mn', 'Fe', 'Co', 'Ni',... +0060 'Cu', 'Zn', 'Ga', 'Ge', 'As', 'Se', 'Br', 'Kr', 'Rb', 'Sr', 'Y', 'Zr', 'Nb', 'Mo', 'Tc',... +0061 'Ru', 'Rh', 'Pd', 'Ag', 'Cd', 'In', 'Sn', 'Sb', 'Te', 'I', 'Xe', 'Cs', 'Ba', 'La', 'Ce',... +0062 'Pr', 'Nd', 'Pm', 'Sm', 'Eu', 'Gd', 'Tb', 'Dy', 'Ho', 'Er', 'Tm', 'Yb', 'Lu', 'Hf', 'Ta',... +0063 'W', 'Re', 'Os', 'Ir', 'Pt', 'Au', 'Hg', 'Tl', 'Pb', 'Bi', 'Po', 'At', 'Rn', 'Fr', 'Ra',... +0064 'Ac', 'Th', 'Pa', 'U', 'Np', 'Pu', 'Am', 'Cm', 'Bk', 'Cf', 'Es', 'Fm', 'Md', 'No', 'Lr',... +0065 'Rf', 'Db', 'Sg', 'Bh', 'Hs', 'Mt', 'Ds', 'Rg', 'Cn', 'R', 'X'}'; +0066 elements.names={'carbon', 'nitrogen', 'oxygen', 'sulfur', 'phosphorus', 'hydrogen', 'helium', 'lithium', 'beryllium', 'boron',... +0067 'fluorine', 'neon', 'sodium', 'magnesium', 'aluminum,', 'silicon',... +0068 'chlorine', 'argon', 'potassium', 'calcium', 'scandium', 'titanium', 'vanadium',... +0069 'chromium', 'manganese', 'iron', 'cobalt', 'nickel', 'copper', 'zinc', 'gallium', 'germanium',... +0070 'arsenic', 'selenium', 'bromine', 'krypton', 'rubidium', 'strontium', 'yttrium', 'zirconium',... +0071 'niobium', 'molybdenum', 'technetium', 'ruthenium', 'rhodium', 'palladium', 'silver', 'cadmium',... +0072 'indium', 'tin', 'antimony', 'tellurium', 'iodine', 'xenon', 'cesium', 'barium', 'lanthanum',... +0073 'cerium', 'praseodymium', 'neodymium', 'promethium', 'samarium', 'europium', 'gadolinium',... +0074 'terbium', 'dysprosium', 'holmium', 'erbium', 'thulium', 'ytterbium', 'lutetium', 'hafnium',... +0075 'tantalum', 'tungsten', 'rhenium', 'osmium', 'iridium', 'platinum', 'gold', 'mercury',... +0076 'thallium', 'lead', 'bismuth', 'polonium', 'astatine', 'radon', 'francium', 'radium',... +0077 'actinium', 'thorium', 'protactinium', 'uranium', 'neptunium', 'plutonium', 'americium',... +0078 'curium', 'berkelium', 'californium', 'einsteinium', 'fermium', 'mendelevium', 'nobelium',... +0079 'lawrencium', 'rutherfordium', 'dubnium', 'seaborgium', 'bohrium', 'hassium', 'meitnerium',... +0080 'darmstadtium', 'roentgenium', 'copernicium', 'generic group', 'bound protein'}'; +0081 +0082 EWs=[12.0107 14.0067 15.9994 32.065 30.973762 1.00794 4.002602 6.941 9.012182 10.811 18.9984032 ... +0083 20.1797 22.98976928 24.305 26.9815386 28.0855 35.453 39.948 39.0983 40.078 44.955912 47.867 50.9415 ... +0084 51.9961 54.938045 55.845 58.933195 58.6934 63.546 65.39 69.723 72.64 74.9216 78.96 79.904 83.798 ... +0085 85.4678 87.62 88.90585 91.224 92.906 95.94 97.9072 101.07 102.905 106.42 107.8682 112.411 114.818 ... +0086 118.71 121.76 127.6 126.904 131.293 132.9054519 137.327 138.90547 140.116 140.90765 144.242 144.9127 ... +0087 150.36 151.964 157.25 158.92535 162.5 164.93 167.259 168.93421 173.04 174.967 178.49 180.94788 183.84 ... +0088 186.207 190.23 192.217 195.084 196.966569 200.59 204.3833 207.2 208.9804 208.9824 209.9871 222.0176 ... +0089 223.0197 226.0254 227.0277 232.03806 231.03588 238.02891 237.0482 244.0642 243.0614 247.0704 247.0703 ... +0090 251.0796 252.083 257.0951 258.0984 259.101 262.1097 261.1088 262 266 264 277 268 271 272 nan nan nan]'; +0091 +0092 %Set the EWs of these groups to 0 +0093 if ignoreRX==true +0094 EWs(end-1:end)=0; +0095 end 0096 -0097 %"H+", if parsed from InChI code, would have the composition "p+1". Change -0098 %to fit with how the rest of the compounds are written -0099 formulas=strrep(formulas,'p+1','H+'); +0097 useMat=zeros(numel(formulas),numel(elements.abbrevs)); +0098 +0099 exitFlag=zeros(numel(formulas),1); 0100 -0101 %Ignore charge to make the parsing easier -0102 formulas=strrep(formulas,'+',''); -0103 formulas=strrep(formulas,'-',''); +0101 %"H+", if parsed from InChI code, would have the composition "p+1". Change +0102 %to fit with how the rest of the compounds are written +0103 formulas=strrep(formulas,'p+1','H+'); 0104 -0105 %Loop through each formula -0106 for i=1:numel(formulas) -0107 if ~isempty(formulas{i}) -0108 sucess=false; %True once at least one element has been read -0109 failed=false; %True if any part of the formula could not be read -0110 formula=formulas{i}; -0111 -0112 %If it's an InChI code. The composition is found between the first -0113 %and the second "/" For some simple molecules such as salts only -0114 %the first "/" is present -0115 if isInchi==true -0116 S=regexp(formula,'/','split'); -0117 if numel(S)>=2 -0118 formula=S{2}; -0119 else -0120 formula=''; -0121 end -0122 end -0123 %Only look at what's between the parantheses (polymers are not -0124 %supported in InChI) -0125 if isInchi==false -0126 LP=strfind(formula,'('); -0127 RP=strfind(formula,')n'); -0128 % if numel(LP)>1 || numel(RP)>1 -0129 % exitFlag(i)=-1; continue; -0130 % end if numel(LP)>1 || numel(RP)>1 -0131 % exitFlag(i)=-1; continue; -0132 % end -0133 if numel(LP)==1 && numel(RP)==1 -0134 %This means that the polymer should be regarded as a -0135 %monomer -0136 if noPolymers==true -0137 %This means that there are one set of parantheses -0138 formula=strrep(formula,'(',''); -0139 formula=strrep(formula,')n',''); -0140 else -0141 %This means that polymers should be ignored -0142 exitFlag(i)=-1; -0143 continue; -0144 end -0145 else -0146 if ~isempty(LP) || ~isempty(RP) -0147 exitFlag(i)=-1; -0148 continue; -0149 end -0150 end -0151 end -0152 -0153 %Get the indexes of the numeric (or ".") characters -0154 nonNumeric=false(numel(formula),1); -0155 nonNumeric(regexp(formula,'[^0-9.]'))=true; +0105 %Ignore charge to make the parsing easier +0106 formulas=strrep(formulas,'+',''); +0107 formulas=strrep(formulas,'-',''); +0108 +0109 %Loop through each formula +0110 for i=1:numel(formulas) +0111 if ~isempty(formulas{i}) +0112 sucess=false; %True once at least one element has been read +0113 failed=false; %True if any part of the formula could not be read +0114 formula=formulas{i}; +0115 +0116 %If it's an InChI code. The composition is found between the first +0117 %and the second "/" For some simple molecules such as salts only +0118 %the first "/" is present +0119 if isInchi==true +0120 S=regexp(formula,'/','split'); +0121 if numel(S)>=2 +0122 formula=S{2}; +0123 else +0124 formula=''; +0125 end +0126 end +0127 %Only look at what's between the parantheses (polymers are not +0128 %supported in InChI) +0129 if isInchi==false +0130 LP=strfind(formula,'('); +0131 RP=strfind(formula,')n'); +0132 % if numel(LP)>1 || numel(RP)>1 +0133 % exitFlag(i)=-1; continue; +0134 % end if numel(LP)>1 || numel(RP)>1 +0135 % exitFlag(i)=-1; continue; +0136 % end +0137 if numel(LP)==1 && numel(RP)==1 +0138 %This means that the polymer should be regarded as a +0139 %monomer +0140 if noPolymers==true +0141 %This means that there are one set of parantheses +0142 formula=strrep(formula,'(',''); +0143 formula=strrep(formula,')n',''); +0144 else +0145 %This means that polymers should be ignored +0146 exitFlag(i)=-1; +0147 continue; +0148 end +0149 else +0150 if ~isempty(LP) || ~isempty(RP) +0151 exitFlag(i)=-1; +0152 continue; +0153 end +0154 end +0155 end 0156 -0157 %Get the indexes of the upper characters (since each element starts -0158 %with an upper character) -0159 upperI=isstrprop(formula,'upper'); -0160 upperX=find(upperI); -0161 -0162 for j=1:numel(upperX) -0163 %The first case is when it's the last character. Then the -0164 %coefficient must be 1 -0165 isLast=false; -0166 if upperX(j)==numel(formula) -0167 coeff=1; -0168 element=formula(upperX(j)); -0169 isLast=true; -0170 end -0171 -0172 if isLast==false -0173 %The second case is when the following character is a -0174 %character -0175 if nonNumeric(upperX(j)+1) -0176 %Is it a new element? -0177 if upperI(upperX(j)+1) -0178 %New element, that means that the coefficient was 1 -0179 %and that the element was only one character -0180 coeff=1; -0181 element=formula(upperX(j)); -0182 else -0183 %This means that it's an element with two -0184 %characters -0185 if j==numel(upperX) -0186 if upperX(j)<numel(formula)-1 -0187 coeff=str2double(formula(upperX(j)+2:end)); -0188 else -0189 coeff=1; -0190 end -0191 else -0192 %Check if there is a number or a new element -0193 %after it -0194 if nonNumeric(upperX(j)+2)==true -0195 coeff=1; -0196 else -0197 coeff=str2double(formula(upperX(j)+2:upperX(j+1)-1)); -0198 end -0199 end -0200 element=formula(upperX(j):upperX(j)+1); -0201 end -0202 else -0203 %Then it is a numeral -0204 if j==numel(upperX) -0205 coeff=str2double(formula(upperX(j)+1:end)); -0206 else -0207 coeff=str2double(formula(upperX(j)+1:upperX(j+1)-1)); -0208 end -0209 element=formula(upperX(j)); -0210 end -0211 end -0212 -0213 %Find the element -0214 I=strcmp(element,elements.abbrevs); -0215 if any(I) -0216 if ~isnan(coeff) -0217 useMat(i,I)=useMat(i,I)+coeff; -0218 sucess=true; -0219 else -0220 failed=true; -0221 break; -0222 end -0223 else -0224 failed=true; -0225 break; -0226 end -0227 end -0228 %A formula that stopped part way through is not parsed, even if the -0229 %elements read before that point were recognised: reporting it as -0230 %parsed would hand callers a composition that is missing everything -0231 %after the first unreadable element -0232 if sucess==false || failed==true -0233 useMat(i,:)=0; %Reset for this formula -0234 exitFlag(i)=-1; -0235 else -0236 exitFlag(i)=1; -0237 end -0238 end -0239 end -0240 -0241 %Remove the elements which are never used. any(...,1) is needed because a -0242 %single formula makes useMat a row vector, for which any() would collapse -0243 %the whole row to one value instead of testing each element -0244 I=~any(useMat,1); -0245 useMat(:,I)=[]; -0246 elements.abbrevs(I)=[]; -0247 elements.names(I)=[]; -0248 EWs(I)=[]; -0249 -0250 %Calcluate the weight contribution of each element in each formula. Note -0251 %that this will give NaNs for all formulas if R or X groups are in EWs, -0252 %since 0*NaN is still NaN. Therefore only use elements with known mass -0253 if nargout>3 -0254 P=bsxfun(@times,useMat(:,~isnan(EWs)),EWs(~isnan(EWs)).'); -0255 MW=sum(P,2); -0256 -0257 %Then remove the calculations for the formulas that use an element of -0258 %unknown mass. This has to be a per-formula test: find() on the -0259 %submatrix returns linear indices into it, which are not row indices of -0260 %MW once more than one unknown-mass element is in play (R and X together -0261 %is the common case), so it blanked the wrong formulas and could grow MW -0262 %past the number of formulas -0263 MW(any(useMat(:,isnan(EWs))~=0,2))=nan; -0264 MW(exitFlag~=1)=nan; -0265 end -0266 end +0157 %Get the indexes of the numeric (or ".") characters +0158 nonNumeric=false(numel(formula),1); +0159 nonNumeric(regexp(formula,'[^0-9.]'))=true; +0160 +0161 %Get the indexes of the upper characters (since each element starts +0162 %with an upper character) +0163 upperI=isstrprop(formula,'upper'); +0164 upperX=find(upperI); +0165 +0166 for j=1:numel(upperX) +0167 %The first case is when it's the last character. Then the +0168 %coefficient must be 1 +0169 isLast=false; +0170 if upperX(j)==numel(formula) +0171 coeff=1; +0172 element=formula(upperX(j)); +0173 isLast=true; +0174 end +0175 +0176 if isLast==false +0177 %The second case is when the following character is a +0178 %character +0179 if nonNumeric(upperX(j)+1) +0180 %Is it a new element? +0181 if upperI(upperX(j)+1) +0182 %New element, that means that the coefficient was 1 +0183 %and that the element was only one character +0184 coeff=1; +0185 element=formula(upperX(j)); +0186 else +0187 %This means that it's an element with two +0188 %characters +0189 if j==numel(upperX) +0190 if upperX(j)<numel(formula)-1 +0191 coeff=str2double(formula(upperX(j)+2:end)); +0192 else +0193 coeff=1; +0194 end +0195 else +0196 %Check if there is a number or a new element +0197 %after it +0198 if nonNumeric(upperX(j)+2)==true +0199 coeff=1; +0200 else +0201 coeff=str2double(formula(upperX(j)+2:upperX(j+1)-1)); +0202 end +0203 end +0204 element=formula(upperX(j):upperX(j)+1); +0205 end +0206 else +0207 %Then it is a numeral +0208 if j==numel(upperX) +0209 coeff=str2double(formula(upperX(j)+1:end)); +0210 else +0211 coeff=str2double(formula(upperX(j)+1:upperX(j+1)-1)); +0212 end +0213 element=formula(upperX(j)); +0214 end +0215 end +0216 +0217 %Find the element +0218 I=strcmp(element,elements.abbrevs); +0219 if any(I) +0220 if ~isnan(coeff) +0221 useMat(i,I)=useMat(i,I)+coeff; +0222 sucess=true; +0223 else +0224 failed=true; +0225 break; +0226 end +0227 else +0228 failed=true; +0229 break; +0230 end +0231 end +0232 %A formula that stopped part way through is not parsed, even if the +0233 %elements read before that point were recognised: reporting it as +0234 %parsed would hand callers a composition that is missing everything +0235 %after the first unreadable element +0236 if sucess==false || failed==true +0237 useMat(i,:)=0; %Reset for this formula +0238 exitFlag(i)=-1; +0239 else +0240 exitFlag(i)=1; +0241 end +0242 end +0243 end +0244 +0245 %Remove the elements which are never used. any(...,1) is needed because a +0246 %single formula makes useMat a row vector, for which any() would collapse +0247 %the whole row to one value instead of testing each element +0248 I=~any(useMat,1); +0249 useMat(:,I)=[]; +0250 elements.abbrevs(I)=[]; +0251 elements.names(I)=[]; +0252 EWs(I)=[]; +0253 +0254 %Calcluate the weight contribution of each element in each formula. Note +0255 %that this will give NaNs for all formulas if R or X groups are in EWs, +0256 %since 0*NaN is still NaN. Therefore only use elements with known mass +0257 if nargout>3 +0258 P=bsxfun(@times,useMat(:,~isnan(EWs)),EWs(~isnan(EWs)).'); +0259 MW=sum(P,2); +0260 +0261 %Then remove the calculations for the formulas that use an element of +0262 %unknown mass. This has to be a per-formula test: find() on the +0263 %submatrix returns linear indices into it, which are not row indices of +0264 %MW once more than one unknown-mass element is in play (R and X together +0265 %is the common case), so it blanked the wrong formulas and could grow MW +0266 %past the number of formulas +0267 MW(any(useMat(:,isnan(EWs))~=0,2))=nan; +0268 MW(exitFlag~=1)=nan; +0269 end +0270 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/queries/parseRxnEqu.html b/docs/html/queries/parseRxnEqu.html index a4068037..e654348e 100644 --- a/docs/html/queries/parseRxnEqu.html +++ b/docs/html/queries/parseRxnEqu.html @@ -37,7 +37,7 @@

    DESCRIPTION ^DESCRIPTION ^CROSS-REFERENCE INFORMATION ^

    @@ -76,7 +77,7 @@

    SOURCE CODE ^% Returns 0010 % ------- 0011 % metabolites : cell -0012 % a cell array with the involved metabolites. +0012 % a unique, sorted cell array with the involved metabolites. 0013 % 0014 % Examples 0015 % -------- @@ -84,71 +85,72 @@

    SOURCE CODE ^% 0018 % Notes 0019 % ----- -0020 % The equations should be written like: +0020 % The equations are written like: 0021 % -0022 % 1 A + 3 B (=> or <=>) 5C + 2 D +0022 % 1 A + 3 B (=> or <=>) 5 C + 2 D 0023 % -0024 % If the equation is expressed as for example '... + (n-1) starch' then -0025 % '(n-1) starch' will be interpreted as one metabolite. -0026 -0027 if ~iscell(equations) -0028 equations={equations}; -0029 end -0030 -0031 metabolites={}; -0032 -0033 %Replace the the direction arrows and plus signs with a weird character -0034 %that will be used for parsing -0035 equations=strrep(equations,' <=> ', '€'); -0036 equations=strrep(equations,' => ', '€'); -0037 equations=strrep(equations,' + ', '€'); -0038 equations=strtrim(equations); -0039 -0040 for i=1:numel(equations) -0041 %Split each equation in possible metabolites -0042 candidates=regexp(equations{i},'€','split'); -0043 -0044 %If the splitting character is at the end (if exchange rxns), then an -0045 %empty string will exist together with the real ones. Remove it -0046 candidates(cellfun(@isempty,candidates))=[]; -0047 -0048 %Now remove the potential coefficient before each metabolite. -0049 %Appended one cell at a time instead of [metabolites;x], which -0050 %recopies the whole (growing) array on every metabolite across every -0051 %equation - quadratic in the total metabolite-occurrence count. -0052 for j=1:numel(candidates) -0053 %If the metabolite has a coefficient it will look as 'number name' -0054 space=strfind(candidates{j},' '); -0055 -0056 if isempty(space) -0057 %Add the metabolite -0058 metabolites{end+1,1}=candidates{j}; %#ok<AGROW> -0059 else -0060 potNumber=candidates{j}(1:space(1)); -0061 %I use str2double here which can't deal with fractions (1/3 glc -0062 %and so on). I do this because I don't want to risk calling -0063 %functions -0064 [~,isNumber]=str2num(potNumber); -0065 -0066 if isNumber==1 -0067 %Remove the coefficient -0068 metName=candidates{j}(space(1)+1:end); -0069 metabolites{end+1,1}=metName; %#ok<AGROW> -0070 else -0071 %The metabolite name contained spaces -0072 metabolites{end+1,1}=candidates{j}; %#ok<AGROW> -0073 end -0074 end -0075 end -0076 end -0077 -0078 metabolites=strtrim(metabolites); -0079 -0080 %Upper/lower case is treated as different names. This should be checked for -0081 %later since it's bad modelling practice -0082 metabolites=unique(metabolites); -0083 -0084 end +0024 % with a space around each arrow and plus sign and between a coefficient and +0025 % its metabolite name. If the equation is expressed as for example +0026 % '... + (n-1) starch', '(n-1) starch' is interpreted as one metabolite. +0027 +0028 if ~iscell(equations) +0029 equations={equations}; +0030 end +0031 +0032 metabolites={}; +0033 +0034 %Replace the the direction arrows and plus signs with a weird character +0035 %that will be used for parsing +0036 equations=strrep(equations,' <=> ', '€'); +0037 equations=strrep(equations,' => ', '€'); +0038 equations=strrep(equations,' + ', '€'); +0039 equations=strtrim(equations); +0040 +0041 for i=1:numel(equations) +0042 %Split each equation in possible metabolites +0043 candidates=regexp(equations{i},'€','split'); +0044 +0045 %If the splitting character is at the end (if exchange rxns), then an +0046 %empty string will exist together with the real ones. Remove it +0047 candidates(cellfun(@isempty,candidates))=[]; +0048 +0049 %Now remove the potential coefficient before each metabolite. +0050 %Appended one cell at a time instead of [metabolites;x], which +0051 %recopies the whole (growing) array on every metabolite across every +0052 %equation - quadratic in the total metabolite-occurrence count. +0053 for j=1:numel(candidates) +0054 %If the metabolite has a coefficient it will look as 'number name' +0055 space=strfind(candidates{j},' '); +0056 +0057 if isempty(space) +0058 %Add the metabolite +0059 metabolites{end+1,1}=candidates{j}; %#ok<AGROW> +0060 else +0061 potNumber=candidates{j}(1:space(1)); +0062 %I use str2double here which can't deal with fractions (1/3 glc +0063 %and so on). I do this because I don't want to risk calling +0064 %functions +0065 [~,isNumber]=str2num(potNumber); +0066 +0067 if isNumber==1 +0068 %Remove the coefficient +0069 metName=candidates{j}(space(1)+1:end); +0070 metabolites{end+1,1}=metName; %#ok<AGROW> +0071 else +0072 %The metabolite name contained spaces +0073 metabolites{end+1,1}=candidates{j}; %#ok<AGROW> +0074 end +0075 end +0076 end +0077 end +0078 +0079 metabolites=strtrim(metabolites); +0080 +0081 %Upper/lower case is treated as different names. This should be checked for +0082 %later since it's bad modelling practice +0083 metabolites=unique(metabolites); +0084 +0085 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/queries/printFluxes.html b/docs/html/queries/printFluxes.html index d8e63abc..7da53f05 100644 --- a/docs/html/queries/printFluxes.html +++ b/docs/html/queries/printFluxes.html @@ -34,7 +34,8 @@

    DESCRIPTION ^DESCRIPTION ^DESCRIPTION ^SOURCE CODE ^% model : struct 0007 % a model structure. 0008 % fluxes : double -0009 % a vector with fluxes. -0010 % -0011 % Name-Value Arguments -0012 % -------------------- -0013 % onlyExchange : logical -0014 % only print exchange fluxes (default true). -0015 % cutOffFlux : double -0016 % only print fluxes with absolute values above or equal to this value -0017 % (default 10^-8). -0018 % outputFile : char -0019 % a file to save the print-out to (default is output to the command -0020 % window). -0021 % outputString : char -0022 % a string that specifies the output of each reaction (default -0023 % "%rxnID\t(%rxnName):\t%flux\n"). -0024 % metaboliteList : cell -0025 % cell array of metabolite names. Only reactions involving any of these -0026 % metabolites will be printed. -0027 % -0028 % Notes -0029 % ----- -0030 % The following codes are available for user-defined output strings: -0031 % -0032 % - %rxnID : reaction ID -0033 % - %rxnName : reaction name -0034 % - %lower : lower bound -0035 % - %upper : upper bound -0036 % - %obj : objective coefficient -0037 % - %eqn : equation -0038 % - %flux : flux -0039 % - %element : equation using the metabolite formulas rather than metabolite -0040 % names -0041 % - %unbalanced : "(*)" if the reaction is unbalanced and "(-)" if it could -0042 % not be parsed -0043 % - %lumped : equation where the elemental compositions for the left/right -0044 % hand sides are lumped -0045 % -0046 % Examples -0047 % -------- -0048 % printFluxes(model, fluxes, onlyExchange, cutOffFlux, outputFile, ... -0049 % outputString, metaboliteList); -0050 -0051 p=parseRAVENargs(varargin, {'onlyExchange',true; 'cutOffFlux',10^-8; 'outputFile',[]; 'outputString',[]; 'metaboliteList',{}}); -0052 onlyExchange=p.onlyExchange; -0053 cutOffFlux=p.cutOffFlux; -0054 if isempty(cutOffFlux) -0055 cutOffFlux=10^-8; -0056 end -0057 outputFile=p.outputFile; -0058 if ~isempty(outputFile) -0059 outputFile=char(outputFile); -0060 fid=fopen(outputFile,'w'); -0061 else -0062 fid=1; -0063 end -0064 outputString=p.outputString; -0065 if isempty(outputString) -0066 outputString='%rxnID\t(%rxnName):\t%flux\n'; -0067 else -0068 outputString=char(outputString); -0069 end -0070 metaboliteList=p.metaboliteList; -0071 if ~isempty(metaboliteList) -0072 metaboliteList=convertCharArray(metaboliteList); -0073 end -0074 if isempty(fluxes) -0075 EM='Empty vector of fluxes, solveLP possibly returned infeasible'; -0076 error('RAVEN:badInput', '%s', EM); -0077 elseif size(fluxes,1)~=numel(model.rxns) -0078 EM='The number of fluxes and the number of reactions must be the same'; -0079 error('RAVEN:badInput', '%s', EM); -0080 end -0081 -0082 %Only keep reactions involving the defined metabolites -0083 if ~isempty(metaboliteList) -0084 I=ismember(upper(model.metNames),upper(metaboliteList)); -0085 [~, K]=find(model.S(I,:)); -0086 -0087 %Delete all other reactions -0088 toDelete=true(numel(model.rxns),1); -0089 toDelete(K)=false; -0090 model=removeReactions(model,toDelete); -0091 fluxes(toDelete,:)=[]; -0092 end -0093 -0094 if onlyExchange==true -0095 fprintf(fid,'EXCHANGE FLUXES:\n'); -0096 else -0097 fprintf(fid,'FLUXES:\n'); -0098 end -0099 -0100 %Remove reactions which are below the cut off -0101 toDelete=abs(fluxes)<cutOffFlux; -0102 toDelete=all(toDelete,2); -0103 model=removeReactions(model,toDelete,true,true); -0104 fluxes(toDelete,:)=[]; -0105 -0106 if any(strfind(outputString,'%eqn')) -0107 %Construct the equations -0108 eqn=constructEquations(model); -0109 else -0110 eqn=cell(numel(model.rxns),1); -0111 eqn(:)={''}; -0112 end -0113 if any(strfind(outputString,'%element')) -0114 %For printing equations using the composition -0115 cModel=model; -0116 cModel.metNames=cModel.metFormulas; -0117 cModel.metNames(cellfun(@isempty,cModel.metNames))={'?'}; -0118 element=constructEquations(cModel); -0119 else -0120 element=cell(numel(model.rxns),1); -0121 element(:)={''}; -0122 end -0123 -0124 if any(strfind(outputString,'%unbalanced')) || any(strfind(outputString,'%lumped')) -0125 balanceStructure=getElementalBalance(model); -0126 end -0127 -0128 unbalanced=cell(numel(model.rxns),1); -0129 unbalanced(:)={''}; -0130 if any(strfind(outputString,'%unbalanced')) -0131 unbalanced(balanceStructure.balanceStatus==0)={'(*)'}; -0132 unbalanced(balanceStructure.balanceStatus<0)={'(-)'}; -0133 end -0134 -0135 lumped=cell(numel(model.rxns),1); -0136 lumped(:)={''}; -0137 if any(strfind(outputString,'%lumped')) -0138 for i=1:numel(model.rxns) -0139 leftGroup=''; -0140 rightGroup=''; -0141 for j=1:numel(balanceStructure.elements.names) -0142 I=balanceStructure.leftComp(i,j); -0143 if I~=0 -0144 if I==1 -0145 leftGroup=[leftGroup balanceStructure.elements.abbrevs{j}]; -0146 else -0147 leftGroup=[leftGroup balanceStructure.elements.abbrevs{j} num2str(I)]; -0148 end -0149 end -0150 I=balanceStructure.rightComp(i,j); -0151 if I~=0 -0152 if I==1 -0153 rightGroup=[rightGroup balanceStructure.elements.abbrevs{j}]; -0154 else -0155 rightGroup=[rightGroup balanceStructure.elements.abbrevs{j} num2str(I)]; -0156 end -0157 end -0158 end -0159 if model.rev(i) -0160 lumped{i}=[leftGroup ' <=> ' rightGroup]; -0161 else -0162 lumped{i}=[leftGroup ' => ' rightGroup]; -0163 end -0164 end -0165 end -0166 -0167 %Use getExchangeRxns' own definition of "exchange reaction" rather than a -0168 %local, incomplete re-implementation: a model that still carries explicit -0169 %boundary metabolites represents an exchange as realMet <=> realMet[b] -- -0170 %one reactant AND one product -- which "no reactants or no products" alone -0171 %does not recognise, silently printing nothing for such a model. -0172 isExchRxn=false(numel(model.rxns),1); -0173 if onlyExchange==true -0174 [~,exchIdx]=getExchangeRxns(model,'all'); -0175 isExchRxn(exchIdx)=true; -0176 end -0177 -0178 for i=1:numel(model.rxns) -0179 %Only print if it is an exchange reaction or if all reactions should be -0180 %printed. -0181 if (onlyExchange==false || isExchRxn(i)) -0182 printString=outputString; -0183 -0184 %Produce the final string -0185 printString=strrep(printString,'%rxnID',model.rxns{i}); -0186 printString=strrep(printString,'%eqn',eqn{i}); -0187 printString=strrep(printString,'%rxnName',model.rxnNames{i}); -0188 printString=strrep(printString,'%lower',num2str(model.lb(i))); -0189 printString=strrep(printString,'%upper',num2str(model.ub(i))); -0190 printString=strrep(printString,'%obj',num2str(model.c(i))); -0191 printString=strrep(printString,'%flux',num2str(fluxes(i,:))); -0192 printString=strrep(printString,'%element',element{i}); -0193 printString=strrep(printString,'%unbalanced',unbalanced{i}); -0194 printString=strrep(printString,'%lumped',lumped{i}); -0195 %Substituted model text (rxnID, eqn, rxnName, etc.) may itself -0196 %contain "%", which fprintf would otherwise reinterpret as a new -0197 %format directive; print as literal data instead, after manually -0198 %expanding the template's own \t and \n escapes. -0199 printString=strrep(printString,'\t',sprintf('\t')); -0200 printString=strrep(printString,'\n',sprintf('\n')); -0201 fprintf(fid,'%s',printString); -0202 end -0203 end -0204 -0205 if fid~=1 -0206 fprintf('File successfully saved.\n'); -0207 fclose(fid); -0208 end -0209 end +0009 % a vector with fluxes, or a matrix with one row per reaction and one +0010 % column per flux distribution. +0011 % +0012 % Name-Value Arguments +0013 % -------------------- +0014 % onlyExchange : logical +0015 % only print exchange fluxes (default true). +0016 % cutOffFlux : double +0017 % only print fluxes with absolute values above or equal to this value +0018 % (default 10^-8). +0019 % outputFile : char +0020 % a file to save the print-out to (default is output to the command +0021 % window). +0022 % outputString : char +0023 % a string that specifies the output of each reaction (default +0024 % "%rxnID\t(%rxnName):\t%flux\n"). +0025 % metaboliteList : cell +0026 % cell array of metabolite names (case insensitive). Only reactions +0027 % involving any of these metabolites are printed. +0028 % +0029 % Notes +0030 % ----- +0031 % The following codes are available for user-defined output strings: +0032 % +0033 % - %rxnID : reaction ID +0034 % - %rxnName : reaction name +0035 % - %lower : lower bound +0036 % - %upper : upper bound +0037 % - %obj : objective coefficient +0038 % - %eqn : equation +0039 % - %flux : flux +0040 % - %element : equation using the metabolite formulas in place of metabolite +0041 % names +0042 % - %unbalanced : "(*)" if the reaction is unbalanced and "(-)" if it could +0043 % not be parsed +0044 % - %lumped : equation where the elemental compositions of the left-hand and +0045 % right-hand sides are lumped +0046 % +0047 % Examples +0048 % -------- +0049 % printFluxes(model, fluxes, onlyExchange, cutOffFlux, outputFile, ... +0050 % outputString, metaboliteList); +0051 +0052 p=parseRAVENargs(varargin, {'onlyExchange',true; 'cutOffFlux',10^-8; 'outputFile',[]; 'outputString',[]; 'metaboliteList',{}}); +0053 onlyExchange=p.onlyExchange; +0054 cutOffFlux=p.cutOffFlux; +0055 if isempty(cutOffFlux) +0056 cutOffFlux=10^-8; +0057 end +0058 outputFile=p.outputFile; +0059 if ~isempty(outputFile) +0060 outputFile=char(outputFile); +0061 fid=fopen(outputFile,'w'); +0062 else +0063 fid=1; +0064 end +0065 outputString=p.outputString; +0066 if isempty(outputString) +0067 outputString='%rxnID\t(%rxnName):\t%flux\n'; +0068 else +0069 outputString=char(outputString); +0070 end +0071 metaboliteList=p.metaboliteList; +0072 if ~isempty(metaboliteList) +0073 metaboliteList=convertCharArray(metaboliteList); +0074 end +0075 if isempty(fluxes) +0076 EM='Empty vector of fluxes, solveLP possibly returned infeasible'; +0077 error('RAVEN:badInput', '%s', EM); +0078 elseif size(fluxes,1)~=numel(model.rxns) +0079 EM='The number of fluxes and the number of reactions must be the same'; +0080 error('RAVEN:badInput', '%s', EM); +0081 end +0082 +0083 %Only keep reactions involving the defined metabolites +0084 if ~isempty(metaboliteList) +0085 I=ismember(upper(model.metNames),upper(metaboliteList)); +0086 [~, K]=find(model.S(I,:)); +0087 +0088 %Delete all other reactions +0089 toDelete=true(numel(model.rxns),1); +0090 toDelete(K)=false; +0091 model=removeReactions(model,toDelete); +0092 fluxes(toDelete,:)=[]; +0093 end +0094 +0095 if onlyExchange==true +0096 fprintf(fid,'EXCHANGE FLUXES:\n'); +0097 else +0098 fprintf(fid,'FLUXES:\n'); +0099 end +0100 +0101 %Remove reactions which are below the cut off +0102 toDelete=abs(fluxes)<cutOffFlux; +0103 toDelete=all(toDelete,2); +0104 model=removeReactions(model,toDelete,true,true); +0105 fluxes(toDelete,:)=[]; +0106 +0107 if any(strfind(outputString,'%eqn')) +0108 %Construct the equations +0109 eqn=constructEquations(model); +0110 else +0111 eqn=cell(numel(model.rxns),1); +0112 eqn(:)={''}; +0113 end +0114 if any(strfind(outputString,'%element')) +0115 %For printing equations using the composition +0116 cModel=model; +0117 cModel.metNames=cModel.metFormulas; +0118 cModel.metNames(cellfun(@isempty,cModel.metNames))={'?'}; +0119 element=constructEquations(cModel); +0120 else +0121 element=cell(numel(model.rxns),1); +0122 element(:)={''}; +0123 end +0124 +0125 if any(strfind(outputString,'%unbalanced')) || any(strfind(outputString,'%lumped')) +0126 balanceStructure=getElementalBalance(model); +0127 end +0128 +0129 unbalanced=cell(numel(model.rxns),1); +0130 unbalanced(:)={''}; +0131 if any(strfind(outputString,'%unbalanced')) +0132 unbalanced(balanceStructure.balanceStatus==0)={'(*)'}; +0133 unbalanced(balanceStructure.balanceStatus<0)={'(-)'}; +0134 end +0135 +0136 lumped=cell(numel(model.rxns),1); +0137 lumped(:)={''}; +0138 if any(strfind(outputString,'%lumped')) +0139 for i=1:numel(model.rxns) +0140 leftGroup=''; +0141 rightGroup=''; +0142 for j=1:numel(balanceStructure.elements.names) +0143 I=balanceStructure.leftComp(i,j); +0144 if I~=0 +0145 if I==1 +0146 leftGroup=[leftGroup balanceStructure.elements.abbrevs{j}]; +0147 else +0148 leftGroup=[leftGroup balanceStructure.elements.abbrevs{j} num2str(I)]; +0149 end +0150 end +0151 I=balanceStructure.rightComp(i,j); +0152 if I~=0 +0153 if I==1 +0154 rightGroup=[rightGroup balanceStructure.elements.abbrevs{j}]; +0155 else +0156 rightGroup=[rightGroup balanceStructure.elements.abbrevs{j} num2str(I)]; +0157 end +0158 end +0159 end +0160 if model.rev(i) +0161 lumped{i}=[leftGroup ' <=> ' rightGroup]; +0162 else +0163 lumped{i}=[leftGroup ' => ' rightGroup]; +0164 end +0165 end +0166 end +0167 +0168 %Use getExchangeRxns' own definition of "exchange reaction" rather than a +0169 %local, incomplete re-implementation: a model that still carries explicit +0170 %boundary metabolites represents an exchange as realMet <=> realMet[b] -- +0171 %one reactant AND one product -- which "no reactants or no products" alone +0172 %does not recognise, silently printing nothing for such a model. +0173 isExchRxn=false(numel(model.rxns),1); +0174 if onlyExchange==true +0175 [~,exchIdx]=getExchangeRxns(model,'all'); +0176 isExchRxn(exchIdx)=true; +0177 end +0178 +0179 for i=1:numel(model.rxns) +0180 %Only print if it is an exchange reaction or if all reactions should be +0181 %printed. +0182 if (onlyExchange==false || isExchRxn(i)) +0183 printString=outputString; +0184 +0185 %Produce the final string +0186 printString=strrep(printString,'%rxnID',model.rxns{i}); +0187 printString=strrep(printString,'%eqn',eqn{i}); +0188 printString=strrep(printString,'%rxnName',model.rxnNames{i}); +0189 printString=strrep(printString,'%lower',num2str(model.lb(i))); +0190 printString=strrep(printString,'%upper',num2str(model.ub(i))); +0191 printString=strrep(printString,'%obj',num2str(model.c(i))); +0192 printString=strrep(printString,'%flux',num2str(fluxes(i,:))); +0193 printString=strrep(printString,'%element',element{i}); +0194 printString=strrep(printString,'%unbalanced',unbalanced{i}); +0195 printString=strrep(printString,'%lumped',lumped{i}); +0196 %Substituted model text (rxnID, eqn, rxnName, etc.) may itself +0197 %contain "%", which fprintf would otherwise reinterpret as a new +0198 %format directive; print as literal data instead, after manually +0199 %expanding the template's own \t and \n escapes. +0200 printString=strrep(printString,'\t',sprintf('\t')); +0201 printString=strrep(printString,'\n',sprintf('\n')); +0202 fprintf(fid,'%s',printString); +0203 end +0204 end +0205 +0206 if fid~=1 +0207 fprintf('File successfully saved.\n'); +0208 fclose(fid); +0209 end +0210 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/queries/printModelStats.html b/docs/html/queries/printModelStats.html index 37ecfdfd..ab8f7425 100644 --- a/docs/html/queries/printModelStats.html +++ b/docs/html/queries/printModelStats.html @@ -4,7 +4,7 @@ Description of printModelStats - + @@ -21,13 +21,18 @@

    printModelStats

    PURPOSE ^

    -
    printModelStats Print some statistics about a model to the screen.
    +
    printModelStats Print statistics about a model to the screen.

    SYNOPSIS ^

    function printModelStats(model, varargin)

    DESCRIPTION ^

    -
     printModelStats  Print some statistics about a model to the screen.
    +
     printModelStats  Print statistics about a model to the screen.
    +
    + Prints the numbers of genes, reactions and metabolites in total and per
    + compartment, the number of unique EC codes (if the eccodes field exists),
    + the number of biochemically unique reactions and the number of unique
    + metabolite names.
     
      Parameters
      ----------
    @@ -37,11 +42,12 @@ 

    DESCRIPTION ^DESCRIPTION ^modelSummary : the shorter view, without the per-compartment breakdown, but - adding the objective function and - given a flux vector - the objective - value and the net uptake and secretion.

    + modelSummary : prints the model dimensions without the per-compartment + breakdown, the objective function and, given a flux vector, the + objective value and the net uptake and secretion.

    CROSS-REFERENCE INFORMATION ^

    @@ -67,184 +73,190 @@

    CROSS-REFERENCE INFORMATION ^
 
 <h2><a name=SOURCE CODE ^

    0001 function printModelStats(model, varargin)
    -0002 % printModelStats  Print some statistics about a model to the screen.
    +0002 % printModelStats  Print statistics about a model to the screen.
     0003 %
    -0004 % Parameters
    -0005 % ----------
    -0006 % model : struct
    -0007 %     a model structure.
    +0004 % Prints the numbers of genes, reactions and metabolites in total and per
    +0005 % compartment, the number of unique EC codes (if the eccodes field exists),
    +0006 % the number of biochemically unique reactions and the number of unique
    +0007 % metabolite names.
     0008 %
    -0009 % Name-Value Arguments
    -0010 % --------------------
    -0011 % printModelIssues : logical
    -0012 %     true if information about unconnected reactions/metabolites and
    -0013 %     elemental balancing should be printed (default false).
    -0014 % printDetails : logical
    -0015 %     true if detailed information should be printed about model issues.
    -0016 %     Only used if printModelIssues is true (default true).
    -0017 %
    -0018 % Examples
    -0019 % --------
    -0020 %     printModelStats(model, printModelIssues, printDetails);
    -0021 %
    -0022 % See also
    -0023 % --------
    -0024 % modelSummary : the shorter view, without the per-compartment breakdown, but
    -0025 %     adding the objective function and - given a flux vector - the objective
    -0026 %     value and the net uptake and secretion.
    -0027 
    -0028 p=parseRAVENargs(varargin, {'printModelIssues',false; 'printDetails',true});
    -0029 printModelIssues=p.printModelIssues;
    -0030 printDetails=p.printDetails;
    -0031 
    -0032 %Escapes model text before it is spliced into an fprintf template: the
    -0033 %assembled string is used as a format string below, so a literal "%" in
    -0034 %e.g. a metabolite or compartment name would otherwise be misread as a
    -0035 %format directive and swallow the remainder of the line.
    -0036 esc=@(s) strrep(s,'%','%%');
    +0009 % Parameters
    +0010 % ----------
    +0011 % model : struct
    +0012 %     a model structure.
    +0013 %
    +0014 % Name-Value Arguments
    +0015 % --------------------
    +0016 % printModelIssues : logical
    +0017 %     true if information about unused metabolites, empty reactions,
    +0018 %     dead-end reactions and metabolites, and elemental balancing is printed
    +0019 %     (default false).
    +0020 % printDetails : logical
    +0021 %     true if the individual metabolites and reactions of each issue are
    +0022 %     listed. Used only if printModelIssues is true (default true).
    +0023 %
    +0024 % Examples
    +0025 % --------
    +0026 %     printModelStats(model, printModelIssues, printDetails);
    +0027 %
    +0028 % See also
    +0029 % --------
    +0030 % modelSummary : prints the model dimensions without the per-compartment
    +0031 %     breakdown, the objective function and, given a flux vector, the
    +0032 %     objective value and the net uptake and secretion.
    +0033 
    +0034 p=parseRAVENargs(varargin, {'printModelIssues',false; 'printDetails',true});
    +0035 printModelIssues=p.printModelIssues;
    +0036 printDetails=p.printDetails;
     0037 
    -0038 fprintf(['Network statistics for ' esc(model.id) ': ' esc(model.name) '\n']);
    -0039 
    -0040 %Get which reactions are present in each compartment
    -0041 rxnComps=sparse(numel(model.rxns),numel(model.comps));
    -0042 
    -0043 %For each compartment, find the metabolites that are present in that
    -0044 %compartment and then the reactions they are involved in
    -0045 for i=1:numel(model.comps)
    -0046     [~, I]=find(model.S(model.metComps==i,:));
    -0047     rxnComps(I,i)=1;
    -0048 end
    -0049 
    -0050 if isfield(model,'eccodes')
    -0051     fprintf(['EC-numbers\t\t\t' num2str(numel(unique(model.eccodes))) '\n']);
    -0052 end
    -0053 
    -0054 %Print information about genes
    -0055 if isfield(model,'genes')
    -0056     fprintf(['Genes*\t\t\t\t' num2str(numel(model.genes)) '\n']);
    -0057     %Find the genes in each compartment
    -0058     for i=1:numel(model.comps)
    -0059         [~, I]=find(model.rxnGeneMat(rxnComps(:,i)==1,:));
    -0060         fprintf(['\t' esc(model.compNames{i}) '\t' num2str(numel(unique(I))) '\n']);
    -0061     end
    -0062 end
    -0063 
    -0064 %Print information about reactions
    -0065 fprintf(['\nReactions*\t\t\t' num2str(numel(model.rxns)) '\n']);
    -0066 for i=1:numel(model.comps)
    -0067     fprintf(['\t' esc(model.compNames{i}) '\t' num2str(sum(rxnComps(:,i))) '\n']);
    +0038 %Escapes model text before it is spliced into an fprintf template: the
    +0039 %assembled string is used as a format string below, so a literal "%" in
    +0040 %e.g. a metabolite or compartment name would otherwise be misread as a
    +0041 %format directive and swallow the remainder of the line.
    +0042 esc=@(s) strrep(s,'%','%%');
    +0043 
    +0044 fprintf(['Network statistics for ' esc(model.id) ': ' esc(model.name) '\n']);
    +0045 
    +0046 %Get which reactions are present in each compartment
    +0047 rxnComps=sparse(numel(model.rxns),numel(model.comps));
    +0048 
    +0049 %For each compartment, find the metabolites that are present in that
    +0050 %compartment and then the reactions they are involved in
    +0051 for i=1:numel(model.comps)
    +0052     [~, I]=find(model.S(model.metComps==i,:));
    +0053     rxnComps(I,i)=1;
    +0054 end
    +0055 
    +0056 if isfield(model,'eccodes')
    +0057     fprintf(['EC-numbers\t\t\t' num2str(numel(unique(model.eccodes))) '\n']);
    +0058 end
    +0059 
    +0060 %Print information about genes
    +0061 if isfield(model,'genes')
    +0062     fprintf(['Genes*\t\t\t\t' num2str(numel(model.genes)) '\n']);
    +0063     %Find the genes in each compartment
    +0064     for i=1:numel(model.comps)
    +0065         [~, I]=find(model.rxnGeneMat(rxnComps(:,i)==1,:));
    +0066         fprintf(['\t' esc(model.compNames{i}) '\t' num2str(numel(unique(I))) '\n']);
    +0067     end
     0068 end
     0069 
    -0070 %Removes the effect of compartments and removes duplicate reactions
    -0071 temp=model;
    -0072 temp.comps(:)={'s'}; %Set all compartments to be the same
    -0073 equ=constructEquations(sortModel(temp,true,true),temp.rxns,false);
    -0074 
    -0075 fprintf(['Unique reactions**\t' num2str(numel(unique(equ))) '\n']);
    -0076 
    -0077 %Print information about metabolites
    -0078 fprintf(['\nMetabolites\t\t\t' num2str(numel(model.mets)) '\n']);
    -0079 for i=1:numel(model.comps)
    -0080     fprintf(['\t' esc(model.compNames{i}) '\t' num2str(sum(model.metComps==i)) '\n']);
    -0081 end
    +0070 %Print information about reactions
    +0071 fprintf(['\nReactions*\t\t\t' num2str(numel(model.rxns)) '\n']);
    +0072 for i=1:numel(model.comps)
    +0073     fprintf(['\t' esc(model.compNames{i}) '\t' num2str(sum(rxnComps(:,i))) '\n']);
    +0074 end
    +0075 
    +0076 %Removes the effect of compartments and removes duplicate reactions
    +0077 temp=model;
    +0078 temp.comps(:)={'s'}; %Set all compartments to be the same
    +0079 equ=constructEquations(sortModel(temp,true,true),temp.rxns,false);
    +0080 
    +0081 fprintf(['Unique reactions**\t' num2str(numel(unique(equ))) '\n']);
     0082 
    -0083 fprintf(['Unique metabolites\t' num2str(numel(unique(model.metNames))) '\n']);
    -0084 
    -0085 fprintf('\n* Genes and reactions are counted for each compartment if any of the corresponding metabolites are in that compartment. The sum may therefore not add up to the total number.\n');
    -0086 fprintf('** Unique reactions are defined as being biochemically unique (no compartmentalization)\n');
    -0087 
    -0088 %Also print some potential problems if there are any
    -0089 if printModelIssues==true
    -0090     fprintf(['\nShort model quality summary for ' esc(model.id) ': ' esc(model.name) '\n']);
    -0091     
    -0092     %Check that all the metabolites are being used
    -0093     involvedMat=model.S;
    -0094     involvedMat(involvedMat~=0)=1;
    -0095     usedMets=sum(involvedMat,2);
    -0096     notPresent=find(usedMets==0);
    -0097     if ~isempty(notPresent)
    -0098         errorText=['Non-used metabolites\t' num2str(numel(notPresent)) '\n'];
    -0099         if printDetails==true
    -0100             for i=1:numel(notPresent)
    -0101                 errorText=[errorText '\t(' esc(model.mets{notPresent(i)}) ') ' esc(model.metNames{notPresent(i)}) '\n'];
    -0102             end
    -0103             errorText=[errorText '\n'];
    -0104         end
    -0105         fprintf(errorText);
    -0106     end
    -0107     
    -0108     %Check if there are empty reactions
    -0109     usedRxns=sum(involvedMat,1);
    -0110     notUsed=find(usedRxns==0);
    -0111     if ~isempty(notUsed)
    -0112         errorText=['Empty reactions\t' num2str(numel(notUsed)) '\n'];
    -0113         if printDetails==true
    -0114             for i=1:numel(notUsed)
    -0115                 errorText=[errorText '\t' esc(model.rxns{notUsed(i)}) '\n'];
    -0116             end
    -0117             errorText=[errorText '\n'];
    -0118         end
    -0119         fprintf(errorText);
    -0120     end
    -0121     
    -0122     %Check if there are dead-end reactions/metabolites
    -0123     [~, deletedReactions, deletedMetabolites]=simplifyModel(model,true,false,false,true);
    -0124     
    -0125     if ~isempty(deletedReactions)
    -0126         errorText=['Dead-end reactions\t' num2str(numel(deletedReactions)) '\n'];
    -0127         if printDetails==true
    -0128             for i=1:numel(deletedReactions)
    -0129                 errorText=[errorText '\t' esc(deletedReactions{i}) '\n'];
    -0130             end
    -0131             errorText=[errorText '\n'];
    -0132         end
    -0133         fprintf(errorText);
    -0134     end
    -0135     
    -0136     %Ignore non-used metabolites
    -0137     deletedMetabolites=setdiff(deletedMetabolites,model.mets(notPresent));
    -0138     %Must map to indexes in order to print names
    -0139     deletedMetabolites=find(ismember(model.mets,deletedMetabolites));
    -0140     if ~isempty(deletedMetabolites)
    -0141         errorText=['Dead-end metabolites\t' num2str(numel(deletedMetabolites)) '\n'];
    -0142         if printDetails==true
    -0143             for i=1:numel(deletedMetabolites)
    -0144                 errorText=[errorText '\t(' esc(model.mets{deletedMetabolites(i)}) ') ' esc(model.metNames{deletedMetabolites(i)}) '\n'];
    -0145             end
    -0146             errorText=[errorText '\n'];
    -0147         end
    -0148         fprintf(errorText);
    -0149     end
    -0150     
    -0151     balanceStructure=getElementalBalance(model);
    -0152     
    -0153     notParsed=find(balanceStructure.balanceStatus<0);
    -0154     notBalanced=find(balanceStructure.balanceStatus==0);
    -0155     
    -0156     if ~isempty(notParsed)
    -0157         errorText=['Reactions which could not be elementally balanced\t' num2str(numel(notParsed)) '\n'];
    -0158         if printDetails==true
    -0159             for i=1:numel(notParsed)
    -0160                 errorText=[errorText '\t' esc(model.rxns{notParsed(i)}) '\n'];
    -0161             end
    -0162             errorText=[errorText '\n'];
    -0163         end
    -0164         fprintf(errorText);
    -0165     end
    -0166     if ~isempty(notBalanced)
    -0167         errorText=['Reactions which are elementally unbalanced\t' num2str(numel(notBalanced)) '\n'];
    -0168         if printDetails==true
    -0169             names=strcat(balanceStructure.elements.names,{', '});
    -0170             for i=1:numel(notBalanced)
    -0171                 badOnes=sprintf('%s', names{abs(balanceStructure.leftComp(notBalanced(i),:)-balanceStructure.rightComp(notBalanced(i),:))>10^-7});
    -0172                 errorText=[errorText '\t' esc(model.rxns{notBalanced(i)}) '\t' esc(badOnes(1:end-2)) '\n'];
    -0173             end
    -0174             errorText=[errorText '\n'];
    -0175         end
    -0176         fprintf(errorText);
    -0177     end
    -0178 end
    -0179 end
    +0083 %Print information about metabolites +0084 fprintf(['\nMetabolites\t\t\t' num2str(numel(model.mets)) '\n']); +0085 for i=1:numel(model.comps) +0086 fprintf(['\t' esc(model.compNames{i}) '\t' num2str(sum(model.metComps==i)) '\n']); +0087 end +0088 +0089 fprintf(['Unique metabolites\t' num2str(numel(unique(model.metNames))) '\n']); +0090 +0091 fprintf('\n* Genes and reactions are counted for each compartment if any of the corresponding metabolites are in that compartment. The sum may therefore not add up to the total number.\n'); +0092 fprintf('** Unique reactions are defined as being biochemically unique (no compartmentalization)\n'); +0093 +0094 %Also print some potential problems if there are any +0095 if printModelIssues==true +0096 fprintf(['\nShort model quality summary for ' esc(model.id) ': ' esc(model.name) '\n']); +0097 +0098 %Check that all the metabolites are being used +0099 involvedMat=model.S; +0100 involvedMat(involvedMat~=0)=1; +0101 usedMets=sum(involvedMat,2); +0102 notPresent=find(usedMets==0); +0103 if ~isempty(notPresent) +0104 errorText=['Non-used metabolites\t' num2str(numel(notPresent)) '\n']; +0105 if printDetails==true +0106 for i=1:numel(notPresent) +0107 errorText=[errorText '\t(' esc(model.mets{notPresent(i)}) ') ' esc(model.metNames{notPresent(i)}) '\n']; +0108 end +0109 errorText=[errorText '\n']; +0110 end +0111 fprintf(errorText); +0112 end +0113 +0114 %Check if there are empty reactions +0115 usedRxns=sum(involvedMat,1); +0116 notUsed=find(usedRxns==0); +0117 if ~isempty(notUsed) +0118 errorText=['Empty reactions\t' num2str(numel(notUsed)) '\n']; +0119 if printDetails==true +0120 for i=1:numel(notUsed) +0121 errorText=[errorText '\t' esc(model.rxns{notUsed(i)}) '\n']; +0122 end +0123 errorText=[errorText '\n']; +0124 end +0125 fprintf(errorText); +0126 end +0127 +0128 %Check if there are dead-end reactions/metabolites +0129 [~, deletedReactions, deletedMetabolites]=simplifyModel(model,true,false,false,true); +0130 +0131 if ~isempty(deletedReactions) +0132 errorText=['Dead-end reactions\t' num2str(numel(deletedReactions)) '\n']; +0133 if printDetails==true +0134 for i=1:numel(deletedReactions) +0135 errorText=[errorText '\t' esc(deletedReactions{i}) '\n']; +0136 end +0137 errorText=[errorText '\n']; +0138 end +0139 fprintf(errorText); +0140 end +0141 +0142 %Ignore non-used metabolites +0143 deletedMetabolites=setdiff(deletedMetabolites,model.mets(notPresent)); +0144 %Must map to indexes in order to print names +0145 deletedMetabolites=find(ismember(model.mets,deletedMetabolites)); +0146 if ~isempty(deletedMetabolites) +0147 errorText=['Dead-end metabolites\t' num2str(numel(deletedMetabolites)) '\n']; +0148 if printDetails==true +0149 for i=1:numel(deletedMetabolites) +0150 errorText=[errorText '\t(' esc(model.mets{deletedMetabolites(i)}) ') ' esc(model.metNames{deletedMetabolites(i)}) '\n']; +0151 end +0152 errorText=[errorText '\n']; +0153 end +0154 fprintf(errorText); +0155 end +0156 +0157 balanceStructure=getElementalBalance(model); +0158 +0159 notParsed=find(balanceStructure.balanceStatus<0); +0160 notBalanced=find(balanceStructure.balanceStatus==0); +0161 +0162 if ~isempty(notParsed) +0163 errorText=['Reactions which could not be elementally balanced\t' num2str(numel(notParsed)) '\n']; +0164 if printDetails==true +0165 for i=1:numel(notParsed) +0166 errorText=[errorText '\t' esc(model.rxns{notParsed(i)}) '\n']; +0167 end +0168 errorText=[errorText '\n']; +0169 end +0170 fprintf(errorText); +0171 end +0172 if ~isempty(notBalanced) +0173 errorText=['Reactions which are elementally unbalanced\t' num2str(numel(notBalanced)) '\n']; +0174 if printDetails==true +0175 names=strcat(balanceStructure.elements.names,{', '}); +0176 for i=1:numel(notBalanced) +0177 badOnes=sprintf('%s', names{abs(balanceStructure.leftComp(notBalanced(i),:)-balanceStructure.rightComp(notBalanced(i),:))>10^-7}); +0178 errorText=[errorText '\t' esc(model.rxns{notBalanced(i)}) '\t' esc(badOnes(1:end-2)) '\n']; +0179 end +0180 errorText=[errorText '\n']; +0181 end +0182 fprintf(errorText); +0183 end +0184 end +0185 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/reconstruction/guessComposition.html b/docs/html/reconstruction/guessComposition.html index 8a712cb4..42345a82 100644 --- a/docs/html/reconstruction/guessComposition.html +++ b/docs/html/reconstruction/guessComposition.html @@ -29,8 +29,8 @@

    SYNOPSIS ^DESCRIPTION ^

     guessComposition  Guess the composition of metabolites without one.
     
    - Attempts to guess the composition of metabolites without information about
    - elemental composition.
    + Derives the elemental composition of metabolites without a formula from the
    + reactions they participate in, and writes it to the metFormulas field.
     
      Parameters
      ----------
    @@ -40,17 +40,18 @@ 

    DESCRIPTION ^DESCRIPTION ^CROSS-REFERENCE INFORMATION ^

    @@ -86,8 +88,8 @@

    SOURCE CODE ^
    0001 function [model, guessedFor, couldNotGuess]=guessComposition(model, varargin)
     0002 % guessComposition  Guess the composition of metabolites without one.
     0003 %
    -0004 % Attempts to guess the composition of metabolites without information about
    -0005 % elemental composition.
    +0004 % Derives the elemental composition of metabolites without a formula from the
    +0005 % reactions they participate in, and writes it to the metFormulas field.
     0006 %
     0007 % Parameters
     0008 % ----------
    @@ -97,161 +99,163 @@ 

    SOURCE CODE ^% Name-Value Arguments 0013 % -------------------- 0014 % printResults : logical -0015 % true if the output should be printed (default true). +0015 % true if the predicted compositions are printed (default true). 0016 % 0017 % Returns 0018 % ------- 0019 % model : struct -0020 % a model structure with information about elemental composition added. -0021 % guessedFor : double -0022 % indexes for the metabolites for which a composition could be guessed. -0023 % couldNotGuess : double -0024 % indexes for the metabolites for which no composition could be -0025 % assigned. -0026 % -0027 % Examples -0028 % -------- -0029 % [model, guessedFor, couldNotGuess] = guessComposition(model, printResults); -0030 % -0031 % Notes -0032 % ----- -0033 % A metabolite's composition can only be resolved from a reaction in which -0034 % every other participating metabolite already has a known composition; -0035 % the function repeats this until no further metabolites can be resolved. -0036 % It requires that the existing compositions and reactions are correct, -0037 % and throws an error if a metabolite would need inconsistent -0038 % compositions in different reactions -- so it can also be used to check -0039 % a model for such errors. Note that successful completion does not -0040 % guarantee correctness if the pre-existing compositions were themselves -0041 % wrong. -0042 -0043 p=parseRAVENargs(varargin, {'printResults',true}); -0044 printResults=p.printResults; -0045 -0046 %The metabolites for which there is no elemental composition -0047 originalMissing=unique(model.metNames(cellfun(@isempty,model.metFormulas))); -0048 guessedFor={}; -0049 -0050 %This is to keep track of if new metabolite compositions were predicted -0051 predicted=true; -0052 while predicted==true -0053 predicted=false; -0054 -0055 %Get the unique names (composition should be independent of -0056 %compartment) for the metabolites without composition -0057 metNames=unique(model.metNames(cellfun(@isempty,model.metFormulas))); -0058 -0059 %Parse the formulas in the model -0060 [elements, useMat, exitFlag]=parseFormulas(model.metFormulas, true,false); -0061 -0062 for i=1:numel(metNames) -0063 %Get the metabolites with this name. Not so neat, but this is a -0064 %fast function anyways -0065 mets=find(ismember(model.metNames,metNames(i))); -0066 -0067 currentComp=[]; +0020 % a model structure with the predicted formulas added to metFormulas, for +0021 % all metabolites that share the metabolite name. +0022 % guessedFor : cell +0023 % metabolite names for which a composition could be guessed. +0024 % couldNotGuess : cell +0025 % metabolite names, among those without a formula in the input model, +0026 % for which no composition could be assigned. +0027 % +0028 % Examples +0029 % -------- +0030 % [model, guessedFor, couldNotGuess] = guessComposition(model, printResults); +0031 % +0032 % Notes +0033 % ----- +0034 % A metabolite's composition can only be resolved from a reaction in which +0035 % every other participating metabolite already has a known composition; +0036 % the function repeats this until no further metabolites can be resolved. +0037 % It requires that the existing compositions and reactions are correct. If a +0038 % metabolite would need different compositions in different reactions, or a +0039 % reaction gives both positive and negative atom counts, the function issues +0040 % a warning and assigns no composition to that metabolite, so the warnings +0041 % can also be used to check a model for such inconsistencies. Completion +0042 % without warnings does not show that the pre-existing compositions are +0043 % correct. +0044 +0045 p=parseRAVENargs(varargin, {'printResults',true}); +0046 printResults=p.printResults; +0047 +0048 %The metabolites for which there is no elemental composition +0049 originalMissing=unique(model.metNames(cellfun(@isempty,model.metFormulas))); +0050 guessedFor={}; +0051 +0052 %This is to keep track of if new metabolite compositions were predicted +0053 predicted=true; +0054 while predicted==true +0055 predicted=false; +0056 +0057 %Get the unique names (composition should be independent of +0058 %compartment) for the metabolites without composition +0059 metNames=unique(model.metNames(cellfun(@isempty,model.metFormulas))); +0060 +0061 %Parse the formulas in the model +0062 [elements, useMat, exitFlag]=parseFormulas(model.metFormulas, true,false); +0063 +0064 for i=1:numel(metNames) +0065 %Get the metabolites with this name. Not so neat, but this is a +0066 %fast function anyways +0067 mets=find(ismember(model.metNames,metNames(i))); 0068 -0069 %Loop through the metabolites -1: Could not assign due to missing -0070 %info. -2: Could not assign due to contradiction 1: Composition -0071 %assigned -0072 metStatus=-1; -0073 for j=1:numel(mets) -0074 %Get the reactions that the metabolite participates in -0075 [~, I]=find(model.S(mets(j),:)); -0076 if any(I) -0077 for k=1:numel(I) -0078 %Loop through the reactions and check if all other mets -0079 %in them have known composition -0080 eqn=model.S(:,I(k)); -0081 %The metabolite's own stoichiometric coefficient in -0082 %this reaction, needed to recover its atom counts from -0083 %the balance below -0084 coeff=eqn(mets(j)); -0085 eqn(mets(j))=0; -0086 if all(exitFlag(eqn~=0)==1) -0087 %This means that all other mets had composition. -0088 %Calculate the resulting composition for the -0089 %unknown one -0090 comp=useMat'*eqn; -0091 -0092 %This can result in round off errors if there are -0093 %stoichiometries with many decimals. Ignore values -0094 %below 10^-12 -0095 comp(abs(comp)<10^-12)=0; -0096 -0097 %Check if the composition consist of both negative -0098 %and positive values. If so, throw an error -0099 if all(comp<=0) || all(comp>=0) -0100 %Normalize by the metabolite's own coefficient, -0101 %so e.g. a coefficient of 2 halves the atom -0102 %counts recovered from the balance -0103 comp=abs(comp)/abs(coeff); -0104 if isempty(currentComp) -0105 currentComp=comp; -0106 end -0107 %Also to deal with round off errors -0108 if all(abs(currentComp-comp)<10^-10) -0109 metStatus=1; -0110 else -0111 metStatus=-2; -0112 break; -0113 -0114 end -0115 else -0116 metStatus=-2; -0117 break; -0118 end -0119 end -0120 end -0121 %If there was contradictions in one compartment, then abort -0122 %for all compartments -0123 if metStatus==-2 -0124 break; -0125 end -0126 else -0127 %The metabolite does not participate, no composition can be -0128 %calculated -0129 metStatus=-1; -0130 end -0131 end -0132 %Check status of the metabolite -0133 switch metStatus -0134 case -2 -0135 EM=['Could not predict composition for "' metNames{i} '" due to inconsistencies']; -0136 warning('RAVEN:warning', '%s', EM); -0137 case 1 -0138 %Calculate and add the composition -0139 str=getCompString(elements,comp); -0140 model.metFormulas(mets)={str}; -0141 if printResults==true -0142 fprintf(['Predicted composition for "' metNames{i} '" to be ' str '\n']); -0143 end -0144 -0145 %Keep track -0146 guessedFor=[guessedFor;metNames(i)]; -0147 -0148 predicted=true; %To loop again -0149 end -0150 end -0151 end -0152 -0153 couldNotGuess=setdiff(originalMissing,guessedFor); -0154 end -0155 -0156 %Helper function for getting the composition string -0157 function str=getCompString(elements,comp) -0158 str=''; -0159 -0160 for i=1:numel(comp) -0161 if comp(i)~=0 -0162 if comp(i)==1 -0163 str=[str elements.abbrevs{i}]; -0164 else -0165 str=[str elements.abbrevs{i} num2str(comp(i))]; -0166 end -0167 end -0168 end -0169 end

    +0069 currentComp=[]; +0070 +0071 %Loop through the metabolites -1: Could not assign due to missing +0072 %info. -2: Could not assign due to contradiction 1: Composition +0073 %assigned +0074 metStatus=-1; +0075 for j=1:numel(mets) +0076 %Get the reactions that the metabolite participates in +0077 [~, I]=find(model.S(mets(j),:)); +0078 if any(I) +0079 for k=1:numel(I) +0080 %Loop through the reactions and check if all other mets +0081 %in them have known composition +0082 eqn=model.S(:,I(k)); +0083 %The metabolite's own stoichiometric coefficient in +0084 %this reaction, needed to recover its atom counts from +0085 %the balance below +0086 coeff=eqn(mets(j)); +0087 eqn(mets(j))=0; +0088 if all(exitFlag(eqn~=0)==1) +0089 %This means that all other mets had composition. +0090 %Calculate the resulting composition for the +0091 %unknown one +0092 comp=useMat'*eqn; +0093 +0094 %This can result in round off errors if there are +0095 %stoichiometries with many decimals. Ignore values +0096 %below 10^-12 +0097 comp(abs(comp)<10^-12)=0; +0098 +0099 %Check if the composition consist of both negative +0100 %and positive values. If so, throw an error +0101 if all(comp<=0) || all(comp>=0) +0102 %Normalize by the metabolite's own coefficient, +0103 %so e.g. a coefficient of 2 halves the atom +0104 %counts recovered from the balance +0105 comp=abs(comp)/abs(coeff); +0106 if isempty(currentComp) +0107 currentComp=comp; +0108 end +0109 %Also to deal with round off errors +0110 if all(abs(currentComp-comp)<10^-10) +0111 metStatus=1; +0112 else +0113 metStatus=-2; +0114 break; +0115 +0116 end +0117 else +0118 metStatus=-2; +0119 break; +0120 end +0121 end +0122 end +0123 %If there was contradictions in one compartment, then abort +0124 %for all compartments +0125 if metStatus==-2 +0126 break; +0127 end +0128 else +0129 %The metabolite does not participate, no composition can be +0130 %calculated +0131 metStatus=-1; +0132 end +0133 end +0134 %Check status of the metabolite +0135 switch metStatus +0136 case -2 +0137 EM=['Could not predict composition for "' metNames{i} '" due to inconsistencies']; +0138 warning('RAVEN:warning', '%s', EM); +0139 case 1 +0140 %Calculate and add the composition +0141 str=getCompString(elements,comp); +0142 model.metFormulas(mets)={str}; +0143 if printResults==true +0144 fprintf(['Predicted composition for "' metNames{i} '" to be ' str '\n']); +0145 end +0146 +0147 %Keep track +0148 guessedFor=[guessedFor;metNames(i)]; +0149 +0150 predicted=true; %To loop again +0151 end +0152 end +0153 end +0154 +0155 couldNotGuess=setdiff(originalMissing,guessedFor); +0156 end +0157 +0158 %Helper function for getting the composition string +0159 function str=getCompString(elements,comp) +0160 str=''; +0161 +0162 for i=1:numel(comp) +0163 if comp(i)~=0 +0164 if comp(i)==1 +0165 str=[str elements.abbrevs{i}]; +0166 else +0167 str=[str elements.abbrevs{i} num2str(comp(i))]; +0168 end +0169 end +0170 end +0171 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/reconstruction/homology/getBlast.html b/docs/html/reconstruction/homology/getBlast.html index f919fd9c..37f6b837 100644 --- a/docs/html/reconstruction/homology/getBlast.html +++ b/docs/html/reconstruction/homology/getBlast.html @@ -46,25 +46,28 @@

    DESCRIPTION ^SOURCE CODE ^% Name-Value Arguments 0020 % -------------------- 0021 % developMode : logical -0022 % true if blastReport should be generated that is used in the unit -0023 % testing function for BLAST+ (default false). +0022 % true if blastReport is filled, as used in the unit test for BLAST+ +0023 % (default false). 0024 % hideVerbose : logical 0025 % true if no status messages should be printed (default false). 0026 % 0027 % Returns 0028 % ------- 0029 % blastStructure : struct -0030 % structure containing the bidirectional homology measurements that -0031 % can be used by getModelFromHomology. -0032 % blastReport : struct -0033 % structure containing MD5 hashes for FASTA database files and -0034 % non-parsed BLAST output data. Will be blank if developMode is false. -0035 % -0036 % Notes -0037 % ----- -0038 % This function calls BLAST+ to perform a bidirectional homology test -0039 % between the organism of interest and a set of other organisms using -0040 % standard settings. The only filtering this function does is the removal -0041 % of hits with an E-value higher than 10e-5. -0042 % -0043 % Examples -0044 % -------- -0045 % [blastStructure,blastReport] = getBlast(organismID,fastaFile,... -0046 % modelIDs,refFastaFiles,developMode,hideVerbose); -0047 % -0048 % See also -0049 % -------- -0050 % getModelFromHomology, getDiamond, runSuppOrth -0051 -0052 p=parseRAVENargs(varargin, {'developMode',false; 'hideVerbose',false}); -0053 developMode=p.developMode; -0054 hideVerbose=p.hideVerbose; -0055 -0056 %Everything should be cell arrays -0057 organismID=convertCharArray(organismID); -0058 fastaFile=convertCharArray(fastaFile); -0059 modelIDs=convertCharArray(modelIDs); -0060 refFastaFiles=convertCharArray(refFastaFiles); -0061 -0062 %Create blank structures for results -0063 blastStructure=[]; -0064 blastReport.dbHashes.phr={}; -0065 blastReport.dbHashes.pot={}; -0066 blastReport.dbHashes.psq={}; -0067 blastReport.dbHashes.pto={}; -0068 blastReport.blastTxtOutput={}; -0069 -0070 %Get the directory for RAVEN Toolbox -0071 ravenPath=findRAVENroot(); +0030 % structure array with one element per search direction and template +0031 % organism (2 times the number of modelIDs), containing the bidirectional +0032 % homology measurements (fromId, toId, fromGenes, toGenes, evalue, +0033 % identity, aligLen, bitscore and ppos) for use by getModelFromHomology. +0034 % blastReport : struct +0035 % structure containing MD5 hashes for FASTA database files and +0036 % unparsed BLAST output data. The fields are empty if developMode is +0037 % false. +0038 % +0039 % Notes +0040 % ----- +0041 % This function calls BLAST+ to perform a bidirectional homology test +0042 % between the organism of interest and a set of other organisms using +0043 % standard settings. The only filtering this function applies is the removal +0044 % of hits with an E-value higher than 10e-5. +0045 % +0046 % Examples +0047 % -------- +0048 % [blastStructure,blastReport] = getBlast(organismID,fastaFile,... +0049 % modelIDs,refFastaFiles,developMode,hideVerbose); +0050 % +0051 % See also +0052 % -------- +0053 % getModelFromHomology, getDiamond, runSuppOrth +0054 +0055 p=parseRAVENargs(varargin, {'developMode',false; 'hideVerbose',false}); +0056 developMode=p.developMode; +0057 hideVerbose=p.hideVerbose; +0058 +0059 %Everything should be cell arrays +0060 organismID=convertCharArray(organismID); +0061 fastaFile=convertCharArray(fastaFile); +0062 modelIDs=convertCharArray(modelIDs); +0063 refFastaFiles=convertCharArray(refFastaFiles); +0064 +0065 %Create blank structures for results +0066 blastStructure=[]; +0067 blastReport.dbHashes.phr={}; +0068 blastReport.dbHashes.pot={}; +0069 blastReport.dbHashes.psq={}; +0070 blastReport.dbHashes.pto={}; +0071 blastReport.blastTxtOutput={}; 0072 -0073 %Generate temporary names for BLAST databases and output files -0074 tmpDB=tempname; -0075 outFile=tempname; -0076 if ispc && contains(tmpDB,' ') -0077 warning(['MATLAB assigned ''%s'' as temporary file path, but it '... -0078 'contains a space character, which is not compatible with '... -0079 'BLAST. Instead, a temporary folder will be initiated at '... -0080 '''C:\\tempRAVEN\\'' which should manually be removed when '... -0081 'finished.'],tmpDB) -0082 tmpDB=tempname('C:\tempRAVEN'); -0083 outFile=tempname('C:\tempRAVEN'); -0084 end -0085 -0086 %Check for existence of files. If no full path is specified for a file, -0087 %assume that it is in the current folder -0088 if isrow(refFastaFiles) -0089 files=horzcat(fastaFile,refFastaFiles); -0090 else -0091 files=vertcat(fastaFile,refFastaFiles); -0092 end -0093 -0094 files=checkFileExistence(files,2); %Copy files to temp dir -0095 fastaFile = files(1); -0096 refFastaFiles = files(2:end); -0097 -0098 %Identify the operating system -0099 if ispc -0100 binEnd='.exe'; -0101 setenv('BLASTDB_LMDB_MAP_SIZE','1000000'); -0102 elseif ismac -0103 binEnd='.mac'; -0104 elseif isunix -0105 binEnd=''; -0106 else -0107 error('RAVEN:badInput', '%s', 'Unknown OS, exiting.') -0108 return -0109 end -0110 -0111 %Fetch the BLAST+ binaries on demand if they are not already present -0112 if ~exist(fullfile(ravenPath,'src','external','blast+',['blastp' binEnd]),'file') -0113 downloadRavenBinaries({'blast+'}); -0114 end -0115 -0116 %Run BLAST multi-threaded to use all logical cores assigned to MATLAB -0117 cores = evalc('feature(''numcores'')'); -0118 cores = strsplit(cores, 'MATLAB was assigned: '); -0119 cores = regexp(cores{2},'^\d*','match'); -0120 cores = cores{1}; -0121 -0122 %Create a database for the new organism and blast each of the refFastaFiles -0123 %against it -0124 [status, message]=system(['"' fullfile(ravenPath,'src','external','blast+',['makeblastdb' binEnd]) '" -in "' fastaFile{1} '" -out "' fullfile(tmpDB, 'tmpDB') '" -dbtype prot']); -0125 if developMode -0126 blastReport.dbHashes.phr{numel(blastReport.dbHashes.phr)+1}=filemd5(fullfile(tmpDB, 'tmpDB.phr')); -0127 blastReport.dbHashes.pot{numel(blastReport.dbHashes.pot)+1}=filemd5(fullfile(tmpDB, 'tmpDB.pot')); -0128 blastReport.dbHashes.psq{numel(blastReport.dbHashes.psq)+1}=filemd5(fullfile(tmpDB, 'tmpDB.psq')); -0129 blastReport.dbHashes.pto{numel(blastReport.dbHashes.pto)+1}=filemd5(fullfile(tmpDB, 'tmpDB.pto')); -0130 end -0131 if status~=0 -0132 error('makeblastdb did not run successfully, error:\n%s',strip(message)) +0073 %Get the directory for RAVEN Toolbox +0074 ravenPath=findRAVENroot(); +0075 +0076 %Generate temporary names for BLAST databases and output files +0077 tmpDB=tempname; +0078 outFile=tempname; +0079 if ispc && contains(tmpDB,' ') +0080 warning(['MATLAB assigned ''%s'' as temporary file path, but it '... +0081 'contains a space character, which is not compatible with '... +0082 'BLAST. Instead, a temporary folder will be initiated at '... +0083 '''C:\\tempRAVEN\\'' which should manually be removed when '... +0084 'finished.'],tmpDB) +0085 tmpDB=tempname('C:\tempRAVEN'); +0086 outFile=tempname('C:\tempRAVEN'); +0087 end +0088 +0089 %Check for existence of files. If no full path is specified for a file, +0090 %assume that it is in the current folder +0091 if isrow(refFastaFiles) +0092 files=horzcat(fastaFile,refFastaFiles); +0093 else +0094 files=vertcat(fastaFile,refFastaFiles); +0095 end +0096 +0097 files=checkFileExistence(files,2); %Copy files to temp dir +0098 fastaFile = files(1); +0099 refFastaFiles = files(2:end); +0100 +0101 %Identify the operating system +0102 if ispc +0103 binEnd='.exe'; +0104 setenv('BLASTDB_LMDB_MAP_SIZE','1000000'); +0105 elseif ismac +0106 binEnd='.mac'; +0107 elseif isunix +0108 binEnd=''; +0109 else +0110 error('RAVEN:badInput', '%s', 'Unknown OS, exiting.') +0111 return +0112 end +0113 +0114 %Fetch the BLAST+ binaries on demand if they are not already present +0115 if ~exist(fullfile(ravenPath,'src','external','blast+',['blastp' binEnd]),'file') +0116 downloadRavenBinaries({'blast+'}); +0117 end +0118 +0119 %Run BLAST multi-threaded to use all logical cores assigned to MATLAB +0120 cores = evalc('feature(''numcores'')'); +0121 cores = strsplit(cores, 'MATLAB was assigned: '); +0122 cores = regexp(cores{2},'^\d*','match'); +0123 cores = cores{1}; +0124 +0125 %Create a database for the new organism and blast each of the refFastaFiles +0126 %against it +0127 [status, message]=system(['"' fullfile(ravenPath,'src','external','blast+',['makeblastdb' binEnd]) '" -in "' fastaFile{1} '" -out "' fullfile(tmpDB, 'tmpDB') '" -dbtype prot']); +0128 if developMode +0129 blastReport.dbHashes.phr{numel(blastReport.dbHashes.phr)+1}=filemd5(fullfile(tmpDB, 'tmpDB.phr')); +0130 blastReport.dbHashes.pot{numel(blastReport.dbHashes.pot)+1}=filemd5(fullfile(tmpDB, 'tmpDB.pot')); +0131 blastReport.dbHashes.psq{numel(blastReport.dbHashes.psq)+1}=filemd5(fullfile(tmpDB, 'tmpDB.psq')); +0132 blastReport.dbHashes.pto{numel(blastReport.dbHashes.pto)+1}=filemd5(fullfile(tmpDB, 'tmpDB.pto')); 0133 end -0134 -0135 for i=1:numel(refFastaFiles) -0136 if ~hideVerbose -0137 fprintf(['BLASTing "' modelIDs{i} '" against "' organismID{1} '"..\n']); -0138 end -0139 [status, message]=system(['"' fullfile(ravenPath,'src','external','blast+',['blastp' binEnd]) '" -query "' refFastaFiles{i} '" -out "' outFile '_' num2str(i) '" -db "' fullfile(tmpDB, 'tmpDB') '" -evalue 10e-5 -outfmt "10 qseqid sseqid evalue pident length bitscore ppos" -num_threads "' cores '"']); -0140 if developMode -0141 blastReport.blastTxtOutput{numel(blastReport.blastTxtOutput)+1}=importdata([outFile '_' num2str(i)]); -0142 end -0143 if status~=0 -0144 error('blastp did not run successfully, error:\n%s',strip(message)) +0134 if status~=0 +0135 error('makeblastdb did not run successfully, error:\n%s',strip(message)) +0136 end +0137 +0138 for i=1:numel(refFastaFiles) +0139 if ~hideVerbose +0140 fprintf(['BLASTing "' modelIDs{i} '" against "' organismID{1} '"..\n']); +0141 end +0142 [status, message]=system(['"' fullfile(ravenPath,'src','external','blast+',['blastp' binEnd]) '" -query "' refFastaFiles{i} '" -out "' outFile '_' num2str(i) '" -db "' fullfile(tmpDB, 'tmpDB') '" -evalue 10e-5 -outfmt "10 qseqid sseqid evalue pident length bitscore ppos" -num_threads "' cores '"']); +0143 if developMode +0144 blastReport.blastTxtOutput{numel(blastReport.blastTxtOutput)+1}=importdata([outFile '_' num2str(i)]); 0145 end -0146 end -0147 delete([tmpDB filesep 'tmpDB*']); -0148 -0149 %Then create a database for each of the reference organisms and blast the -0150 %new organism against them -0151 for i=1:numel(refFastaFiles) -0152 if ~hideVerbose -0153 fprintf(['BLASTing "' organismID{1} '" against "' modelIDs{i} '"..\n']); -0154 end -0155 [status, message]=system(['"' fullfile(ravenPath,'src','external','blast+',['makeblastdb' binEnd]) '" -in "' refFastaFiles{i} '" -out "' fullfile(tmpDB, 'tmpDB') '" -dbtype prot']); -0156 if status~=0 -0157 error('makeblastdb did not run successfully, error:\n%s',strip(message)) -0158 end -0159 [status, message]=system(['"' fullfile(ravenPath,'src','external','blast+',['blastp' binEnd]) '" -query "' fastaFile{1} '" -out "' outFile '_r' num2str(i) '" -db "' fullfile(tmpDB, 'tmpDB') '" -evalue 10e-5 -outfmt "10 qseqid sseqid evalue pident length bitscore ppos" -num_threads "' cores '"']); -0160 if developMode -0161 blastReport.dbHashes.phr{numel(blastReport.dbHashes.phr)+1}=filemd5(fullfile(tmpDB, 'tmpDB.phr')); -0162 blastReport.dbHashes.pot{numel(blastReport.dbHashes.pot)+1}=filemd5(fullfile(tmpDB, 'tmpDB.pot')); -0163 blastReport.dbHashes.psq{numel(blastReport.dbHashes.psq)+1}=filemd5(fullfile(tmpDB, 'tmpDB.psq')); -0164 blastReport.dbHashes.pto{numel(blastReport.dbHashes.pto)+1}=filemd5(fullfile(tmpDB, 'tmpDB.pto')); -0165 blastReport.blastTxtOutput{numel(blastReport.blastTxtOutput)+1}=importdata([outFile '_r' num2str(i)]); -0166 end -0167 if status~=0 -0168 error('blastp did not run successfully, error:\n%s',strip(message)) -0169 end -0170 delete([tmpDB filesep 'tmpDB*']); -0171 end -0172 -0173 %Done with the BLAST, do the parsing of the text files -0174 for i=1:numel(refFastaFiles)*2 -0175 tempStruct=[]; -0176 if i<=numel(refFastaFiles) -0177 tempStruct.fromId=modelIDs{i}; -0178 tempStruct.toId=organismID{1}; -0179 A=readtable([outFile '_' num2str(i)],'Delimiter',',','Format','%s%s%f%f%f%f%f'); -0180 else -0181 tempStruct.fromId=organismID{1}; -0182 tempStruct.toId=modelIDs{i-numel(refFastaFiles)}; -0183 A=readtable([outFile '_r' num2str(i-numel(refFastaFiles))],'Delimiter',',','Format','%s%s%f%f%f%f%f'); -0184 end -0185 tempStruct.fromGenes=A{:,1}; -0186 tempStruct.toGenes=A{:,2}; -0187 tempStruct.evalue=table2array(A(:,3)); -0188 tempStruct.identity=table2array(A(:,4)); -0189 tempStruct.aligLen=table2array(A(:,5)); -0190 tempStruct.bitscore=table2array(A(:,6)); -0191 tempStruct.ppos=table2array(A(:,7)); -0192 blastStructure=[blastStructure tempStruct]; -0193 end -0194 -0195 %Remove the old tempfiles -0196 delete([outFile '*']); -0197 %Remove the temp fasta files -0198 delete(files{:}) -0199 end -0200 -0201 function hash = filemd5(path) -0202 %Compute MD5 of a file via the Java MessageDigest API (no toolbox required). -0203 md = java.security.MessageDigest.getInstance('MD5'); -0204 fis = java.io.FileInputStream(java.io.File(path)); -0205 buf = javaArray('byte', 65536); -0206 while true -0207 n = fis.read(buf); -0208 if n < 0; break; end -0209 md.update(buf, 0, n); -0210 end -0211 fis.close(); -0212 bytes = typecast(md.digest(), 'uint8'); -0213 hash = lower(sprintf('%02x', bytes)); -0214 end +0146 if status~=0 +0147 error('blastp did not run successfully, error:\n%s',strip(message)) +0148 end +0149 end +0150 delete([tmpDB filesep 'tmpDB*']); +0151 +0152 %Then create a database for each of the reference organisms and blast the +0153 %new organism against them +0154 for i=1:numel(refFastaFiles) +0155 if ~hideVerbose +0156 fprintf(['BLASTing "' organismID{1} '" against "' modelIDs{i} '"..\n']); +0157 end +0158 [status, message]=system(['"' fullfile(ravenPath,'src','external','blast+',['makeblastdb' binEnd]) '" -in "' refFastaFiles{i} '" -out "' fullfile(tmpDB, 'tmpDB') '" -dbtype prot']); +0159 if status~=0 +0160 error('makeblastdb did not run successfully, error:\n%s',strip(message)) +0161 end +0162 [status, message]=system(['"' fullfile(ravenPath,'src','external','blast+',['blastp' binEnd]) '" -query "' fastaFile{1} '" -out "' outFile '_r' num2str(i) '" -db "' fullfile(tmpDB, 'tmpDB') '" -evalue 10e-5 -outfmt "10 qseqid sseqid evalue pident length bitscore ppos" -num_threads "' cores '"']); +0163 if developMode +0164 blastReport.dbHashes.phr{numel(blastReport.dbHashes.phr)+1}=filemd5(fullfile(tmpDB, 'tmpDB.phr')); +0165 blastReport.dbHashes.pot{numel(blastReport.dbHashes.pot)+1}=filemd5(fullfile(tmpDB, 'tmpDB.pot')); +0166 blastReport.dbHashes.psq{numel(blastReport.dbHashes.psq)+1}=filemd5(fullfile(tmpDB, 'tmpDB.psq')); +0167 blastReport.dbHashes.pto{numel(blastReport.dbHashes.pto)+1}=filemd5(fullfile(tmpDB, 'tmpDB.pto')); +0168 blastReport.blastTxtOutput{numel(blastReport.blastTxtOutput)+1}=importdata([outFile '_r' num2str(i)]); +0169 end +0170 if status~=0 +0171 error('blastp did not run successfully, error:\n%s',strip(message)) +0172 end +0173 delete([tmpDB filesep 'tmpDB*']); +0174 end +0175 +0176 %Done with the BLAST, do the parsing of the text files +0177 for i=1:numel(refFastaFiles)*2 +0178 tempStruct=[]; +0179 if i<=numel(refFastaFiles) +0180 tempStruct.fromId=modelIDs{i}; +0181 tempStruct.toId=organismID{1}; +0182 A=readtable([outFile '_' num2str(i)],'Delimiter',',','Format','%s%s%f%f%f%f%f'); +0183 else +0184 tempStruct.fromId=organismID{1}; +0185 tempStruct.toId=modelIDs{i-numel(refFastaFiles)}; +0186 A=readtable([outFile '_r' num2str(i-numel(refFastaFiles))],'Delimiter',',','Format','%s%s%f%f%f%f%f'); +0187 end +0188 tempStruct.fromGenes=A{:,1}; +0189 tempStruct.toGenes=A{:,2}; +0190 tempStruct.evalue=table2array(A(:,3)); +0191 tempStruct.identity=table2array(A(:,4)); +0192 tempStruct.aligLen=table2array(A(:,5)); +0193 tempStruct.bitscore=table2array(A(:,6)); +0194 tempStruct.ppos=table2array(A(:,7)); +0195 blastStructure=[blastStructure tempStruct]; +0196 end +0197 +0198 %Remove the old tempfiles +0199 delete([outFile '*']); +0200 %Remove the temp fasta files +0201 delete(files{:}) +0202 end +0203 +0204 function hash = filemd5(path) +0205 %Compute MD5 of a file via the Java MessageDigest API (no toolbox required). +0206 md = java.security.MessageDigest.getInstance('MD5'); +0207 fis = java.io.FileInputStream(java.io.File(path)); +0208 buf = javaArray('byte', 65536); +0209 while true +0210 n = fis.read(buf); +0211 if n < 0; break; end +0212 md.update(buf, 0, n); +0213 end +0214 fis.close(); +0215 bytes = typecast(md.digest(), 'uint8'); +0216 hash = lower(sprintf('%02x', bytes)); +0217 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/reconstruction/homology/getDiamond.html b/docs/html/reconstruction/homology/getDiamond.html index c186ab0e..a4a0c58b 100644 --- a/docs/html/reconstruction/homology/getDiamond.html +++ b/docs/html/reconstruction/homology/getDiamond.html @@ -46,28 +46,30 @@

    DESCRIPTION ^SOURCE CODE ^% Name-Value Arguments 0020 % -------------------- 0021 % developMode : logical -0022 % true if blastReport should be generated that is used in the unit -0023 % testing function for DIAMOND (default false). +0022 % true if diamondReport is filled, as used in the unit test for DIAMOND +0023 % (default false). 0024 % hideVerbose : logical 0025 % true if no status messages should be printed (default false). 0026 % 0027 % Returns 0028 % ------- 0029 % blastStructure : struct -0030 % structure containing the bidirectional homology measurements which -0031 % are used by getModelFromHomology. -0032 % diamondReport : struct -0033 % structure containing MD5 hashes for FASTA database files and -0034 % non-parsed BLAST output data. Will be blank if developMode is false. -0035 % -0036 % Notes -0037 % ----- -0038 % This function calls DIAMOND to perform a bidirectional homology search -0039 % between the organism of interest and a set of other organisms using the -0040 % "--more-sensitive" setting from DIAMOND. For the most sensitive results, -0041 % the use of getBlast() is adviced, however, getDiamond() is a fast -0042 % alternative (>15x faster). The blastStructure generated is in the same -0043 % format as those obtained from getBlast(). -0044 % -0045 % Examples -0046 % -------- -0047 % [blastStructure,diamondReport] = getDiamond(organismID,fastaFile,... -0048 % modelIDs,refFastaFiles,developMode,hideVerbose); -0049 % -0050 % See also -0051 % -------- -0052 % getModelFromHomology, getBlast, runSuppOrth -0053 -0054 p=parseRAVENargs(varargin, {'developMode',false; 'hideVerbose',false}); -0055 developMode=p.developMode; -0056 hideVerbose=p.hideVerbose; -0057 -0058 %Everything should be cell arrays -0059 organismID=convertCharArray(organismID); -0060 fastaFile=convertCharArray(fastaFile); -0061 modelIDs=convertCharArray(modelIDs); -0062 refFastaFiles=convertCharArray(refFastaFiles); -0063 -0064 %Create blank structures for results -0065 blastStructure=[]; -0066 diamondReport.dbHashes={}; -0067 diamondReport.diamondTxtOutput={}; -0068 -0069 %Get the directory for RAVEN Toolbox. -0070 ravenPath=findRAVENroot(); -0071 -0072 %Generate temporary names for DIAMOND databases and output files -0073 tmpDB=tempname; -0074 outFile=tempname; -0075 -0076 %Check for existence of files. If no full path is specified for a file, -0077 %assume that it is in the current folder -0078 if isrow(refFastaFiles) -0079 files=horzcat(fastaFile,refFastaFiles); -0080 else -0081 files=vertcat(fastaFile,refFastaFiles); -0082 end -0083 -0084 files=checkFileExistence(files,2); %Copy files to temp dir -0085 fastaFile = files(1); -0086 refFastaFiles = files(2:end); -0087 -0088 %Identify the operating system -0089 if ispc -0090 binEnd='.exe'; -0091 elseif ismac -0092 binEnd='.mac'; -0093 elseif isunix -0094 binEnd=''; -0095 else -0096 error('RAVEN:badInput', '%s', 'Unknown OS, exiting.') -0097 return -0098 end -0099 -0100 %Fetch the DIAMOND binary on demand if it is not already present -0101 if ~exist(fullfile(ravenPath,'src','external','diamond',['diamond' binEnd]),'file') -0102 downloadRavenBinaries({'diamond'}); -0103 end -0104 -0105 %Run DIAMOND multi-threaded to use all logical cores assigned to MATLAB. -0106 cores = evalc('feature(''numcores'')'); -0107 cores = strsplit(cores, 'MATLAB was assigned: '); -0108 cores = regexp(cores{2},'^\d*','match'); -0109 cores = cores{1}; -0110 -0111 %Create a database for the new organism and blast each of the refFastaFiles -0112 %against it -0113 [status, message]=system(['"' fullfile(ravenPath,'src','external','diamond',['diamond' binEnd]) '" makedb --in "' fastaFile{1} '" --db "' fullfile(tmpDB) '"']); -0114 if developMode -0115 diamondReport.dbHashes{numel(diamondReport.dbHashes)+1} = char(regexp(message,'[a-f0-9]{32}','match')); -0116 end -0117 if status~=0 -0118 error('DIAMOND makedb did not run successfully, error:\n%s',strip(message)) -0119 end -0120 -0121 for i=1:numel(refFastaFiles) -0122 if ~hideVerbose -0123 fprintf(['Running DIAMOND blastp with "' modelIDs{i} '" against "' organismID{1} '"..\n']); -0124 end -0125 [status, message]=system(['"' fullfile(ravenPath,'src','external','diamond',['diamond' binEnd]) '" blastp --query "' refFastaFiles{i} '" --out "' outFile '_' num2str(i) '" --db "' fullfile(tmpDB) '" --more-sensitive --outfmt 6 qseqid sseqid evalue pident length bitscore ppos --threads ' cores ]); -0126 if developMode -0127 diamondReport.diamondTxtOutput{numel(diamondReport.diamondTxtOutput)+1}=importdata([outFile '_' num2str(i)]); -0128 end -0129 if status~=0 -0130 error('DIAMOND blastp did not run successfully, error:\n%s',strip(message)) -0131 end -0132 end -0133 delete([tmpDB filesep 'tmpDB*']); -0134 -0135 %Then create a database for each of the reference organisms and blast the -0136 %new organism against them -0137 for i=1:numel(refFastaFiles) -0138 if ~hideVerbose -0139 fprintf(['Running DIAMOND blastp with "' organismID{1} '" against "' modelIDs{i} '"..\n']); -0140 end -0141 [status, message1]=system(['"' fullfile(ravenPath,'src','external','diamond',['diamond' binEnd]) '" makedb --in "' refFastaFiles{i} '" --db "' fullfile(tmpDB) '"']); -0142 if status~=0 -0143 error('DIAMOND makedb did not run successfully, error:\n%s',strip(message1)) -0144 end -0145 [status, message]=system(['"' fullfile(ravenPath,'src','external','diamond',['diamond' binEnd]) '" blastp --query "' fastaFile{1} '" --out "' outFile '_r' num2str(i) '" --db "' fullfile(tmpDB) '" --more-sensitive --outfmt 6 qseqid sseqid evalue pident length bitscore ppos --threads ' cores]); -0146 if developMode -0147 diamondReport.dbHashes{numel(diamondReport.dbHashes)+1} = char(regexp(message1,'[a-f0-9]{32}','match')); -0148 diamondReport.diamondTxtOutput{numel(diamondReport.diamondTxtOutput)+1}=importdata([outFile '_r' num2str(i)]); -0149 end -0150 if status~=0 -0151 error('DIAMOND blastp did not run successfully, error:\n%s',strip(message)) -0152 end -0153 delete([tmpDB filesep 'tmpDB*']); -0154 end -0155 -0156 %Done with the DIAMOND blastp, do the parsing of the text files -0157 for i=1:numel(refFastaFiles)*2 -0158 tempStruct=[]; -0159 if i<=numel(refFastaFiles) -0160 tempStruct.fromId=modelIDs{i}; -0161 tempStruct.toId=organismID{1}; -0162 A=readtable([outFile '_' num2str(i)],'Delimiter','\t','Format','%s%s%f%f%f%f%f'); -0163 else -0164 tempStruct.fromId=organismID{1}; -0165 tempStruct.toId=modelIDs{i-numel(refFastaFiles)}; -0166 A=readtable([outFile '_r' num2str(i-numel(refFastaFiles))],'Delimiter','\t','Format','%s%s%f%f%f%f%f'); -0167 end -0168 tempStruct.fromGenes=A{:,1}; -0169 tempStruct.toGenes=A{:,2}; -0170 tempStruct.evalue=table2array(A(:,3)); -0171 tempStruct.identity=table2array(A(:,4)); -0172 tempStruct.aligLen=table2array(A(:,5)); -0173 tempStruct.bitscore=table2array(A(:,6)); -0174 tempStruct.ppos=table2array(A(:,7)); -0175 blastStructure=[blastStructure tempStruct]; -0176 end -0177 -0178 %Remove the old tempfiles -0179 delete([outFile '*']); -0180 %Remove the temp fasta files -0181 delete(files{:}) -0182 end +0030 % structure array with one element per search direction and template +0031 % organism (2 times the number of modelIDs), containing the bidirectional +0032 % homology measurements (fromId, toId, fromGenes, toGenes, evalue, +0033 % identity, aligLen, bitscore and ppos) for use by getModelFromHomology. +0034 % diamondReport : struct +0035 % structure containing MD5 hashes for FASTA database files and +0036 % unparsed DIAMOND output data. The fields are empty if developMode is +0037 % false. +0038 % +0039 % Notes +0040 % ----- +0041 % This function calls DIAMOND to perform a bidirectional homology search +0042 % between the organism of interest and a set of other organisms using the +0043 % "--more-sensitive" setting from DIAMOND. getBlast() gives the most +0044 % sensitive results; getDiamond() is >15x faster. The blastStructure is in +0045 % the same format as that obtained from getBlast(). +0046 % +0047 % Examples +0048 % -------- +0049 % [blastStructure,diamondReport] = getDiamond(organismID,fastaFile,... +0050 % modelIDs,refFastaFiles,developMode,hideVerbose); +0051 % +0052 % See also +0053 % -------- +0054 % getModelFromHomology, getBlast, runSuppOrth +0055 +0056 p=parseRAVENargs(varargin, {'developMode',false; 'hideVerbose',false}); +0057 developMode=p.developMode; +0058 hideVerbose=p.hideVerbose; +0059 +0060 %Everything should be cell arrays +0061 organismID=convertCharArray(organismID); +0062 fastaFile=convertCharArray(fastaFile); +0063 modelIDs=convertCharArray(modelIDs); +0064 refFastaFiles=convertCharArray(refFastaFiles); +0065 +0066 %Create blank structures for results +0067 blastStructure=[]; +0068 diamondReport.dbHashes={}; +0069 diamondReport.diamondTxtOutput={}; +0070 +0071 %Get the directory for RAVEN Toolbox. +0072 ravenPath=findRAVENroot(); +0073 +0074 %Generate temporary names for DIAMOND databases and output files +0075 tmpDB=tempname; +0076 outFile=tempname; +0077 +0078 %Check for existence of files. If no full path is specified for a file, +0079 %assume that it is in the current folder +0080 if isrow(refFastaFiles) +0081 files=horzcat(fastaFile,refFastaFiles); +0082 else +0083 files=vertcat(fastaFile,refFastaFiles); +0084 end +0085 +0086 files=checkFileExistence(files,2); %Copy files to temp dir +0087 fastaFile = files(1); +0088 refFastaFiles = files(2:end); +0089 +0090 %Identify the operating system +0091 if ispc +0092 binEnd='.exe'; +0093 elseif ismac +0094 binEnd='.mac'; +0095 elseif isunix +0096 binEnd=''; +0097 else +0098 error('RAVEN:badInput', '%s', 'Unknown OS, exiting.') +0099 return +0100 end +0101 +0102 %Fetch the DIAMOND binary on demand if it is not already present +0103 if ~exist(fullfile(ravenPath,'src','external','diamond',['diamond' binEnd]),'file') +0104 downloadRavenBinaries({'diamond'}); +0105 end +0106 +0107 %Run DIAMOND multi-threaded to use all logical cores assigned to MATLAB. +0108 cores = evalc('feature(''numcores'')'); +0109 cores = strsplit(cores, 'MATLAB was assigned: '); +0110 cores = regexp(cores{2},'^\d*','match'); +0111 cores = cores{1}; +0112 +0113 %Create a database for the new organism and blast each of the refFastaFiles +0114 %against it +0115 [status, message]=system(['"' fullfile(ravenPath,'src','external','diamond',['diamond' binEnd]) '" makedb --in "' fastaFile{1} '" --db "' fullfile(tmpDB) '"']); +0116 if developMode +0117 diamondReport.dbHashes{numel(diamondReport.dbHashes)+1} = char(regexp(message,'[a-f0-9]{32}','match')); +0118 end +0119 if status~=0 +0120 error('DIAMOND makedb did not run successfully, error:\n%s',strip(message)) +0121 end +0122 +0123 for i=1:numel(refFastaFiles) +0124 if ~hideVerbose +0125 fprintf(['Running DIAMOND blastp with "' modelIDs{i} '" against "' organismID{1} '"..\n']); +0126 end +0127 [status, message]=system(['"' fullfile(ravenPath,'src','external','diamond',['diamond' binEnd]) '" blastp --query "' refFastaFiles{i} '" --out "' outFile '_' num2str(i) '" --db "' fullfile(tmpDB) '" --more-sensitive --outfmt 6 qseqid sseqid evalue pident length bitscore ppos --threads ' cores ]); +0128 if developMode +0129 diamondReport.diamondTxtOutput{numel(diamondReport.diamondTxtOutput)+1}=importdata([outFile '_' num2str(i)]); +0130 end +0131 if status~=0 +0132 error('DIAMOND blastp did not run successfully, error:\n%s',strip(message)) +0133 end +0134 end +0135 delete([tmpDB filesep 'tmpDB*']); +0136 +0137 %Then create a database for each of the reference organisms and blast the +0138 %new organism against them +0139 for i=1:numel(refFastaFiles) +0140 if ~hideVerbose +0141 fprintf(['Running DIAMOND blastp with "' organismID{1} '" against "' modelIDs{i} '"..\n']); +0142 end +0143 [status, message1]=system(['"' fullfile(ravenPath,'src','external','diamond',['diamond' binEnd]) '" makedb --in "' refFastaFiles{i} '" --db "' fullfile(tmpDB) '"']); +0144 if status~=0 +0145 error('DIAMOND makedb did not run successfully, error:\n%s',strip(message1)) +0146 end +0147 [status, message]=system(['"' fullfile(ravenPath,'src','external','diamond',['diamond' binEnd]) '" blastp --query "' fastaFile{1} '" --out "' outFile '_r' num2str(i) '" --db "' fullfile(tmpDB) '" --more-sensitive --outfmt 6 qseqid sseqid evalue pident length bitscore ppos --threads ' cores]); +0148 if developMode +0149 diamondReport.dbHashes{numel(diamondReport.dbHashes)+1} = char(regexp(message1,'[a-f0-9]{32}','match')); +0150 diamondReport.diamondTxtOutput{numel(diamondReport.diamondTxtOutput)+1}=importdata([outFile '_r' num2str(i)]); +0151 end +0152 if status~=0 +0153 error('DIAMOND blastp did not run successfully, error:\n%s',strip(message)) +0154 end +0155 delete([tmpDB filesep 'tmpDB*']); +0156 end +0157 +0158 %Done with the DIAMOND blastp, do the parsing of the text files +0159 for i=1:numel(refFastaFiles)*2 +0160 tempStruct=[]; +0161 if i<=numel(refFastaFiles) +0162 tempStruct.fromId=modelIDs{i}; +0163 tempStruct.toId=organismID{1}; +0164 A=readtable([outFile '_' num2str(i)],'Delimiter','\t','Format','%s%s%f%f%f%f%f'); +0165 else +0166 tempStruct.fromId=organismID{1}; +0167 tempStruct.toId=modelIDs{i-numel(refFastaFiles)}; +0168 A=readtable([outFile '_r' num2str(i-numel(refFastaFiles))],'Delimiter','\t','Format','%s%s%f%f%f%f%f'); +0169 end +0170 tempStruct.fromGenes=A{:,1}; +0171 tempStruct.toGenes=A{:,2}; +0172 tempStruct.evalue=table2array(A(:,3)); +0173 tempStruct.identity=table2array(A(:,4)); +0174 tempStruct.aligLen=table2array(A(:,5)); +0175 tempStruct.bitscore=table2array(A(:,6)); +0176 tempStruct.ppos=table2array(A(:,7)); +0177 blastStructure=[blastStructure tempStruct]; +0178 end +0179 +0180 %Remove the old tempfiles +0181 delete([outFile '*']); +0182 %Remove the temp fasta files +0183 delete(files{:}) +0184 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/reconstruction/homology/getModelFromHomology.html b/docs/html/reconstruction/homology/getModelFromHomology.html index bc19f0a5..c7a40a77 100644 --- a/docs/html/reconstruction/homology/getModelFromHomology.html +++ b/docs/html/reconstruction/homology/getModelFromHomology.html @@ -48,28 +48,30 @@

    DESCRIPTION ^DESCRIPTION ^DESCRIPTION ^DESCRIPTION ^SOURCE CODE ^% Name-Value Arguments 0022 % -------------------- 0023 % preferredOrder : cell -0024 % the order in which reactions should be added from the models. If not -0025 % supplied, reactions will be included from all models, otherwise one -0026 % gene will only result in reactions from one model (default {}). -0027 % strictness : double -0028 % integer that specifies which reactions should be included (default 1): -0029 % -0030 % - 1 : map new genes to old for all pairs which have acceptable BLASTP -0031 % results in both directions. -0032 % - 2 : map new genes to old for all pairs which have acceptable BLASTP -0033 % results in the correspondent direction (mapping can be done in the -0034 % opposite direction, see mapNewGenesToOld below). -0035 % - 3 : check all BLASTP results and retain only the best hit (by -0036 % bitscore) for each gene pair in each direction separately, then map -0037 % new genes to old for all pairs which have acceptable BLASTP results -0038 % in both directions. +0024 % the order in which reactions are added from the models, as a cell array +0025 % of model IDs. If not supplied, reactions are included from all models, +0026 % otherwise one gene results in reactions from one model only +0027 % (default {}). +0028 % strictness : double +0029 % integer that specifies which reactions are included (default 1): +0030 % +0031 % - 1 : map new genes to old for all pairs which have BLASTP results +0032 % that pass the filters (maxE, minLen, minIde) in both directions. +0033 % - 2 : map new genes to old for all pairs which have BLASTP results +0034 % that pass the filters in one direction, selected by mapNewGenesToOld. +0035 % - 3 : apply the filters (maxE, minLen, minIde), retain only the best hit +0036 % (highest bitscore, ties included) among the hits that passed them for +0037 % each query gene in each direction separately, then map new genes to +0038 % old for all pairs which have BLASTP results in both directions. 0039 % onlyGenesInModels : logical 0040 % consider BLASTP results only for genes that exist in the models. This 0041 % tends to import a larger fraction from the existing models but may -0042 % give less reliable results. Has effect only if strictness=3 +0042 % give less reliable results. Changes the result only if strictness=3 0043 % (default false). 0044 % maxE : double -0045 % only look at genes with E-values <= this value (default 10^-30). -0046 % minLen : double -0047 % only look at genes with alignment length >= this value (default 100). -0048 % Measured against KEGG and OMA orthology across four organisms: any -0049 % value at or below 150 performs the same, while higher values discard -0050 % real orthologs without reducing the number of wrong ones. -0051 % minIde : double -0052 % only look at genes with identity >= this value (default 40 (%)). -0053 % The filter that decides the outcome, and 40 is the measured optimum -0054 % once a wrongly transferred reaction is treated as worse than a missing -0055 % one. maxE, by contrast, changes nothing between 10^-4 and 10^-50. -0056 % mapNewGenesToOld : logical -0057 % determines how to match genes if not looking at only 1-1 orthologs. -0058 % Either map the new genes to the old or old genes to new. The default -0059 % is to map the new genes (default true). -0060 % complexPolicy : char -0061 % what happens to an "and"-linked subunit without an ortholog: -0062 % "flag" keeps the reaction with the subunit renamed -0063 % OLD_MODELID_geneName, for a curator to review; "keep" drops just that -0064 % subunit; "drop" drops the whole reaction (default "flag"). -0065 % keepGeneFree : logical -0066 % also transfer the template reactions that have no grRule (spontaneous, -0067 % transport, exchange, artificial). By default they are dropped, because -0068 % nothing supports them in the new organism. Set it when the template is -0069 % a curated reference whose gene-free reactions belong in every derived -0070 % model. With several templates, a gene-free reaction comes in once per -0071 % template (default false). -0072 % preserveNotes : logical -0073 % keep each transferred reaction's rxnNotes, rxnReferences and -0074 % rxnConfidenceScores from the template. By default the notes say the -0075 % reaction was included by getModelFromHomology and the confidence score -0076 % is 2 (default false). -0077 % -0078 % Returns -0079 % ------- -0080 % draftModel : struct -0081 % a model structure for the new organism. -0082 % hitGenes : struct -0083 % collect the old and new genes. -0084 % -0085 % Examples -0086 % -------- -0087 % draftModel = getModelFromHomology(models, blastStructure, getModelFor); -0088 % -0089 % Notes -0090 % ----- -0091 % The models in the "models" structure should have named the metabolites in -0092 % the same manner, have their reversible reactions in the same direction -0093 % (run sortModel), and use the same compartment names. To avoid keeping -0094 % unneccesary old genes, the models should not have "or"-relations in their -0095 % grRules (use expandModel). -0096 % -0097 % The gene ids of the new organism in blastStructure become its gene ids in -0098 % the draft model, so they cannot contain whitespace, parentheses, "|" or -0099 % "&". FASTA headers from UniProt, such as "sp|P33284|HXK_KLULA", should be -0100 % reduced to the accession first. -0101 % -0102 % The resulting draft model contains only reactions associated with -0103 % orthologous genes, plus the gene-free ones with keepGeneFree. With -0104 % complexPolicy "flag", the old (original) genes involved in "and" relations -0105 % in grRules without any orthologs are still included in the draft model as -0106 % OLD_MODELID_geneName. A gene that several template genes map to appears -0107 % once in a grRule. The draft does not keep the version, date or -0108 % descriptive annotation of the templates. -0109 % -0110 % "to" and "from" means relative to the new organism. +0045 % only look at genes with E-values < this value (default 10^-30). In a +0046 % benchmark against KEGG and OMA orthology, with the minIde filter in +0047 % place, values between 10^-4 and 10^-50 gave the same result. +0048 % minLen : double +0049 % only look at genes with alignment length >= this value (default 100). +0050 % Measured against KEGG and OMA orthology across four organisms: any +0051 % value at or below 150 performs the same, while higher values discard +0052 % real orthologs without reducing the number of wrong ones. +0053 % minIde : double +0054 % only look at genes with identity >= this value (default 40 (%)). +0055 % This filter determines the result. In the calibration, with a wrongly +0056 % transferred reaction counted as worse than a missing one, 40 lies in the +0057 % 35 to 45 range that the KEGG and OMA references favour. +0058 % mapNewGenesToOld : logical +0059 % determines how to match genes when strictness is 2: true maps the new +0060 % genes to the old, false maps the old genes to the new (default true). +0061 % complexPolicy : char +0062 % what happens to an "and"-linked subunit without an ortholog (case +0063 % insensitive): "flag" keeps the reaction with the subunit renamed +0064 % OLD_MODELID_geneName, for review; "keep" drops that subunit; +0065 % "drop" drops the whole reaction (default "flag"). +0066 % keepGeneFree : logical +0067 % also transfer the template reactions that have no grRule (spontaneous, +0068 % transport, exchange, artificial). By default they are dropped, because +0069 % no gene supports them in the new organism. When the template is a +0070 % curated reference whose gene-free reactions belong in every derived +0071 % model, true transfers them. With several templates, a gene-free +0072 % reaction comes in once per template (default false). +0073 % preserveNotes : logical +0074 % keep rxnNotes and rxnConfidenceScores of each transferred reaction from +0075 % the template. By default the notes say the reaction was included by +0076 % getModelFromHomology and the confidence score is 2. The rxnReferences of +0077 % the template reactions are always kept (default false). +0078 % +0079 % Returns +0080 % ------- +0081 % draftModel : struct +0082 % a model structure for the new organism. +0083 % hitGenes : struct +0084 % structure with the fields oldGenes (the template genes that were +0085 % mapped) and newGenes (the corresponding genes of the new organism). +0086 % +0087 % Examples +0088 % -------- +0089 % draftModel = getModelFromHomology(models, blastStructure, getModelFor); +0090 % +0091 % Notes +0092 % ----- +0093 % The models in the "models" structure should name the metabolites in the +0094 % same manner, have their reversible reactions in the same direction (run +0095 % sortModel), and use the same compartment names. To avoid keeping +0096 % unnecessary old genes, the models should not have "or"-relations in their +0097 % grRules (use expandModel). +0098 % +0099 % The gene ids of the new organism in blastStructure become its gene ids in +0100 % the draft model, so they cannot contain whitespace, parentheses, "|" or +0101 % "&". FASTA headers from UniProt, such as "sp|P33284|HXK_KLULA", should be +0102 % reduced to the accession first. +0103 % +0104 % The resulting draft model contains only reactions associated with +0105 % orthologous genes, plus the gene-free ones with keepGeneFree. With +0106 % complexPolicy "flag", the old (original) genes involved in "and" relations +0107 % in grRules without any orthologs are included in the draft model as +0108 % OLD_MODELID_geneName. A gene that several template genes map to appears +0109 % once in a grRule. The draft does not keep the version, date or +0110 % descriptive annotation of the templates. 0111 % -0112 % The hits can also come from SuppOrth consensus orthologs (hitsFromSuppOrth, -0113 % runSuppOrth). Their e-value, identity and alignment length are -0114 % placeholders that pass the filters, so minSupport and the other filters of -0115 % hitsFromSuppOrth decide which pairs are used. -0116 % -0117 % See also -0118 % -------- -0119 % getBlast, getDiamond, hitsFromSuppOrth, runSuppOrth, makeFakeBlastStructure -0120 -0121 hitGenes.oldGenes = []; % collect the old genes from the template model (organism) -0122 hitGenes.newGenes = []; % collect the new genes of the draft model (target organism) -0123 -0124 getModelFor=char(getModelFor); -0125 -0126 p=parseRAVENargs(varargin, {'preferredOrder',[]; 'strictness',1; ... -0127 'onlyGenesInModels',false; 'maxE',10^-30; 'minLen',100; 'minIde',40; ... -0128 'mapNewGenesToOld',true; 'complexPolicy','flag'; 'keepGeneFree',false; ... -0129 'preserveNotes',false}); -0130 complexPolicy=lower(char(p.complexPolicy)); -0131 if ~ismember(complexPolicy,{'flag','keep','drop'}) -0132 error('complexPolicy must be "flag", "keep" or "drop", not "%s"',complexPolicy); -0133 end -0134 keepGeneFree=p.keepGeneFree; -0135 preserveNotes=p.preserveNotes; -0136 preferredOrder=p.preferredOrder; -0137 if isempty(preferredOrder) -0138 preferredOrder=[]; -0139 else -0140 preferredOrder=convertCharArray(preferredOrder); -0141 [row,col]=size(preferredOrder); -0142 if col>row -0143 preferredOrder=transpose(preferredOrder); -0144 end -0145 end -0146 switch p.strictness -0147 case 2 -0148 bidirectional=false; bestHitsOnly=false; -0149 case 3 -0150 bidirectional=true; bestHitsOnly=true; -0151 otherwise -0152 bidirectional=true; bestHitsOnly=false; -0153 end -0154 onlyGenesInModels=p.onlyGenesInModels; -0155 maxE=p.maxE; -0156 minLen=p.minLen; -0157 minIde=p.minIde; -0158 mapNewGenesToOld=p.mapNewGenesToOld; -0159 -0160 if isfield(models,'S') -0161 models={models}; -0162 end -0163 -0164 %Check that all the information is in the blast structure -0165 modelNames=cell(numel(models),1); -0166 for i=1:numel(models) -0167 modelNames{i}=models{i}.id; -0168 %Gene short names, geneMiriams and proteins are often different -0169 %between species, safer not to include them -0170 if isfield(models{i},'geneShortNames') -0171 models{i}=rmfield(models{i},'geneShortNames'); -0172 end -0173 if isfield(models{i},'geneMiriams') -0174 models{i}=rmfield(models{i},'geneMiriams'); -0175 end -0176 if isfield(models{i},'proteins') -0177 models{i}=rmfield(models{i},'proteins'); -0178 end -0179 %The geneFrom field also loses meaning if the genes are replaced by -0180 %orthologs -0181 if isfield(models{i},'geneFrom') -0182 models{i}=rmfield(models{i},'geneFrom'); -0183 end -0184 end -0185 -0186 %Check that genes do not begin with ( or end with ), as this makes problematic grRules -0187 for i=1:numel(blastStructure) -0188 problemGenes = startsWith(blastStructure(i).fromGenes,'(') | endsWith(blastStructure(i).fromGenes,')'); -0189 if any(problemGenes) -0190 error(['One or multiple gene identifiers from ' blastStructure(i).fromId ... -0191 ' starts with ''('' and/or ends with '')'', which is not allowed']) -0192 end -0193 end -0194 -0195 %Assume for now that all information is there and that it is correct. This -0196 %is important to fix since no further checks are being made! -0197 -0198 %Check whether provided fasta files use the same gene identifiers as -0199 %provided template models -0200 for i=1:numel(blastStructure) -0201 if ~strcmp(blastStructure(i).fromId,getModelFor) -0202 j=strcmpi(blastStructure(i).fromId,modelNames); -0203 if j==0 -0204 error(['While the blastStructure contains sequences from '... -0205 'organismID "%s" (as\nprovided in getBlast), none of '... -0206 'template models have this id (as model.id)'],... -0207 string(blastStructure(i).fromId)); -0208 end -0209 k=sum(ismember(blastStructure(i).fromGenes,models{j}.genes)); -0210 if k<(numel(models{j}.genes)*0.05) -0211 error(['Less than 5%% of the genes in the template model '... -0212 'with model.id "%s"\ncan be found in the blastStructure. '... -0213 'Ensure that the protein FASTA\nused in getBlast and '... -0214 'the template model used in getModelFromHomology\nuse '... -0215 'the same style of gene identifiers'],models{j}.id) -0216 end -0217 end -0218 end -0219 -0220 %Standardize grRules of template models -0221 for i=1:length(models) -0222 fprintf('\nStandardizing grRules of template model with ID "%s" ...',models{i}.id); -0223 [models{i}.grRules,models{i}.rxnGeneMat]=standardizeGrRules(models{i},false); -0224 end -0225 fprintf(' done\n'); -0226 -0227 %Remove all gene matches that are below the cutoffs -0228 for i=1:numel(blastStructure) -0229 indexes=blastStructure(i).evalue<maxE & blastStructure(i).aligLen>=minLen & blastStructure(i).identity>=minIde; %Do it in this direction to lose NaNs -0230 blastStructure(i).fromGenes(~indexes)=[]; -0231 blastStructure(i).toGenes(~indexes)=[]; -0232 blastStructure(i).evalue(~indexes)=[]; -0233 blastStructure(i).identity(~indexes)=[]; -0234 blastStructure(i).aligLen(~indexes)=[]; -0235 blastStructure(i).bitscore(~indexes)=[]; -0236 blastStructure(i).ppos(~indexes)=[]; -0237 end -0238 -0239 %Remove all reactions from the models that have no genes encoding for them, -0240 %unless keepGeneFree. Also remove all genes that encode for no reactions. -0241 %There should not be any but there might be mistakes -0242 geneFreeRxns=cell(numel(models),1); -0243 geneFreeRxns(:)={{}}; -0244 for i=1:numel(models) -0245 [hasGenes, ~]=find(models{i}.rxnGeneMat); -0246 hasNoGenes=1:numel(models{i}.rxns); -0247 hasNoGenes(hasGenes)=[]; -0248 if keepGeneFree -0249 geneFreeRxns{i}=models{i}.rxns(hasNoGenes); -0250 else -0251 models{i}=removeReactions(models{i},hasNoGenes,true,true); -0252 end -0253 end -0254 -0255 %Create a structure that contains all genes used in the blasts in any -0256 %direction for each of the models in "models" and for the new organism. The -0257 %first cell is for the new organism and then according to the preferred -0258 %order. If no such order is supplied, then according to the order in -0259 %"models" -0260 allGenes=cell(numel(models)+1,1); -0261 if isempty(preferredOrder) -0262 useOrder=modelNames; -0263 else -0264 useOrder=preferredOrder; -0265 end -0266 -0267 %Get the corresponding indexes for those models in the "models" structure -0268 useOrderIndexes=zeros(numel(models),1); -0269 for i=1:numel(models) -0270 [~, index]=ismember(models{i}.id,useOrder); -0271 useOrderIndexes(index)=i; -0272 end -0273 -0274 %Remove all genes from the blast structure that have no genes in the models -0275 if onlyGenesInModels==true -0276 modelGenes={}; -0277 for i=1:numel(models) -0278 modelGenes=[modelGenes;models{i}.genes]; -0279 end -0280 for i=1:numel(blastStructure) -0281 %Check to see if it should match the toId or fromId -0282 if strcmpi(blastStructure(i).fromId,getModelFor) -0283 I=ismember(blastStructure(i).toGenes,modelGenes); -0284 else -0285 I=ismember(blastStructure(i).fromGenes,modelGenes); -0286 end -0287 blastStructure(i).fromGenes(~I)=[]; -0288 blastStructure(i).toGenes(~I)=[]; -0289 blastStructure(i).evalue(~I)=[]; -0290 blastStructure(i).identity(~I)=[]; -0291 blastStructure(i).aligLen(~I)=[]; -0292 blastStructure(i).bitscore(~I)=[]; -0293 blastStructure(i).ppos(~I)=[]; -0294 -0295 %Check that no matching in blastStructure is empty. This happens if -0296 %no genes in the models are present in the corresponding sheet -0297 if isempty(blastStructure(i).fromGenes) -0298 EM=['No genes in matching from ' blastStructure(i).fromId ' to ' blastStructure(i).toId ' are present in the corresponding model']; -0299 error('RAVEN:badInput', '%s', EM); -0300 end -0301 end -0302 end -0303 -0304 %If only best orthologs are to be used then all other measurements are -0305 %deleted from the blastStructure. All code after this stays the same. This -0306 %means that preferred order can still matter. The best ortholog is selected -0307 %by bitscore, which unlike E-value does not shift when the search database -0308 %changes size. -0309 if bestHitsOnly -0310 for i=1:numel(blastStructure) -0311 keep=false(numel(blastStructure(i).toGenes),1); -0312 [allFromGenes, ~, I]=unique(blastStructure(i).fromGenes); -0313 -0314 %It would be nice to get rid of this loop -0315 for j=1:numel(allFromGenes) -0316 allMatches=find(I==j); -0317 bestMatches=allMatches(blastStructure(i).bitscore(allMatches)==max(blastStructure(i).bitscore(allMatches))); -0318 -0319 %Keep the best matches -0320 keep(bestMatches)=true; -0321 end +0112 % "to" and "from" are relative to the new organism: "to" is a BLAST search from +0113 % a template organism to the new organism, "from" is a search from the new +0114 % organism to a template organism. +0115 % +0116 % The hits can also come from SuppOrth consensus orthologs (hitsFromSuppOrth, +0117 % runSuppOrth). Their e-value, identity and alignment length are +0118 % placeholders that pass the filters, so minSupport and the other filters of +0119 % hitsFromSuppOrth decide which pairs are used. +0120 % +0121 % See also +0122 % -------- +0123 % getBlast, getDiamond, hitsFromSuppOrth, runSuppOrth, makeFakeBlastStructure +0124 +0125 hitGenes.oldGenes = []; % collect the old genes from the template model (organism) +0126 hitGenes.newGenes = []; % collect the new genes of the draft model (target organism) +0127 +0128 getModelFor=char(getModelFor); +0129 +0130 p=parseRAVENargs(varargin, {'preferredOrder',[]; 'strictness',1; ... +0131 'onlyGenesInModels',false; 'maxE',10^-30; 'minLen',100; 'minIde',40; ... +0132 'mapNewGenesToOld',true; 'complexPolicy','flag'; 'keepGeneFree',false; ... +0133 'preserveNotes',false}); +0134 complexPolicy=lower(char(p.complexPolicy)); +0135 if ~ismember(complexPolicy,{'flag','keep','drop'}) +0136 error('complexPolicy must be "flag", "keep" or "drop", not "%s"',complexPolicy); +0137 end +0138 keepGeneFree=p.keepGeneFree; +0139 preserveNotes=p.preserveNotes; +0140 preferredOrder=p.preferredOrder; +0141 if isempty(preferredOrder) +0142 preferredOrder=[]; +0143 else +0144 preferredOrder=convertCharArray(preferredOrder); +0145 [row,col]=size(preferredOrder); +0146 if col>row +0147 preferredOrder=transpose(preferredOrder); +0148 end +0149 end +0150 switch p.strictness +0151 case 2 +0152 bidirectional=false; bestHitsOnly=false; +0153 case 3 +0154 bidirectional=true; bestHitsOnly=true; +0155 otherwise +0156 bidirectional=true; bestHitsOnly=false; +0157 end +0158 onlyGenesInModels=p.onlyGenesInModels; +0159 maxE=p.maxE; +0160 minLen=p.minLen; +0161 minIde=p.minIde; +0162 mapNewGenesToOld=p.mapNewGenesToOld; +0163 +0164 if isfield(models,'S') +0165 models={models}; +0166 end +0167 +0168 %Check that all the information is in the blast structure +0169 modelNames=cell(numel(models),1); +0170 for i=1:numel(models) +0171 modelNames{i}=models{i}.id; +0172 %Gene short names, geneMiriams and proteins are often different +0173 %between species, safer not to include them +0174 if isfield(models{i},'geneShortNames') +0175 models{i}=rmfield(models{i},'geneShortNames'); +0176 end +0177 if isfield(models{i},'geneMiriams') +0178 models{i}=rmfield(models{i},'geneMiriams'); +0179 end +0180 if isfield(models{i},'proteins') +0181 models{i}=rmfield(models{i},'proteins'); +0182 end +0183 %The geneFrom field also loses meaning if the genes are replaced by +0184 %orthologs +0185 if isfield(models{i},'geneFrom') +0186 models{i}=rmfield(models{i},'geneFrom'); +0187 end +0188 end +0189 +0190 %Check that genes do not begin with ( or end with ), as this makes problematic grRules +0191 for i=1:numel(blastStructure) +0192 problemGenes = startsWith(blastStructure(i).fromGenes,'(') | endsWith(blastStructure(i).fromGenes,')'); +0193 if any(problemGenes) +0194 error(['One or multiple gene identifiers from ' blastStructure(i).fromId ... +0195 ' starts with ''('' and/or ends with '')'', which is not allowed']) +0196 end +0197 end +0198 +0199 %Assume for now that all information is there and that it is correct. This +0200 %is important to fix since no further checks are being made! +0201 +0202 %Check whether provided fasta files use the same gene identifiers as +0203 %provided template models +0204 for i=1:numel(blastStructure) +0205 if ~strcmp(blastStructure(i).fromId,getModelFor) +0206 j=strcmpi(blastStructure(i).fromId,modelNames); +0207 if j==0 +0208 error(['While the blastStructure contains sequences from '... +0209 'organismID "%s" (as\nprovided in getBlast), none of '... +0210 'template models have this id (as model.id)'],... +0211 string(blastStructure(i).fromId)); +0212 end +0213 k=sum(ismember(blastStructure(i).fromGenes,models{j}.genes)); +0214 if k<(numel(models{j}.genes)*0.05) +0215 error(['Less than 5%% of the genes in the template model '... +0216 'with model.id "%s"\ncan be found in the blastStructure. '... +0217 'Ensure that the protein FASTA\nused in getBlast and '... +0218 'the template model used in getModelFromHomology\nuse '... +0219 'the same style of gene identifiers'],models{j}.id) +0220 end +0221 end +0222 end +0223 +0224 %Standardize grRules of template models +0225 for i=1:length(models) +0226 fprintf('\nStandardizing grRules of template model with ID "%s" ...',models{i}.id); +0227 [models{i}.grRules,models{i}.rxnGeneMat]=standardizeGrRules(models{i},false); +0228 end +0229 fprintf(' done\n'); +0230 +0231 %Remove all gene matches that are below the cutoffs +0232 for i=1:numel(blastStructure) +0233 indexes=blastStructure(i).evalue<maxE & blastStructure(i).aligLen>=minLen & blastStructure(i).identity>=minIde; %Do it in this direction to lose NaNs +0234 blastStructure(i).fromGenes(~indexes)=[]; +0235 blastStructure(i).toGenes(~indexes)=[]; +0236 blastStructure(i).evalue(~indexes)=[]; +0237 blastStructure(i).identity(~indexes)=[]; +0238 blastStructure(i).aligLen(~indexes)=[]; +0239 blastStructure(i).bitscore(~indexes)=[]; +0240 blastStructure(i).ppos(~indexes)=[]; +0241 end +0242 +0243 %Remove all reactions from the models that have no genes encoding for them, +0244 %unless keepGeneFree. Also remove all genes that encode for no reactions. +0245 %There should not be any but there might be mistakes +0246 geneFreeRxns=cell(numel(models),1); +0247 geneFreeRxns(:)={{}}; +0248 for i=1:numel(models) +0249 [hasGenes, ~]=find(models{i}.rxnGeneMat); +0250 hasNoGenes=1:numel(models{i}.rxns); +0251 hasNoGenes(hasGenes)=[]; +0252 if keepGeneFree +0253 geneFreeRxns{i}=models{i}.rxns(hasNoGenes); +0254 else +0255 models{i}=removeReactions(models{i},hasNoGenes,true,true); +0256 end +0257 end +0258 +0259 %Create a structure that contains all genes used in the blasts in any +0260 %direction for each of the models in "models" and for the new organism. The +0261 %first cell is for the new organism and then according to the preferred +0262 %order. If no such order is supplied, then according to the order in +0263 %"models" +0264 allGenes=cell(numel(models)+1,1); +0265 if isempty(preferredOrder) +0266 useOrder=modelNames; +0267 else +0268 useOrder=preferredOrder; +0269 end +0270 +0271 %Get the corresponding indexes for those models in the "models" structure +0272 useOrderIndexes=zeros(numel(models),1); +0273 for i=1:numel(models) +0274 [~, index]=ismember(models{i}.id,useOrder); +0275 useOrderIndexes(index)=i; +0276 end +0277 +0278 %Remove all genes from the blast structure that have no genes in the models +0279 if onlyGenesInModels==true +0280 modelGenes={}; +0281 for i=1:numel(models) +0282 modelGenes=[modelGenes;models{i}.genes]; +0283 end +0284 for i=1:numel(blastStructure) +0285 %Check to see if it should match the toId or fromId +0286 if strcmpi(blastStructure(i).fromId,getModelFor) +0287 I=ismember(blastStructure(i).toGenes,modelGenes); +0288 else +0289 I=ismember(blastStructure(i).fromGenes,modelGenes); +0290 end +0291 blastStructure(i).fromGenes(~I)=[]; +0292 blastStructure(i).toGenes(~I)=[]; +0293 blastStructure(i).evalue(~I)=[]; +0294 blastStructure(i).identity(~I)=[]; +0295 blastStructure(i).aligLen(~I)=[]; +0296 blastStructure(i).bitscore(~I)=[]; +0297 blastStructure(i).ppos(~I)=[]; +0298 +0299 %Check that no matching in blastStructure is empty. This happens if +0300 %no genes in the models are present in the corresponding sheet +0301 if isempty(blastStructure(i).fromGenes) +0302 EM=['No genes in matching from ' blastStructure(i).fromId ' to ' blastStructure(i).toId ' are present in the corresponding model']; +0303 error('RAVEN:badInput', '%s', EM); +0304 end +0305 end +0306 end +0307 +0308 %If only best orthologs are to be used then all other measurements are +0309 %deleted from the blastStructure. All code after this stays the same. This +0310 %means that preferred order can still matter. The best ortholog is selected +0311 %by bitscore, which unlike E-value does not shift when the search database +0312 %changes size. +0313 if bestHitsOnly +0314 for i=1:numel(blastStructure) +0315 keep=false(numel(blastStructure(i).toGenes),1); +0316 [allFromGenes, ~, I]=unique(blastStructure(i).fromGenes); +0317 +0318 %It would be nice to get rid of this loop +0319 for j=1:numel(allFromGenes) +0320 allMatches=find(I==j); +0321 bestMatches=allMatches(blastStructure(i).bitscore(allMatches)==max(blastStructure(i).bitscore(allMatches))); 0322 -0323 %Delete all matches that were not best matches -0324 blastStructure(i).fromGenes(~keep)=[]; -0325 blastStructure(i).toGenes(~keep)=[]; -0326 blastStructure(i).evalue(~keep)=[]; -0327 blastStructure(i).identity(~keep)=[]; -0328 blastStructure(i).aligLen(~keep)=[]; -0329 blastStructure(i).bitscore(~keep)=[]; -0330 blastStructure(i).ppos(~keep)=[]; -0331 end -0332 end -0333 -0334 useOrder=[{getModelFor};useOrder]; -0335 -0336 for i=1:numel(blastStructure) -0337 [~, toIndex]=ismember(blastStructure(i).toId,useOrder); -0338 [~, fromIndex]=ismember(blastStructure(i).fromId,useOrder); -0339 -0340 %Add all genes to the corresponding list in allGenes -0341 allGenes{toIndex}=[allGenes{toIndex};blastStructure(i).toGenes]; -0342 allGenes{fromIndex}=[allGenes{fromIndex};blastStructure(i).fromGenes]; -0343 end -0344 -0345 %Keep only the unique gene names -0346 maxOtherGeneNr=0; %Determines the dimension of the connectivity matrixes -0347 for i=1:numel(allGenes) -0348 allGenes{i}=unique(allGenes{i}); -0349 if i>1 -0350 if numel(allGenes{i})>maxOtherGeneNr -0351 maxOtherGeneNr=numel(allGenes{i}); -0352 end -0353 end -0354 end -0355 -0356 %Generate a cell array of matrixes that describes how the genes in the new -0357 %organism map to the models. Each cell matches to the corresponding model -0358 %in useOrder (starting at 2 of course). First dimension is gene in new -0359 %organism, second which gene it is in the other organism. The second matrix -0360 %describes how they map back. -0361 -0362 %As it is now, a significant match is indicated by a 1. This could be -0363 %expanded to contain some kind of significance level. The first dimension -0364 %is still the genes in the new model. +0323 %Keep the best matches +0324 keep(bestMatches)=true; +0325 end +0326 +0327 %Delete all matches that were not best matches +0328 blastStructure(i).fromGenes(~keep)=[]; +0329 blastStructure(i).toGenes(~keep)=[]; +0330 blastStructure(i).evalue(~keep)=[]; +0331 blastStructure(i).identity(~keep)=[]; +0332 blastStructure(i).aligLen(~keep)=[]; +0333 blastStructure(i).bitscore(~keep)=[]; +0334 blastStructure(i).ppos(~keep)=[]; +0335 end +0336 end +0337 +0338 useOrder=[{getModelFor};useOrder]; +0339 +0340 for i=1:numel(blastStructure) +0341 [~, toIndex]=ismember(blastStructure(i).toId,useOrder); +0342 [~, fromIndex]=ismember(blastStructure(i).fromId,useOrder); +0343 +0344 %Add all genes to the corresponding list in allGenes +0345 allGenes{toIndex}=[allGenes{toIndex};blastStructure(i).toGenes]; +0346 allGenes{fromIndex}=[allGenes{fromIndex};blastStructure(i).fromGenes]; +0347 end +0348 +0349 %Keep only the unique gene names +0350 maxOtherGeneNr=0; %Determines the dimension of the connectivity matrixes +0351 for i=1:numel(allGenes) +0352 allGenes{i}=unique(allGenes{i}); +0353 if i>1 +0354 if numel(allGenes{i})>maxOtherGeneNr +0355 maxOtherGeneNr=numel(allGenes{i}); +0356 end +0357 end +0358 end +0359 +0360 %Generate a cell array of matrixes that describes how the genes in the new +0361 %organism map to the models. Each cell matches to the corresponding model +0362 %in useOrder (starting at 2 of course). First dimension is gene in new +0363 %organism, second which gene it is in the other organism. The second matrix +0364 %describes how they map back. 0365 -0366 allTo=cell(numel(useOrder)-1,1); -0367 allFrom=cell(numel(useOrder)-1,1); -0368 -0369 for i=1:numel(useOrder)-1 -0370 allTo{i}=sparse(numel(allGenes{1}),numel(allGenes{i+1})); -0371 allFrom{i}=sparse(numel(allGenes{1}),numel(allGenes{i+1})); -0372 end -0373 -0374 %Fill the matches to other species -0375 for i=1:numel(blastStructure) -0376 if strcmp(blastStructure(i).toId,getModelFor) -0377 %This was "to" the new organism. They should all match so no checks -0378 %are being made -0379 [~, a]=ismember(blastStructure(i).toGenes,allGenes{1}); -0380 [~, fromModel]=ismember(blastStructure(i).fromId,useOrder); -0381 [~, b]=ismember(blastStructure(i).fromGenes,allGenes{fromModel}); -0382 idx = sub2ind(size(allTo{fromModel-1}), a, b); -0383 allTo{fromModel-1}(idx)=1; -0384 else -0385 %This was "from" the new organism -0386 [~, a]=ismember(blastStructure(i).fromGenes,allGenes{1}); -0387 [~, toModel]=ismember(blastStructure(i).toId,useOrder); -0388 [~, b]=ismember(blastStructure(i).toGenes,allGenes{toModel}); -0389 idx = sub2ind(size(allFrom{toModel-1}), a, b); -0390 allFrom{toModel-1}(idx)=1; -0391 end -0392 end -0393 -0394 %Now we have all the gene matches in a convenient way. For all the genes in -0395 %the new organism get the genes that should be included from other -0396 %organisms. If all genes should be included this simply means keep the -0397 %allFrom matrix as it is. If only orthologs which could be mapped in both -0398 %BLAST directions are to be included then only those elements are kept. -0399 -0400 finalMappings=cell(numel(useOrder)-1,1); -0401 if bidirectional -0402 for j=1:numel(allFrom) -0403 finalMappings{j}=allTo{j}~=0 & allFrom{j}~=0; -0404 end -0405 else -0406 if mapNewGenesToOld==true -0407 finalMappings=allFrom; -0408 else -0409 finalMappings=allTo; -0410 end -0411 end -0412 -0413 %Remove all genes from the mapping that are not in the models. This does not -0414 %do much if only genes in the models were used for the original mapping. -0415 %Also simplify the finalMapping and allGenes structures so that they only -0416 %contain mappings that exist -0417 usedNewGenes=false(numel(allGenes{1}),1); -0418 -0419 for i=1:numel(allGenes)-1 -0420 %First remove mappings for those genes that are not in the model -0421 if onlyGenesInModels==false -0422 a=ismember(allGenes{i+1},models{useOrderIndexes(i)}.genes); -0423 finalMappings{i}(:,~a)=false; -0424 end -0425 -0426 %Then remove unused ones and simplify -0427 [a, b]=find(finalMappings{i}); -0428 usedGenes=false(numel(allGenes{i+1}),1); -0429 usedNewGenes(a)=true; -0430 usedGenes(b)=true; -0431 finalMappings{i}=finalMappings{i}(:,usedGenes); -0432 allGenes{i+1}=allGenes{i+1}(usedGenes); -0433 end -0434 -0435 %Remove all new genes that have not been mapped to anything -0436 allGenes{1}=allGenes{1}(usedNewGenes); -0437 for i=1:numel(finalMappings) -0438 finalMappings{i}=finalMappings{i}(usedNewGenes,:); -0439 end -0440 -0441 %Gene ids end up in grRules, which are split at whitespace and parentheses, -0442 %and which COBRA also splits at "|" and "&" -0443 unusable=allGenes{1}(~cellfun(@isempty,regexp(allGenes{1},'[\s()|&]','once'))); -0444 if ~isempty(unusable) -0445 error('RAVEN:badInput',['%d gene ids of "%s" cannot be written into a grRule, '... -0446 'because they contain whitespace, parentheses, "|" or "&": %s. Map them '... -0447 'to gene ids first, or use the accession alone for UniProt headers such '... -0448 'as "sp|P33284|HXK_KLULA".'],numel(unusable),getModelFor,... -0449 strjoin(unusable(1:min(3,end))',', ')); -0450 end -0451 -0452 %Now is it time to choose which reactions should be included from which -0453 %models. If there is a preferred order specified then each gene can only -0454 %result in reactions from one model, otherwise they should all be included +0366 %As it is now, a significant match is indicated by a 1. This could be +0367 %expanded to contain some kind of significance level. The first dimension +0368 %is still the genes in the new model. +0369 +0370 allTo=cell(numel(useOrder)-1,1); +0371 allFrom=cell(numel(useOrder)-1,1); +0372 +0373 for i=1:numel(useOrder)-1 +0374 allTo{i}=sparse(numel(allGenes{1}),numel(allGenes{i+1})); +0375 allFrom{i}=sparse(numel(allGenes{1}),numel(allGenes{i+1})); +0376 end +0377 +0378 %Fill the matches to other species +0379 for i=1:numel(blastStructure) +0380 if strcmp(blastStructure(i).toId,getModelFor) +0381 %This was "to" the new organism. They should all match so no checks +0382 %are being made +0383 [~, a]=ismember(blastStructure(i).toGenes,allGenes{1}); +0384 [~, fromModel]=ismember(blastStructure(i).fromId,useOrder); +0385 [~, b]=ismember(blastStructure(i).fromGenes,allGenes{fromModel}); +0386 idx = sub2ind(size(allTo{fromModel-1}), a, b); +0387 allTo{fromModel-1}(idx)=1; +0388 else +0389 %This was "from" the new organism +0390 [~, a]=ismember(blastStructure(i).fromGenes,allGenes{1}); +0391 [~, toModel]=ismember(blastStructure(i).toId,useOrder); +0392 [~, b]=ismember(blastStructure(i).toGenes,allGenes{toModel}); +0393 idx = sub2ind(size(allFrom{toModel-1}), a, b); +0394 allFrom{toModel-1}(idx)=1; +0395 end +0396 end +0397 +0398 %Now we have all the gene matches in a convenient way. For all the genes in +0399 %the new organism get the genes that should be included from other +0400 %organisms. If all genes should be included this simply means keep the +0401 %allFrom matrix as it is. If only orthologs which could be mapped in both +0402 %BLAST directions are to be included then only those elements are kept. +0403 +0404 finalMappings=cell(numel(useOrder)-1,1); +0405 if bidirectional +0406 for j=1:numel(allFrom) +0407 finalMappings{j}=allTo{j}~=0 & allFrom{j}~=0; +0408 end +0409 else +0410 if mapNewGenesToOld==true +0411 finalMappings=allFrom; +0412 else +0413 finalMappings=allTo; +0414 end +0415 end +0416 +0417 %Remove all genes from the mapping that are not in the models. This does not +0418 %do much if only genes in the models were used for the original mapping. +0419 %Also simplify the finalMapping and allGenes structures so that they only +0420 %contain mappings that exist +0421 usedNewGenes=false(numel(allGenes{1}),1); +0422 +0423 for i=1:numel(allGenes)-1 +0424 %First remove mappings for those genes that are not in the model +0425 if onlyGenesInModels==false +0426 a=ismember(allGenes{i+1},models{useOrderIndexes(i)}.genes); +0427 finalMappings{i}(:,~a)=false; +0428 end +0429 +0430 %Then remove unused ones and simplify +0431 [a, b]=find(finalMappings{i}); +0432 usedGenes=false(numel(allGenes{i+1}),1); +0433 usedNewGenes(a)=true; +0434 usedGenes(b)=true; +0435 finalMappings{i}=finalMappings{i}(:,usedGenes); +0436 allGenes{i+1}=allGenes{i+1}(usedGenes); +0437 end +0438 +0439 %Remove all new genes that have not been mapped to anything +0440 allGenes{1}=allGenes{1}(usedNewGenes); +0441 for i=1:numel(finalMappings) +0442 finalMappings{i}=finalMappings{i}(usedNewGenes,:); +0443 end +0444 +0445 %Gene ids end up in grRules, which are split at whitespace and parentheses, +0446 %and which COBRA also splits at "|" and "&" +0447 unusable=allGenes{1}(~cellfun(@isempty,regexp(allGenes{1},'[\s()|&]','once'))); +0448 if ~isempty(unusable) +0449 error('RAVEN:badInput',['%d gene ids of "%s" cannot be written into a grRule, '... +0450 'because they contain whitespace, parentheses, "|" or "&": %s. Map them '... +0451 'to gene ids first, or use the accession alone for UniProt headers such '... +0452 'as "sp|P33284|HXK_KLULA".'],numel(unusable),getModelFor,... +0453 strjoin(unusable(1:min(3,end))',', ')); +0454 end 0455 -0456 %Start by simplifying the models by removing genes/reactions that are not -0457 %used. This is where it gets weird with complexes, especially "or" -0458 %complexes. In this step only reactions which are encoded by one single -0459 %gene, or where all genes should be deleted, are deleted. The info on the -0460 %full complex is still present in the grRules -0461 -0462 for i=1:numel(models) -0463 a=ismember(models{useOrderIndexes(i)}.genes,allGenes{i+1}); -0464 -0465 %Remove reactions that are not associated to any of the genes in -0466 %allGenes, thereby also keeping complexes where only for one of the -0467 %genes was matched -0468 [rxnsToKeep,~] = find(models{useOrderIndexes(i)}.rxnGeneMat(:,a)); -0469 rxnsToRemove = repmat(1,numel(models{useOrderIndexes(i)}.rxns),1); -0470 rxnsToRemove(rxnsToKeep) = 0; -0471 rxnsToRemove(ismember(models{useOrderIndexes(i)}.rxns,geneFreeRxns{useOrderIndexes(i)})) = 0; -0472 rxnsToRemove = find(rxnsToRemove); -0473 models{useOrderIndexes(i)}=removeReactions(models{useOrderIndexes(i)},rxnsToRemove,true,true,true); -0474 end -0475 -0476 %Since mergeModels function will be used in the end, the models are -0477 %simplified further by deleting genes/reactions in the order specified by -0478 %preferredOrder. This means that the last model will only contain reactions -0479 %for genes that mapped only to that model -0480 -0481 allUsedGenes=false(numel(allGenes{1}),1); -0482 -0483 if ~isempty(preferredOrder) && numel(models)>1 -0484 [usedGenes, ~]=find(finalMappings{1}); %All that are used in the first model in preferredOrder -0485 allUsedGenes(usedGenes)=true; -0486 for i=2:numel(finalMappings) -0487 [usedGenes, ~]=find(finalMappings{i}); -0488 usedGenes=unique(usedGenes); -0489 a=ismember(usedGenes,find(allUsedGenes)); -0490 -0491 [~, genesToDelete]=find(finalMappings{i}(usedGenes(a),:)); %IMPORTANT! IS it really correct to remove all genes that map? -0492 genesToDelete=unique(genesToDelete); %Maybe not needed, but for clarity if nothing else -0493 -0494 %Remove all the genes that were already found and add the other -0495 %ones to allUsedGenes -0496 if ~isempty(genesToDelete) -0497 models{useOrderIndexes(i)}=removeGenes(models{useOrderIndexes(i)},allGenes{i+1}(genesToDelete),true,true,false); -0498 end -0499 allUsedGenes(usedGenes)=true; -0500 -0501 %Remove the deleted genes from finalMappings and allGenes. -0502 finalMappings{i}(:,genesToDelete)=[]; -0503 allGenes{i+1}(genesToDelete)=[]; -0504 end -0505 end -0506 -0507 %Now loop through the models and update the gene associations. Genes not -0508 %belonging to the new organism will be renamed as "OLD_MODELID_gene" -0509 for i=1:numel(models) -0510 %Find all the new genes that should be used for this model -0511 [newGenes, oldGenes]=find(finalMappings{i}); -0512 -0513 %Create a new gene list with the genes from the new organism and those -0514 %genes that could not be removed -0515 replaceableGenes=allGenes{i+1}(unique(oldGenes)); -0516 nonReplaceableGenes=setdiff(models{useOrderIndexes(i)}.genes,replaceableGenes); -0517 fullGeneList=[allGenes{1}(unique(newGenes));nonReplaceableGenes]; -0518 -0519 %Just to save some indexing later. This is the LAST index of -0520 %replaceable ones -0521 nonRepStartIndex=numel(unique(newGenes)); +0456 %Now is it time to choose which reactions should be included from which +0457 %models. If there is a preferred order specified then each gene can only +0458 %result in reactions from one model, otherwise they should all be included +0459 +0460 %Start by simplifying the models by removing genes/reactions that are not +0461 %used. This is where it gets weird with complexes, especially "or" +0462 %complexes. In this step only reactions which are encoded by one single +0463 %gene, or where all genes should be deleted, are deleted. The info on the +0464 %full complex is still present in the grRules +0465 +0466 for i=1:numel(models) +0467 a=ismember(models{useOrderIndexes(i)}.genes,allGenes{i+1}); +0468 +0469 %Remove reactions that are not associated to any of the genes in +0470 %allGenes, thereby also keeping complexes where only for one of the +0471 %genes was matched +0472 [rxnsToKeep,~] = find(models{useOrderIndexes(i)}.rxnGeneMat(:,a)); +0473 rxnsToRemove = repmat(1,numel(models{useOrderIndexes(i)}.rxns),1); +0474 rxnsToRemove(rxnsToKeep) = 0; +0475 rxnsToRemove(ismember(models{useOrderIndexes(i)}.rxns,geneFreeRxns{useOrderIndexes(i)})) = 0; +0476 rxnsToRemove = find(rxnsToRemove); +0477 models{useOrderIndexes(i)}=removeReactions(models{useOrderIndexes(i)},rxnsToRemove,true,true,true); +0478 end +0479 +0480 %Since mergeModels function will be used in the end, the models are +0481 %simplified further by deleting genes/reactions in the order specified by +0482 %preferredOrder. This means that the last model will only contain reactions +0483 %for genes that mapped only to that model +0484 +0485 allUsedGenes=false(numel(allGenes{1}),1); +0486 +0487 if ~isempty(preferredOrder) && numel(models)>1 +0488 [usedGenes, ~]=find(finalMappings{1}); %All that are used in the first model in preferredOrder +0489 allUsedGenes(usedGenes)=true; +0490 for i=2:numel(finalMappings) +0491 [usedGenes, ~]=find(finalMappings{i}); +0492 usedGenes=unique(usedGenes); +0493 a=ismember(usedGenes,find(allUsedGenes)); +0494 +0495 [~, genesToDelete]=find(finalMappings{i}(usedGenes(a),:)); %IMPORTANT! IS it really correct to remove all genes that map? +0496 genesToDelete=unique(genesToDelete); %Maybe not needed, but for clarity if nothing else +0497 +0498 %Remove all the genes that were already found and add the other +0499 %ones to allUsedGenes +0500 if ~isempty(genesToDelete) +0501 models{useOrderIndexes(i)}=removeGenes(models{useOrderIndexes(i)},allGenes{i+1}(genesToDelete),true,true,false); +0502 end +0503 allUsedGenes(usedGenes)=true; +0504 +0505 %Remove the deleted genes from finalMappings and allGenes. +0506 finalMappings{i}(:,genesToDelete)=[]; +0507 allGenes{i+1}(genesToDelete)=[]; +0508 end +0509 end +0510 +0511 %Now loop through the models and update the gene associations. Genes not +0512 %belonging to the new organism will be renamed as "OLD_MODELID_gene" +0513 for i=1:numel(models) +0514 %Find all the new genes that should be used for this model +0515 [newGenes, oldGenes]=find(finalMappings{i}); +0516 +0517 %Create a new gene list with the genes from the new organism and those +0518 %genes that could not be removed +0519 replaceableGenes=allGenes{i+1}(unique(oldGenes)); +0520 nonReplaceableGenes=setdiff(models{useOrderIndexes(i)}.genes,replaceableGenes); +0521 fullGeneList=[allGenes{1}(unique(newGenes));nonReplaceableGenes]; 0522 -0523 %Construct a new rxnGeneMat -0524 newRxnGeneMat=sparse(numel(models{useOrderIndexes(i)}.rxns),numel(fullGeneList)); -0525 -0526 %Now update the rxnGeneMat. This is a little tricky and could -0527 %probably be done in a more efficient way, but I just loop through the -0528 %reactions and add them one by one -0529 for j=1:numel(models{useOrderIndexes(i)}.rxns) -0530 %Get the old genes encoding for this reaction -0531 [~, oldGeneIds]=find(models{useOrderIndexes(i)}.rxnGeneMat(j,:)); -0532 -0533 %Tokenize the grRule once; all gene substitutions operate on the -0534 %token list so that no gene ID can partially match another. -0535 tokens=gprTokenize(models{useOrderIndexes(i)}.grRules{j}); +0523 %Just to save some indexing later. This is the LAST index of +0524 %replaceable ones +0525 nonRepStartIndex=numel(unique(newGenes)); +0526 +0527 %Construct a new rxnGeneMat +0528 newRxnGeneMat=sparse(numel(models{useOrderIndexes(i)}.rxns),numel(fullGeneList)); +0529 +0530 %Now update the rxnGeneMat. This is a little tricky and could +0531 %probably be done in a more efficient way, but I just loop through the +0532 %reactions and add them one by one +0533 for j=1:numel(models{useOrderIndexes(i)}.rxns) +0534 %Get the old genes encoding for this reaction +0535 [~, oldGeneIds]=find(models{useOrderIndexes(i)}.rxnGeneMat(j,:)); 0536 -0537 %Update the matrix for each gene. This includes replacing one gene -0538 %with several new ones if there were several matches -0539 for k=1:numel(oldGeneIds) -0540 %Match the gene to one in the gene list. This is done as a text -0541 %match. Could probably be done better, but the indexing is -0542 %complex -0543 -0544 geneName=models{useOrderIndexes(i)}.genes(oldGeneIds(k)); -0545 -0546 %First search in the mappable genes -0547 mapIndex=find(ismember(allGenes{i+1},geneName)); -0548 -0549 if ~isempty(mapIndex) -0550 % add the old genes -0551 hitGenes.oldGenes = [hitGenes.oldGenes, {geneName}]; +0537 %Tokenize the grRule once; all gene substitutions operate on the +0538 %token list so that no gene ID can partially match another. +0539 tokens=gprTokenize(models{useOrderIndexes(i)}.grRules{j}); +0540 +0541 %Update the matrix for each gene. This includes replacing one gene +0542 %with several new ones if there were several matches +0543 for k=1:numel(oldGeneIds) +0544 %Match the gene to one in the gene list. This is done as a text +0545 %match. Could probably be done better, but the indexing is +0546 %complex +0547 +0548 geneName=models{useOrderIndexes(i)}.genes(oldGeneIds(k)); +0549 +0550 %First search in the mappable genes +0551 mapIndex=find(ismember(allGenes{i+1},geneName)); 0552 -0553 %Get the new genes for that gene -0554 a=find(finalMappings{i}(:,mapIndex)); -0555 -0556 %Find the positions of these genes in the final gene list -0557 [~, b]=ismember(allGenes{1}(a),fullGeneList); -0558 -0559 %Update the matrix -0560 newRxnGeneMat(j,b)=1; -0561 -0562 %Build repString for hitGenes (display only) -0563 repString=fullGeneList{b(1)}; -0564 if numel(b)>1 -0565 for l=2:numel(b) -0566 repString=[repString ' or ' fullGeneList{b(l)}]; %#ok<AGROW> -0567 end -0568 repString=['(' repString ')']; -0569 end -0570 hitGenes.newGenes = [hitGenes.newGenes, {repString}]; -0571 -0572 %Replace the old gene token with the new gene(s) -0573 tokens=gprReplaceToken(tokens,geneName{1},fullGeneList(b)); -0574 else -0575 %Then search in the non-replaceable genes. There could only -0576 %be one match here -0577 index=find(ismember(nonReplaceableGenes,geneName)); -0578 -0579 %Update the matrix -0580 newRxnGeneMat(j,nonRepStartIndex+index)=1; -0581 -0582 oldName=strcat('OLD_',models{useOrderIndexes(i)}.id,'_',geneName{1}); -0583 tokens=gprReplaceToken(tokens,geneName{1},{oldName}); -0584 end -0585 end -0586 -0587 models{useOrderIndexes(i)}.grRules{j}=gprBuildRule(tokens); -0588 end -0589 -0590 %Add the new list of genes -0591 models{useOrderIndexes(i)}.rxnGeneMat=newRxnGeneMat; -0592 if ~isempty(nonReplaceableGenes) -0593 models{useOrderIndexes(i)}.genes=[allGenes{1}(unique(newGenes));strcat('OLD_',models{useOrderIndexes(i)}.id,'_',nonReplaceableGenes)]; -0594 else -0595 models{useOrderIndexes(i)}.genes=allGenes{1}(unique(newGenes)); -0596 end -0597 if isfield(models{useOrderIndexes(i)},'geneComps') -0598 geneComps=models{useOrderIndexes(i)}.geneComps(1); -0599 models{useOrderIndexes(i)}.geneComps=zeros(numel(models{useOrderIndexes(i)}.genes),1); -0600 %Assume that all genes are in the same compartment, and this -0601 %compartment is specified for the first gene -0602 models{useOrderIndexes(i)}.geneComps(:)=geneComps; -0603 end -0604 end -0605 -0606 %Now merge the models. All information should be correct except for "or" -0607 %complexes -0608 draftModel=mergeModels(models,'metNames'); +0553 if ~isempty(mapIndex) +0554 % add the old genes +0555 hitGenes.oldGenes = [hitGenes.oldGenes, {geneName}]; +0556 +0557 %Get the new genes for that gene +0558 a=find(finalMappings{i}(:,mapIndex)); +0559 +0560 %Find the positions of these genes in the final gene list +0561 [~, b]=ismember(allGenes{1}(a),fullGeneList); +0562 +0563 %Update the matrix +0564 newRxnGeneMat(j,b)=1; +0565 +0566 %Build repString for hitGenes (display only) +0567 repString=fullGeneList{b(1)}; +0568 if numel(b)>1 +0569 for l=2:numel(b) +0570 repString=[repString ' or ' fullGeneList{b(l)}]; %#ok<AGROW> +0571 end +0572 repString=['(' repString ')']; +0573 end +0574 hitGenes.newGenes = [hitGenes.newGenes, {repString}]; +0575 +0576 %Replace the old gene token with the new gene(s) +0577 tokens=gprReplaceToken(tokens,geneName{1},fullGeneList(b)); +0578 else +0579 %Then search in the non-replaceable genes. There could only +0580 %be one match here +0581 index=find(ismember(nonReplaceableGenes,geneName)); +0582 +0583 %Update the matrix +0584 newRxnGeneMat(j,nonRepStartIndex+index)=1; +0585 +0586 oldName=strcat('OLD_',models{useOrderIndexes(i)}.id,'_',geneName{1}); +0587 tokens=gprReplaceToken(tokens,geneName{1},{oldName}); +0588 end +0589 end +0590 +0591 models{useOrderIndexes(i)}.grRules{j}=gprBuildRule(tokens); +0592 end +0593 +0594 %Add the new list of genes +0595 models{useOrderIndexes(i)}.rxnGeneMat=newRxnGeneMat; +0596 if ~isempty(nonReplaceableGenes) +0597 models{useOrderIndexes(i)}.genes=[allGenes{1}(unique(newGenes));strcat('OLD_',models{useOrderIndexes(i)}.id,'_',nonReplaceableGenes)]; +0598 else +0599 models{useOrderIndexes(i)}.genes=allGenes{1}(unique(newGenes)); +0600 end +0601 if isfield(models{useOrderIndexes(i)},'geneComps') +0602 geneComps=models{useOrderIndexes(i)}.geneComps(1); +0603 models{useOrderIndexes(i)}.geneComps=zeros(numel(models{useOrderIndexes(i)}.genes),1); +0604 %Assume that all genes are in the same compartment, and this +0605 %compartment is specified for the first gene +0606 models{useOrderIndexes(i)}.geneComps(:)=geneComps; +0607 end +0608 end 0609 -0610 %Remove OLD_ genes that appear only in OR branches (they were added for -0611 %AND-complex genes with no orthologs; in OR branches they are redundant). -0612 %Uses token-level removal to avoid partial-match hazards. -0613 for oldI=1:numel(draftModel.grRules) -0614 if ~contains(draftModel.grRules{oldI},'OLD_') -0615 continue -0616 end -0617 tkns=gprTokenize(draftModel.grRules{oldI}); -0618 again=true; -0619 while again -0620 again=false; -0621 for kk=1:numel(tkns) -0622 if startsWith(tkns{kk},'OLD_') -0623 if kk>1 && strcmp(tkns{kk-1},'or') -0624 tkns(kk-1:kk)=[]; -0625 again=true; break -0626 elseif kk<numel(tkns) && strcmp(tkns{kk+1},'or') -0627 tkns(kk:kk+1)=[]; -0628 again=true; break -0629 end -0630 end -0631 end -0632 end -0633 draftModel.grRules{oldI}=gprBuildRule(tkns); -0634 end -0635 -0636 %Apply the complex policy to the OLD_ genes left in "and" relations, and -0637 %write each grRule without repeated genes or redundant brackets -0638 toRemove=false(numel(draftModel.rxns),1); -0639 for j=1:numel(draftModel.grRules) -0640 rule=draftModel.grRules{j}; -0641 if isempty(rule) -0642 continue -0643 end -0644 if contains(rule,'OLD_') && strcmp(complexPolicy,'drop') -0645 toRemove(j)=true; -0646 elseif contains(rule,'OLD_') && strcmp(complexPolicy,'keep') -0647 draftModel.grRules{j}=gprSimplify(rule,'OLD_'); -0648 toRemove(j)=isempty(draftModel.grRules{j}); -0649 else -0650 draftModel.grRules{j}=gprSimplify(rule,''); -0651 end -0652 end -0653 if any(toRemove) -0654 draftModel=removeReactions(draftModel,draftModel.rxns(toRemove),true,true,true); -0655 end -0656 -0657 %Change name of the resulting model -0658 draftModel.id=getModelFor; -0659 name='Generated by getModelFromHomology using '; -0660 for i=1:numel(models) -0661 if i<numel(models) -0662 name=[name models{i}.id ', ']; -0663 else -0664 name=[name models{i}.id]; -0665 end -0666 end -0667 draftModel.name=name; -0668 %The draft is a new model: the templates' version, date and description are -0669 %not its own -0670 draftModel=rmfield(draftModel,intersect({'version','date','description'},fieldnames(draftModel))); -0671 if isfield(draftModel,'annotation') -0672 descriptive={'taxonomy','sourceUrl','note','givenName','familyName','authors','email','organization'}; -0673 draftModel.annotation=rmfield(draftModel.annotation,intersect(descriptive,fieldnames(draftModel.annotation))); -0674 end -0675 if ~preserveNotes || ~isfield(draftModel,'rxnNotes') -0676 draftModel.rxnNotes=cell(length(draftModel.rxns),1); -0677 draftModel.rxnNotes(:)={'Included by getModelFromHomology'}; +0610 %Now merge the models. All information should be correct except for "or" +0611 %complexes +0612 draftModel=mergeModels(models,'metNames'); +0613 +0614 %Remove OLD_ genes that appear only in OR branches (they were added for +0615 %AND-complex genes with no orthologs; in OR branches they are redundant). +0616 %Uses token-level removal to avoid partial-match hazards. +0617 for oldI=1:numel(draftModel.grRules) +0618 if ~contains(draftModel.grRules{oldI},'OLD_') +0619 continue +0620 end +0621 tkns=gprTokenize(draftModel.grRules{oldI}); +0622 again=true; +0623 while again +0624 again=false; +0625 for kk=1:numel(tkns) +0626 if startsWith(tkns{kk},'OLD_') +0627 if kk>1 && strcmp(tkns{kk-1},'or') +0628 tkns(kk-1:kk)=[]; +0629 again=true; break +0630 elseif kk<numel(tkns) && strcmp(tkns{kk+1},'or') +0631 tkns(kk:kk+1)=[]; +0632 again=true; break +0633 end +0634 end +0635 end +0636 end +0637 draftModel.grRules{oldI}=gprBuildRule(tkns); +0638 end +0639 +0640 %Apply the complex policy to the OLD_ genes left in "and" relations, and +0641 %write each grRule without repeated genes or redundant brackets +0642 toRemove=false(numel(draftModel.rxns),1); +0643 for j=1:numel(draftModel.grRules) +0644 rule=draftModel.grRules{j}; +0645 if isempty(rule) +0646 continue +0647 end +0648 if contains(rule,'OLD_') && strcmp(complexPolicy,'drop') +0649 toRemove(j)=true; +0650 elseif contains(rule,'OLD_') && strcmp(complexPolicy,'keep') +0651 draftModel.grRules{j}=gprSimplify(rule,'OLD_'); +0652 toRemove(j)=isempty(draftModel.grRules{j}); +0653 else +0654 draftModel.grRules{j}=gprSimplify(rule,''); +0655 end +0656 end +0657 if any(toRemove) +0658 draftModel=removeReactions(draftModel,draftModel.rxns(toRemove),true,true,true); +0659 end +0660 +0661 %Change name of the resulting model +0662 draftModel.id=getModelFor; +0663 name='Generated by getModelFromHomology using '; +0664 for i=1:numel(models) +0665 if i<numel(models) +0666 name=[name models{i}.id ', ']; +0667 else +0668 name=[name models{i}.id]; +0669 end +0670 end +0671 draftModel.name=name; +0672 %The draft is a new model: the templates' version, date and description are +0673 %not its own +0674 draftModel=rmfield(draftModel,intersect({'version','date','description'},fieldnames(draftModel))); +0675 if isfield(draftModel,'annotation') +0676 descriptive={'taxonomy','sourceUrl','note','givenName','familyName','authors','email','organization'}; +0677 draftModel.annotation=rmfield(draftModel.annotation,intersect(descriptive,fieldnames(draftModel.annotation))); 0678 end -0679 if ~preserveNotes || ~isfield(draftModel,'rxnConfidenceScores') -0680 draftModel.rxnConfidenceScores=NaN(length(draftModel.rxns),1); -0681 draftModel.rxnConfidenceScores(:)=2; +0679 if ~preserveNotes || ~isfield(draftModel,'rxnNotes') +0680 draftModel.rxnNotes=cell(length(draftModel.rxns),1); +0681 draftModel.rxnNotes(:)={'Included by getModelFromHomology'}; 0682 end -0683 draftModel=deleteUnusedGenes(draftModel,0); -0684 %Standardize grRules and notify if problematic grRules are found -0685 [draftModel.grRules,draftModel.rxnGeneMat]=standardizeGrRules(draftModel,false); -0686 draftModel=deleteUnusedGenes(draftModel,false); -0687 end -0688 -0689 -0690 function tokens=gprTokenize(rule) -0691 % Split a GPR rule into tokens: gene IDs, "and", "or", "(", ")". -0692 % Brackets are separated from adjacent text by whitespace before splitting. -0693 if isempty(rule) -0694 tokens={}; -0695 return -0696 end -0697 r=regexprep(rule,'([\(\)])',' $1 '); -0698 r=regexprep(r,'\s+',' '); -0699 tokens=strsplit(strtrim(r),' '); -0700 tokens=tokens(~cellfun(@isempty,tokens)); -0701 end -0702 -0703 -0704 function rule=gprBuildRule(tokens) -0705 % Reconstruct a GPR rule string from a token list. -0706 if isempty(tokens) -0707 rule=''; -0708 return -0709 end -0710 rule=strjoin(tokens,' '); -0711 rule=strrep(rule,'( ','('); -0712 rule=strrep(rule,' )',')'); +0683 if ~preserveNotes || ~isfield(draftModel,'rxnConfidenceScores') +0684 draftModel.rxnConfidenceScores=NaN(length(draftModel.rxns),1); +0685 draftModel.rxnConfidenceScores(:)=2; +0686 end +0687 draftModel=deleteUnusedGenes(draftModel,0); +0688 %Standardize grRules and notify if problematic grRules are found +0689 [draftModel.grRules,draftModel.rxnGeneMat]=standardizeGrRules(draftModel,false); +0690 draftModel=deleteUnusedGenes(draftModel,false); +0691 end +0692 +0693 +0694 function tokens=gprTokenize(rule) +0695 % Split a GPR rule into tokens: gene IDs, "and", "or", "(", ")". +0696 % Brackets are separated from adjacent text by whitespace before splitting. +0697 if isempty(rule) +0698 tokens={}; +0699 return +0700 end +0701 r=regexprep(rule,'([\(\)])',' $1 '); +0702 r=regexprep(r,'\s+',' '); +0703 tokens=strsplit(strtrim(r),' '); +0704 tokens=tokens(~cellfun(@isempty,tokens)); +0705 end +0706 +0707 +0708 function rule=gprBuildRule(tokens) +0709 % Reconstruct a GPR rule string from a token list. +0710 if isempty(tokens) +0711 rule=''; +0712 return 0713 end -0714 -0715 -0716 function tokens=gprReplaceToken(tokens,oldGene,newGenes) -0717 % Replace each occurrence of token oldGene with the gene(s) in newGenes. -0718 % newGenes is a cell array; multiple genes are wrapped in parentheses and -0719 % joined with " or ". -0720 k=1; -0721 while k<=numel(tokens) -0722 if strcmp(tokens{k},oldGene) -0723 if numel(newGenes)==1 -0724 tokens{k}=newGenes{1}; -0725 k=k+1; -0726 else -0727 seq={'('}; -0728 for g=1:numel(newGenes) -0729 seq{end+1}=newGenes{g}; %#ok<AGROW> -0730 if g<numel(newGenes) -0731 seq{end+1}='or'; %#ok<AGROW> -0732 end -0733 end -0734 seq{end+1}=')'; -0735 tokens=[tokens(1:k-1),seq,tokens(k+1:end)]; -0736 k=k+numel(seq); -0737 end -0738 else -0739 k=k+1; -0740 end -0741 end -0742 end -0743 -0744 -0745 function rule=gprSimplify(rule,removePrefix) -0746 % Rewrite a grRule as a tree: genes starting with removePrefix (if any) are -0747 % dropped from their group, groups of the same operator are flattened, -0748 % repeated genes or groups are removed, and brackets appear only around a -0749 % group of the other operator. A rule with no gene left becomes ''. -0750 tokens=gprTokenize(rule); -0751 if isempty(tokens) -0752 rule=''; -0753 return -0754 end -0755 [node,~]=gprParseOr(tokens,1); -0756 node=gprPrune(node,removePrefix); -0757 if isempty(node) -0758 rule=''; -0759 else -0760 rule=gprRender(node,''); -0761 end -0762 end -0763 -0764 -0765 function [node,pos]=gprParseOr(tokens,pos) -0766 [node,pos]=gprParseAnd(tokens,pos); -0767 kids={node}; -0768 while pos<=numel(tokens) && strcmpi(tokens{pos},'or') -0769 [node,pos]=gprParseAnd(tokens,pos+1); -0770 kids{end+1}=node; %#ok<AGROW> -0771 end -0772 if numel(kids)>1 -0773 node=struct('op','or','gene','','kids',{kids}); -0774 end +0714 rule=strjoin(tokens,' '); +0715 rule=strrep(rule,'( ','('); +0716 rule=strrep(rule,' )',')'); +0717 end +0718 +0719 +0720 function tokens=gprReplaceToken(tokens,oldGene,newGenes) +0721 % Replace each occurrence of token oldGene with the gene(s) in newGenes. +0722 % newGenes is a cell array; multiple genes are wrapped in parentheses and +0723 % joined with " or ". +0724 k=1; +0725 while k<=numel(tokens) +0726 if strcmp(tokens{k},oldGene) +0727 if numel(newGenes)==1 +0728 tokens{k}=newGenes{1}; +0729 k=k+1; +0730 else +0731 seq={'('}; +0732 for g=1:numel(newGenes) +0733 seq{end+1}=newGenes{g}; %#ok<AGROW> +0734 if g<numel(newGenes) +0735 seq{end+1}='or'; %#ok<AGROW> +0736 end +0737 end +0738 seq{end+1}=')'; +0739 tokens=[tokens(1:k-1),seq,tokens(k+1:end)]; +0740 k=k+numel(seq); +0741 end +0742 else +0743 k=k+1; +0744 end +0745 end +0746 end +0747 +0748 +0749 function rule=gprSimplify(rule,removePrefix) +0750 % Rewrite a grRule as a tree: genes starting with removePrefix (if any) are +0751 % dropped from their group, groups of the same operator are flattened, +0752 % repeated genes or groups are removed, and brackets appear only around a +0753 % group of the other operator. A rule with no gene left becomes ''. +0754 tokens=gprTokenize(rule); +0755 if isempty(tokens) +0756 rule=''; +0757 return +0758 end +0759 [node,~]=gprParseOr(tokens,1); +0760 node=gprPrune(node,removePrefix); +0761 if isempty(node) +0762 rule=''; +0763 else +0764 rule=gprRender(node,''); +0765 end +0766 end +0767 +0768 +0769 function [node,pos]=gprParseOr(tokens,pos) +0770 [node,pos]=gprParseAnd(tokens,pos); +0771 kids={node}; +0772 while pos<=numel(tokens) && strcmpi(tokens{pos},'or') +0773 [node,pos]=gprParseAnd(tokens,pos+1); +0774 kids{end+1}=node; %#ok<AGROW> 0775 end -0776 -0777 -0778 function [node,pos]=gprParseAnd(tokens,pos) -0779 [node,pos]=gprParseAtom(tokens,pos); -0780 kids={node}; -0781 while pos<=numel(tokens) && strcmpi(tokens{pos},'and') -0782 [node,pos]=gprParseAtom(tokens,pos+1); -0783 kids{end+1}=node; %#ok<AGROW> -0784 end -0785 if numel(kids)>1 -0786 node=struct('op','and','gene','','kids',{kids}); -0787 end +0776 if numel(kids)>1 +0777 node=struct('op','or','gene','','kids',{kids}); +0778 end +0779 end +0780 +0781 +0782 function [node,pos]=gprParseAnd(tokens,pos) +0783 [node,pos]=gprParseAtom(tokens,pos); +0784 kids={node}; +0785 while pos<=numel(tokens) && strcmpi(tokens{pos},'and') +0786 [node,pos]=gprParseAtom(tokens,pos+1); +0787 kids{end+1}=node; %#ok<AGROW> 0788 end -0789 -0790 -0791 function [node,pos]=gprParseAtom(tokens,pos) -0792 if pos>numel(tokens) -0793 error('Malformed grRule: it ends with an operator'); -0794 end -0795 if strcmp(tokens{pos},'(') -0796 [node,pos]=gprParseOr(tokens,pos+1); -0797 if pos>numel(tokens) || ~strcmp(tokens{pos},')') -0798 error('Malformed grRule: unbalanced brackets'); -0799 end -0800 pos=pos+1; -0801 else -0802 node=struct('op','gene','gene',tokens{pos},'kids',{{}}); -0803 pos=pos+1; -0804 end -0805 end -0806 -0807 -0808 function node=gprPrune(node,removePrefix) -0809 if strcmp(node.op,'gene') -0810 if ~isempty(removePrefix) && startsWith(node.gene,removePrefix) -0811 node=[]; -0812 end -0813 return -0814 end -0815 kids={}; -0816 seen={}; -0817 for k=1:numel(node.kids) -0818 kid=gprPrune(node.kids{k},removePrefix); -0819 if isempty(kid) -0820 continue -0821 end -0822 if strcmp(kid.op,node.op) -0823 parts=kid.kids; -0824 else -0825 parts={kid}; -0826 end -0827 for m=1:numel(parts) -0828 key=gprRender(parts{m},node.op); -0829 if ~ismember(key,seen) -0830 seen{end+1}=key; %#ok<AGROW> -0831 kids{end+1}=parts{m}; %#ok<AGROW> -0832 end -0833 end -0834 end -0835 if isempty(kids) -0836 node=[]; -0837 elseif numel(kids)==1 -0838 node=kids{1}; -0839 else -0840 node.kids=kids; -0841 end -0842 end -0843 -0844 -0845 function rule=gprRender(node,parentOp) -0846 if strcmp(node.op,'gene') -0847 rule=node.gene; -0848 return -0849 end -0850 parts=cellfun(@(k) gprRender(k,node.op),node.kids,'UniformOutput',false); -0851 rule=strjoin(parts,[' ' node.op ' ']); -0852 if ~isempty(parentOp) && ~strcmp(parentOp,node.op) -0853 rule=['(' rule ')']; -0854 end -0855 end +0789 if numel(kids)>1 +0790 node=struct('op','and','gene','','kids',{kids}); +0791 end +0792 end +0793 +0794 +0795 function [node,pos]=gprParseAtom(tokens,pos) +0796 if pos>numel(tokens) +0797 error('Malformed grRule: it ends with an operator'); +0798 end +0799 if strcmp(tokens{pos},'(') +0800 [node,pos]=gprParseOr(tokens,pos+1); +0801 if pos>numel(tokens) || ~strcmp(tokens{pos},')') +0802 error('Malformed grRule: unbalanced brackets'); +0803 end +0804 pos=pos+1; +0805 else +0806 node=struct('op','gene','gene',tokens{pos},'kids',{{}}); +0807 pos=pos+1; +0808 end +0809 end +0810 +0811 +0812 function node=gprPrune(node,removePrefix) +0813 if strcmp(node.op,'gene') +0814 if ~isempty(removePrefix) && startsWith(node.gene,removePrefix) +0815 node=[]; +0816 end +0817 return +0818 end +0819 kids={}; +0820 seen={}; +0821 for k=1:numel(node.kids) +0822 kid=gprPrune(node.kids{k},removePrefix); +0823 if isempty(kid) +0824 continue +0825 end +0826 if strcmp(kid.op,node.op) +0827 parts=kid.kids; +0828 else +0829 parts={kid}; +0830 end +0831 for m=1:numel(parts) +0832 key=gprRender(parts{m},node.op); +0833 if ~ismember(key,seen) +0834 seen{end+1}=key; %#ok<AGROW> +0835 kids{end+1}=parts{m}; %#ok<AGROW> +0836 end +0837 end +0838 end +0839 if isempty(kids) +0840 node=[]; +0841 elseif numel(kids)==1 +0842 node=kids{1}; +0843 else +0844 node.kids=kids; +0845 end +0846 end +0847 +0848 +0849 function rule=gprRender(node,parentOp) +0850 if strcmp(node.op,'gene') +0851 rule=node.gene; +0852 return +0853 end +0854 parts=cellfun(@(k) gprRender(k,node.op),node.kids,'UniformOutput',false); +0855 rule=strjoin(parts,[' ' node.op ' ']); +0856 if ~isempty(parentOp) && ~strcmp(parentOp,node.op) +0857 rule=['(' rule ')']; +0858 end +0859 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/reconstruction/homology/hitsFromSuppOrth.html b/docs/html/reconstruction/homology/hitsFromSuppOrth.html index ba1e3830..64cff190 100644 --- a/docs/html/reconstruction/homology/hitsFromSuppOrth.html +++ b/docs/html/reconstruction/homology/hitsFromSuppOrth.html @@ -44,7 +44,7 @@

    DESCRIPTION ^SOURCE CODE ^% organismID : char 0017 % SuppOrth species label of the organism to build a model for: the 0018 % FASTA file name without extension, or the --labels value. It becomes -0019 % fromId/toId unless speciesToModel maps it to another id. +0019 % fromId and toId unless speciesToModel maps it to another id. 0020 % 0021 % Name-Value Arguments 0022 % -------------------- diff --git a/docs/html/reconstruction/homology/makeFakeBlastStructure.html b/docs/html/reconstruction/homology/makeFakeBlastStructure.html index 287b2148..04b56599 100644 --- a/docs/html/reconstruction/homology/makeFakeBlastStructure.html +++ b/docs/html/reconstruction/homology/makeFakeBlastStructure.html @@ -29,12 +29,11 @@

    SYNOPSIS ^DESCRIPTION ^

     makeFakeBlastStructure  Make a fake blastStructure from an ortholog list.
     
    - This is a structure that would normally be generated by getBlast. It
    - allows to feed a predefined list of orthologs to getModelFromHomology
    - while retaining the further use of that function. For this function to
    - work, it is crucial that the orthologList is a cell array where the first
    - column contains the genes from the source organism, and the second column
    - contains the genes from the target organism.
    + The structure has the format that getBlast generates. It feeds a
    + predefined list of orthologs to getModelFromHomology. The orthologList
    + is a cell array with at least 10 rows and two columns: the first column
    + contains the genes from the source organism and the second column the
    + genes from the target organism.
     
      Parameters
      ----------
    @@ -52,9 +51,10 @@ 

    DESCRIPTION ^SOURCE CODE ^
    0001 function blastStructure=makeFakeBlastStructure(orthologList,sourceModelID,getModelFor)
     0002 % makeFakeBlastStructure  Make a fake blastStructure from an ortholog list.
     0003 %
    -0004 % This is a structure that would normally be generated by getBlast. It
    -0005 % allows to feed a predefined list of orthologs to getModelFromHomology
    -0006 % while retaining the further use of that function. For this function to
    -0007 % work, it is crucial that the orthologList is a cell array where the first
    -0008 % column contains the genes from the source organism, and the second column
    -0009 % contains the genes from the target organism.
    -0010 %
    -0011 % Parameters
    -0012 % ----------
    -0013 % orthologList : cell
    -0014 %     cell array of orthologous genes, where the first column contains the
    -0015 %     genes from the source organism, while the second column contains the
    -0016 %     genes from the target organism.
    -0017 % sourceModelID : char
    -0018 %     ID of the model that will be used as template, that contains the
    -0019 %     genes in the first column of orthologList.
    -0020 % getModelFor : char
    -0021 %     the name of the organism to build a model for, identical to the
    -0022 %     getModelFor parameter in the getModelFromHomology function.
    -0023 %
    -0024 % Returns
    -0025 % -------
    -0026 % blastStructure : struct
    -0027 %     a fake blastStructure, where the evalue, identity and aligLen are
    -0028 %     set at extreme values, such that all orthologous pairs will pass the
    -0029 %     filter when running getModelFromHomology.
    +0004 % The structure has the format that getBlast generates. It feeds a
    +0005 % predefined list of orthologs to getModelFromHomology. The orthologList
    +0006 % is a cell array with at least 10 rows and two columns: the first column
    +0007 % contains the genes from the source organism and the second column the
    +0008 % genes from the target organism.
    +0009 %
    +0010 % Parameters
    +0011 % ----------
    +0012 % orthologList : cell
    +0013 %     cell array of orthologous genes, where the first column contains the
    +0014 %     genes from the source organism, while the second column contains the
    +0015 %     genes from the target organism.
    +0016 % sourceModelID : char
    +0017 %     ID of the model that will be used as template, that contains the
    +0018 %     genes in the first column of orthologList.
    +0019 % getModelFor : char
    +0020 %     the name of the organism to build a model for, identical to the
    +0021 %     getModelFor parameter in the getModelFromHomology function.
    +0022 %
    +0023 % Returns
    +0024 % -------
    +0025 % blastStructure : struct
    +0026 %     a fake blastStructure with one element per direction. For every pair,
    +0027 %     evalue is 0, identity is 100, aligLen is 1000, bitscore is 100 and
    +0028 %     ppos is 100. All orthologous pairs pass the filters of
    +0029 %     getModelFromHomology.
     0030 %
     0031 % Examples
     0032 % --------
    diff --git a/docs/html/reconstruction/kegg/buildGlobalGPR.html b/docs/html/reconstruction/kegg/buildGlobalGPR.html
    index ad5ee699..d26ceb5e 100644
    --- a/docs/html/reconstruction/kegg/buildGlobalGPR.html
    +++ b/docs/html/reconstruction/kegg/buildGlobalGPR.html
    @@ -4,7 +4,7 @@
     
       Description of buildGlobalGPR
       
    -  
    +  
       
       
       
    @@ -21,19 +21,19 @@ 

    buildGlobalGPR

    PURPOSE ^

    -
    buildGlobalGPR Join every KEGG organism's genes onto the reference
    +
    buildGlobalGPR Join the genes of all KEGG organisms to reference reactions by KO id.

    SYNOPSIS ^

    function [genes,rxnGeneMat]=buildGlobalGPR(rxns,koReaction,organismGeneKO)

    DESCRIPTION ^

    -
     buildGlobalGPR  Join every KEGG organism's genes onto the reference
    - reactions through their shared KO (KEGG Orthology) ids.
    +
     buildGlobalGPR  Join the genes of all KEGG organisms to reference reactions by KO id.
     
    - Builds two sparse gene-KO / KO-reaction incidence matrices sharing one
    - KO axis, and multiplies them, rather than expanding the join row by
    - row --- organismGeneKO can carry millions of rows (every gene of every
    - KEGG organism), so a per-row loop is not viable.
    + Joins every KEGG organism's genes onto the reference reactions through their
    + shared KO (KEGG Orthology) ids. The function builds two sparse incidence
    + matrices (gene to KO and KO to reaction) that share one KO axis, and
    + multiplies them. The join is not expanded row by row, because organismGeneKO
    + can contain millions of rows (every gene of every KEGG organism).
     
      Parameters
      ----------
    @@ -50,7 +50,7 @@ 

    DESCRIPTION ^CROSS-REFERENCE INFORMATION ^
 
 <h2><a name=SOURCE CODE ^

    0001 function [genes,rxnGeneMat]=buildGlobalGPR(rxns,koReaction,organismGeneKO)
    -0002 % buildGlobalGPR  Join every KEGG organism's genes onto the reference
    -0003 % reactions through their shared KO (KEGG Orthology) ids.
    -0004 %
    -0005 % Builds two sparse gene-KO / KO-reaction incidence matrices sharing one
    -0006 % KO axis, and multiplies them, rather than expanding the join row by
    -0007 % row --- organismGeneKO can carry millions of rows (every gene of every
    -0008 % KEGG organism), so a per-row loop is not viable.
    +0002 % buildGlobalGPR  Join the genes of all KEGG organisms to reference reactions by KO id.
    +0003 %
    +0004 % Joins every KEGG organism's genes onto the reference reactions through their
    +0005 % shared KO (KEGG Orthology) ids. The function builds two sparse incidence
    +0006 % matrices (gene to KO and KO to reaction) that share one KO axis, and
    +0007 % multiplies them. The join is not expanded row by row, because organismGeneKO
    +0008 % can contain millions of rows (every gene of every KEGG organism).
     0009 %
     0010 % Parameters
     0011 % ----------
    @@ -90,7 +90,7 @@ 

    SOURCE CODE ^% genes : cell array 0023 % sorted unique 'organism:gene' identifiers. 0024 % rxnGeneMat : sparse double -0025 % numel(rxns) x numel(genes); rxnGeneMat(i,j)=1 when gene j shares at +0025 % numel(rxns) by numel(genes); rxnGeneMat(i,j)=1 when gene j shares at 0026 % least one KO with reaction i. 0027 0028 %Map each ko_reaction row onto the reference model's own reaction order; diff --git a/docs/html/reconstruction/kegg/buildGlobalKEGGModel.html b/docs/html/reconstruction/kegg/buildGlobalKEGGModel.html index 09133068..6437c0fc 100644 --- a/docs/html/reconstruction/kegg/buildGlobalKEGGModel.html +++ b/docs/html/reconstruction/kegg/buildGlobalKEGGModel.html @@ -30,10 +30,10 @@

    DESCRIPTION ^
     buildGlobalKEGGModel  Download and assemble the global KEGG model.
     
      Fetches the toolbox-assets <kegg version>_core.tar.gz bundle (the
    - gene-free reference model plus the ko_reaction / organism_gene_ko /
    - rxn_flags relational tables), then joins every organism's
    - genes onto the reference reactions through their shared KO ids. The
    - KEGG version is read from keggDataVersion, not hardcoded here.
    + gene-free reference model plus the ko_reaction, organism_gene_ko and
    + rxn_flags relational tables), then joins every organism's genes onto
    + the reference reactions through their shared KO ids. The KEGG version
    + is read from keggDataVersion.
     
      Parameters
      ----------
    @@ -43,9 +43,9 @@ 

    DESCRIPTION ^DESCRIPTION ^CROSS-REFERENCE INFORMATION ^

    This function calls:
      -
    • buildGlobalGPR buildGlobalGPR Join every KEGG organism's genes onto the reference
    • keggDataVersion keggDataVersion The toolbox-assets release tag for KEGG artefacts.
    • readKEGGTable readKEGGTable Read a gzipped KEGG relational table into a table with
    +
  • buildGlobalGPR buildGlobalGPR Join the genes of all KEGG organisms to reference reactions by KO id.
  • keggDataVersion keggDataVersion The toolbox-assets release tag for KEGG artefacts.
  • readKEGGTable readKEGGTable Read a gzipped KEGG relational table into a table of cellstr columns.
  • This function is called by: @@ -72,10 +72,10 @@

    SOURCE CODE ^% buildGlobalKEGGModel Download and assemble the global KEGG model. 0003 % 0004 % Fetches the toolbox-assets <kegg version>_core.tar.gz bundle (the -0005 % gene-free reference model plus the ko_reaction / organism_gene_ko / -0006 % rxn_flags relational tables), then joins every organism's -0007 % genes onto the reference reactions through their shared KO ids. The -0008 % KEGG version is read from keggDataVersion, not hardcoded here. +0005 % gene-free reference model plus the ko_reaction, organism_gene_ko and +0006 % rxn_flags relational tables), then joins every organism's genes onto +0007 % the reference reactions through their shared KO ids. The KEGG version +0008 % is read from keggDataVersion. 0009 % 0010 % Parameters 0011 % ---------- @@ -85,9 +85,9 @@

    SOURCE CODE ^% Returns 0016 % ------- 0017 % model : struct -0018 % the full global KEGG model (all reactions/metabolites, genes and -0019 % rxnGeneMat spanning every KEGG organism). Callers narrow this down -0020 % to one organism (see getKEGGModelForOrganism). +0018 % the full global KEGG model (all reactions and metabolites, genes and +0019 % rxnGeneMat spanning every KEGG organism). getKEGGModelForOrganism +0020 % narrows it to one organism. 0021 % KOModel : struct 0022 % a minimal model struct whose rxns are the KO ids linked to at 0023 % least one reaction (getKEGGModelForOrganism's HMM-search path uses diff --git a/docs/html/reconstruction/kegg/getKEGGModelForOrganism.html b/docs/html/reconstruction/kegg/getKEGGModelForOrganism.html index 28ab0731..ee40d40a 100644 --- a/docs/html/reconstruction/kegg/getKEGGModelForOrganism.html +++ b/docs/html/reconstruction/kegg/getKEGGModelForOrganism.html @@ -31,84 +31,83 @@

    DESCRIPTION ^DESCRIPTION ^SOURCE CODE ^% 0004 % Reconstructs a genome-scale metabolic model based on protein homology to 0005 % the orthologies in KEGG. If the target species is not available in KEGG, -0006 % the user must select a closely related species. It is also possible to -0007 % circumvent protein homology search (see fastaFile parameter for more -0008 % details). +0006 % the function stops with an error; give a closely related species as +0007 % organismID. Without a fastaFile, the +0008 % protein homology search is skipped (see the fastaFile parameter). 0009 % 0010 % Parameters 0011 % ---------- 0012 % organismID : char 0013 % three or four letter abbreviation of the organism (as used in KEGG). -0014 % If not available, use a closely related species. This is used for -0015 % determing the phylogenetic distance. Use "eukaryotes" or "prokaryotes" -0016 % to get a model for the whole domain. Only applicable if fastaFile is -0017 % empty, i.e. no homology search should be performed. -0018 % -0019 % Name-Value Arguments -0020 % -------------------- -0021 % fastaFile : char -0022 % a FASTA file that contains the protein sequences of the organism for -0023 % which to reconstruct a model. If no FASTA file is supplied then a -0024 % model is reconstructed based only on the organism abbreviation. This -0025 % option ignores all settings except for keepSpontaneous, -0026 % keepUndefinedStoich, keepIncomplete and keepGeneral. -0027 % dataDir : char -0028 % directory for which to retrieve the input data, styled as -0029 % kegg118_prokaryotes or kegg118_eukaryotes, indicating the KEGG version -0030 % and whether the HMMs were trained on pro- or eukaryotic sequences. The -0031 % directory name matches the published HMM library it is paired with. The -0032 % prebuilt concatenated KO HMM library (dataDir.hmm) is downloaded here -0033 % from the corresponding toolbox-assets release if not already present. This -0034 % parameter should ALWAYS be provided. -0035 % outDir : char -0036 % directory to save the results from the quering of the hidden Markov -0037 % models. The output is specific for the input sequences and the -0038 % settings used. It is stored in this manner so that the function can -0039 % continue if interrupted or if it should run in parallel. Be careful -0040 % not to leave output files from different organisms or runs with -0041 % different settings in the same folder. They will not be overwritten -0042 % (default is a temporary dir where all *.out files are deleted before -0043 % and after doing the reconstruction). -0044 % keepSpontaneous : logical -0045 % include reactions labeled as "spontaneous" (default true). -0046 % keepUndefinedStoich : logical -0047 % include reactions in the form n A <=> n+1 A. These will be dealt with -0048 % as two separate metabolites (default true). -0049 % keepIncomplete : logical -0050 % include reactions which have been labelled as "incomplete", -0051 % "erroneous" or "unclear" (default true). -0052 % keepGeneral : logical -0053 % include reactions which have been labelled as "general reaction". -0054 % These are reactions on the form "an aldehyde <=> an alcohol", and are -0055 % therefore unsuited for modelling purposes. Note that not all reactions -0056 % have this type of annotation, and the script will therefore not be -0057 % able to remove all such reactions (default false). -0058 % cutOff : double -0059 % significance score from HMMer needed to assign genes to a KO (default -0060 % 10^-30). -0061 % minScoreRatioKO : double -0062 % ignore genes in a KO if their score is <log(score)/log(best score in -0063 % KO). This is to "prune" KOs which have many genes and where some are -0064 % clearly a better fit (default 0.3, lower is less strict). -0065 % minScoreRatioG : double -0066 % a gene is only assigned to KOs for which the score is -0067 % >=log(score)/log(best score) for that gene. This is to prevent that a -0068 % gene which clearly belongs to one KO is assigned also to KOs with much -0069 % lower scores (default 0.9, lower is less strict). -0070 % maxPhylDist : double -0071 % -1 to only use sequences from the same domain (Prokaryota, Eukaryota); -0072 % any other (positive) value to only use sequences for organisms where -0073 % the phylogenetic distance is at the most this large (as calculated in -0074 % getPhylDist). Only used when reconstructing from KEGG annotations (no -0075 % fastaFile) (default Inf, which means that all sequences will be used). -0076 % globalModel : struct -0077 % structure containing both model and KOModel structures as generated -0078 % by getModelFromKEGG. These will otherwise be loaded by via -0079 % getModelFromKEGG. Providing globalKEGGmodel can speed up model -0080 % generation if getKEGGModelForOrganism is run multiple times for -0081 % different strains. Example: -0082 % [globalModel.model,globalModel.KOModel] = getModelFromKEGG (default -0083 % empty, global model is loaded by getModelFromKEGG). -0084 % -0085 % Returns -0086 % ------- -0087 % model : struct -0088 % the reconstructed model. -0089 % -0090 % Notes -0091 % ----- -0092 % The reconstruction works in one of two modes: -0093 % -0094 % 1. From KEGG annotations (no fastaFile supplied). The reactions -0095 % associated with organismID in the local KEGG database are kept; -0096 % maxPhylDist controls which organism annotations are considered. -0097 % 2. From protein homology (fastaFile supplied). The query proteome is -0098 % searched, in a single hmmsearch, against a prebuilt KEGG-version- and -0099 % domain-specific concatenated KO HMM library (e.g. kegg118_eukaryotes), -0100 % downloaded from the corresponding RAVEN release if not already present -0101 % in dataDir. Hits are filtered by cutOff and the minScoreRatioKO / -0102 % minScoreRatioG ratios into a KO-gene matrix, from which the model is -0103 % built (spontaneous reactions without genes are kept if keepSpontaneous -0104 % is true). -0105 % -0106 % The global KEGG model (reaction-KO relationships) is either supplied via -0107 % globalModel, built from a local KEGG FTP dump in dataDir\keggdb (which -0108 % requires a KEGG FTP license), or loaded from the internal RAVEN version. -0109 % -0110 % Examples -0111 % -------- -0112 % model=getKEGGModelForOrganism(organismID,fastaFile,dataDir,... -0113 % outDir,keepSpontaneous,keepUndefinedStoich,keepIncomplete,... -0114 % keepGeneral,cutOff,minScoreRatioKO,minScoreRatioG,maxPhylDist); -0115 -0116 p=parseRAVENargs(varargin, {'fastaFile',[]; 'dataDir',[]; 'outDir',[]; ... -0117 'keepSpontaneous',true; 'keepUndefinedStoich',true; ... -0118 'keepIncomplete',true; 'keepGeneral',false; 'cutOff',10^-30; ... -0119 'minScoreRatioKO',0.3; 'minScoreRatioG',0.9; 'maxPhylDist',inf; ... -0120 'globalModel',[]}); -0121 fastaFile=p.fastaFile; -0122 if isempty(fastaFile) -0123 fastaFile=[]; -0124 else -0125 fastaFile=char(fastaFile); -0126 end -0127 dataDir=p.dataDir; -0128 if isempty(dataDir) -0129 dataDir=[]; -0130 else -0131 dataDir=char(dataDir); -0132 end -0133 outDir=p.outDir; -0134 if isempty(outDir) -0135 outDir=tempdir; -0136 %Delete all *.out files if any exist -0137 delete(fullfile(outDir,'*.out')); -0138 else -0139 outDir=char(outDir); -0140 end -0141 keepSpontaneous=p.keepSpontaneous; -0142 if isempty(keepSpontaneous) -0143 keepSpontaneous=true; -0144 end -0145 keepUndefinedStoich=p.keepUndefinedStoich; -0146 if isempty(keepUndefinedStoich) -0147 keepUndefinedStoich=true; -0148 end -0149 keepIncomplete=p.keepIncomplete; -0150 if isempty(keepIncomplete) -0151 keepIncomplete=true; -0152 end -0153 keepGeneral=p.keepGeneral; -0154 if isempty(keepGeneral) -0155 keepGeneral=false; -0156 end -0157 cutOff=p.cutOff; -0158 if isempty(cutOff) -0159 cutOff=10^-30; -0160 end -0161 minScoreRatioKO=p.minScoreRatioKO; -0162 if isempty(minScoreRatioKO) -0163 minScoreRatioKO=0.3; -0164 end -0165 minScoreRatioG=p.minScoreRatioG; -0166 if isempty(minScoreRatioG) -0167 minScoreRatioG=0.9; -0168 end -0169 maxPhylDist=p.maxPhylDist; -0170 if isempty(maxPhylDist) -0171 maxPhylDist=inf; -0172 %Include all sequences for each reaction -0173 end -0174 globalModel=p.globalModel; -0175 -0176 if isempty(fastaFile) -0177 fprintf(['\n*** The model reconstruction from KEGG based on the annotation available for KEGG Species <strong>' organismID '</strong> ***\n\n']); -0178 else -0179 fprintf('\n*** The model reconstruction from KEGG based on the protein homology search against KEGG Orthology specific HMMs ***\n\n'); -0180 %Check if query fasta exists -0181 fastaFile=checkFileExistence(fastaFile,2); %Copy file to temp dir -0182 end -0183 -0184 %Run the external binaries multi-threaded to use all logical cores assigned -0185 %to MATLAB -0186 cores = evalc('feature(''numcores'')'); -0187 cores = strsplit(cores, 'MATLAB was assigned: '); -0188 cores = regexp(cores{2},'^\d*','match'); -0189 cores = cores{1}; -0190 -0191 %Get the directory for RAVEN Toolbox. -0192 ravenPath=findRAVENroot(); +0014 % If the organism is not available, a closely related species can be +0015 % given. The abbreviation determines the genes that are kept when +0016 % fastaFile is empty. "eukaryotes" or "prokaryotes" gives a model for +0017 % the whole domain. Only applicable if fastaFile is empty, that is when +0018 % no homology search is performed. +0019 % +0020 % Name-Value Arguments +0021 % -------------------- +0022 % fastaFile : char +0023 % a FASTA file that contains the protein sequences of the organism for +0024 % which to reconstruct a model. If no FASTA file is supplied then a +0025 % model is reconstructed based only on the organism abbreviation. In this +0026 % case, cutOff, minScoreRatioKO, minScoreRatioG and dataDir are not +0027 % used (default []). +0028 % dataDir : char +0029 % directory of the HMM library, named kegg118_prokaryotes or +0030 % kegg118_eukaryotes. The name gives the KEGG version and whether the +0031 % HMMs were trained on pro- or eukaryotic sequences, and matches the +0032 % published HMM library it is paired with. The prebuilt concatenated KO +0033 % HMM library (dataDir.hmm) is downloaded from the corresponding +0034 % toolbox-assets release if it is not already present. Required when +0035 % fastaFile is supplied (default []). +0036 % outDir : char +0037 % directory that is created if it does not exist when fastaFile is +0038 % supplied. The function writes no result files to it (default is the +0039 % temporary directory, where all *.out files are deleted before the +0040 % reconstruction). +0041 % keepSpontaneous : logical +0042 % include reactions labelled as "spontaneous" (default true). +0043 % keepUndefinedStoich : logical +0044 % include reactions in the form n A <=> n+1 A. These are treated as +0045 % two separate metabolites (default true). +0046 % keepIncomplete : logical +0047 % include reactions which have been labelled as "incomplete", +0048 % "erroneous" or "unclear" (default true). +0049 % keepGeneral : logical +0050 % include reactions which have been labelled as "general reaction". +0051 % These are reactions of the form "an aldehyde <=> an alcohol". Not all +0052 % such reactions have this label, so the function cannot remove all of +0053 % them (default false). +0054 % cutOff : double +0055 % maximum full-sequence E-value from HMMER for assigning a gene to a KO +0056 % (default 10^-30). +0057 % minScoreRatioKO : double +0058 % a gene is removed from a KO if log(score)/log(best score in KO) is +0059 % below this value, where score is the E-value of the gene. This +0060 % removes genes from KOs with many genes where some have a lower +0061 % E-value (default 0.3, lower is less strict). +0062 % minScoreRatioG : double +0063 % a gene is only assigned to KOs for which log(score)/log(best score) +0064 % is at least this value, where best score is the lowest E-value of +0065 % that gene over its KOs after the minScoreRatioKO step. This removes the +0066 % assignment of a gene to KOs with a much higher E-value than its best KO +0067 % (default 0.9, lower is less strict). +0068 % maxPhylDist : double +0069 % only used when fastaFile is empty and organismID is "eukaryotes" or +0070 % "prokaryotes". -1 keeps genes from organisms in the same domain +0071 % (Prokaryota, Eukaryota) as H. sapiens ("hsa") or E. coli ("eco"), +0072 % respectively. Any other value, including Inf, does not restrict the +0073 % domain, and the value is not compared with the distances calculated +0074 % in getPhylDist (default Inf). +0075 % globalModel : struct +0076 % structure containing both model and KOModel structures as generated +0077 % by getModelFromKEGG. When empty, the global model is loaded with +0078 % getModelFromKEGG. Providing globalModel avoids reloading it when +0079 % getKEGGModelForOrganism is run several times, for example for +0080 % different strains. Example: +0081 % [globalModel.model,globalModel.KOModel] = getModelFromKEGG (default +0082 % []). +0083 % +0084 % Returns +0085 % ------- +0086 % model : struct +0087 % the reconstructed model. +0088 % +0089 % Notes +0090 % ----- +0091 % The reconstruction works in one of two modes: +0092 % +0093 % 1. From KEGG annotations (no fastaFile supplied). The reactions +0094 % associated with organismID in the local KEGG database are kept; +0095 % maxPhylDist applies to organismID "eukaryotes" and "prokaryotes". +0096 % 2. From protein homology (fastaFile supplied). The query proteome is +0097 % searched, in a single hmmsearch, against a prebuilt KEGG-version- and +0098 % domain-specific concatenated KO HMM library (for example +0099 % kegg118_eukaryotes), downloaded from the corresponding toolbox-assets +0100 % release if not already present in dataDir. Hits are filtered by cutOff, +0101 % minScoreRatioKO and minScoreRatioG into a KO-gene matrix, from which the +0102 % model is built (spontaneous reactions without genes are kept if +0103 % keepSpontaneous is true). +0104 % +0105 % The global KEGG model (reaction-KO relationships) is either supplied via +0106 % globalModel or loaded with getModelFromKEGG. +0107 % +0108 % Examples +0109 % -------- +0110 % model=getKEGGModelForOrganism(organismID,fastaFile,dataDir,... +0111 % outDir,keepSpontaneous,keepUndefinedStoich,keepIncomplete,... +0112 % keepGeneral,cutOff,minScoreRatioKO,minScoreRatioG,maxPhylDist); +0113 +0114 p=parseRAVENargs(varargin, {'fastaFile',[]; 'dataDir',[]; 'outDir',[]; ... +0115 'keepSpontaneous',true; 'keepUndefinedStoich',true; ... +0116 'keepIncomplete',true; 'keepGeneral',false; 'cutOff',10^-30; ... +0117 'minScoreRatioKO',0.3; 'minScoreRatioG',0.9; 'maxPhylDist',inf; ... +0118 'globalModel',[]}); +0119 fastaFile=p.fastaFile; +0120 if isempty(fastaFile) +0121 fastaFile=[]; +0122 else +0123 fastaFile=char(fastaFile); +0124 end +0125 dataDir=p.dataDir; +0126 if isempty(dataDir) +0127 dataDir=[]; +0128 else +0129 dataDir=char(dataDir); +0130 end +0131 outDir=p.outDir; +0132 if isempty(outDir) +0133 outDir=tempdir; +0134 %Delete all *.out files if any exist +0135 delete(fullfile(outDir,'*.out')); +0136 else +0137 outDir=char(outDir); +0138 end +0139 keepSpontaneous=p.keepSpontaneous; +0140 if isempty(keepSpontaneous) +0141 keepSpontaneous=true; +0142 end +0143 keepUndefinedStoich=p.keepUndefinedStoich; +0144 if isempty(keepUndefinedStoich) +0145 keepUndefinedStoich=true; +0146 end +0147 keepIncomplete=p.keepIncomplete; +0148 if isempty(keepIncomplete) +0149 keepIncomplete=true; +0150 end +0151 keepGeneral=p.keepGeneral; +0152 if isempty(keepGeneral) +0153 keepGeneral=false; +0154 end +0155 cutOff=p.cutOff; +0156 if isempty(cutOff) +0157 cutOff=10^-30; +0158 end +0159 minScoreRatioKO=p.minScoreRatioKO; +0160 if isempty(minScoreRatioKO) +0161 minScoreRatioKO=0.3; +0162 end +0163 minScoreRatioG=p.minScoreRatioG; +0164 if isempty(minScoreRatioG) +0165 minScoreRatioG=0.9; +0166 end +0167 maxPhylDist=p.maxPhylDist; +0168 if isempty(maxPhylDist) +0169 maxPhylDist=inf; +0170 %Include all sequences for each reaction +0171 end +0172 globalModel=p.globalModel; +0173 +0174 if isempty(fastaFile) +0175 fprintf(['\n*** The model reconstruction from KEGG based on the annotation available for KEGG Species <strong>' organismID '</strong> ***\n\n']); +0176 else +0177 fprintf('\n*** The model reconstruction from KEGG based on the protein homology search against KEGG Orthology specific HMMs ***\n\n'); +0178 %Check if query fasta exists +0179 fastaFile=checkFileExistence(fastaFile,2); %Copy file to temp dir +0180 end +0181 +0182 %Run the external binaries multi-threaded to use all logical cores assigned +0183 %to MATLAB +0184 cores = evalc('feature(''numcores'')'); +0185 cores = strsplit(cores, 'MATLAB was assigned: '); +0186 cores = regexp(cores{2},'^\d*','match'); +0187 cores = cores{1}; +0188 +0189 %Get the directory for RAVEN Toolbox. +0190 ravenPath=findRAVENroot(); +0191 +0192 libraryFile=''; 0193 -0194 libraryFile=''; -0195 -0196 %Checking if dataDir is consistent. It must point to a RAVEN-provided -0197 %pre-trained HMM set, compatible with the current RAVEN version. The -0198 %reconstruction uses the concatenated KO HMM library (a single -0199 %gzip-compressed flatfile, queried in one hmmsearch); if it is not already -0200 %present it is downloaded from the corresponding toolbox-assets release -0201 %(https://github.com/CompMetEng/toolbox-assets). -0202 %Only needed for the protein-homology path (fastaFile supplied): the -0203 %annotation-only path never touches libraryFile, so skip the (100+ MB) -0204 %download/extraction entirely when there is no FASTA file to search. -0205 if ~isempty(dataDir) && ~isempty(fastaFile) -0206 kver=keggDataVersion(); -0207 hmmOptions={[kver '_eukaryotes'],[kver '_prokaryotes']}; -0208 if ~endsWith(dataDir,hmmOptions) -0209 error(['Pre-trained HMMs set is not recognised. dataDir must match one of: ' strjoin(hmmOptions,' or ')]) -0210 end -0211 %dataDir points to a RAVEN-provided set. Its name matches the -0212 %published HMM library asset, so it doubles as the download filename. -0213 hmmName=hmmOptions{endsWith(dataDir,hmmOptions)}; -0214 libraryFile=[dataDir '.hmm']; -0215 if isfile(libraryFile) -0216 fprintf(['NOTE: Found <strong>' libraryFile '</strong> HMM library, it will therefore be used during reconstruction\n']); -0217 elseif isfile([libraryFile '.gz']) -0218 fprintf('Extracting the HMM library file... '); -0219 gunzip([libraryFile '.gz']); -0220 fprintf('COMPLETE\n'); -0221 else -0222 fetchToolboxAsset(fileparts(libraryFile),kver,[hmmName '.hmm.gz']); -0223 fprintf('Extracting the HMM library file... '); -0224 gunzip([libraryFile '.gz']); -0225 fprintf('COMPLETE\n'); -0226 end -0227 %Check that the HMM library is available -0228 if ~isfile(libraryFile) -0229 error(['The HMM library seems improperly extracted and not found at ',strrep(libraryFile,'\','/'),'. Please remove the corresponding .gz file and rerun getKEGGModelForOrganism']); -0230 end -0231 end -0232 -0233 %Check if the fasta-file contains "/" or "\". If not then it is probably just -0234 %a file name. Expand to full path. -0235 if any(fastaFile) -0236 if ~any(strfind(fastaFile,'\')) && ~any(strfind(fastaFile,'/')) -0237 fastaFile=which(fastaFile); -0238 end -0239 if ~isfolder(outDir) -0240 mkdir(outDir); -0241 end -0242 end -0243 -0244 %Load the global KEGG model. Can be provided as input to skip loading -0245 %when calling getKEGGModelForOrganism multiple times for different strains. -0246 if ~isempty(globalModel) -0247 model=globalModel.model; -0248 KOModel=globalModel.KOModel; -0249 else -0250 [model, KOModel]=getModelFromKEGG(keepSpontaneous,keepUndefinedStoich,keepIncomplete,keepGeneral); -0251 end -0252 model.id=organismID; -0253 model.c=zeros(numel(model.rxns),1); -0254 -0255 %If no FASTA file is supplied, then just remove all genes which are not for -0256 %the given organism ID -0257 if isempty(fastaFile) -0258 %Check if organismID can be found in KEGG species list or is -0259 %set to "eukaryotes" or "prokaryotes" -0260 phylDistsFull=getPhylDist(false); -0261 if ~ismember(organismID,[phylDistsFull.ids 'eukaryotes' 'prokaryotes']) -0262 error('Provided organismID is incorrect. Only species abbreviations from KEGG Species List or "eukaryotes"/"prokaryotes" are allowed.'); -0263 end -0264 -0265 fprintf(['Pruning the model from <strong>non-' organismID '</strong> genes... ']); -0266 if ismember(organismID,{'eukaryotes','prokaryotes'}) -0267 phylDists=getPhylDist(maxPhylDist==-1); -0268 if strcmp(organismID,'eukaryotes') -0269 proxyid='hsa'; -0270 %Use H. sapiens here -0271 else -0272 proxyid='eco'; -0273 %Use E. coli here -0274 end -0275 [~, phylDistId]=ismember(proxyid,phylDists.ids); -0276 idsToKeep=phylDists.ids(~isinf(phylDists.distMat(phylDistId,:))); -0277 taxIDs=cellfun(@(x) x{1},cellfun(@(x) strsplit(x,':'),model.genes,'UniformOutput',false),'UniformOutput',false); -0278 I=ismember(upper(taxIDs),upper(idsToKeep)); -0279 else -0280 %KEGG organism IDs may have three or four letters -0281 organismID=strcat(organismID,':'); -0282 %Add colon for accurate matching -0283 if length(organismID)==4 -0284 I=cellfun(@(x) strcmpi(x(1:4),organismID),model.genes); -0285 elseif length(organismID)==5 -0286 I=cellfun(@(x) strcmpi(x(1:5),organismID),model.genes); -0287 end -0288 end -0289 %Remove those genes -0290 model.genes=model.genes(I); -0291 model.rxnGeneMat=model.rxnGeneMat(:,I); -0292 fprintf('COMPLETE\n'); -0293 end -0294 -0295 %First remove all reactions without genes -0296 if keepSpontaneous==true -0297 fprintf('Removing non-spontaneous reactions without GPR rules... '); -0298 load(fullfile(ravenPath,'src','reconstruction','kegg','keggModel.mat'),'isSpontaneous'); -0299 I=~any(model.rxnGeneMat,2)&~ismember(model.rxns,isSpontaneous); -0300 spontRxnsWithGenes=model.rxns(any(model.rxnGeneMat,2)&~ismember(model.rxns,isSpontaneous)); -0301 else -0302 fprintf('Removing reactions without GPR rules... '); -0303 I=~any(model.rxnGeneMat,2); -0304 end -0305 model=removeReactions(model,I,true); -0306 fprintf('COMPLETE\n'); -0307 -0308 %Clean gene names -0309 fprintf('Fixing gene names in the model... '); -0310 %Get rid of the prefix organism id -0311 model.genes=regexprep(model.genes,'^\w+?:',''); -0312 fprintf('COMPLETE\n'); -0313 -0314 %If no FASTA file is supplied, then we are done here -0315 if isempty(fastaFile) -0316 %Create grRules -0317 fprintf('Constructing GPR associations and annotations for the model... '); -0318 model.grRules=cell(numel(model.rxns),1); -0319 model.grRules(:)={''}; -0320 %Add the gene associations as "or" -0321 for i=1:numel(model.rxns) -0322 %Find the involved genes -0323 I=find(model.rxnGeneMat(i,:)); -0324 if any(I) -0325 model.grRules{i}=['(' model.genes{I(1)}]; -0326 for j=2:numel(I) -0327 model.grRules{i}=[model.grRules{i} ' or ' model.genes{I(j)}]; -0328 end -0329 model.grRules{i}=[model.grRules{i} ')']; -0330 end -0331 end -0332 %Fix grRules and reconstruct rxnGeneMat -0333 [grRules,rxnGeneMat] = standardizeGrRules(model); %Give detailed output -0334 model.grRules = grRules; -0335 model.rxnGeneMat = rxnGeneMat; -0336 %Add geneMiriams, assuming that it follows the syntax -0337 %kegg.genes/organismID:geneName -0338 model.geneMiriams=''; -0339 for i=1:numel(model.genes) -0340 model.geneMiriams{i,1}.name{1,1}='kegg.genes'; -0341 model.geneMiriams{i,1}.value{1,1}=strcat(lower(organismID),model.genes{i,1}); -0342 end -0343 %Add the description to the reactions. The reference model has no -0344 %reaction notes, so readYAMLmodel leaves the field out -0345 if ~isfield(model,'rxnNotes') -0346 model.rxnNotes=repmat({''},numel(model.rxns),1); -0347 end -0348 for i=1:numel(model.rxns) -0349 if ~isempty(model.rxnNotes{i}) -0350 model.rxnNotes(i)=strcat('Included by getKEGGModelForOrganism (without HMMs).',model.rxnNotes(i)); -0351 model.rxnNotes(i)=strrep(model.rxnNotes(i),'.','. '); -0352 else -0353 model.rxnNotes(i)={'Included by getKEGGModelForOrganism (without HMMs)'}; -0354 end -0355 end -0356 fprintf('COMPLETE\n\n'); -0357 fprintf('*** Model reconstruction complete ***\n'); -0358 return; -0359 end -0360 -0361 %Query the whole proteome against the concatenated KO HMM library in a -0362 %single hmmsearch. With the profile library as the query and the -0363 %proteome as the target sequence database, per-hit E-values stay -0364 %comparable across KOs (same search direction, same effective database -0365 %size), and no per-organism phylogenetic-distance subsampling is needed -0366 %because the library sequence set is already fixed. -0367 if ispc -0368 binEnd='.exe'; -0369 elseif ismac -0370 binEnd='.mac'; -0371 else -0372 binEnd=''; -0373 end -0374 %Fetch the HMMER binary on demand if it is not already present -0375 if ~exist(fullfile(ravenPath,'src','external','hmmer',['hmmsearch' binEnd]),'file') -0376 downloadRavenBinaries({'hmmer'}); -0377 end -0378 tblFile=[tempname '.tblout']; -0379 fprintf('Querying the user-specified FASTA file against the KEGG Orthology specific HMMs... '); -0380 [status, output]=system(['"' fullfile(ravenPath,'src','external','hmmer',['hmmsearch' binEnd]) '" --cpu "' num2str(cores) '" --tblout "' tblFile '" "' libraryFile '" "' fastaFile '"']); -0381 if status~=0 -0382 EM=['Error when querying the concatenated HMM library:\n' output]; -0383 error('RAVEN:badInput', '%s', EM); -0384 end -0385 fprintf('COMPLETE\n'); -0386 -0387 fprintf('Parsing the HMM search results... '); -0388 koGeneMat=zeros(numel(KOModel.rxns),3000); %Make room for 3000 genes -0389 genes=cell(3000,1); -0390 %Map each KO id to its row in koGeneMat -0391 koTable=java.util.Hashtable; -0392 for i=1:numel(KOModel.rxns) -0393 koTable.put(KOModel.rxns{i},i); -0394 end -0395 %Store the column index for each gene in a hash list -0396 hTable=java.util.Hashtable; -0397 geneCounter=0; -0398 fid=fopen(tblFile,'r'); -0399 while 1 -0400 tline=fgetl(fid); -0401 if ~ischar(tline) -0402 break; -0403 end -0404 %Skip the comment/header lines that hmmsearch --tblout writes -0405 if isempty(tline) || tline(1)=='#' -0406 continue; -0407 end -0408 elements=regexp(strtrim(tline),'\s+','split'); -0409 if numel(elements)<5 -0410 continue; -0411 end -0412 %With the profile library as the query, the target name (col 1) is -0413 %the proteome gene, the query name (col 3) is the KO, and the -0414 %full-sequence E-value is in col 5 -0415 gene=elements{1}; -0416 ko=elements{3}; -0417 score=str2double(elements{5}); -0418 koIdx=koTable.get(ko); -0419 if isempty(koIdx) || isnan(score) || score>cutOff -0420 continue; -0421 end -0422 %If the score is exactly 0, change it to a very small value to avoid -0423 %NaN -0424 if score==0 -0425 score=10^-250; -0426 end -0427 I=hTable.get(gene); -0428 if any(I) -0429 %Keep the best (smallest) E-value for this gene-KO pair -0430 if koGeneMat(koIdx,I)==0 || score<koGeneMat(koIdx,I) -0431 koGeneMat(koIdx,I)=score; -0432 end -0433 else -0434 geneCounter=geneCounter+1; -0435 hTable.put(gene,geneCounter); -0436 genes{geneCounter}=gene; -0437 koGeneMat(koIdx,geneCounter)=score; -0438 end -0439 end -0440 fclose(fid); -0441 delete(tblFile); -0442 fprintf('COMPLETE\n'); -0443 -0444 fprintf('Removing gene, KEGG Orthology associations below minScoreRatioKO, minScoreRatioG... '); -0445 koGeneMat=koGeneMat(:,1:geneCounter); -0446 -0447 %Remove the genes for each KO that are below minScoreRatioKO. -0448 for i=1:size(koGeneMat,1) -0449 J=find(koGeneMat(i,:)); -0450 if any(J) -0451 koGeneMat(i,J(log(koGeneMat(i,J))/log(min(koGeneMat(i,J)))<minScoreRatioKO))=0; -0452 end -0453 end -0454 -0455 %Remove the KOs for each gene that are below minScoreRatioG -0456 for i=1:size(koGeneMat,2) -0457 J=find(koGeneMat(:,i)); -0458 if any(J) -0459 koGeneMat(J(log(koGeneMat(J,i))/log(min(koGeneMat(J,i)))<minScoreRatioG),i)=0; -0460 end -0461 end -0462 fprintf('COMPLETE\n'); -0463 -0464 fprintf('Adding gene annotations to the model... '); -0465 %Create the new model -0466 model.genes=genes(1:geneCounter); -0467 model.grRules=cell(numel(model.rxns),1); -0468 model.grRules(:)={''}; -0469 model.rxnGeneMat=sparse(numel(model.rxns),numel(model.genes)); -0470 -0471 %Loop through the reactions and add the corresponding genes -0472 for i=1:numel(model.rxns) -0473 if isstruct(model.rxnMiriams{i}) -0474 %Get all KOs -0475 I=find(strcmpi(model.rxnMiriams{i}.name,'kegg.orthology')); -0476 KOs=model.rxnMiriams{i}.value(I); -0477 %Find the KOs and the corresponding genes -0478 J=ismember(KOModel.rxns,KOs); -0479 [~, K]=find(koGeneMat(J,:)); -0480 -0481 if any(K) -0482 model.rxnGeneMat(i,K)=1; -0483 %Also delete KOs for which no genes were found. If no genes at -0484 %all were matched to the reaction it will be deleted later -0485 L=sum(koGeneMat(J,:),2)==0; -0486 model.rxnMiriams{i}.value(I(L))=[]; -0487 model.rxnMiriams{i}.name(I(L))=[]; -0488 end -0489 end -0490 end -0491 fprintf('COMPLETE\n'); -0492 -0493 %Find and delete all reactions without genes. This also removes genes that -0494 %are not used (which could happen because minScoreRatioG and -0495 %minScoreRatioKO). If keepSpontaneous==true, the spontaneous reactions -0496 %without genes are kept in the model. Spontaneous reactions with original -0497 %gene associations are treated in the same way, like the rest of the -0498 %reactions - if gene associations were removed during HMM search, such -0499 %reactions are deleted from the model -0500 if keepSpontaneous==true -0501 %Not the most comprise way to delete reactions without genes, but this -0502 %makes the code easier to understand. Firstly the non-spontaneous -0503 %reactions without genes are removed. After that, the second deletion -0504 %step removes spontaneous reactions, which had gene associations before -0505 %HMM search, but no longer have after it -0506 fprintf('Removing non-spontaneous reactions which after HMM search no longer have GPR rules... '); -0507 I=~any(model.rxnGeneMat,2)&~ismember(model.rxns,isSpontaneous); +0194 %Checking if dataDir is consistent. It must point to a RAVEN-provided +0195 %pre-trained HMM set, compatible with the current RAVEN version. The +0196 %reconstruction uses the concatenated KO HMM library (a single +0197 %gzip-compressed flatfile, queried in one hmmsearch); if it is not already +0198 %present it is downloaded from the corresponding toolbox-assets release +0199 %(https://github.com/CompMetEng/toolbox-assets). +0200 %Only needed for the protein-homology path (fastaFile supplied): the +0201 %annotation-only path never touches libraryFile, so skip the (100+ MB) +0202 %download/extraction entirely when there is no FASTA file to search. +0203 if ~isempty(dataDir) && ~isempty(fastaFile) +0204 kver=keggDataVersion(); +0205 hmmOptions={[kver '_eukaryotes'],[kver '_prokaryotes']}; +0206 if ~endsWith(dataDir,hmmOptions) +0207 error(['Pre-trained HMMs set is not recognised. dataDir must match one of: ' strjoin(hmmOptions,' or ')]) +0208 end +0209 %dataDir points to a RAVEN-provided set. Its name matches the +0210 %published HMM library asset, so it doubles as the download filename. +0211 hmmName=hmmOptions{endsWith(dataDir,hmmOptions)}; +0212 libraryFile=[dataDir '.hmm']; +0213 if isfile(libraryFile) +0214 fprintf(['NOTE: Found <strong>' libraryFile '</strong> HMM library, it will therefore be used during reconstruction\n']); +0215 elseif isfile([libraryFile '.gz']) +0216 fprintf('Extracting the HMM library file... '); +0217 gunzip([libraryFile '.gz']); +0218 fprintf('COMPLETE\n'); +0219 else +0220 fetchToolboxAsset(fileparts(libraryFile),kver,[hmmName '.hmm.gz']); +0221 fprintf('Extracting the HMM library file... '); +0222 gunzip([libraryFile '.gz']); +0223 fprintf('COMPLETE\n'); +0224 end +0225 %Check that the HMM library is available +0226 if ~isfile(libraryFile) +0227 error(['The HMM library seems improperly extracted and not found at ',strrep(libraryFile,'\','/'),'. Please remove the corresponding .gz file and rerun getKEGGModelForOrganism']); +0228 end +0229 end +0230 +0231 %Check if the fasta-file contains "/" or "\". If not then it is probably just +0232 %a file name. Expand to full path. +0233 if any(fastaFile) +0234 if ~any(strfind(fastaFile,'\')) && ~any(strfind(fastaFile,'/')) +0235 fastaFile=which(fastaFile); +0236 end +0237 if ~isfolder(outDir) +0238 mkdir(outDir); +0239 end +0240 end +0241 +0242 %Load the global KEGG model. Can be provided as input to skip loading +0243 %when calling getKEGGModelForOrganism multiple times for different strains. +0244 if ~isempty(globalModel) +0245 model=globalModel.model; +0246 KOModel=globalModel.KOModel; +0247 else +0248 [model, KOModel]=getModelFromKEGG(keepSpontaneous,keepUndefinedStoich,keepIncomplete,keepGeneral); +0249 end +0250 model.id=organismID; +0251 model.c=zeros(numel(model.rxns),1); +0252 +0253 %If no FASTA file is supplied, then just remove all genes which are not for +0254 %the given organism ID +0255 if isempty(fastaFile) +0256 %Check if organismID can be found in KEGG species list or is +0257 %set to "eukaryotes" or "prokaryotes" +0258 phylDistsFull=getPhylDist(false); +0259 if ~ismember(organismID,[phylDistsFull.ids 'eukaryotes' 'prokaryotes']) +0260 error('Provided organismID is incorrect. Only species abbreviations from KEGG Species List or "eukaryotes"/"prokaryotes" are allowed.'); +0261 end +0262 +0263 fprintf(['Pruning the model from <strong>non-' organismID '</strong> genes... ']); +0264 if ismember(organismID,{'eukaryotes','prokaryotes'}) +0265 phylDists=getPhylDist(maxPhylDist==-1); +0266 if strcmp(organismID,'eukaryotes') +0267 proxyid='hsa'; +0268 %Use H. sapiens here +0269 else +0270 proxyid='eco'; +0271 %Use E. coli here +0272 end +0273 [~, phylDistId]=ismember(proxyid,phylDists.ids); +0274 idsToKeep=phylDists.ids(~isinf(phylDists.distMat(phylDistId,:))); +0275 taxIDs=cellfun(@(x) x{1},cellfun(@(x) strsplit(x,':'),model.genes,'UniformOutput',false),'UniformOutput',false); +0276 I=ismember(upper(taxIDs),upper(idsToKeep)); +0277 else +0278 %KEGG organism IDs may have three or four letters +0279 organismID=strcat(organismID,':'); +0280 %Add colon for accurate matching +0281 if length(organismID)==4 +0282 I=cellfun(@(x) strcmpi(x(1:4),organismID),model.genes); +0283 elseif length(organismID)==5 +0284 I=cellfun(@(x) strcmpi(x(1:5),organismID),model.genes); +0285 end +0286 end +0287 %Remove those genes +0288 model.genes=model.genes(I); +0289 model.rxnGeneMat=model.rxnGeneMat(:,I); +0290 fprintf('COMPLETE\n'); +0291 end +0292 +0293 %First remove all reactions without genes +0294 if keepSpontaneous==true +0295 fprintf('Removing non-spontaneous reactions without GPR rules... '); +0296 load(fullfile(ravenPath,'src','reconstruction','kegg','keggModel.mat'),'isSpontaneous'); +0297 I=~any(model.rxnGeneMat,2)&~ismember(model.rxns,isSpontaneous); +0298 spontRxnsWithGenes=model.rxns(any(model.rxnGeneMat,2)&~ismember(model.rxns,isSpontaneous)); +0299 else +0300 fprintf('Removing reactions without GPR rules... '); +0301 I=~any(model.rxnGeneMat,2); +0302 end +0303 model=removeReactions(model,I,true); +0304 fprintf('COMPLETE\n'); +0305 +0306 %Clean gene names +0307 fprintf('Fixing gene names in the model... '); +0308 %Get rid of the prefix organism id +0309 model.genes=regexprep(model.genes,'^\w+?:',''); +0310 fprintf('COMPLETE\n'); +0311 +0312 %If no FASTA file is supplied, then we are done here +0313 if isempty(fastaFile) +0314 %Create grRules +0315 fprintf('Constructing GPR associations and annotations for the model... '); +0316 model.grRules=cell(numel(model.rxns),1); +0317 model.grRules(:)={''}; +0318 %Add the gene associations as "or" +0319 for i=1:numel(model.rxns) +0320 %Find the involved genes +0321 I=find(model.rxnGeneMat(i,:)); +0322 if any(I) +0323 model.grRules{i}=['(' model.genes{I(1)}]; +0324 for j=2:numel(I) +0325 model.grRules{i}=[model.grRules{i} ' or ' model.genes{I(j)}]; +0326 end +0327 model.grRules{i}=[model.grRules{i} ')']; +0328 end +0329 end +0330 %Fix grRules and reconstruct rxnGeneMat +0331 [grRules,rxnGeneMat] = standardizeGrRules(model); %Give detailed output +0332 model.grRules = grRules; +0333 model.rxnGeneMat = rxnGeneMat; +0334 %Add geneMiriams, assuming that it follows the syntax +0335 %kegg.genes/organismID:geneName +0336 model.geneMiriams=''; +0337 for i=1:numel(model.genes) +0338 model.geneMiriams{i,1}.name{1,1}='kegg.genes'; +0339 model.geneMiriams{i,1}.value{1,1}=strcat(lower(organismID),model.genes{i,1}); +0340 end +0341 %Add the description to the reactions. The reference model has no +0342 %reaction notes, so readYAMLmodel leaves the field out +0343 if ~isfield(model,'rxnNotes') +0344 model.rxnNotes=repmat({''},numel(model.rxns),1); +0345 end +0346 for i=1:numel(model.rxns) +0347 if ~isempty(model.rxnNotes{i}) +0348 model.rxnNotes(i)=strcat('Included by getKEGGModelForOrganism (without HMMs).',model.rxnNotes(i)); +0349 model.rxnNotes(i)=strrep(model.rxnNotes(i),'.','. '); +0350 else +0351 model.rxnNotes(i)={'Included by getKEGGModelForOrganism (without HMMs)'}; +0352 end +0353 end +0354 fprintf('COMPLETE\n\n'); +0355 fprintf('*** Model reconstruction complete ***\n'); +0356 return; +0357 end +0358 +0359 %Query the whole proteome against the concatenated KO HMM library in a +0360 %single hmmsearch. With the profile library as the query and the +0361 %proteome as the target sequence database, per-hit E-values stay +0362 %comparable across KOs (same search direction, same effective database +0363 %size), and no per-organism phylogenetic-distance subsampling is needed +0364 %because the library sequence set is already fixed. +0365 if ispc +0366 binEnd='.exe'; +0367 elseif ismac +0368 binEnd='.mac'; +0369 else +0370 binEnd=''; +0371 end +0372 %Fetch the HMMER binary on demand if it is not already present +0373 if ~exist(fullfile(ravenPath,'src','external','hmmer',['hmmsearch' binEnd]),'file') +0374 downloadRavenBinaries({'hmmer'}); +0375 end +0376 tblFile=[tempname '.tblout']; +0377 fprintf('Querying the user-specified FASTA file against the KEGG Orthology specific HMMs... '); +0378 [status, output]=system(['"' fullfile(ravenPath,'src','external','hmmer',['hmmsearch' binEnd]) '" --cpu "' num2str(cores) '" --tblout "' tblFile '" "' libraryFile '" "' fastaFile '"']); +0379 if status~=0 +0380 EM=['Error when querying the concatenated HMM library:\n' output]; +0381 error('RAVEN:badInput', '%s', EM); +0382 end +0383 fprintf('COMPLETE\n'); +0384 +0385 fprintf('Parsing the HMM search results... '); +0386 koGeneMat=zeros(numel(KOModel.rxns),3000); %Make room for 3000 genes +0387 genes=cell(3000,1); +0388 %Map each KO id to its row in koGeneMat +0389 koTable=java.util.Hashtable; +0390 for i=1:numel(KOModel.rxns) +0391 koTable.put(KOModel.rxns{i},i); +0392 end +0393 %Store the column index for each gene in a hash list +0394 hTable=java.util.Hashtable; +0395 geneCounter=0; +0396 fid=fopen(tblFile,'r'); +0397 while 1 +0398 tline=fgetl(fid); +0399 if ~ischar(tline) +0400 break; +0401 end +0402 %Skip the comment/header lines that hmmsearch --tblout writes +0403 if isempty(tline) || tline(1)=='#' +0404 continue; +0405 end +0406 elements=regexp(strtrim(tline),'\s+','split'); +0407 if numel(elements)<5 +0408 continue; +0409 end +0410 %With the profile library as the query, the target name (col 1) is +0411 %the proteome gene, the query name (col 3) is the KO, and the +0412 %full-sequence E-value is in col 5 +0413 gene=elements{1}; +0414 ko=elements{3}; +0415 score=str2double(elements{5}); +0416 koIdx=koTable.get(ko); +0417 if isempty(koIdx) || isnan(score) || score>cutOff +0418 continue; +0419 end +0420 %If the score is exactly 0, change it to a very small value to avoid +0421 %NaN +0422 if score==0 +0423 score=10^-250; +0424 end +0425 I=hTable.get(gene); +0426 if any(I) +0427 %Keep the best (smallest) E-value for this gene-KO pair +0428 if koGeneMat(koIdx,I)==0 || score<koGeneMat(koIdx,I) +0429 koGeneMat(koIdx,I)=score; +0430 end +0431 else +0432 geneCounter=geneCounter+1; +0433 hTable.put(gene,geneCounter); +0434 genes{geneCounter}=gene; +0435 koGeneMat(koIdx,geneCounter)=score; +0436 end +0437 end +0438 fclose(fid); +0439 delete(tblFile); +0440 fprintf('COMPLETE\n'); +0441 +0442 fprintf('Removing gene, KEGG Orthology associations below minScoreRatioKO, minScoreRatioG... '); +0443 koGeneMat=koGeneMat(:,1:geneCounter); +0444 +0445 %Remove the genes for each KO that are below minScoreRatioKO. +0446 for i=1:size(koGeneMat,1) +0447 J=find(koGeneMat(i,:)); +0448 if any(J) +0449 koGeneMat(i,J(log(koGeneMat(i,J))/log(min(koGeneMat(i,J)))<minScoreRatioKO))=0; +0450 end +0451 end +0452 +0453 %Remove the KOs for each gene that are below minScoreRatioG +0454 for i=1:size(koGeneMat,2) +0455 J=find(koGeneMat(:,i)); +0456 if any(J) +0457 koGeneMat(J(log(koGeneMat(J,i))/log(min(koGeneMat(J,i)))<minScoreRatioG),i)=0; +0458 end +0459 end +0460 fprintf('COMPLETE\n'); +0461 +0462 fprintf('Adding gene annotations to the model... '); +0463 %Create the new model +0464 model.genes=genes(1:geneCounter); +0465 model.grRules=cell(numel(model.rxns),1); +0466 model.grRules(:)={''}; +0467 model.rxnGeneMat=sparse(numel(model.rxns),numel(model.genes)); +0468 +0469 %Loop through the reactions and add the corresponding genes +0470 for i=1:numel(model.rxns) +0471 if isstruct(model.rxnMiriams{i}) +0472 %Get all KOs +0473 I=find(strcmpi(model.rxnMiriams{i}.name,'kegg.orthology')); +0474 KOs=model.rxnMiriams{i}.value(I); +0475 %Find the KOs and the corresponding genes +0476 J=ismember(KOModel.rxns,KOs); +0477 [~, K]=find(koGeneMat(J,:)); +0478 +0479 if any(K) +0480 model.rxnGeneMat(i,K)=1; +0481 %Also delete KOs for which no genes were found. If no genes at +0482 %all were matched to the reaction it will be deleted later +0483 L=sum(koGeneMat(J,:),2)==0; +0484 model.rxnMiriams{i}.value(I(L))=[]; +0485 model.rxnMiriams{i}.name(I(L))=[]; +0486 end +0487 end +0488 end +0489 fprintf('COMPLETE\n'); +0490 +0491 %Find and delete all reactions without genes. This also removes genes that +0492 %are not used (which could happen because minScoreRatioG and +0493 %minScoreRatioKO). If keepSpontaneous==true, the spontaneous reactions +0494 %without genes are kept in the model. Spontaneous reactions with original +0495 %gene associations are treated in the same way, like the rest of the +0496 %reactions - if gene associations were removed during HMM search, such +0497 %reactions are deleted from the model +0498 if keepSpontaneous==true +0499 %Not the most comprise way to delete reactions without genes, but this +0500 %makes the code easier to understand. Firstly the non-spontaneous +0501 %reactions without genes are removed. After that, the second deletion +0502 %step removes spontaneous reactions, which had gene associations before +0503 %HMM search, but no longer have after it +0504 fprintf('Removing non-spontaneous reactions which after HMM search no longer have GPR rules... '); +0505 I=~any(model.rxnGeneMat,2)&~ismember(model.rxns,isSpontaneous); +0506 model=removeReactions(model,I,true,true); +0507 I=~any(model.rxnGeneMat,2)&ismember(model.rxns,spontRxnsWithGenes); 0508 model=removeReactions(model,I,true,true); -0509 I=~any(model.rxnGeneMat,2)&ismember(model.rxns,spontRxnsWithGenes); -0510 model=removeReactions(model,I,true,true); -0511 else -0512 %Just simply check for any new reactions without genes and remove -0513 %it -0514 fprintf('Removing reactions which after HMM search no longer have GPR rules... '); -0515 I=~any(model.rxnGeneMat,2); -0516 model=removeReactions(model,I,true,true); -0517 end -0518 fprintf('COMPLETE\n'); -0519 -0520 fprintf('Constructing GPR rules and finalizing the model... '); -0521 %Add the gene associations as "or" -0522 for i=1:numel(model.rxns) -0523 %Find the involved genes -0524 I=find(model.rxnGeneMat(i,:)); -0525 if any(I) -0526 model.grRules{i}=['(' model.genes{I(1)}]; -0527 for j=2:numel(I) -0528 model.grRules{i}=[model.grRules{i} ' or ' model.genes{I(j)}]; -0529 end -0530 model.grRules{i}=[model.grRules{i} ')']; -0531 end -0532 end -0533 -0534 %Fix grRules and reconstruct rxnGeneMat -0535 [grRules,rxnGeneMat] = standardizeGrRules(model,false); %Give detailed output -0536 model.grRules = grRules; -0537 model.rxnGeneMat = rxnGeneMat; -0538 -0539 %Add the description to the reactions. The reference model has no reaction -0540 %notes, so readYAMLmodel leaves the field out -0541 if ~isfield(model,'rxnNotes') -0542 model.rxnNotes=repmat({''},numel(model.rxns),1); -0543 end -0544 for i=1:numel(model.rxns) -0545 if ~isempty(model.rxnNotes{i}) -0546 model.rxnNotes(i)=strcat('Included by getKEGGModelForOrganism (using HMMs).',model.rxnNotes(i)); -0547 model.rxnNotes(i)=strrep(model.rxnNotes(i),'.','. '); -0548 else -0549 model.rxnNotes(i)={'Included by getKEGGModelForOrganism (using HMMs)'}; -0550 end -0551 end -0552 %Remove the temp fasta file -0553 delete(fastaFile) -0554 fprintf('COMPLETE\n\n*** Model reconstruction complete ***\n'); -0555 end

    +0509 else +0510 %Just simply check for any new reactions without genes and remove +0511 %it +0512 fprintf('Removing reactions which after HMM search no longer have GPR rules... '); +0513 I=~any(model.rxnGeneMat,2); +0514 model=removeReactions(model,I,true,true); +0515 end +0516 fprintf('COMPLETE\n'); +0517 +0518 fprintf('Constructing GPR rules and finalizing the model... '); +0519 %Add the gene associations as "or" +0520 for i=1:numel(model.rxns) +0521 %Find the involved genes +0522 I=find(model.rxnGeneMat(i,:)); +0523 if any(I) +0524 model.grRules{i}=['(' model.genes{I(1)}]; +0525 for j=2:numel(I) +0526 model.grRules{i}=[model.grRules{i} ' or ' model.genes{I(j)}]; +0527 end +0528 model.grRules{i}=[model.grRules{i} ')']; +0529 end +0530 end +0531 +0532 %Fix grRules and reconstruct rxnGeneMat +0533 [grRules,rxnGeneMat] = standardizeGrRules(model,false); %Give detailed output +0534 model.grRules = grRules; +0535 model.rxnGeneMat = rxnGeneMat; +0536 +0537 %Add the description to the reactions. The reference model has no reaction +0538 %notes, so readYAMLmodel leaves the field out +0539 if ~isfield(model,'rxnNotes') +0540 model.rxnNotes=repmat({''},numel(model.rxns),1); +0541 end +0542 for i=1:numel(model.rxns) +0543 if ~isempty(model.rxnNotes{i}) +0544 model.rxnNotes(i)=strcat('Included by getKEGGModelForOrganism (using HMMs).',model.rxnNotes(i)); +0545 model.rxnNotes(i)=strrep(model.rxnNotes(i),'.','. '); +0546 else +0547 model.rxnNotes(i)={'Included by getKEGGModelForOrganism (using HMMs)'}; +0548 end +0549 end +0550 %Remove the temp fasta file +0551 delete(fastaFile) +0552 fprintf('COMPLETE\n\n*** Model reconstruction complete ***\n'); +0553 end

    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/reconstruction/kegg/getModelFromKEGG.html b/docs/html/reconstruction/kegg/getModelFromKEGG.html index df3b67f6..ad8c1b5e 100644 --- a/docs/html/reconstruction/kegg/getModelFromKEGG.html +++ b/docs/html/reconstruction/kegg/getModelFromKEGG.html @@ -29,19 +29,18 @@

    SYNOPSIS ^DESCRIPTION ^

     getModelFromKEGG  Load the global KEGG model.
     
    - Loads the global KEGG reaction/gene model from keggModel.mat. On first
    - use --- when no keggModel.mat is present yet --- the underlying
    - artefacts (the gene-free reference model plus the KO/reaction/organism-
    - gene relational tables, published as a toolbox-assets release) are
    - downloaded and assembled instead, and the result is cached to
    - keggModel.mat so later calls load instantly. The first build can take
    - a while (the organism-gene table covers every KEGG organism) and needs
    - a few hundred MB of disk.
    + Loads the global KEGG reaction and gene model from keggModel.mat. When
    + keggModel.mat is not present, the underlying artefacts (the gene-free
    + reference model plus the KO, reaction and organism-gene relational
    + tables, published as a toolbox-assets release) are downloaded and
    + assembled, and the result is saved to keggModel.mat for later calls. The
    + first build takes longer because the organism-gene table covers every
    + KEGG organism, and needs a few hundred MB of disk.
     
      Name-Value Arguments
      --------------------
      keepSpontaneous : logical
    -     include reactions labeled as "spontaneous" (default true).
    +     include reactions labelled as "spontaneous" (default true).
      keepUndefinedStoich : logical
          include reactions in the form n A <=> n+1 A (default true).
      keepIncomplete : logical
    @@ -60,9 +59,9 @@ 

    DESCRIPTION ^SOURCE CODE ^
    0001 function [model,KOModel]=getModelFromKEGG(varargin)
     0002 % getModelFromKEGG  Load the global KEGG model.
     0003 %
    -0004 % Loads the global KEGG reaction/gene model from keggModel.mat. On first
    -0005 % use --- when no keggModel.mat is present yet --- the underlying
    -0006 % artefacts (the gene-free reference model plus the KO/reaction/organism-
    -0007 % gene relational tables, published as a toolbox-assets release) are
    -0008 % downloaded and assembled instead, and the result is cached to
    -0009 % keggModel.mat so later calls load instantly. The first build can take
    -0010 % a while (the organism-gene table covers every KEGG organism) and needs
    -0011 % a few hundred MB of disk.
    -0012 %
    -0013 % Name-Value Arguments
    -0014 % --------------------
    -0015 % keepSpontaneous : logical
    -0016 %     include reactions labeled as "spontaneous" (default true).
    -0017 % keepUndefinedStoich : logical
    -0018 %     include reactions in the form n A <=> n+1 A (default true).
    -0019 % keepIncomplete : logical
    -0020 %     include reactions labelled as "incomplete", "erroneous" or "unclear"
    -0021 %     (default true).
    -0022 % keepGeneral : logical
    -0023 %     include reactions labelled as "general reaction" (default false).
    -0024 %
    -0025 % Returns
    -0026 % -------
    -0027 % model : struct
    -0028 %     a model structure with all reactions and the metabolites used in them.
    -0029 % KOModel : struct
    -0030 %     a model structure representing KEGG Orthology ids and their associated
    -0031 %     genes. KO ids are stored as reactions.
    -0032 %
    -0033 % Notes
    -0034 % -----
    -0035 % The model output can be used as a template for fillGaps. In that case,
    -0036 % remove the genes and rxnGeneMat fields before parsing:
    -0037 %     model = rmfield(model, 'genes'), etc.
    -0038 %
    -0039 % See also
    -0040 % --------
    -0041 % getKEGGModelForOrganism, getPhylDist, fillGaps
    -0042 
    -0043 ravenPath=findRAVENroot();
    -0044 
    -0045 p=parseRAVENargs(varargin, {'keepSpontaneous',true; 'keepUndefinedStoich',true; ...
    -0046     'keepIncomplete',true; 'keepGeneral',false});
    -0047 keepSpontaneous=p.keepSpontaneous;
    -0048 keepUndefinedStoich=p.keepUndefinedStoich;
    -0049 keepIncomplete=p.keepIncomplete;
    -0050 keepGeneral=p.keepGeneral;
    -0051 
    -0052 modelFile=fullfile(ravenPath,'src','reconstruction','kegg','keggModel.mat');
    -0053 if isfile(modelFile)
    -0054     fprintf(['Importing the global KEGG model from ' strrep(modelFile,'\','/') '... ']);
    -0055     load(modelFile,'model','KOModel','isSpontaneous','isUndefinedStoich','isIncomplete','isGeneral');
    -0056     fprintf('COMPLETE\n');
    -0057 else
    -0058     [model,KOModel,isSpontaneous,isUndefinedStoich,isIncomplete,isGeneral]=buildGlobalKEGGModel(ravenPath);
    -0059     fprintf(['Saving the global KEGG model to ' strrep(modelFile,'\','/') ' for future use... ']);
    -0060     save(modelFile,'model','KOModel','isSpontaneous','isUndefinedStoich','isIncomplete','isGeneral','-v7.3');
    -0061     fprintf('COMPLETE\n');
    -0062 end
    -0063 
    -0064 if keepSpontaneous==false
    -0065     model=removeReactions(model,intersect(isSpontaneous,model.rxns),true,true);
    -0066 end
    -0067 if keepUndefinedStoich==false
    -0068     model=removeReactions(model,intersect(isUndefinedStoich,model.rxns),true,true);
    -0069 end
    -0070 if keepIncomplete==false
    -0071     model=removeReactions(model,intersect(isIncomplete,model.rxns),true,true);
    -0072 end
    -0073 if keepGeneral==false
    -0074     model=removeReactions(model,intersect(isGeneral,model.rxns),true,true);
    -0075 end
    -0076 end

    +0004 % Loads the global KEGG reaction and gene model from keggModel.mat. When +0005 % keggModel.mat is not present, the underlying artefacts (the gene-free +0006 % reference model plus the KO, reaction and organism-gene relational +0007 % tables, published as a toolbox-assets release) are downloaded and +0008 % assembled, and the result is saved to keggModel.mat for later calls. The +0009 % first build takes longer because the organism-gene table covers every +0010 % KEGG organism, and needs a few hundred MB of disk. +0011 % +0012 % Name-Value Arguments +0013 % -------------------- +0014 % keepSpontaneous : logical +0015 % include reactions labelled as "spontaneous" (default true). +0016 % keepUndefinedStoich : logical +0017 % include reactions in the form n A <=> n+1 A (default true). +0018 % keepIncomplete : logical +0019 % include reactions labelled as "incomplete", "erroneous" or "unclear" +0020 % (default true). +0021 % keepGeneral : logical +0022 % include reactions labelled as "general reaction" (default false). +0023 % +0024 % Returns +0025 % ------- +0026 % model : struct +0027 % a model structure with all reactions and the metabolites used in them. +0028 % KOModel : struct +0029 % a model structure representing KEGG Orthology ids and their associated +0030 % genes. KO ids are stored as reactions. +0031 % +0032 % Notes +0033 % ----- +0034 % The model output can be used as a template for fillGaps after the genes +0035 % and rxnGeneMat fields are removed: +0036 % model = rmfield(model, 'genes') and model = rmfield(model, 'rxnGeneMat'). +0037 % +0038 % See also +0039 % -------- +0040 % getKEGGModelForOrganism, getPhylDist, fillGaps +0041 +0042 ravenPath=findRAVENroot(); +0043 +0044 p=parseRAVENargs(varargin, {'keepSpontaneous',true; 'keepUndefinedStoich',true; ... +0045 'keepIncomplete',true; 'keepGeneral',false}); +0046 keepSpontaneous=p.keepSpontaneous; +0047 keepUndefinedStoich=p.keepUndefinedStoich; +0048 keepIncomplete=p.keepIncomplete; +0049 keepGeneral=p.keepGeneral; +0050 +0051 modelFile=fullfile(ravenPath,'src','reconstruction','kegg','keggModel.mat'); +0052 if isfile(modelFile) +0053 fprintf(['Importing the global KEGG model from ' strrep(modelFile,'\','/') '... ']); +0054 load(modelFile,'model','KOModel','isSpontaneous','isUndefinedStoich','isIncomplete','isGeneral'); +0055 fprintf('COMPLETE\n'); +0056 else +0057 [model,KOModel,isSpontaneous,isUndefinedStoich,isIncomplete,isGeneral]=buildGlobalKEGGModel(ravenPath); +0058 fprintf(['Saving the global KEGG model to ' strrep(modelFile,'\','/') ' for future use... ']); +0059 save(modelFile,'model','KOModel','isSpontaneous','isUndefinedStoich','isIncomplete','isGeneral','-v7.3'); +0060 fprintf('COMPLETE\n'); +0061 end +0062 +0063 if keepSpontaneous==false +0064 model=removeReactions(model,intersect(isSpontaneous,model.rxns),true,true); +0065 end +0066 if keepUndefinedStoich==false +0067 model=removeReactions(model,intersect(isUndefinedStoich,model.rxns),true,true); +0068 end +0069 if keepIncomplete==false +0070 model=removeReactions(model,intersect(isIncomplete,model.rxns),true,true); +0071 end +0072 if keepGeneral==false +0073 model=removeReactions(model,intersect(isGeneral,model.rxns),true,true); +0074 end +0075 end

    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/reconstruction/kegg/getPhylDist.html b/docs/html/reconstruction/kegg/getPhylDist.html index bd4553c2..131c3e80 100644 --- a/docs/html/reconstruction/kegg/getPhylDist.html +++ b/docs/html/reconstruction/kegg/getPhylDist.html @@ -30,7 +30,7 @@

    DESCRIPTION ^
     getPhylDist  Load the pre-built KEGG phylogenetic distance matrix.
     
      Loads the pre-built phylogenetic distance matrix from keggPhylDist.mat.
    - The matrix ships with RAVEN; it is derived from the KEGG taxonomy, which
    + The matrix ships with RAVEN and is derived from the KEGG taxonomy, which
      is a toolbox-assets release asset.
     
      Name-Value Arguments
    @@ -69,7 +69,7 @@ 

    SOURCE CODE ^% getPhylDist Load the pre-built KEGG phylogenetic distance matrix. 0003 % 0004 % Loads the pre-built phylogenetic distance matrix from keggPhylDist.mat. -0005 % The matrix ships with RAVEN; it is derived from the KEGG taxonomy, which +0005 % The matrix ships with RAVEN and is derived from the KEGG taxonomy, which 0006 % is a toolbox-assets release asset. 0007 % 0008 % Name-Value Arguments diff --git a/docs/html/reconstruction/kegg/index.html b/docs/html/reconstruction/kegg/index.html index 270bba12..a2264cb3 100644 --- a/docs/html/reconstruction/kegg/index.html +++ b/docs/html/reconstruction/kegg/index.html @@ -19,7 +19,7 @@

    Index for reconstruction/kegg

    Matlab files in this directory:

    -
     buildGlobalGPRbuildGlobalGPR Join every KEGG organism's genes onto the reference
     buildGlobalKEGGModelbuildGlobalKEGGModel Download and assemble the global KEGG model.
     getKEGGModelForOrganismgetKEGGModelForOrganism Reconstruct a model from KEGG protein homology.
     getModelFromKEGGgetModelFromKEGG Load the global KEGG model.
     getPhylDistgetPhylDist Load the pre-built KEGG phylogenetic distance matrix.
     keggDataVersionkeggDataVersion The toolbox-assets release tag for KEGG artefacts.
     readKEGGTablereadKEGGTable Read a gzipped KEGG relational table into a table with
    + 
    buildGlobalGPRbuildGlobalGPR Join the genes of all KEGG organisms to reference reactions by KO id.  buildGlobalKEGGModelbuildGlobalKEGGModel Download and assemble the global KEGG model.  getKEGGModelForOrganismgetKEGGModelForOrganism Reconstruct a model from KEGG protein homology.  getModelFromKEGGgetModelFromKEGG Load the global KEGG model.  getPhylDistgetPhylDist Load the pre-built KEGG phylogenetic distance matrix.  keggDataVersionkeggDataVersion The toolbox-assets release tag for KEGG artefacts.  readKEGGTablereadKEGGTable Read a gzipped KEGG relational table into a table of cellstr columns.

    Other Matlab-specific files in this directory:

      diff --git a/docs/html/reconstruction/kegg/keggDataVersion.html b/docs/html/reconstruction/kegg/keggDataVersion.html index 12472b63..9e0651de 100644 --- a/docs/html/reconstruction/kegg/keggDataVersion.html +++ b/docs/html/reconstruction/kegg/keggDataVersion.html @@ -29,16 +29,15 @@

      SYNOPSIS ^DESCRIPTION ^

       keggDataVersion  The toolbox-assets release tag for KEGG artefacts.
       
      - Reads reconstruction/kegg/KEGG_VERSION.md --- the single place this
      - version is recorded --- so every function that downloads a KEGG
      - artefact (getModelFromKEGG, getKEGGModelForOrganism) stays in sync
      - when toolbox-assets publishes a new KEGG release. The version is the
      - file's first line.
      + Reads the first line of src/reconstruction/kegg/KEGG_VERSION.md. The file is
      + the single place where the version is recorded, so that every function that
      + downloads a KEGG artefact (getModelFromKEGG, getKEGGModelForOrganism) uses
      + the same release.
       
        Returns
        -------
        version : char
      -     the toolbox-assets release tag, e.g. 'kegg118'.
      + the toolbox-assets release tag, for example 'kegg118'.

    CROSS-REFERENCE INFORMATION ^

    @@ -56,26 +55,25 @@

    SOURCE CODE ^
    0001 function version=keggDataVersion()
     0002 % keggDataVersion  The toolbox-assets release tag for KEGG artefacts.
     0003 %
    -0004 % Reads reconstruction/kegg/KEGG_VERSION.md --- the single place this
    -0005 % version is recorded --- so every function that downloads a KEGG
    -0006 % artefact (getModelFromKEGG, getKEGGModelForOrganism) stays in sync
    -0007 % when toolbox-assets publishes a new KEGG release. The version is the
    -0008 % file's first line.
    -0009 %
    -0010 % Returns
    -0011 % -------
    -0012 % version : char
    -0013 %     the toolbox-assets release tag, e.g. 'kegg118'.
    -0014 
    -0015 ravenPath=findRAVENroot();
    -0016 versionFile=fullfile(ravenPath,'src','reconstruction','kegg','KEGG_VERSION.md');
    -0017 fid=fopen(versionFile,'r');
    -0018 if fid==-1
    -0019     error('keggDataVersion:fileNotFound','Cannot read %s.',strrep(versionFile,'\','/'))
    -0020 end
    -0021 version=strtrim(fgetl(fid));
    -0022 fclose(fid);
    -0023 end
    +0004 % Reads the first line of src/reconstruction/kegg/KEGG_VERSION.md. The file is +0005 % the single place where the version is recorded, so that every function that +0006 % downloads a KEGG artefact (getModelFromKEGG, getKEGGModelForOrganism) uses +0007 % the same release. +0008 % +0009 % Returns +0010 % ------- +0011 % version : char +0012 % the toolbox-assets release tag, for example 'kegg118'. +0013 +0014 ravenPath=findRAVENroot(); +0015 versionFile=fullfile(ravenPath,'src','reconstruction','kegg','KEGG_VERSION.md'); +0016 fid=fopen(versionFile,'r'); +0017 if fid==-1 +0018 error('keggDataVersion:fileNotFound','Cannot read %s.',strrep(versionFile,'\','/')) +0019 end +0020 version=strtrim(fgetl(fid)); +0021 fclose(fid); +0022 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/reconstruction/kegg/readKEGGTable.html b/docs/html/reconstruction/kegg/readKEGGTable.html index ce9bc14d..c377249a 100644 --- a/docs/html/reconstruction/kegg/readKEGGTable.html +++ b/docs/html/reconstruction/kegg/readKEGGTable.html @@ -4,7 +4,7 @@ Description of readKEGGTable - + @@ -21,21 +21,21 @@

    readKEGGTable

    PURPOSE ^

    -
    readKEGGTable Read a gzipped KEGG relational table into a table with
    +
    readKEGGTable Read a gzipped KEGG relational table into a table of cellstr columns.

    SYNOPSIS ^

    function tbl=readKEGGTable(gzFile)

    DESCRIPTION ^

    -
     readKEGGTable  Read a gzipped KEGG relational table into a table with
    - every column forced to char cellstr (matching RAVEN's own
    - cell-array-of-char convention, regardless of the caller's default
    - text-import type).
    +
     readKEGGTable  Read a gzipped KEGG relational table into a table of cellstr columns.
    +
    + Every column is a cellstr (RAVEN's cell-array-of-char convention), whatever the
    + default text-import type of the caller.
     
      Parameters
      ----------
      gzFile : char
    -     path to the gzipped TSV file, e.g. '.../kegg118_ko_reaction.tsv.gz'.
    +     path to the gzipped TSV file, for example '.../kegg118_ko_reaction.tsv.gz'.
     
      Returns
      -------
    @@ -56,15 +56,15 @@ 

    CROSS-REFERENCE INFORMATION ^
 
 <h2><a name=SOURCE CODE ^

    0001 function tbl=readKEGGTable(gzFile)
    -0002 % readKEGGTable  Read a gzipped KEGG relational table into a table with
    -0003 % every column forced to char cellstr (matching RAVEN's own
    -0004 % cell-array-of-char convention, regardless of the caller's default
    -0005 % text-import type).
    +0002 % readKEGGTable  Read a gzipped KEGG relational table into a table of cellstr columns.
    +0003 %
    +0004 % Every column is a cellstr (RAVEN's cell-array-of-char convention), whatever the
    +0005 % default text-import type of the caller.
     0006 %
     0007 % Parameters
     0008 % ----------
     0009 % gzFile : char
    -0010 %     path to the gzipped TSV file, e.g. '.../kegg118_ko_reaction.tsv.gz'.
    +0010 %     path to the gzipped TSV file, for example '.../kegg118_ko_reaction.tsv.gz'.
     0011 %
     0012 % Returns
     0013 % -------
    diff --git a/docs/html/solver/getMinNrFluxes.html b/docs/html/solver/getMinNrFluxes.html
    index 0a960792..45848c56 100644
    --- a/docs/html/solver/getMinNrFluxes.html
    +++ b/docs/html/solver/getMinNrFluxes.html
    @@ -40,16 +40,19 @@ 

    DESCRIPTION ^DESCRIPTION ^DESCRIPTION ^DESCRIPTION ^CROSS-REFERENCE INFORMATION ^

    This function calls:
      -
    • checkSolution checkSolution Check if a solver result is feasible and optimal.
    • optimizeProb optimizeProb Optimize an LP or MILP formulated in COBRA terms.
    • solveLP solveLP Solve a linear programming problem.
    +
  • checkSolution checkSolution Check if a solver result is feasible and optimal.
  • optimizeProb optimizeProb Optimise an LP or MILP formulated in COBRA terms.
  • solveLP solveLP Solve a linear programming problem.
  • This function is called by:
    • solveLP solveLP Solve a linear programming problem.
    @@ -131,531 +139,539 @@

    SOURCE CODE ^% Name-Value Arguments 0013 % -------------------- 0014 % toMinimize : cell or logical or double -0015 % either a cell array of reaction IDs, a logical vector with the same -0016 % number of elements as reactions in the model, or a vector of indexes -0017 % for the reactions that should be minimized (default model.rxns). +0015 % a cell array of reaction IDs, a logical vector with the same number +0016 % of elements as reactions in the model, or a vector of indexes for the +0017 % reactions that should be minimised (default model.rxns). 0018 % params : struct -0019 % *obsolete option*. -0020 % scores : double -0021 % vector of weights for the reactions. Negative scores should not have -0022 % flux. Positive scores are not possible in this implementation, and -0023 % they are changed to max(scores(scores<0)), or to 0 if scores has no -0024 % negative entry at all. Must have the same dimension as toMinimize -0025 % (find(toMinimize) if it is a logical vector) (default -1 for all -0026 % reactions). -0027 % formulation : char -0028 % which MILP formulation to build (default 'irrev'): -0029 % -0030 % - 'irrev' : convert the model to irreversible format and use one -0031 % binary per irreversible reaction, so a reversible reaction that is -0032 % minimized costs two binaries. The magnitude of the fluxes is -0033 % estimated with an LP first, which also provides a start solution. -0034 % - 'reversible' : keep the model reversible and use one binary per -0035 % reaction, at the cost of a larger constraint matrix. Fewer binaries -0036 % makes this the faster formulation on large problems, but it needs a -0037 % solver that handles MILPs well and no LP estimate of the flux -0038 % magnitudes is made, so an arbitrary large flux is assumed. -0039 % verbose : logical -0040 % if true, the MILP progression will be shown (default false). -0041 % resolveTies : logical -0042 % if true, pin the MILP's degenerate optimum to a canonical answer instead of -0043 % relying on Seed alone: hold the primary (scores) objective at its optimum, -0044 % then minimize the count of "on" reactions, then, among the sparsest, minimize -0045 % their summed reaction-id rank (lowest ids preferred). Two extra MILP solves; -0046 % best-effort, so a phase that does not converge within TimeLimit is dropped -0047 % with a warning rather than changing the result. Only implemented for -0048 % formulation='reversible' -- errors if combined with 'irrev'. Matches -0049 % raven-toolbox's resolve_ties (raven-gecko-parity#104) (default false). -0050 % -0051 % Returns -0052 % ------- -0053 % x : double -0054 % the corresponding fluxes for the full model. -0055 % I : double -0056 % the indexes of the reactions in toMinimize that were used in the -0057 % solution. -0058 % exitFlag : double -0059 % exit status: -0060 % -0061 % - 1 : optimal solution found -0062 % - -1 : no solution found, either because the problem is infeasible or -0063 % because the solver reached its time limit before finding one -0064 % - -2 : a solution was found but is not proven optimal, because the -0065 % solver stopped before reaching optimality. It is returned, but it -0066 % may use more fluxes than the minimum -0067 % -0068 % Examples -0069 % -------- -0070 % [x, I, exitFlag] = getMinNrFluxes(model, toMinimize, params, scores); -0071 % -0072 % Notes -0073 % ----- -0074 % Uses 1000 mmol/gDW/h as an arbitary large flux. Could possibly cause -0075 % problems if the fluxes in the model are larger than that. -0076 -0077 p=parseRAVENargs(varargin, {'toMinimize',[]; 'params',[]; 'scores',[]; ... -0078 'formulation','irrev'; 'verbose',false; 'resolveTies',false}); -0079 toMinimize=p.toMinimize; -0080 params=p.params; -0081 scores=p.scores; -0082 formulation=p.formulation; -0083 verbose=p.verbose; -0084 resolveTies=p.resolveTies; -0085 if isempty(toMinimize) -0086 toMinimize=model.rxns; -0087 elseif ~islogical(toMinimize) && ~isnumeric(toMinimize) -0088 toMinimize=convertCharArray(toMinimize); -0089 else -0090 toMinimize=model.rxns(toMinimize); -0091 end -0092 -0093 if isempty(formulation) -0094 formulation='irrev'; -0095 end -0096 formulation=lower(char(formulation)); -0097 if ~ismember(formulation,{'irrev','reversible'}) -0098 EM='Valid options for formulation are "irrev" or "reversible"'; -0099 error('RAVEN:badInput', '%s', EM); -0100 end -0101 -0102 %For passing parameters to the solver -0103 if isempty(params) -0104 params=struct(); -0105 end -0106 -0107 if isempty(scores) -0108 %It says that the default is -1, but that is to fit with other code -0109 scores=ones(numel(toMinimize),1)*1; -0110 else -0111 if numel(scores)~=numel(toMinimize) -0112 EM='The number of scores must be the same as the number of reactions to minimize'; -0113 error('RAVEN:badInput', '%s', EM); -0114 end -0115 -0116 %Change positive scores to have a small negative weight. This is a -0117 %temporary solution. If no score is negative there is nothing to clamp -0118 %to (max([]) errors here rather than filling in a value, since the -0119 %right-hand side is a computed empty array, not the literal [] MATLAB -0120 %treats as a delete), so clamp to 0 instead: every reaction is then -0121 %equally (un)weighted. -0122 negScores=scores<0; -0123 if any(negScores) -0124 scores(~negScores)=max(scores(negScores)); -0125 else -0126 scores(~negScores)=0; -0127 end -0128 -0129 %It says that the default is -1, but that is to fit with other code -0130 scores=scores*-1; -0131 end -0132 -0133 if strcmp(formulation,'reversible') -0134 [x,I,exitFlag]=minNrFluxesReversible(model,toMinimize,scores,params,verbose,resolveTies); -0135 else -0136 if resolveTies -0137 EM='resolveTies is only implemented for formulation=''reversible'''; -0138 error('RAVEN:badInput', '%s', EM); -0139 end -0140 [x,I,exitFlag]=minNrFluxesIrrev(model,toMinimize,scores,params,verbose); -0141 end -0142 end -0143 -0144 function [x,I,exitFlag]=minNrFluxesIrrev(model,toMinimize,scores,params,verbose) -0145 %One binary per irreversible reaction. Reversible reactions to minimize are -0146 %split by convertToIrrev and therefore carry two binaries each. -0147 -0148 exitFlag=1; -0149 -0150 %Check if the model is in irreversible format -0151 if any(model.rev) -0152 %Convert the model to irreversible format -0153 irrevModel=convertToIrrev(model); -0154 -0155 %Find the indexes for the reactions in toMinimize -0156 [indexes, I]=ismember(strrep(irrevModel.rxns,'_REV',''),toMinimize); -0157 else -0158 irrevModel=model; -0159 -0160 %Find the indexes for the reactions in toMinimize -0161 [indexes, I]=ismember(irrevModel.rxns,toMinimize); -0162 end -0163 -0164 indexes=find(indexes); -0165 %Adjust scores to fit with reversible -0166 scores=scores(I(indexes)); +0019 % solver parameters passed to optimizeProb. With formulation +0020 % 'reversible', intTol (10^-9), TimeLimit (300) and Seed (26) are set +0021 % when absent (default empty struct). +0022 % scores : double +0023 % vector of weights for the reactions. A reaction with a negative +0024 % score adds the negated score to the minimised objective when it +0025 % carries flux. Scores of 0 or above are not supported and are +0026 % changed to max(scores(scores<0)), or to 0 if scores has no negative +0027 % entry at all. Must have the same dimension as toMinimize +0028 % (find(toMinimize) if it is a logical vector) (default -1 for all +0029 % reactions). +0030 % formulation : char +0031 % which MILP formulation to build (default 'irrev'): +0032 % +0033 % - 'irrev' : convert the model to irreversible format and use one +0034 % binary per irreversible reaction, so a reversible reaction that is +0035 % minimised needs two binaries. The magnitude of the fluxes is +0036 % estimated with an LP first, which also provides a start solution. +0037 % - 'reversible' : keep the model reversible and use one binary per +0038 % reaction, with a larger constraint matrix. The formulation has +0039 % fewer binaries but needs a solver that handles MILPs well. No LP +0040 % estimate of the flux magnitudes is made, and a fixed flux of 1000 is +0041 % assumed. +0042 % verbose : logical +0043 % if true, the MILP progression will be shown (default false). +0044 % resolveTies : logical +0045 % if true, select one optimum among the degenerate optima of the MILP +0046 % instead of relying on Seed: the primary (scores) objective is held +0047 % at its optimum, the count of "on" reactions is minimised, and among +0048 % the sparsest solutions the summed reaction-id rank is minimised +0049 % (lowest ids preferred). This takes two extra MILP solves. A phase +0050 % that is not proven optimal within TimeLimit gives a warning, and the +0051 % solution of the last phase is used when it is feasible. Only +0052 % implemented for formulation='reversible'; combined with 'irrev' it +0053 % gives an error. Matches resolve_ties in raven-toolbox +0054 % (raven-gecko-parity#104) (default false). +0055 % +0056 % Returns +0057 % ------- +0058 % x : double +0059 % the corresponding fluxes for the full model. +0060 % I : double +0061 % the indexes of the reactions in toMinimize that were used in the +0062 % solution. +0063 % exitFlag : double +0064 % exit status: +0065 % +0066 % - 1 : optimal solution found +0067 % - -1 : no solution found, either because the problem is infeasible or +0068 % because the solver reached its time limit before finding one +0069 % - -2 : a solution was found but is not proven optimal, because the +0070 % solver stopped before reaching optimality. It is returned and may +0071 % use more fluxes than the minimum +0072 % +0073 % Examples +0074 % -------- +0075 % [x, I, exitFlag] = getMinNrFluxes(model, toMinimize, params, scores); +0076 % +0077 % Notes +0078 % ----- +0079 % The binary of a reaction is linked to its flux through a large flux +0080 % value. With formulation 'irrev' the value is the larger of 1000 and 5 +0081 % times the largest flux of an LP solution; with 'reversible' it is 1000 +0082 % (mmol/gDW/h). A flux above this value is cut off by the binary constraint +0083 % of the reaction, so the MILP can become infeasible. +0084 +0085 p=parseRAVENargs(varargin, {'toMinimize',[]; 'params',[]; 'scores',[]; ... +0086 'formulation','irrev'; 'verbose',false; 'resolveTies',false}); +0087 toMinimize=p.toMinimize; +0088 params=p.params; +0089 scores=p.scores; +0090 formulation=p.formulation; +0091 verbose=p.verbose; +0092 resolveTies=p.resolveTies; +0093 if isempty(toMinimize) +0094 toMinimize=model.rxns; +0095 elseif ~islogical(toMinimize) && ~isnumeric(toMinimize) +0096 toMinimize=convertCharArray(toMinimize); +0097 else +0098 toMinimize=model.rxns(toMinimize); +0099 end +0100 +0101 if isempty(formulation) +0102 formulation='irrev'; +0103 end +0104 formulation=lower(char(formulation)); +0105 if ~ismember(formulation,{'irrev','reversible'}) +0106 EM='Valid options for formulation are "irrev" or "reversible"'; +0107 error('RAVEN:badInput', '%s', EM); +0108 end +0109 +0110 %For passing parameters to the solver +0111 if isempty(params) +0112 params=struct(); +0113 end +0114 +0115 if isempty(scores) +0116 %It says that the default is -1, but that is to fit with other code +0117 scores=ones(numel(toMinimize),1)*1; +0118 else +0119 if numel(scores)~=numel(toMinimize) +0120 EM='The number of scores must be the same as the number of reactions to minimize'; +0121 error('RAVEN:badInput', '%s', EM); +0122 end +0123 +0124 %Change positive scores to have a small negative weight. This is a +0125 %temporary solution. If no score is negative there is nothing to clamp +0126 %to (max([]) errors here rather than filling in a value, since the +0127 %right-hand side is a computed empty array, not the literal [] MATLAB +0128 %treats as a delete), so clamp to 0 instead: every reaction is then +0129 %equally (un)weighted. +0130 negScores=scores<0; +0131 if any(negScores) +0132 scores(~negScores)=max(scores(negScores)); +0133 else +0134 scores(~negScores)=0; +0135 end +0136 +0137 %It says that the default is -1, but that is to fit with other code +0138 scores=scores*-1; +0139 end +0140 +0141 if strcmp(formulation,'reversible') +0142 [x,I,exitFlag]=minNrFluxesReversible(model,toMinimize,scores,params,verbose,resolveTies); +0143 else +0144 if resolveTies +0145 EM='resolveTies is only implemented for formulation=''reversible'''; +0146 error('RAVEN:badInput', '%s', EM); +0147 end +0148 [x,I,exitFlag]=minNrFluxesIrrev(model,toMinimize,scores,params,verbose); +0149 end +0150 end +0151 +0152 function [x,I,exitFlag]=minNrFluxesIrrev(model,toMinimize,scores,params,verbose) +0153 %One binary per irreversible reaction. Reversible reactions to minimize are +0154 %split by convertToIrrev and therefore carry two binaries each. +0155 +0156 exitFlag=1; +0157 +0158 %Check if the model is in irreversible format +0159 if any(model.rev) +0160 %Convert the model to irreversible format +0161 irrevModel=convertToIrrev(model); +0162 +0163 %Find the indexes for the reactions in toMinimize +0164 [indexes, I]=ismember(strrep(irrevModel.rxns,'_REV',''),toMinimize); +0165 else +0166 irrevModel=model; 0167 -0168 %Add binary constraints in the following manner: - Add one unique -0169 %"metabolite" for each integer reaction as a substrate. -0170 % These metabolites can have net production -0171 %- Add reactions for the production of each of those metabolites. The -0172 % amount produced in one reaction unit must be larger than the largest -0173 % possible flux in the model (but not too large to avoid bad scaling) -0174 -0175 %Calculate a solution to the problem without any constraints. This is to -0176 %get an estimate about the magnitude of fluxes in the model and to get a -0177 %feasible start solution. -0178 sol=solveLP(irrevModel,1); -0179 -0180 %Return an empty solution if the non-constrained problem could not be solved -0181 if isempty(sol.x) -0182 x=[]; -0183 I=[]; -0184 exitFlag=-1; -0185 return; -0186 end +0168 %Find the indexes for the reactions in toMinimize +0169 [indexes, I]=ismember(irrevModel.rxns,toMinimize); +0170 end +0171 +0172 indexes=find(indexes); +0173 %Adjust scores to fit with reversible +0174 scores=scores(I(indexes)); +0175 +0176 %Add binary constraints in the following manner: - Add one unique +0177 %"metabolite" for each integer reaction as a substrate. +0178 % These metabolites can have net production +0179 %- Add reactions for the production of each of those metabolites. The +0180 % amount produced in one reaction unit must be larger than the largest +0181 % possible flux in the model (but not too large to avoid bad scaling) +0182 +0183 %Calculate a solution to the problem without any constraints. This is to +0184 %get an estimate about the magnitude of fluxes in the model and to get a +0185 %feasible start solution. +0186 sol=solveLP(irrevModel,1); 0187 -0188 %Take the maximal times 5 to have a safe margin. If it is smaller than 1000, -0189 %then use 1000 instead. -0190 maxFlux=max(max(sol.x)*5,1000); -0191 -0192 intArray=speye(numel(irrevModel.rxns))*-1; -0193 intArray=intArray(indexes,:); -0194 prob.a=[irrevModel.S;intArray]; -0195 a=[sparse(numel(irrevModel.mets),numel(indexes));speye(numel(indexes))*maxFlux]; -0196 prob.a=[prob.a a]; -0197 prob.ints.sub=numel(irrevModel.rxns)+1:numel(irrevModel.rxns)+numel(indexes); -0198 -0199 prob.c=[zeros(numel(irrevModel.rxns),1);scores(:);zeros(size(prob.a,1),1)]; %Minimize the number of fluxes -0200 prob.A=[prob.a -speye(size(prob.a,1))]; -0201 prob.blc=[irrevModel.b(:,1);zeros(numel(indexes),1)]; -0202 if size(irrevModel.b,2)==2 -0203 prob.buc=[irrevModel.b(:,2);inf(numel(indexes),1)]; -0204 else -0205 prob.buc=[irrevModel.b(:,1);inf(numel(indexes),1)]; -0206 end -0207 prob.blx=[irrevModel.lb;zeros(numel(indexes),1)]; -0208 prob.bux=[irrevModel.ub;ones(numel(indexes),1)]; -0209 prob.lb = [prob.blx; prob.blc]; -0210 prob.ub = [prob.bux; prob.buc]; -0211 prob.osense=1; -0212 prob.csense=repmat('E', 1, size(prob.a,1),1); -0213 prob.b=zeros(size(prob.a,1), 1); -0214 -0215 %Use the output from the linear solution as starting point. Only the values -0216 %for the integer variables will be used, but all are supplied. -0217 prob.sol.int.xx=zeros(numel(prob.c),1); -0218 prob.sol.int.xx(prob.ints.sub(sol.x(indexes)>10^-12))=1; -0219 prob.x0=[]; -0220 prob.vartype=repmat('C', size(prob.A,2), 1); -0221 prob.vartype(prob.ints.sub) = 'I'; % with .lb = 0 and .ub = 1, they are binary -0222 % integers (glpk in octave only allows "continuous" or "", not "binary") -0223 prob=rmfield(prob,{'blx','bux','blc','buc'}); -0224 -0225 % Optimize the problem -0226 res = optimizeProb(prob,params,verbose); -0227 [isFeasible, isOptimal]=checkSolution(res); -0228 -0229 if ~isFeasible -0230 x=[]; -0231 I=[]; -0232 exitFlag=-1; -0233 if res.hitTimeLimit -0234 %Nothing is returned either way, but the caller reports -1 as "no -0235 %feasible solution exists", which is not what happened here. -0236 EM='Time limit reached before finding a solution. Try increasing the TimeLimit parameter.'; -0237 warning('RAVEN:warning', '%s', EM); -0238 end -0239 return; -0240 end -0241 if res.hitTimeLimit || ~isOptimal -0242 %A feasible but suboptimal solution, i.e. the solver stopped before -0243 %reaching optimality. The solution is still returned, but must not be -0244 %reported as optimal: it may use more fluxes than the minimum. -0245 %Not every solver reports this in its status, gurobi for one presents a -0246 %MILP solution found before the limit as optimal, so res.hitTimeLimit is -0247 %consulted as well. -0248 exitFlag=-2; -0249 end -0250 -0251 xx=res.full(1:numel(irrevModel.rxns)); -0252 I=res.full(numel(xx)+1:end); -0253 -0254 %Map back to original model from irrevModel -0255 x=xx(1:numel(model.rxns)); -0256 if numel(irrevModel.rxns)>numel(model.rxns) -0257 x(model.rev~=0)=x(model.rev~=0)-xx(numel(model.rxns)+1:end); -0258 end -0259 -0260 I=ismember(toMinimize,strrep(irrevModel.rxns(indexes(I>10^-12)),'_REV','')); -0261 end -0262 -0263 function [x,I,exitFlag]=minNrFluxesReversible(model,toMinimize,scores,params,verbose,resolveTies) -0264 %One binary per reaction, on the reversible model. Trades a larger -0265 %constraint matrix for half the binaries on reversible reactions. -0266 -0267 exitFlag=1; -0268 -0269 %The trick to make this possible using a reversible model is that for -0270 %reversible reactions, we do the following. Set the flux Vi == Vipos - Vineg. -0271 %What happens then is that if the flux is positive, Vipos will have a nonzero value, -0272 %and when the flux is negative, Vineg will have a positive value. In addition -0273 %we can add an arbitrary constant to both Vipos and Vineg. For example, if the -0274 %flux Vi is -1, Vineg can be 4 and Vipos 3. This is however not a problem, -0275 %because we can be sure that Vineg + Vipos >= abs(Vi). Since we are interested -0276 %in forcing the ints to be on when there is a flux, it does not matter if we overestimate -0277 %the flux! So, we can simply constrain the boolean Yi to Yi*Maxflux >= Vineg + Vipos. -0278 -0279 %The matrix then becomes as this: -0280 % S p n int b var -0281 % SSSSSSSS - -0282 % SSSSSSSS - -0283 % SSSSSSSS - -0284 % SSSSSSSS - -0285 % SSSSSSSS - -0286 % SSSSSSSS - -0287 % - 1 - -0288 % - 1 - -0289 % - M - -0290 % - M - -0291 % - - M - -0292 % - - M - -0293 % An example with 8 rxns and 6 metabolites. - means -1, M max flux, and S is the S matrix. -0294 % 4 rxns are to be minimized(1,3,5,7) and 1,7 are reversible. The p and n -0295 % are the Vipos and Vineg variables (2 rxns of each). The ints are the Yi for -0296 % the variables that are to be minimized (the rest of the rxns does not have any). -0297 % The mets here are the constraints, so right under the S matrix, you have -0298 % Vi == Vipos - Vineg for the reactions 1 and 7 while the two next rows represent -0299 % the non-reversible rxns 3 and 5, where we simply say that yi*M >= Vi. The last -0300 % 2 rows are the reactions yi*M >= Vipos + Vineg. To the right, we first have a -0301 % -I matrix for setting the b constraints, and under that we have rxns that are -0302 % just variables (rxns) between 0 and Inf to complete the constraints mentioned above. -0303 %Ex: yi*M >= Vipos + Vineg is impl. as yi*M - Vipos - Vineg - var == 0, 0 <= var <= Inf. -0304 % -0305 %All rows should be equal to zero, so we do not set the b vector in the problem -0306 %The reactions should be constrained as follows -0307 %S - as given in model.lb and model.ub -0308 %pos and neg - between 0 and inf -0309 %ints - between 0 and 1 -0310 %b - as stated in the model.b vector - if this has one column, that is lb and ub (fixed value), if two columns, that is lb and ub -0311 %var - between zero and inf -0312 -0313 [minLog, I]=ismember(model.rxns,toMinimize); -0314 indexes=find(minLog); -0315 revIndexes = find(minLog & (model.rev == 1)); -0316 irrevIndexes = find(minLog & (model.rev == 0)); -0317 revIndexesInInd = find(model.rev(indexes) == 1); -0318 irrevIndexesInInd = find(model.rev(indexes) == 0); -0319 -0320 %The int columns are ordered as indexes, i.e. by position in the model, -0321 %while the scores arrive ordered as toMinimize -0322 scores=scores(I(indexes)); -0323 -0324 %Add binary constraints in the following manner: - Add one unique -0325 %"metabolite" for each integer reaction as a substrate. -0326 % These metabolites can have net production -0327 %- Add reactions for the production of each of those metabolites. The -0328 % amount produced in one reaction unit must be larger than the largest -0329 % possible flux in the model (but not too large to avoid bad scaling) -0330 -0331 maxFlux=1000; -0332 %we build the total matrix as blocks: [S pos neg int b var] -0333 -0334 %s block -0335 intArray=speye(numel(model.rxns))*-1; -0336 intArrayRev=intArray(revIndexes,:); -0337 intArrayIrrev = intArray(irrevIndexes,:); -0338 sBlock=[model.S;intArrayRev;intArrayIrrev;sparse(numel(revIndexes), numel(model.rxns))]; %the S matrix and what is below -0339 -0340 %pos/neg blocks -0341 revposorneg1 = sparse(numel(model.mets), numel(revIndexes)); -0342 revpos2 = speye(numel(revIndexes)); -0343 revneg2 = -revpos2; -0344 revposneg3 = sparse(numel(irrevIndexes), numel(revIndexes)); -0345 revposneg4 = revneg2; -0346 posBlock = [revposorneg1;revpos2;revposneg3;revposneg4]; -0347 negBlock = [revposorneg1;revneg2;revposneg3;revposneg4]; -0348 -0349 %int block -0350 int1 = sparse(numel(model.mets), numel(indexes)); -0351 int2 = sparse(numel(revIndexes), numel(indexes)); -0352 tmpEye = speye(numel(indexes))*maxFlux; -0353 int3 = tmpEye(irrevIndexesInInd,:);%we here select only the irrev indexes from the indexes -0354 int4 = tmpEye(revIndexesInInd,:); -0355 intBlock = [int1;int2;int3;int4]; +0188 %Return an empty solution if the non-constrained problem could not be solved +0189 if isempty(sol.x) +0190 x=[]; +0191 I=[]; +0192 exitFlag=-1; +0193 return; +0194 end +0195 +0196 %Take the maximal times 5 to have a safe margin. If it is smaller than 1000, +0197 %then use 1000 instead. +0198 maxFlux=max(max(sol.x)*5,1000); +0199 +0200 intArray=speye(numel(irrevModel.rxns))*-1; +0201 intArray=intArray(indexes,:); +0202 prob.a=[irrevModel.S;intArray]; +0203 a=[sparse(numel(irrevModel.mets),numel(indexes));speye(numel(indexes))*maxFlux]; +0204 prob.a=[prob.a a]; +0205 prob.ints.sub=numel(irrevModel.rxns)+1:numel(irrevModel.rxns)+numel(indexes); +0206 +0207 prob.c=[zeros(numel(irrevModel.rxns),1);scores(:);zeros(size(prob.a,1),1)]; %Minimize the number of fluxes +0208 prob.A=[prob.a -speye(size(prob.a,1))]; +0209 prob.blc=[irrevModel.b(:,1);zeros(numel(indexes),1)]; +0210 if size(irrevModel.b,2)==2 +0211 prob.buc=[irrevModel.b(:,2);inf(numel(indexes),1)]; +0212 else +0213 prob.buc=[irrevModel.b(:,1);inf(numel(indexes),1)]; +0214 end +0215 prob.blx=[irrevModel.lb;zeros(numel(indexes),1)]; +0216 prob.bux=[irrevModel.ub;ones(numel(indexes),1)]; +0217 prob.lb = [prob.blx; prob.blc]; +0218 prob.ub = [prob.bux; prob.buc]; +0219 prob.osense=1; +0220 prob.csense=repmat('E', 1, size(prob.a,1),1); +0221 prob.b=zeros(size(prob.a,1), 1); +0222 +0223 %Use the output from the linear solution as starting point. Only the values +0224 %for the integer variables will be used, but all are supplied. +0225 prob.sol.int.xx=zeros(numel(prob.c),1); +0226 prob.sol.int.xx(prob.ints.sub(sol.x(indexes)>10^-12))=1; +0227 prob.x0=[]; +0228 prob.vartype=repmat('C', size(prob.A,2), 1); +0229 prob.vartype(prob.ints.sub) = 'I'; % with .lb = 0 and .ub = 1, they are binary +0230 % integers (glpk in octave only allows "continuous" or "", not "binary") +0231 prob=rmfield(prob,{'blx','bux','blc','buc'}); +0232 +0233 % Optimize the problem +0234 res = optimizeProb(prob,params,verbose); +0235 [isFeasible, isOptimal]=checkSolution(res); +0236 +0237 if ~isFeasible +0238 x=[]; +0239 I=[]; +0240 exitFlag=-1; +0241 if res.hitTimeLimit +0242 %Nothing is returned either way, but the caller reports -1 as "no +0243 %feasible solution exists", which is not what happened here. +0244 EM='Time limit reached before finding a solution. Try increasing the TimeLimit parameter.'; +0245 warning('RAVEN:warning', '%s', EM); +0246 end +0247 return; +0248 end +0249 if res.hitTimeLimit || ~isOptimal +0250 %A feasible but suboptimal solution, i.e. the solver stopped before +0251 %reaching optimality. The solution is still returned, but must not be +0252 %reported as optimal: it may use more fluxes than the minimum. +0253 %Not every solver reports this in its status, gurobi for one presents a +0254 %MILP solution found before the limit as optimal, so res.hitTimeLimit is +0255 %consulted as well. +0256 exitFlag=-2; +0257 end +0258 +0259 xx=res.full(1:numel(irrevModel.rxns)); +0260 I=res.full(numel(xx)+1:end); +0261 +0262 %Map back to original model from irrevModel +0263 x=xx(1:numel(model.rxns)); +0264 if numel(irrevModel.rxns)>numel(model.rxns) +0265 x(model.rev~=0)=x(model.rev~=0)-xx(numel(model.rxns)+1:end); +0266 end +0267 +0268 I=ismember(toMinimize,strrep(irrevModel.rxns(indexes(I>10^-12)),'_REV','')); +0269 end +0270 +0271 function [x,I,exitFlag]=minNrFluxesReversible(model,toMinimize,scores,params,verbose,resolveTies) +0272 %One binary per reaction, on the reversible model. Trades a larger +0273 %constraint matrix for half the binaries on reversible reactions. +0274 +0275 exitFlag=1; +0276 +0277 %The trick to make this possible using a reversible model is that for +0278 %reversible reactions, we do the following. Set the flux Vi == Vipos - Vineg. +0279 %What happens then is that if the flux is positive, Vipos will have a nonzero value, +0280 %and when the flux is negative, Vineg will have a positive value. In addition +0281 %we can add an arbitrary constant to both Vipos and Vineg. For example, if the +0282 %flux Vi is -1, Vineg can be 4 and Vipos 3. This is however not a problem, +0283 %because we can be sure that Vineg + Vipos >= abs(Vi). Since we are interested +0284 %in forcing the ints to be on when there is a flux, it does not matter if we overestimate +0285 %the flux! So, we can simply constrain the boolean Yi to Yi*Maxflux >= Vineg + Vipos. +0286 +0287 %The matrix then becomes as this: +0288 % S p n int b var +0289 % SSSSSSSS - +0290 % SSSSSSSS - +0291 % SSSSSSSS - +0292 % SSSSSSSS - +0293 % SSSSSSSS - +0294 % SSSSSSSS - +0295 % - 1 - +0296 % - 1 - +0297 % - M - +0298 % - M - +0299 % - - M - +0300 % - - M - +0301 % An example with 8 rxns and 6 metabolites. - means -1, M max flux, and S is the S matrix. +0302 % 4 rxns are to be minimized(1,3,5,7) and 1,7 are reversible. The p and n +0303 % are the Vipos and Vineg variables (2 rxns of each). The ints are the Yi for +0304 % the variables that are to be minimized (the rest of the rxns does not have any). +0305 % The mets here are the constraints, so right under the S matrix, you have +0306 % Vi == Vipos - Vineg for the reactions 1 and 7 while the two next rows represent +0307 % the non-reversible rxns 3 and 5, where we simply say that yi*M >= Vi. The last +0308 % 2 rows are the reactions yi*M >= Vipos + Vineg. To the right, we first have a +0309 % -I matrix for setting the b constraints, and under that we have rxns that are +0310 % just variables (rxns) between 0 and Inf to complete the constraints mentioned above. +0311 %Ex: yi*M >= Vipos + Vineg is impl. as yi*M - Vipos - Vineg - var == 0, 0 <= var <= Inf. +0312 % +0313 %All rows should be equal to zero, so we do not set the b vector in the problem +0314 %The reactions should be constrained as follows +0315 %S - as given in model.lb and model.ub +0316 %pos and neg - between 0 and inf +0317 %ints - between 0 and 1 +0318 %b - as stated in the model.b vector - if this has one column, that is lb and ub (fixed value), if two columns, that is lb and ub +0319 %var - between zero and inf +0320 +0321 [minLog, I]=ismember(model.rxns,toMinimize); +0322 indexes=find(minLog); +0323 revIndexes = find(minLog & (model.rev == 1)); +0324 irrevIndexes = find(minLog & (model.rev == 0)); +0325 revIndexesInInd = find(model.rev(indexes) == 1); +0326 irrevIndexesInInd = find(model.rev(indexes) == 0); +0327 +0328 %The int columns are ordered as indexes, i.e. by position in the model, +0329 %while the scores arrive ordered as toMinimize +0330 scores=scores(I(indexes)); +0331 +0332 %Add binary constraints in the following manner: - Add one unique +0333 %"metabolite" for each integer reaction as a substrate. +0334 % These metabolites can have net production +0335 %- Add reactions for the production of each of those metabolites. The +0336 % amount produced in one reaction unit must be larger than the largest +0337 % possible flux in the model (but not too large to avoid bad scaling) +0338 +0339 maxFlux=1000; +0340 %we build the total matrix as blocks: [S pos neg int b var] +0341 +0342 %s block +0343 intArray=speye(numel(model.rxns))*-1; +0344 intArrayRev=intArray(revIndexes,:); +0345 intArrayIrrev = intArray(irrevIndexes,:); +0346 sBlock=[model.S;intArrayRev;intArrayIrrev;sparse(numel(revIndexes), numel(model.rxns))]; %the S matrix and what is below +0347 +0348 %pos/neg blocks +0349 revposorneg1 = sparse(numel(model.mets), numel(revIndexes)); +0350 revpos2 = speye(numel(revIndexes)); +0351 revneg2 = -revpos2; +0352 revposneg3 = sparse(numel(irrevIndexes), numel(revIndexes)); +0353 revposneg4 = revneg2; +0354 posBlock = [revposorneg1;revpos2;revposneg3;revposneg4]; +0355 negBlock = [revposorneg1;revneg2;revposneg3;revposneg4]; 0356 -0357 %b block -0358 b1 = speye(numel(model.mets))*-1; -0359 b2 = sparse(numel(indexes) + numel(revIndexes), numel(model.mets)); -0360 bBlock = [b1;b2]; -0361 -0362 %var block -0363 var1 = sparse(numel(model.mets), numel(indexes)); -0364 var2 = sparse(numel(revIndexes), numel(indexes)); -0365 tmpEye = speye(numel(indexes))*-1; -0366 var3 = tmpEye(irrevIndexesInInd,:); -0367 var4 = tmpEye(revIndexesInInd,:); -0368 varBlock = [var1;var2;var3;var4]; +0357 %int block +0358 int1 = sparse(numel(model.mets), numel(indexes)); +0359 int2 = sparse(numel(revIndexes), numel(indexes)); +0360 tmpEye = speye(numel(indexes))*maxFlux; +0361 int3 = tmpEye(irrevIndexesInInd,:);%we here select only the irrev indexes from the indexes +0362 int4 = tmpEye(revIndexesInInd,:); +0363 intBlock = [int1;int2;int3;int4]; +0364 +0365 %b block +0366 b1 = speye(numel(model.mets))*-1; +0367 b2 = sparse(numel(indexes) + numel(revIndexes), numel(model.mets)); +0368 bBlock = [b1;b2]; 0369 -0370 prob.A = [sBlock posBlock negBlock intBlock bBlock varBlock]; -0371 prob.a = prob.A;%I think this is needed as well -0372 -0373 prob.c=[zeros(numel(model.rxns),1);zeros(numel(revIndexes)*2,1); scores(:);zeros(numel(model.mets) + numel(indexes),1)]; %Minimize the sum of reaction scores for reactions that are on -0374 -0375 %ub and lb, text copied from above -0376 %S - as given in model.lb and model.ub -0377 %pos and neg - between 0 and inf -0378 %ints - between 0 and 1 -0379 %b - as stated in the model.b vector - if this has one column, that is lb and ub (fixed value), if two columns, that is lb and ub -0380 %var - between zero and inf -0381 -0382 if size(model.b,2)==2 -0383 bub = model.b(:,2); -0384 else -0385 bub = model.b(:,1); -0386 end -0387 -0388 prob.lb = [model.lb;zeros(numel(revIndexes)*2,1);zeros(numel(indexes),1);model.b(:,1);zeros(numel(indexes),1)]; -0389 prob.ub = [model.ub;inf(numel(revIndexes)*2,1);ones(numel(indexes),1);bub;inf(numel(indexes),1)]; -0390 -0391 prob.b=zeros(size(prob.a,1), 1); -0392 -0393 intsIndexes = find(prob.c ~= 0); -0394 prob.x0=[]; -0395 prob.vartype=repmat('C', 1, size(prob.A,2)); -0396 prob.vartype(intsIndexes) = 'B'; -0397 prob.csense = repmat('E', 1, size(prob.A,1)); -0398 prob.osense=1; %minimize the objective -0399 -0400 %Defaults that suit this formulation, without discarding what the caller set -0401 if ~isfield(params,'intTol') -0402 params.intTol = 10^-9; -0403 end -0404 if ~isfield(params,'TimeLimit') -0405 params.TimeLimit = 300; -0406 end -0407 if ~isfield(params,'Seed') -0408 %Although the solver reports "optimal solution found", different seeds -0409 %can return solutions with noticeably different objective values, so a -0410 %fixed one is used to keep results reproducible -0411 params.Seed = 26; -0412 end -0413 -0414 % Optimize the problem -0415 res = optimizeProb(prob,params,verbose); -0416 [isFeasible, isOptimal]=checkSolution(res); -0417 -0418 if ~isFeasible -0419 x=[]; -0420 I=[]; -0421 exitFlag=-1; -0422 if res.hitTimeLimit -0423 %Nothing is returned either way, but the caller reports -1 as "no -0424 %feasible solution exists", which is not what happened here. -0425 EM='Time limit reached before finding a solution. Try increasing the TimeLimit parameter.'; -0426 warning('RAVEN:warning', '%s', EM); -0427 end -0428 return; -0429 end -0430 if res.hitTimeLimit || ~isOptimal -0431 %A feasible but suboptimal solution, i.e. the solver stopped before -0432 %reaching optimality. The solution is still returned, but must not be -0433 %reported as optimal: it may use more fluxes than the minimum. -0434 %Not every solver reports this in its status, gurobi for one presents a -0435 %MILP solution found before the limit as optimal, so res.hitTimeLimit is -0436 %consulted as well. -0437 exitFlag=-2; -0438 end -0439 -0440 if resolveTies -0441 %This MILP is highly degenerate: many reaction subsets reach the same -0442 %score optimum, and the solver returns an arbitrary one -- reproducible -0443 %for a fixed solver build + Seed, but fragile to a solver version bump -0444 %or an unrelated change upstream of the MILP (see raven-gecko-parity#104, -0445 %where this was demonstrated on genome-scale Human-GEM: the same Seed, -0446 %same problem, but a different MILP construction path flipped a tied -0447 %pair of reactions). This runs a lexicographic phase 2, holding the -0448 %score objective at its optimum with a floor constraint: first minimise -0449 %the count of "on" reactions (the sparsest optimum), then, among the -0450 %sparsest, minimise their summed reaction-id rank (prefer lower ids). -0451 %Ports raven-toolbox's _resolve_ties_fill (raven-toolbox#114). -0452 % -0453 %intCols identifies the int/binary variable columns directly from the -0454 %block layout (prob.c = [zeros(rxns); zeros(2*revIndexes); scores(:); -0455 %zeros(mets+indexes)]) rather than via find(prob.c~=0): a zero-scored -0456 %reaction would otherwise silently drop out of the column count and -0457 %misalign every later index into "indexes". -0458 intCols = numel(model.rxns) + 2*numel(revIndexes) + (1:numel(indexes)); -0459 primaryObj = full(prob.c(intCols)' * res.full(intCols)); -0460 tol = max(abs(primaryObj)*1e-7, 1e-7); -0461 -0462 floorRow = sparse(1,size(prob.A,2)); -0463 floorRow(intCols) = prob.c(intCols)'; -0464 tieProb = prob; -0465 tieProb.A = [prob.A; floorRow]; -0466 tieProb.a = tieProb.A; -0467 tieProb.b = [prob.b; primaryObj+tol]; -0468 tieProb.csense = [prob.csense 'L']; +0370 %var block +0371 var1 = sparse(numel(model.mets), numel(indexes)); +0372 var2 = sparse(numel(revIndexes), numel(indexes)); +0373 tmpEye = speye(numel(indexes))*-1; +0374 var3 = tmpEye(irrevIndexesInInd,:); +0375 var4 = tmpEye(revIndexesInInd,:); +0376 varBlock = [var1;var2;var3;var4]; +0377 +0378 prob.A = [sBlock posBlock negBlock intBlock bBlock varBlock]; +0379 prob.a = prob.A;%I think this is needed as well +0380 +0381 prob.c=[zeros(numel(model.rxns),1);zeros(numel(revIndexes)*2,1); scores(:);zeros(numel(model.mets) + numel(indexes),1)]; %Minimize the sum of reaction scores for reactions that are on +0382 +0383 %ub and lb, text copied from above +0384 %S - as given in model.lb and model.ub +0385 %pos and neg - between 0 and inf +0386 %ints - between 0 and 1 +0387 %b - as stated in the model.b vector - if this has one column, that is lb and ub (fixed value), if two columns, that is lb and ub +0388 %var - between zero and inf +0389 +0390 if size(model.b,2)==2 +0391 bub = model.b(:,2); +0392 else +0393 bub = model.b(:,1); +0394 end +0395 +0396 prob.lb = [model.lb;zeros(numel(revIndexes)*2,1);zeros(numel(indexes),1);model.b(:,1);zeros(numel(indexes),1)]; +0397 prob.ub = [model.ub;inf(numel(revIndexes)*2,1);ones(numel(indexes),1);bub;inf(numel(indexes),1)]; +0398 +0399 prob.b=zeros(size(prob.a,1), 1); +0400 +0401 intsIndexes = find(prob.c ~= 0); +0402 prob.x0=[]; +0403 prob.vartype=repmat('C', 1, size(prob.A,2)); +0404 prob.vartype(intsIndexes) = 'B'; +0405 prob.csense = repmat('E', 1, size(prob.A,1)); +0406 prob.osense=1; %minimize the objective +0407 +0408 %Defaults that suit this formulation, without discarding what the caller set +0409 if ~isfield(params,'intTol') +0410 params.intTol = 10^-9; +0411 end +0412 if ~isfield(params,'TimeLimit') +0413 params.TimeLimit = 300; +0414 end +0415 if ~isfield(params,'Seed') +0416 %Although the solver reports "optimal solution found", different seeds +0417 %can return solutions with noticeably different objective values, so a +0418 %fixed one is used to keep results reproducible +0419 params.Seed = 26; +0420 end +0421 +0422 % Optimize the problem +0423 res = optimizeProb(prob,params,verbose); +0424 [isFeasible, isOptimal]=checkSolution(res); +0425 +0426 if ~isFeasible +0427 x=[]; +0428 I=[]; +0429 exitFlag=-1; +0430 if res.hitTimeLimit +0431 %Nothing is returned either way, but the caller reports -1 as "no +0432 %feasible solution exists", which is not what happened here. +0433 EM='Time limit reached before finding a solution. Try increasing the TimeLimit parameter.'; +0434 warning('RAVEN:warning', '%s', EM); +0435 end +0436 return; +0437 end +0438 if res.hitTimeLimit || ~isOptimal +0439 %A feasible but suboptimal solution, i.e. the solver stopped before +0440 %reaching optimality. The solution is still returned, but must not be +0441 %reported as optimal: it may use more fluxes than the minimum. +0442 %Not every solver reports this in its status, gurobi for one presents a +0443 %MILP solution found before the limit as optimal, so res.hitTimeLimit is +0444 %consulted as well. +0445 exitFlag=-2; +0446 end +0447 +0448 if resolveTies +0449 %This MILP is highly degenerate: many reaction subsets reach the same +0450 %score optimum, and the solver returns an arbitrary one -- reproducible +0451 %for a fixed solver build + Seed, but fragile to a solver version bump +0452 %or an unrelated change upstream of the MILP (see raven-gecko-parity#104, +0453 %where this was demonstrated on genome-scale Human-GEM: the same Seed, +0454 %same problem, but a different MILP construction path flipped a tied +0455 %pair of reactions). This runs a lexicographic phase 2, holding the +0456 %score objective at its optimum with a floor constraint: first minimise +0457 %the count of "on" reactions (the sparsest optimum), then, among the +0458 %sparsest, minimise their summed reaction-id rank (prefer lower ids). +0459 %Ports raven-toolbox's _resolve_ties_fill (raven-toolbox#114). +0460 % +0461 %intCols identifies the int/binary variable columns directly from the +0462 %block layout (prob.c = [zeros(rxns); zeros(2*revIndexes); scores(:); +0463 %zeros(mets+indexes)]) rather than via find(prob.c~=0): a zero-scored +0464 %reaction would otherwise silently drop out of the column count and +0465 %misalign every later index into "indexes". +0466 intCols = numel(model.rxns) + 2*numel(revIndexes) + (1:numel(indexes)); +0467 primaryObj = full(prob.c(intCols)' * res.full(intCols)); +0468 tol = max(abs(primaryObj)*1e-7, 1e-7); 0469 -0470 %An integer objective needs only a cheap absolute-gap proof, not a tiny -0471 %relative one -- RAVEN's own MIPGap default (1e-12) can be slow to prove -0472 %on a genome-scale problem where a gap below 1 already proves optimality -0473 %exactly (mirrors raven-toolbox's own MIPGap=0/MIPGapAbs=0.4 for the same -0474 %two phases). -0475 tieParams = params; -0476 tieParams.MIPGap = 0; -0477 tieParams.MIPGapAbs = 0.4; -0478 -0479 unproven = {}; -0480 -0481 %Phase 2a: fewest "on" reactions among the score-optimal solutions. -0482 countObj = zeros(size(prob.c)); -0483 countObj(intCols) = 1; -0484 tieProb.c = countObj; -0485 res2a = optimizeProb(tieProb,tieParams,verbose); -0486 [isFeasible2a, isOptimal2a] = checkSolution(res2a); -0487 if ~isOptimal2a -0488 unproven{end+1} = 'phase2a-parsimony'; -0489 end -0490 if isFeasible2a -0491 kmin = full(countObj'*res2a.full); -0492 -0493 %Phase 2b: among the sparsest, prefer lower reaction ids -- a -0494 %deterministic tie-break independent of solver/seed. -0495 countRow = sparse(1,size(tieProb.A,2)); -0496 countRow(intCols) = 1; -0497 tieProb2b = tieProb; -0498 tieProb2b.A = [tieProb.A; countRow]; -0499 tieProb2b.a = tieProb2b.A; -0500 tieProb2b.b = [tieProb.b; kmin+0.5]; -0501 tieProb2b.csense = [tieProb.csense 'L']; -0502 -0503 [~, sortOrder] = sort(model.rxns(indexes)); -0504 ranks = zeros(numel(indexes),1); -0505 ranks(sortOrder) = (1:numel(indexes))'; -0506 idObj = zeros(size(prob.c)); -0507 idObj(intCols) = ranks; -0508 tieProb2b.c = idObj; -0509 res2b = optimizeProb(tieProb2b,tieParams,verbose); -0510 [isFeasible2b, isOptimal2b] = checkSolution(res2b); -0511 if ~isOptimal2b -0512 unproven{end+1} = 'phase2b-idrank'; -0513 end -0514 if isFeasible2b -0515 %Adopt the tie-resolved solution; exitFlag still reflects -0516 %whether the *primary* (scores) objective was proven optimal, -0517 %same as without resolveTies. -0518 res = res2b; -0519 end -0520 end -0521 if ~isempty(unproven) -0522 EM = ['getMinNrFluxes tie resolution did not converge (' strjoin(unproven,', ') '): the selection among equally score-optimal solutions is itself an unproven incumbent, so resolveTies has reduced but not removed the run-to-run spread. Raise params.TimeLimit for a proven tie-break.']; -0523 warning('RAVEN:warning', '%s', EM); -0524 end -0525 end -0526 -0527 x=res.full(1:numel(model.rxns));%the fluxes -0528 -0529 ints = res.full(intsIndexes); -0530 %The margin for integers in gurobi is 10^-5; use 10^-3 to have some margin. -0531 I=ismember(toMinimize, model.rxns(indexes(ints > 10^-3))); -0532 -0533 sel = (ints > 10^-12) & (ints < 0.5); -0534 if sum(sel) > 0 -0535 %This may indicate that there is a problem with the tolerances in the solver -0536 disp(['getMinNrFluxes: Some variables meant to be boolean in the MILP have intermediate values. Num vars: ' num2str(sum(sel))]) -0537 end -0538 -0539 end

    +0470 floorRow = sparse(1,size(prob.A,2)); +0471 floorRow(intCols) = prob.c(intCols)'; +0472 tieProb = prob; +0473 tieProb.A = [prob.A; floorRow]; +0474 tieProb.a = tieProb.A; +0475 tieProb.b = [prob.b; primaryObj+tol]; +0476 tieProb.csense = [prob.csense 'L']; +0477 +0478 %An integer objective needs only a cheap absolute-gap proof, not a tiny +0479 %relative one -- RAVEN's own MIPGap default (1e-12) can be slow to prove +0480 %on a genome-scale problem where a gap below 1 already proves optimality +0481 %exactly (mirrors raven-toolbox's own MIPGap=0/MIPGapAbs=0.4 for the same +0482 %two phases). +0483 tieParams = params; +0484 tieParams.MIPGap = 0; +0485 tieParams.MIPGapAbs = 0.4; +0486 +0487 unproven = {}; +0488 +0489 %Phase 2a: fewest "on" reactions among the score-optimal solutions. +0490 countObj = zeros(size(prob.c)); +0491 countObj(intCols) = 1; +0492 tieProb.c = countObj; +0493 res2a = optimizeProb(tieProb,tieParams,verbose); +0494 [isFeasible2a, isOptimal2a] = checkSolution(res2a); +0495 if ~isOptimal2a +0496 unproven{end+1} = 'phase2a-parsimony'; +0497 end +0498 if isFeasible2a +0499 kmin = full(countObj'*res2a.full); +0500 +0501 %Phase 2b: among the sparsest, prefer lower reaction ids -- a +0502 %deterministic tie-break independent of solver/seed. +0503 countRow = sparse(1,size(tieProb.A,2)); +0504 countRow(intCols) = 1; +0505 tieProb2b = tieProb; +0506 tieProb2b.A = [tieProb.A; countRow]; +0507 tieProb2b.a = tieProb2b.A; +0508 tieProb2b.b = [tieProb.b; kmin+0.5]; +0509 tieProb2b.csense = [tieProb.csense 'L']; +0510 +0511 [~, sortOrder] = sort(model.rxns(indexes)); +0512 ranks = zeros(numel(indexes),1); +0513 ranks(sortOrder) = (1:numel(indexes))'; +0514 idObj = zeros(size(prob.c)); +0515 idObj(intCols) = ranks; +0516 tieProb2b.c = idObj; +0517 res2b = optimizeProb(tieProb2b,tieParams,verbose); +0518 [isFeasible2b, isOptimal2b] = checkSolution(res2b); +0519 if ~isOptimal2b +0520 unproven{end+1} = 'phase2b-idrank'; +0521 end +0522 if isFeasible2b +0523 %Adopt the tie-resolved solution; exitFlag still reflects +0524 %whether the *primary* (scores) objective was proven optimal, +0525 %same as without resolveTies. +0526 res = res2b; +0527 end +0528 end +0529 if ~isempty(unproven) +0530 EM = ['getMinNrFluxes tie resolution did not converge (' strjoin(unproven,', ') '): the selection among equally score-optimal solutions is itself an unproven incumbent, so resolveTies has reduced but not removed the run-to-run spread. Raise params.TimeLimit for a proven tie-break.']; +0531 warning('RAVEN:warning', '%s', EM); +0532 end +0533 end +0534 +0535 x=res.full(1:numel(model.rxns));%the fluxes +0536 +0537 ints = res.full(intsIndexes); +0538 %The margin for integers in gurobi is 10^-5; use 10^-3 to have some margin. +0539 I=ismember(toMinimize, model.rxns(indexes(ints > 10^-3))); +0540 +0541 sel = (ints > 10^-12) & (ints < 0.5); +0542 if sum(sel) > 0 +0543 %This may indicate that there is a problem with the tolerances in the solver +0544 disp(['getMinNrFluxes: Some variables meant to be boolean in the MILP have intermediate values. Num vars: ' num2str(sum(sel))]) +0545 end +0546 +0547 end

    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/solver/index.html b/docs/html/solver/index.html index 7ebd6eed..5046e977 100644 --- a/docs/html/solver/index.html +++ b/docs/html/solver/index.html @@ -19,7 +19,7 @@

    Index for solver

    Matlab files in this directory:

    -
     checkSolutioncheckSolution Check if a solver result is feasible and optimal.
     getMinNrFluxesgetMinNrFluxes Find the minimal set of fluxes that satisfy the model.
     optimizeProboptimizeProb Optimize an LP or MILP formulated in COBRA terms.
     setRavenSolversetRavenSolver Set the solver for RAVEN.
     solveLPsolveLP Solve a linear programming problem.
     splitProbForConditioningsplitProbForConditioning Improve LP conditioning by splitting wide columns.
    + checkSolutioncheckSolution Check if a solver result is feasible and optimal.  getMinNrFluxesgetMinNrFluxes Find the minimal set of fluxes that satisfy the model.  optimizeProboptimizeProb Optimise an LP or MILP formulated in COBRA terms.  setRavenSolversetRavenSolver Set the solver for RAVEN.  solveLPsolveLP Solve a linear programming problem.  splitProbForConditioningsplitProbForConditioning Improve LP conditioning by splitting wide columns. diff --git a/docs/html/solver/optimizeProb.html b/docs/html/solver/optimizeProb.html index 715151bd..6b95268f 100644 --- a/docs/html/solver/optimizeProb.html +++ b/docs/html/solver/optimizeProb.html @@ -4,7 +4,7 @@ Description of optimizeProb - + @@ -21,30 +21,31 @@

    optimizeProb

    PURPOSE ^

    -
    optimizeProb Optimize an LP or MILP formulated in COBRA terms.
    +
    optimizeProb Optimise an LP or MILP formulated in COBRA terms.

    SYNOPSIS ^

    function res = optimizeProb(prob,varargin)

    DESCRIPTION ^

    -
     optimizeProb  Optimize an LP or MILP formulated in COBRA terms.
    +
     optimizeProb  Optimise an LP or MILP formulated in COBRA terms.
     
      Parameters
      ----------
      prob : struct
    -     COBRA-style LP/MILP problem struct to be optimised.
    +     COBRA-style problem struct of an LP or MILP to be optimised.
     
      Name-Value Arguments
      --------------------
      params : struct
          solver-specific parameters. In addition to solver parameters, the
    -     field "maxRatio" can be set to a number > 1 to improve numerical
    -     conditioning: before solving (LP only) any column whose coefficients
    -     span more than maxRatio is split via auxiliary metabolites/variables,
    -     preserving the feasible region. See splitProbForConditioning. The
    -     field is consumed here and not forwarded to the solver.
    +     field "maxRatio" can be set to a finite number > 1 for numerical
    +     conditioning: before solving an LP, any column whose coefficients
    +     span more than maxRatio is split with auxiliary metabolites and
    +     variables, which preserves the feasible region. See
    +     splitProbForConditioning. The field is removed before the parameters
    +     are passed to the solver, and is ignored for MILPs.
      verbose : logical
    -     if true MILP progress is shown (default true).
    +     if true, MILP progress is shown (default true).
     
      Returns
      -------
    @@ -52,16 +53,16 @@ 

    DESCRIPTION ^SUBFUNCTIONS ^SOURCE CODE ^

    0001 function res = optimizeProb(prob,varargin)
    -0002 % optimizeProb  Optimize an LP or MILP formulated in COBRA terms.
    +0002 % optimizeProb  Optimise an LP or MILP formulated in COBRA terms.
     0003 %
     0004 % Parameters
     0005 % ----------
     0006 % prob : struct
    -0007 %     COBRA-style LP/MILP problem struct to be optimised.
    +0007 %     COBRA-style problem struct of an LP or MILP to be optimised.
     0008 %
     0009 % Name-Value Arguments
     0010 % --------------------
     0011 % params : struct
     0012 %     solver-specific parameters. In addition to solver parameters, the
    -0013 %     field "maxRatio" can be set to a number > 1 to improve numerical
    -0014 %     conditioning: before solving (LP only) any column whose coefficients
    -0015 %     span more than maxRatio is split via auxiliary metabolites/variables,
    -0016 %     preserving the feasible region. See splitProbForConditioning. The
    -0017 %     field is consumed here and not forwarded to the solver.
    -0018 % verbose : logical
    -0019 %     if true MILP progress is shown (default true).
    -0020 %
    -0021 % Returns
    -0022 % -------
    -0023 % res : struct
    -0024 %     the output structure from the selected solver RAVENSOLVER (COBRA
    -0025 %     style). In addition to the fields set by the solver, the logical field
    -0026 %     "hitTimeLimit" is true if the solver stopped because it reached its
    -0027 %     time limit. Whatever solution is in res is then either absent or not
    -0028 %     proven optimal.
    -0029 %
    -0030 % Notes
    -0031 % -----
    -0032 % MILPs can be solved with GLPK, either RAVEN's bundled GLPK ('glpk') or
    -0033 % the COBRA Toolbox's GLPK interface ('cobra' with GLPK as its MILP solver).
    -0034 % A RAVEN:glpkMILP warning then recommends Gurobi, which is much faster and
    -0035 % more reliable on larger MILPs (such as ftINIT or getMinNrFluxes on
    -0036 % genome-scale models).
    -0037 %
    -0038 % See also
    -0039 % --------
    -0040 % splitProbForConditioning
    -0041 
    -0042 p=parseRAVENargs(varargin, {'params',[]; 'verbose',true});
    -0043 params=p.params;
    -0044 if isempty(params)
    -0045     params=struct();
    -0046 end
    -0047 verbose=p.verbose;
    -0048 
    -0049 %Set as global variable for speed improvement if optimizeProb is run many times
    -0050 global RAVENSOLVER;
    -0051 %CBT_MILP_SOLVER is a COBRA Toolbox global, set by changeCobraSolver, and
    -0052 %has to be declared as such to be readable in the MILP check below
    -0053 global CBT_MILP_SOLVER;
    -0054 if isempty(RAVENSOLVER)
    -0055     if(~ispref('RAVEN','solver'))
    -0056         error('RAVEN:badInput', '%s', 'RAVEN solver not defined or unknown. Try using setRavenSolver(''solver'').');
    -0057     else
    -0058         RAVENSOLVER = getpref('RAVEN','solver');
    -0059     end
    -0060 end
    -0061 solver=RAVENSOLVER;
    -0062 if ~all(lower(prob.vartype) == 'c')
    -0063     milp=true;
    -0064     if strcmp(solver,'glpk') || (strcmp(solver,'cobra') && strcmp(CBT_MILP_SOLVER,'glpk'))
    -0065         warning('RAVEN:glpkMILP', ['Solving a MILP with GLPK. This works for small ' ...
    -0066             'problems, but for larger problems Gurobi is much faster and more reliable; ' ...
    -0067             'select it with setRavenSolver(''gurobi''). Silence this warning with ' ...
    -0068             'warning(''off'',''RAVEN:glpkMILP'').'])
    -0069     end
    -0070 else
    -0071     milp=false;
    -0072 end
    -0073 
    -0074 %% Define default parameters, which will then be used to make solver-
    -0075 % specific solverparams structures
    -0076 defaultparams.feasTol        = 1e-9;
    -0077 defaultparams.optTol         = 1e-9;
    -0078 defaultparams.objTol         = 1e-6;
    -0079 defaultparams.timeLimit      = 1000;
    -0080 %defaultparams.iterationLimit = 1000;
    -0081 defaultparams.intTol         = 1e-12;
    -0082 defaultparams.relMipGapTol   = 1e-12;
    -0083 defaultparams.absMipGapTol   = 1e-12;
    -0084 if milp
    -0085     defaultparams.MIPGap     = 1e-12;
    -0086     defaultparams.Seed       = 1;
    -0087 end
    -0088 
    -0089 %% Optionally improve numerical conditioning before solving. This is opt-in
    -0090 % through params.maxRatio and only applied to LPs. Columns whose coefficients
    -0091 % span more than maxRatio are split via auxiliary metabolites/variables, which
    -0092 % preserves the feasible region but reduces the spread within each column. The
    -0093 % added rows/columns are stripped from the result further down.
    -0094 condApplied = false;
    -0095 if isfield(params,'maxRatio')
    -0096     maxRatio = params.maxRatio;
    -0097     params   = rmfield(params,'maxRatio');
    -0098     if ~milp && isnumeric(maxRatio) && isscalar(maxRatio) && isfinite(maxRatio) && maxRatio>1
    -0099         [prob, condInfo] = splitProbForConditioning(prob, maxRatio);
    -0100         condApplied = condInfo.applied;
    -0101     end
    -0102 end
    -0103 
    -0104 res.obj=[];
    -0105 switch solver
    -0106     %% Use whatever solver is set by COBRA Toolbox changeCobraSolver
    -0107     case 'cobra'
    -0108         if milp
    -0109             cparams=struct('timeLimit',1e9,'printLevel',0,'intTol',1e-6,'relMipGapTol',1e-9);
    -0110             cparams=structUpdate(cparams,params);
    -0111             res=solveCobraMILP(prob,cparams);
    -0112         else
    -0113             res=solveCobraLP(prob);
    -0114         end
    -0115         %% Use Gurobi in a MATLAB environment
    -0116     case 'gurobi'
    -0117         if milp
    -0118             if verbose
    -0119                 solverparams.OutputFlag = 1;
    -0120             else
    -0121                 solverparams.OutputFlag = 0;
    -0122             end
    -0123             solverparams.IntFeasTol = 10^-9; %min val for gurobi
    -0124             solverparams.MIPGap = defaultparams.MIPGap;
    -0125             solverparams.Seed = defaultparams.Seed;
    -0126         else
    -0127             solverparams.OutputFlag = 0;
    -0128         end
    -0129         solverparams.DisplayInterval= 1; % Level of verbosity
    -0130         solverparams.TimeLimit      = defaultparams.timeLimit;
    -0131         solverparams.FeasibilityTol = defaultparams.feasTol;
    -0132         solverparams.OptimalityTol  = defaultparams.optTol;
    -0133         solverparams.Presolve       = 2;
    -0134         % getCurrentTask requires the Parallel Computing Toolbox; without it
    -0135         % the call itself errors, so guard it rather than requiring the
    -0136         % toolbox for every gurobi solve.
    -0137         if exist('getCurrentTask','file') && ~isempty(getCurrentTask) % If run in parallel, then one thread per gurobi
    -0138             solverparams.Threads=1;
    -0139         end
    -0140         solverparams = structUpdate(solverparams,params);
    -0141 
    -0142         % Restructering problem according to gurobi format
    -0143         if isfield(prob, 'csense')
    -0144             prob.sense = renameparams(prob.csense, {'L','G','E'}, {'<','>','='});
    -0145             prob = rmfield(prob, {'csense'});
    -0146         end
    -0147         % gurobi minimises unless told otherwise, so an absent osense means
    -0148         % minimisation; osense is also needed below to sign the duals
    -0149         osense = 1;
    -0150         if isfield(prob, 'osense')
    -0151             osense = prob.osense;
    -0152             if osense < 0
    -0153                 prob.modelsense = 'max';
    -0154             else
    -0155                 prob.modelsense = 'min';
    -0156             end
    -0157             prob = rmfield(prob, {'osense'});
    -0158         end
    -0159         [prob.obj, prob.rhs, prob.vtype] = deal(prob.c, prob.b, prob.vartype);
    -0160         prob = rmfield(prob, {'c','b','vartype'});
    -0161 
    -0162         resG = gurobi(prob,solverparams);
    -0163 
    -0164         % Name output fields the same as COBRA does. Every field below is
    -0165         % optional in the gurobi result: which ones are present depends on
    -0166         % the status and on the method gurobi chose (a barrier solve returns
    -0167         % no simplex basis, for instance). They are therefore read
    -0168         % individually rather than under one try/catch, which would have
    -0169         % reported a missing basis on an optimal solve as "infeasible".
    -0170         res.origStat = resG.status;
    -0171         if isfield(resG,'x')
    -0172             res.full = resG.x;
    -0173         end
    -0174         if isfield(resG,'objval')
    -0175             res.obj = resG.objval;
    -0176         end
    -0177         if isfield(resG,'pi') && isfield(resG,'rc')
    -0178             res.dual     = -resG.pi*osense;
    -0179             res.rcost    = -resG.rc*osense;
    -0180         end
    -0181         if milp && strcmp(resG.status, 'TIME_LIMIT') && isfield(resG,'x') && isfield(resG,'objval')
    -0182             % An incumbent was found before the time limit. It is usable but
    -0183             % not proven optimal, so it is reported as feasible rather than
    -0184             % optimal; callers that must not act on a suboptimal MILP
    -0185             % solution (getMinNrFluxes, solveLP) key on that distinction.
    -0186             resG.status = 'SUBOPTIMAL';
    -0187         end
    -0188         switch resG.status
    -0189             case 'OPTIMAL'
    -0190                 res.stat = 1;
    -0191             case {'UNBOUNDED','SUBOPTIMAL'}
    -0192                 res.stat = 2;
    -0193             otherwise
    -0194                 res.stat = 0;
    -0195         end
    -0196         if ~milp
    -0197             if isfield(resG,'vbasis')
    -0198                 res.vbasis = resG.vbasis;
    -0199             end
    -0200             if isfield(resG,'cbasis')
    -0201                 res.cbasis = resG.cbasis;
    -0202             end
    -0203         elseif isfield(resG,'mipgap')
    -0204             res.mipgap = resG.mipgap;
    -0205         end
    -0206         if res.stat>0 && ~isfield(res,'full')
    -0207             % A status that claims a solution without returning one
    -0208             res.stat = 0;
    -0209         end
    -0210         %% Use GLPK using RAVEN-provided binary
    -0211     case 'glpk'
    -0212         solverparams.scale   = 1; % Auto scaling
    -0213         %solverparams.tmlim   = defaultparams.timeLimit;
    -0214         solverparams.tolbnd  = defaultparams.feasTol;
    -0215         solverparams.toldj   = defaultparams.optTol;
    -0216         solverparams.tolint  = defaultparams.intTol;
    -0217         solverparams.tolobj  = defaultparams.objTol;
    -0218         solverparams.msglev  = 0; % Level of verbosity
    -0219         if milp
    -0220             solverparams.tmlim = defaultparams.timeLimit; % seconds
    -0221         end
    -0222         solverparams = structUpdate(solverparams,params);
    -0223 
    -0224         prob.csense = renameparams(prob.csense, {'L','G','E'}, {'U','L','S'});
    -0225 
    -0226         % Ensure that RAVEN glpk binary is used, return to original
    -0227         % directory afterwards
    -0228         [ravenDir,currDir]=findRAVENroot();
    -0229         cd(fullfile(ravenDir,'src','external','GLPKmex'))
    -0230         [xopt, fmin, errnum, extra] = glpk(prob.c, prob.A, prob.b, prob.lb, prob.ub, prob.csense, prob.vartype, prob.osense, solverparams);
    -0231         cd(currDir)
    -0232 
    -0233         switch errnum % 1 = undefined; 2 = feasible; 3 = infeasible; 4 = no feasible solution; 5 = optimal; 6 = no unbounded solution
    -0234             case 5
    -0235                 res.stat = 1; % Optimal
    -0236             case 2
    -0237                 res.stat = 2; % Feasible, but not optimal
    -0238             otherwise
    -0239                 res.stat = 0;
    -0240         end
    -0241         res.origStat = errnum;
    -0242         res.full     = xopt;
    -0243         res.obj      = fmin;
    -0244         res.dual     = -extra.lambda*prob.osense;
    -0245         res.rcost    = -extra.redcosts*prob.osense;
    -0246         if milp
    -0247             % glpk reports no gap; a proven optimum has none, anything else
    -0248             % is not accepted by callers that test the gap
    -0249             if res.stat == 1
    -0250                 res.mipgap = 0;
    -0251             else
    -0252                 res.mipgap = inf;
    -0253             end
    -0254         end
    -0255         %% Use scip
    -0256     case {'soplex','scip'} % Old 'soplex' option also allowed
    -0257         % scip takes a row range [rl,ru] rather than a right-hand side plus
    -0258         % a sense, so csense has to be translated into that range. Passing
    -0259         % [-b,b] is only the same problem when every row is an equality with
    -0260         % b = 0, which is the case for solveLP but for none of the MILPs
    -0261         % built elsewhere in RAVEN.
    -0262         rl = full(prob.b(:));
    -0263         ru = rl;
    -0264         csense = prob.csense(:);
    -0265         rl(csense=='L') = -inf;   % A*x <= b
    -0266         ru(csense=='G') = inf;    % A*x >= b
    -0267         [xopt,fval,exitflag,scipStats] = scip([], prob.c, prob.A, rl, ru, prob.lb, prob.ub, prob.vartype);
    -0268 
    -0269         %   [x,fval,exitflag,stats] = scip(H, f, A, rl, ru, lb, ub, xtype, sos, qc, nl, x0, opts)
    -0270         %
    -0271         %   Input arguments*:
    -0272         %       H - quadratic objective matrix (sparse, optional [NOT TRIL / TRIU])
    -0273         %       f - linear objective vector
    -0274         %       A - linear constraint matrix (sparse)
    -0275         %       rl - linear constraint lhs
    -0276         %       ru - linear constraint rhs
    -0277         %       lb - decision variable lower bounds
    -0278         %       ub - decision variable upper bounds
    -0279         %       xtype - string of variable integrality ("c" continuous, "i" integer, "b" binary)
    -0280         %       sos - SOS structure with fields type, index and weight (see below)
    -0281         %       qc - Quadratic Constraints structure with fields Q, l, qrl and qru (see below)
    -0282         %       nl - Nonlinear Objective and Constraints structure (see below)
    -0283         %       x0 - primal solution
    -0284         %       opts - solver options (see below)
    -0285         %
    -0286         %   Return arguments:
    -0287         %       x - solution vector
    -0288         %       fval - objective value at the solution
    -0289         %       exitflag - exit status (see below)
    -0290         %       stats - statistics structure
    -0291         %
    -0292         %   Option Fields (all optional, see also optiset for a list):
    -0293         %       solverOpts - specific SCIP options (list of pairs of parameter names and values)
    -0294         %       maxiter - maximum LP solver iterations
    -0295         %       maxnodes - maximum nodes to explore
    -0296         %       maxtime - maximum execution time [s]
    -0297         %       tolrfun - primal feasibility tolerance
    -0298         %       display - solver display level [0-5]
    -0299         %       probfile - write problem to given file
    -0300         %       presolvedfile - write presolved problem to file
    -0301         %
    -0302         %   Return Status, as returned by the MatlabSCIPInterface build that
    -0303         %   RAVEN provides and that setRavenSolver points at
    -0304         %   (github.com/scipopt/MatlabSCIPInterface). Verified against that
    -0305         %   binary with a bounded, an infeasible and an unbounded problem:
    -0306         %       1 - Problem Solved to Optimality
    -0307         %       2 - Problem is Infeasible
    -0308         %       3 - Problem is Unbounded
    -0309         %
    -0310         %   The OPTI Toolbox build of scip uses a different, longer set in
    -0311         %   which optimality is 11, infeasibility 12 and unboundedness 13,
    -0312         %   with the lower numbers meaning "a limit was reached". Those codes
    -0313         %   are accepted below as well, so either build reports optimality
    -0314         %   correctly; only OPTI's 1 ("User Interrupted") is ambiguous, and
    -0315         %   reading it as optimal is no worse than the previous mapping,
    -0316         %   which recognised no status from the shipped build at all.
    -0317         
    -0318         res.origStat = exitflag;
    -0319         res.full = xopt;
    -0320         res.obj  = fval;
    -0321 
    -0322         switch exitflag
    -0323             case {1, 11}                  % solved to optimality
    -0324                 res.stat = 1;
    -0325             case {3, 13}                  % unbounded
    -0326                 res.stat = 2;
    -0327             case {5, 6, 7, 8, 9, 10}      % OPTI: a limit was reached, so a
    -0328                 res.stat = 2;             % solution may exist but is not proven optimal
    -0329             otherwise                     % 2/12 infeasible, 0 unknown, 4, 14
    -0330                 res.stat = 0;
    -0331         end
    -0332 
    -0333         if milp
    -0334             % Callers such as ftINIT compare this against their own
    -0335             % params.MIPGap, so it has to be the quantity the gurobi branch
    -0336             % reports there: a relative gap. It is derived from the bounds
    -0337             % rather than read from scipStats.BBgap, whose units are not
    -0338             % documented. inf where no gap can be established, so that a
    -0339             % caller testing "gap small enough" does not accept the solution.
    -0340             if res.stat == 1
    -0341                 res.mipgap = 0;
    -0342             else
    -0343                 res.mipgap = inf;
    -0344                 primal = scipStats.PrimalBound;
    -0345                 if isfinite(primal) && abs(primal) > 1e-10
    -0346                     res.mipgap = abs(primal-scipStats.DualBound)/abs(primal);
    -0347                 end
    -0348             end
    -0349         end
    -0350     otherwise
    -0351         error('RAVEN solver not defined or unknown. Try using setRavenSolver(''solver'').');
    -0352 end
    -0353 %Flag a solve that ended on the time limit. res.stat cannot express this on
    -0354 %its own: a solver that times out with an incumbent solution in hand reports
    -0355 %the same status as one that finished, and a solver that times out with
    -0356 %nothing reports the same status as an infeasible problem. The raw status in
    -0357 %res.origStat does distinguish the two, but only in solver-specific terms,
    -0358 %so it is translated here where the solver is still known.
    -0359 res.hitTimeLimit=hitTimeLimit(solver,res,milp);
    -0360 if res.stat>0
    -0361     res.full=res.full(1:size(prob.a,2));
    -0362 end
    -0363 %Strip the auxiliary rows/columns that were added for conditioning, so that
    -0364 %the result matches the dimensions of the original problem. res.full is
    -0365 %already truncated to the original variables above (prob.a is left untouched).
    -0366 if condApplied
    -0367     if isfield(res,'dual') && numel(res.dual)>condInfo.nOrigRows
    -0368         res.dual=res.dual(1:condInfo.nOrigRows);
    -0369     end
    -0370     if isfield(res,'rcost') && numel(res.rcost)>condInfo.nOrigCols
    -0371         res.rcost=res.rcost(1:condInfo.nOrigCols);
    -0372     end
    -0373 end
    +0013 %     field "maxRatio" can be set to a finite number > 1 for numerical
    +0014 %     conditioning: before solving an LP, any column whose coefficients
    +0015 %     span more than maxRatio is split with auxiliary metabolites and
    +0016 %     variables, which preserves the feasible region. See
    +0017 %     splitProbForConditioning. The field is removed before the parameters
    +0018 %     are passed to the solver, and is ignored for MILPs.
    +0019 % verbose : logical
    +0020 %     if true, MILP progress is shown (default true).
    +0021 %
    +0022 % Returns
    +0023 % -------
    +0024 % res : struct
    +0025 %     the output structure from the selected solver RAVENSOLVER (COBRA
    +0026 %     style). In addition to the fields set by the solver, the logical field
    +0027 %     "hitTimeLimit" is true if the solver stopped because it reached its
    +0028 %     time limit. The solution in res is then either absent or not proven
    +0029 %     optimal.
    +0030 %
    +0031 % Notes
    +0032 % -----
    +0033 % MILPs can be solved with GLPK, either RAVEN's bundled GLPK ('glpk') or
    +0034 % the COBRA Toolbox's GLPK interface ('cobra' with GLPK as its MILP solver).
    +0035 % The RAVEN:glpkMILP warning then states that GLPK works for small problems
    +0036 % and that Gurobi is much faster and more reliable for larger ones (such as
    +0037 % ftINIT or getMinNrFluxes on genome-scale models).
    +0038 %
    +0039 % See also
    +0040 % --------
    +0041 % splitProbForConditioning
    +0042 
    +0043 p=parseRAVENargs(varargin, {'params',[]; 'verbose',true});
    +0044 params=p.params;
    +0045 if isempty(params)
    +0046     params=struct();
    +0047 end
    +0048 verbose=p.verbose;
    +0049 
    +0050 %Set as global variable for speed improvement if optimizeProb is run many times
    +0051 global RAVENSOLVER;
    +0052 %CBT_MILP_SOLVER is a COBRA Toolbox global, set by changeCobraSolver, and
    +0053 %has to be declared as such to be readable in the MILP check below
    +0054 global CBT_MILP_SOLVER;
    +0055 if isempty(RAVENSOLVER)
    +0056     if(~ispref('RAVEN','solver'))
    +0057         error('RAVEN:badInput', '%s', 'RAVEN solver not defined or unknown. Try using setRavenSolver(''solver'').');
    +0058     else
    +0059         RAVENSOLVER = getpref('RAVEN','solver');
    +0060     end
    +0061 end
    +0062 solver=RAVENSOLVER;
    +0063 if ~all(lower(prob.vartype) == 'c')
    +0064     milp=true;
    +0065     if strcmp(solver,'glpk') || (strcmp(solver,'cobra') && strcmp(CBT_MILP_SOLVER,'glpk'))
    +0066         warning('RAVEN:glpkMILP', ['Solving a MILP with GLPK. This works for small ' ...
    +0067             'problems, but for larger problems Gurobi is much faster and more reliable; ' ...
    +0068             'select it with setRavenSolver(''gurobi''). Silence this warning with ' ...
    +0069             'warning(''off'',''RAVEN:glpkMILP'').'])
    +0070     end
    +0071 else
    +0072     milp=false;
    +0073 end
    +0074 
    +0075 %% Define default parameters, which will then be used to make solver-
    +0076 % specific solverparams structures
    +0077 defaultparams.feasTol        = 1e-9;
    +0078 defaultparams.optTol         = 1e-9;
    +0079 defaultparams.objTol         = 1e-6;
    +0080 defaultparams.timeLimit      = 1000;
    +0081 %defaultparams.iterationLimit = 1000;
    +0082 defaultparams.intTol         = 1e-12;
    +0083 defaultparams.relMipGapTol   = 1e-12;
    +0084 defaultparams.absMipGapTol   = 1e-12;
    +0085 if milp
    +0086     defaultparams.MIPGap     = 1e-12;
    +0087     defaultparams.Seed       = 1;
    +0088 end
    +0089 
    +0090 %% Optionally improve numerical conditioning before solving. This is opt-in
    +0091 % through params.maxRatio and only applied to LPs. Columns whose coefficients
    +0092 % span more than maxRatio are split via auxiliary metabolites/variables, which
    +0093 % preserves the feasible region but reduces the spread within each column. The
    +0094 % added rows/columns are stripped from the result further down.
    +0095 condApplied = false;
    +0096 if isfield(params,'maxRatio')
    +0097     maxRatio = params.maxRatio;
    +0098     params   = rmfield(params,'maxRatio');
    +0099     if ~milp && isnumeric(maxRatio) && isscalar(maxRatio) && isfinite(maxRatio) && maxRatio>1
    +0100         [prob, condInfo] = splitProbForConditioning(prob, maxRatio);
    +0101         condApplied = condInfo.applied;
    +0102     end
    +0103 end
    +0104 
    +0105 res.obj=[];
    +0106 switch solver
    +0107     %% Use whatever solver is set by COBRA Toolbox changeCobraSolver
    +0108     case 'cobra'
    +0109         if milp
    +0110             cparams=struct('timeLimit',1e9,'printLevel',0,'intTol',1e-6,'relMipGapTol',1e-9);
    +0111             cparams=structUpdate(cparams,params);
    +0112             res=solveCobraMILP(prob,cparams);
    +0113         else
    +0114             res=solveCobraLP(prob);
    +0115         end
    +0116         %% Use Gurobi in a MATLAB environment
    +0117     case 'gurobi'
    +0118         if milp
    +0119             if verbose
    +0120                 solverparams.OutputFlag = 1;
    +0121             else
    +0122                 solverparams.OutputFlag = 0;
    +0123             end
    +0124             solverparams.IntFeasTol = 10^-9; %min val for gurobi
    +0125             solverparams.MIPGap = defaultparams.MIPGap;
    +0126             solverparams.Seed = defaultparams.Seed;
    +0127         else
    +0128             solverparams.OutputFlag = 0;
    +0129         end
    +0130         solverparams.DisplayInterval= 1; % Level of verbosity
    +0131         solverparams.TimeLimit      = defaultparams.timeLimit;
    +0132         solverparams.FeasibilityTol = defaultparams.feasTol;
    +0133         solverparams.OptimalityTol  = defaultparams.optTol;
    +0134         solverparams.Presolve       = 2;
    +0135         % getCurrentTask requires the Parallel Computing Toolbox; without it
    +0136         % the call itself errors, so guard it rather than requiring the
    +0137         % toolbox for every gurobi solve.
    +0138         if exist('getCurrentTask','file') && ~isempty(getCurrentTask) % If run in parallel, then one thread per gurobi
    +0139             solverparams.Threads=1;
    +0140         end
    +0141         solverparams = structUpdate(solverparams,params);
    +0142 
    +0143         % Restructering problem according to gurobi format
    +0144         if isfield(prob, 'csense')
    +0145             prob.sense = renameparams(prob.csense, {'L','G','E'}, {'<','>','='});
    +0146             prob = rmfield(prob, {'csense'});
    +0147         end
    +0148         % gurobi minimises unless told otherwise, so an absent osense means
    +0149         % minimisation; osense is also needed below to sign the duals
    +0150         osense = 1;
    +0151         if isfield(prob, 'osense')
    +0152             osense = prob.osense;
    +0153             if osense < 0
    +0154                 prob.modelsense = 'max';
    +0155             else
    +0156                 prob.modelsense = 'min';
    +0157             end
    +0158             prob = rmfield(prob, {'osense'});
    +0159         end
    +0160         [prob.obj, prob.rhs, prob.vtype] = deal(prob.c, prob.b, prob.vartype);
    +0161         prob = rmfield(prob, {'c','b','vartype'});
    +0162 
    +0163         resG = gurobi(prob,solverparams);
    +0164 
    +0165         % Name output fields the same as COBRA does. Every field below is
    +0166         % optional in the gurobi result: which ones are present depends on
    +0167         % the status and on the method gurobi chose (a barrier solve returns
    +0168         % no simplex basis, for instance). They are therefore read
    +0169         % individually rather than under one try/catch, which would have
    +0170         % reported a missing basis on an optimal solve as "infeasible".
    +0171         res.origStat = resG.status;
    +0172         if isfield(resG,'x')
    +0173             res.full = resG.x;
    +0174         end
    +0175         if isfield(resG,'objval')
    +0176             res.obj = resG.objval;
    +0177         end
    +0178         if isfield(resG,'pi') && isfield(resG,'rc')
    +0179             res.dual     = -resG.pi*osense;
    +0180             res.rcost    = -resG.rc*osense;
    +0181         end
    +0182         if milp && strcmp(resG.status, 'TIME_LIMIT') && isfield(resG,'x') && isfield(resG,'objval')
    +0183             % An incumbent was found before the time limit. It is usable but
    +0184             % not proven optimal, so it is reported as feasible rather than
    +0185             % optimal; callers that must not act on a suboptimal MILP
    +0186             % solution (getMinNrFluxes, solveLP) key on that distinction.
    +0187             resG.status = 'SUBOPTIMAL';
    +0188         end
    +0189         switch resG.status
    +0190             case 'OPTIMAL'
    +0191                 res.stat = 1;
    +0192             case {'UNBOUNDED','SUBOPTIMAL'}
    +0193                 res.stat = 2;
    +0194             otherwise
    +0195                 res.stat = 0;
    +0196         end
    +0197         if ~milp
    +0198             if isfield(resG,'vbasis')
    +0199                 res.vbasis = resG.vbasis;
    +0200             end
    +0201             if isfield(resG,'cbasis')
    +0202                 res.cbasis = resG.cbasis;
    +0203             end
    +0204         elseif isfield(resG,'mipgap')
    +0205             res.mipgap = resG.mipgap;
    +0206         end
    +0207         if res.stat>0 && ~isfield(res,'full')
    +0208             % A status that claims a solution without returning one
    +0209             res.stat = 0;
    +0210         end
    +0211         %% Use GLPK using RAVEN-provided binary
    +0212     case 'glpk'
    +0213         solverparams.scale   = 1; % Auto scaling
    +0214         %solverparams.tmlim   = defaultparams.timeLimit;
    +0215         solverparams.tolbnd  = defaultparams.feasTol;
    +0216         solverparams.toldj   = defaultparams.optTol;
    +0217         solverparams.tolint  = defaultparams.intTol;
    +0218         solverparams.tolobj  = defaultparams.objTol;
    +0219         solverparams.msglev  = 0; % Level of verbosity
    +0220         if milp
    +0221             solverparams.tmlim = defaultparams.timeLimit; % seconds
    +0222         end
    +0223         solverparams = structUpdate(solverparams,params);
    +0224 
    +0225         prob.csense = renameparams(prob.csense, {'L','G','E'}, {'U','L','S'});
    +0226 
    +0227         % Ensure that RAVEN glpk binary is used, return to original
    +0228         % directory afterwards
    +0229         [ravenDir,currDir]=findRAVENroot();
    +0230         cd(fullfile(ravenDir,'src','external','GLPKmex'))
    +0231         [xopt, fmin, errnum, extra] = glpk(prob.c, prob.A, prob.b, prob.lb, prob.ub, prob.csense, prob.vartype, prob.osense, solverparams);
    +0232         cd(currDir)
    +0233 
    +0234         switch errnum % 1 = undefined; 2 = feasible; 3 = infeasible; 4 = no feasible solution; 5 = optimal; 6 = no unbounded solution
    +0235             case 5
    +0236                 res.stat = 1; % Optimal
    +0237             case 2
    +0238                 res.stat = 2; % Feasible, but not optimal
    +0239             otherwise
    +0240                 res.stat = 0;
    +0241         end
    +0242         res.origStat = errnum;
    +0243         res.full     = xopt;
    +0244         res.obj      = fmin;
    +0245         res.dual     = -extra.lambda*prob.osense;
    +0246         res.rcost    = -extra.redcosts*prob.osense;
    +0247         if milp
    +0248             % glpk reports no gap; a proven optimum has none, anything else
    +0249             % is not accepted by callers that test the gap
    +0250             if res.stat == 1
    +0251                 res.mipgap = 0;
    +0252             else
    +0253                 res.mipgap = inf;
    +0254             end
    +0255         end
    +0256         %% Use scip
    +0257     case {'soplex','scip'} % Old 'soplex' option also allowed
    +0258         % scip takes a row range [rl,ru] rather than a right-hand side plus
    +0259         % a sense, so csense has to be translated into that range. Passing
    +0260         % [-b,b] is only the same problem when every row is an equality with
    +0261         % b = 0, which is the case for solveLP but for none of the MILPs
    +0262         % built elsewhere in RAVEN.
    +0263         rl = full(prob.b(:));
    +0264         ru = rl;
    +0265         csense = prob.csense(:);
    +0266         rl(csense=='L') = -inf;   % A*x <= b
    +0267         ru(csense=='G') = inf;    % A*x >= b
    +0268         [xopt,fval,exitflag,scipStats] = scip([], prob.c, prob.A, rl, ru, prob.lb, prob.ub, prob.vartype);
    +0269 
    +0270         %   [x,fval,exitflag,stats] = scip(H, f, A, rl, ru, lb, ub, xtype, sos, qc, nl, x0, opts)
    +0271         %
    +0272         %   Input arguments*:
    +0273         %       H - quadratic objective matrix (sparse, optional [NOT TRIL / TRIU])
    +0274         %       f - linear objective vector
    +0275         %       A - linear constraint matrix (sparse)
    +0276         %       rl - linear constraint lhs
    +0277         %       ru - linear constraint rhs
    +0278         %       lb - decision variable lower bounds
    +0279         %       ub - decision variable upper bounds
    +0280         %       xtype - string of variable integrality ("c" continuous, "i" integer, "b" binary)
    +0281         %       sos - SOS structure with fields type, index and weight (see below)
    +0282         %       qc - Quadratic Constraints structure with fields Q, l, qrl and qru (see below)
    +0283         %       nl - Nonlinear Objective and Constraints structure (see below)
    +0284         %       x0 - primal solution
    +0285         %       opts - solver options (see below)
    +0286         %
    +0287         %   Return arguments:
    +0288         %       x - solution vector
    +0289         %       fval - objective value at the solution
    +0290         %       exitflag - exit status (see below)
    +0291         %       stats - statistics structure
    +0292         %
    +0293         %   Option Fields (all optional, see also optiset for a list):
    +0294         %       solverOpts - specific SCIP options (list of pairs of parameter names and values)
    +0295         %       maxiter - maximum LP solver iterations
    +0296         %       maxnodes - maximum nodes to explore
    +0297         %       maxtime - maximum execution time [s]
    +0298         %       tolrfun - primal feasibility tolerance
    +0299         %       display - solver display level [0-5]
    +0300         %       probfile - write problem to given file
    +0301         %       presolvedfile - write presolved problem to file
    +0302         %
    +0303         %   Return Status, as returned by the MatlabSCIPInterface build that
    +0304         %   RAVEN provides and that setRavenSolver points at
    +0305         %   (github.com/scipopt/MatlabSCIPInterface). Verified against that
    +0306         %   binary with a bounded, an infeasible and an unbounded problem:
    +0307         %       1 - Problem Solved to Optimality
    +0308         %       2 - Problem is Infeasible
    +0309         %       3 - Problem is Unbounded
    +0310         %
    +0311         %   The OPTI Toolbox build of scip uses a different, longer set in
    +0312         %   which optimality is 11, infeasibility 12 and unboundedness 13,
    +0313         %   with the lower numbers meaning "a limit was reached". Those codes
    +0314         %   are accepted below as well, so either build reports optimality
    +0315         %   correctly; only OPTI's 1 ("User Interrupted") is ambiguous, and
    +0316         %   reading it as optimal is no worse than the previous mapping,
    +0317         %   which recognised no status from the shipped build at all.
    +0318         
    +0319         res.origStat = exitflag;
    +0320         res.full = xopt;
    +0321         res.obj  = fval;
    +0322 
    +0323         switch exitflag
    +0324             case {1, 11}                  % solved to optimality
    +0325                 res.stat = 1;
    +0326             case {3, 13}                  % unbounded
    +0327                 res.stat = 2;
    +0328             case {5, 6, 7, 8, 9, 10}      % OPTI: a limit was reached, so a
    +0329                 res.stat = 2;             % solution may exist but is not proven optimal
    +0330             otherwise                     % 2/12 infeasible, 0 unknown, 4, 14
    +0331                 res.stat = 0;
    +0332         end
    +0333 
    +0334         if milp
    +0335             % Callers such as ftINIT compare this against their own
    +0336             % params.MIPGap, so it has to be the quantity the gurobi branch
    +0337             % reports there: a relative gap. It is derived from the bounds
    +0338             % rather than read from scipStats.BBgap, whose units are not
    +0339             % documented. inf where no gap can be established, so that a
    +0340             % caller testing "gap small enough" does not accept the solution.
    +0341             if res.stat == 1
    +0342                 res.mipgap = 0;
    +0343             else
    +0344                 res.mipgap = inf;
    +0345                 primal = scipStats.PrimalBound;
    +0346                 if isfinite(primal) && abs(primal) > 1e-10
    +0347                     res.mipgap = abs(primal-scipStats.DualBound)/abs(primal);
    +0348                 end
    +0349             end
    +0350         end
    +0351     otherwise
    +0352         error('RAVEN solver not defined or unknown. Try using setRavenSolver(''solver'').');
    +0353 end
    +0354 %Flag a solve that ended on the time limit. res.stat cannot express this on
    +0355 %its own: a solver that times out with an incumbent solution in hand reports
    +0356 %the same status as one that finished, and a solver that times out with
    +0357 %nothing reports the same status as an infeasible problem. The raw status in
    +0358 %res.origStat does distinguish the two, but only in solver-specific terms,
    +0359 %so it is translated here where the solver is still known.
    +0360 res.hitTimeLimit=hitTimeLimit(solver,res,milp);
    +0361 if res.stat>0
    +0362     res.full=res.full(1:size(prob.a,2));
    +0363 end
    +0364 %Strip the auxiliary rows/columns that were added for conditioning, so that
    +0365 %the result matches the dimensions of the original problem. res.full is
    +0366 %already truncated to the original variables above (prob.a is left untouched).
    +0367 if condApplied
    +0368     if isfield(res,'dual') && numel(res.dual)>condInfo.nOrigRows
    +0369         res.dual=res.dual(1:condInfo.nOrigRows);
    +0370     end
    +0371     if isfield(res,'rcost') && numel(res.rcost)>condInfo.nOrigCols
    +0372         res.rcost=res.rcost(1:condInfo.nOrigCols);
    +0373     end
     0374 end
    -0375 
    -0376 function s_merged=structUpdate(s_old,s_new)
    -0377 %Remove overlapping fields from first struct;
    -0378 %Obtain all unique names of remaining fields;
    -0379 %Merge both structs
    -0380 s_merged = rmfield(s_old, intersect(fieldnames(s_old), fieldnames(s_new)));
    -0381 names = [fieldnames(s_merged); fieldnames(s_new)];
    -0382 s_merged = cell2struct([struct2cell(s_merged); struct2cell(s_new)], names, 1);
    -0383 end
    -0384 
    -0385 function timeLimit = hitTimeLimit(solver,res,milp)
    -0386 %Report whether the solver that produced res stopped on its time limit. Each
    -0387 %solver has its own status codes for this, and the "cobra" option hands the
    -0388 %status of the solver that COBRA dispatched to straight through, so that
    -0389 %solver is looked up as well.
    -0390 global CBT_LP_SOLVER CBT_MILP_SOLVER
    -0391 timeLimit=false;
    -0392 if ~isfield(res,'origStat')
    -0393     return;
    -0394 end
    -0395 if strcmp(solver,'cobra')
    -0396     if milp
    -0397         solver=CBT_MILP_SOLVER;
    -0398     else
    -0399         solver=CBT_LP_SOLVER;
    -0400     end
    -0401     if isempty(solver) || ~ischar(solver)
    -0402         return;
    -0403     end
    -0404 end
    -0405 origStat=res.origStat;
    -0406 isText=ischar(origStat) || isstring(origStat);
    -0407 isCode=isnumeric(origStat) && isscalar(origStat);
    -0408 switch lower(solver)
    -0409     case 'gurobi'
    -0410         timeLimit=isText && strcmp(origStat,'TIME_LIMIT');
    -0411     case 'mosek'
    -0412         timeLimit=isText && strcmp(origStat,'MSK_RES_TRM_MAX_TIME');
    -0413     case 'glpk'
    -0414         %109 from the simplex method, 209 from the interior point method
    -0415         timeLimit=isCode && ismember(origStat,[109 209]);
    -0416     case {'scip','soplex'}
    -0417         timeLimit=isCode && origStat==5;
    -0418     case {'ibm_cplex','tomlab_cplex','cplex_direct'}
    -0419         %11 for an LP, 107 and 108 for a MILP with and without an integer
    -0420         %solution found before the limit was reached
    -0421         timeLimit=isCode && ismember(origStat,[11 107 108]);
    -0422 end
    +0375 end
    +0376 
    +0377 function s_merged=structUpdate(s_old,s_new)
    +0378 %Remove overlapping fields from first struct;
    +0379 %Obtain all unique names of remaining fields;
    +0380 %Merge both structs
    +0381 s_merged = rmfield(s_old, intersect(fieldnames(s_old), fieldnames(s_new)));
    +0382 names = [fieldnames(s_merged); fieldnames(s_new)];
    +0383 s_merged = cell2struct([struct2cell(s_merged); struct2cell(s_new)], names, 1);
    +0384 end
    +0385 
    +0386 function timeLimit = hitTimeLimit(solver,res,milp)
    +0387 %Report whether the solver that produced res stopped on its time limit. Each
    +0388 %solver has its own status codes for this, and the "cobra" option hands the
    +0389 %status of the solver that COBRA dispatched to straight through, so that
    +0390 %solver is looked up as well.
    +0391 global CBT_LP_SOLVER CBT_MILP_SOLVER
    +0392 timeLimit=false;
    +0393 if ~isfield(res,'origStat')
    +0394     return;
    +0395 end
    +0396 if strcmp(solver,'cobra')
    +0397     if milp
    +0398         solver=CBT_MILP_SOLVER;
    +0399     else
    +0400         solver=CBT_LP_SOLVER;
    +0401     end
    +0402     if isempty(solver) || ~ischar(solver)
    +0403         return;
    +0404     end
    +0405 end
    +0406 origStat=res.origStat;
    +0407 isText=ischar(origStat) || isstring(origStat);
    +0408 isCode=isnumeric(origStat) && isscalar(origStat);
    +0409 switch lower(solver)
    +0410     case 'gurobi'
    +0411         timeLimit=isText && strcmp(origStat,'TIME_LIMIT');
    +0412     case 'mosek'
    +0413         timeLimit=isText && strcmp(origStat,'MSK_RES_TRM_MAX_TIME');
    +0414     case 'glpk'
    +0415         %109 from the simplex method, 209 from the interior point method
    +0416         timeLimit=isCode && ismember(origStat,[109 209]);
    +0417     case {'scip','soplex'}
    +0418         timeLimit=isCode && origStat==5;
    +0419     case {'ibm_cplex','tomlab_cplex','cplex_direct'}
    +0420         %11 for an LP, 107 and 108 for a MILP with and without an integer
    +0421         %solution found before the limit was reached
    +0422         timeLimit=isCode && ismember(origStat,[11 107 108]);
     0423 end
    -0424 
    -0425 function paramlist = renameparams(paramlist,old,new)
    -0426 if ~iscell(paramlist)
    -0427     wasNoCell = true;
    -0428     paramlist={paramlist};
    -0429 else
    -0430     wasNoCell = false;
    -0431 end
    -0432 for i=1:numel(old)
    -0433     paramlist = regexprep(paramlist,old{i},new{i});
    -0434 end
    -0435 if wasNoCell
    -0436     paramlist=paramlist{1};
    -0437 end
    -0438 end
    +0424 end +0425 +0426 function paramlist = renameparams(paramlist,old,new) +0427 if ~iscell(paramlist) +0428 wasNoCell = true; +0429 paramlist={paramlist}; +0430 else +0431 wasNoCell = false; +0432 end +0433 for i=1:numel(old) +0434 paramlist = regexprep(paramlist,old{i},new{i}); +0435 end +0436 if wasNoCell +0437 paramlist=paramlist{1}; +0438 end +0439 end

    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/solver/setRavenSolver.html b/docs/html/solver/setRavenSolver.html index c5ac5b78..f4a1159e 100644 --- a/docs/html/solver/setRavenSolver.html +++ b/docs/html/solver/setRavenSolver.html @@ -38,15 +38,15 @@

    DESCRIPTION ^SOURCE CODE ^% string with solver name. The following options are available: 0011 % 0012 % - glpk : uses RAVEN-provided binaries. Solves LPs and MILPs; for -0013 % MILPs a RAVEN:glpkMILP warning recommends Gurobi, which is much -0014 % faster and more reliable on larger problems. +0013 % MILPs the RAVEN:glpkMILP warning states that Gurobi is much faster +0014 % and more reliable for larger problems. 0015 % - gurobi : requires a working Gurobi installation. 0016 % - scip : requires a SCIP MEX interface. RAVEN provides a 0017 % precompiled binary for Windows only. 0018 % - cobra : requires a working COBRA Toolbox installation. RAVEN 0019 % parses the problem to COBRA and uses the solver that is set by -0020 % changeCobraSolver. MILPs are refused when COBRA's MILP solver is -0021 % GLPK. +0020 % changeCobraSolver. For MILPs, the RAVEN:glpkMILP warning is issued +0021 % when COBRA's MILP solver is GLPK. 0022 % 0023 % Examples 0024 % -------- diff --git a/docs/html/solver/solveLP.html b/docs/html/solver/solveLP.html index 85788599..424a2120 100644 --- a/docs/html/solver/solveLP.html +++ b/docs/html/solver/solveLP.html @@ -37,28 +37,28 @@

    DESCRIPTION ^DESCRIPTION ^DESCRIPTION ^CROSS-REFERENCE INFORMATION ^

    This function calls:
      -
    • checkSolution checkSolution Check if a solver result is feasible and optimal.
    • getMinNrFluxes getMinNrFluxes Find the minimal set of fluxes that satisfy the model.
    • optimizeProb optimizeProb Optimize an LP or MILP formulated in COBRA terms.
    • solveLP solveLP Solve a linear programming problem.
    +
  • checkSolution checkSolution Check if a solver result is feasible and optimal.
  • getMinNrFluxes getMinNrFluxes Find the minimal set of fluxes that satisfy the model.
  • optimizeProb optimizeProb Optimise an LP or MILP formulated in COBRA terms.
  • solveLP solveLP Solve a linear programming problem.
  • This function is called by:
    • getMinNrFluxes getMinNrFluxes Find the minimal set of fluxes that satisfy the model.
    • solveLP solveLP Solve a linear programming problem.
    @@ -105,28 +106,28 @@

    SOURCE CODE ^% Name-Value Arguments 0010 % -------------------- 0011 % minFlux : double -0012 % determines if a second optimization should be performed in order to -0013 % get rid of loops in the flux distribution (default 0): +0012 % determines if a second optimisation is performed to remove loops +0013 % from the flux distribution (default 0): 0014 % -0015 % - 0 : no such optimization is performed -0016 % - 1 : the sum of abs(fluxes) is minimized. This is the fastest way of -0017 % getting rid of loops -0018 % - 2 : *option is deprecated* -0019 % - 3 : the number of fluxes is minimized. This can result in the flux -0020 % distributions that are the easiest to interpret. Note that this -0021 % optimization can be very slow +0015 % - 0 : no second optimisation is performed +0016 % - 1 : the sum of abs(fluxes) is minimised with an LP, which does not +0017 % need the MILP that the minimisation of the number of fluxes requires +0018 % - 2 : deprecated; gives an error +0019 % - 3 : the number of fluxes is minimised with getMinNrFluxes, which +0020 % solves a MILP and can take long. The result has the fewest active +0021 % reactions, which can make the flux distribution easier to interpret 0022 % params : struct -0023 % solver parameters, forwarded to optimizeProb. Mostly obsolete, but the -0024 % field "maxRatio" (a number > 1) can be set to improve numerical -0025 % conditioning of ill-scaled models: any reaction whose stoichiometric -0026 % coefficients span more than maxRatio is transiently split via -0027 % auxiliary metabolites before solving, which preserves the feasible -0028 % region (and thus the flux distribution of the original reactions). A -0029 % common value is 1e6 (no splitting by default). +0023 % solver parameters, forwarded to optimizeProb. The field "maxRatio" +0024 % (a number > 1) can be set for numerical conditioning of ill-scaled +0025 % models: any reaction whose stoichiometric coefficients span more +0026 % than maxRatio is split with auxiliary metabolites before solving, +0027 % which preserves the feasible region (and thus the flux distribution +0028 % of the original reactions). A common value is 1e6. No splitting is done +0029 % when maxRatio is absent (default struct with relGap=0.4). 0030 % hsSol : struct -0031 % hot-start solution for the LP solver. This can significantly speed up -0032 % the process if many similar optimization problems are solved -0033 % iteratively. Only used if minFlux is 0 or 1. +0031 % hot-start solution for the LP solver, as hsSolOut from a previous +0032 % call. It speeds up the solution when many similar optimisation +0033 % problems are solved iteratively. A hsSol with stat<1 is ignored. 0034 % 0035 % Returns 0036 % ------- @@ -135,204 +136,205 @@

    SOURCE CODE ^% 0040 % - f : objective value 0041 % - x : primal (flux distribution) -0042 % - stat : exit flag (1 = optimization terminated successfully, 0 = +0042 % - stat : exit flag (1 = optimisation terminated successfully, 0 = 0043 % solution is feasible but not necessarily optimal, -1 = no feasible -0044 % solution found, -2 = solution found but flux minimization failed) -0045 % - msg : string describing the status of the optimization +0044 % solution found, -2 = solution found but flux minimisation failed) +0045 % - msg : string describing the status of the optimisation 0046 % - sPrice : shadow price (only reported if minFlux was 0) 0047 % - rCost : reduced cost (only reported if minFlux was 0) 0048 % hsSolOut : struct 0049 % solution to be used as hot-start solution (see the input parameters). -0050 % Only used if minFlux is 0 or 1. -0051 % -0052 % Examples -0053 % -------- -0054 % [solution, hsSolOut] = solveLP(model, minFlux, params, hsSol); -0055 -0056 p=parseRAVENargs(varargin, {'minFlux',0; 'params',[]; 'hsSol',[]}); -0057 minFlux=p.minFlux; -0058 params=p.params; -0059 if isempty(params) -0060 params.relGap=0.4; -0061 end -0062 hsSol=p.hsSol; -0063 -0064 %Default return values -0065 hsSolOut=[]; -0066 solution.x=[]; -0067 solution.f=[]; -0068 solution.stat=-1; -0069 -0070 %Check for valid lb and ub -0071 if ~isnumeric([model.lb,model.ub]) -0072 invalidBound = true(numel(model.lb),1); -0073 else -0074 invalidBound = false(numel(model.lb),1); -0075 end -0076 invalidBound = invalidBound | model.lb>model.ub; -0077 invalidBound = invalidBound | logical(sum(isnan([model.lb,model.ub]),2)); -0078 if any(invalidBound) -0079 error(['Invalid set of bounds for reaction(s): ', strjoin(model.rxns(invalidBound),', '), '.']) -0080 end -0081 %Check for valid objective -0082 if ~isnumeric(model.c) || any(isnan(model.c)) -0083 error('Invalid defintion of objective function in model.c.') -0084 end -0085 %Check for valid S-matrix -0086 if ~allfinite(model.S) -0087 invalidS = ~isfinite(model.S); -0088 error(['Invalid coefficients defined for reaction(s): ', strjoin(model.rxns(any(invalidS)),', '), '.']) -0089 end -0090 -0091 %Ignore the hot-start if the previous solution was not feasible -0092 if isfield(hsSol,'stat') -0093 if hsSol.stat<1 -0094 hsSol=[]; -0095 end -0096 end -0097 -0098 % Setup the linear problem for glpk -0099 prob = []; -0100 prob.c = [model.c*-1;zeros(size(model.S,1),1)]; -0101 prob.b = zeros(size(model.S,1), 1); -0102 prob.lb = [model.lb; model.b(:,1)]; -0103 prob.ub = [model.ub; model.b(:,min(size(model.b,2),2))]; -0104 prob.csense = repmat('E', 1, length(prob.b)); % Equality constraints -0105 prob.osense = 1; -0106 prob.vartype = repmat('C', 1, length(prob.c)); % Fluxes are continuous values -0107 prob.A = [model.S -speye(size(model.S,1))]; -0108 prob.a = model.S; -0109 -0110 %If hot-start should be used -0111 if ~isempty(hsSol) -0112 prob.vbasis=hsSol.vbasis; -0113 prob.cbasis=hsSol.cbasis; -0114 end -0115 -0116 % Parse the problem to the LP solver -0117 res = optimizeProb(prob,params); -0118 % Swap the sign of the obj-value -0119 res.obj = -res.obj; -0120 -0121 %Check if the problem was feasible and that the solution was optimal -0122 [isFeasible, isOptimal]=checkSolution(res); -0123 -0124 %If the problem was infeasible using hot-start it is often possible to -0125 %re-solve it without hot-start and get a feasible solution -0126 if ~isFeasible && ~isempty(hsSol) -0127 prob=rmfield(prob,{'vbasis','cbasis'}); -0128 res=optimizeProb(prob,params); -0129 [isFeasible, isOptimal]=checkSolution(res); -0130 end -0131 -0132 %Return without solution if the problem was infeasible -0133 if ~isFeasible -0134 solution.msg='The problem is infeasible'; -0135 return; -0136 end -0137 if ~isOptimal -0138 solution.msg='The problem is feasible, but not necessarily optimal'; -0139 solution.stat=0; -0140 else -0141 %All is well -0142 solution.stat=1; -0143 solution.msg='Optimal solution found'; -0144 end -0145 -0146 %Construct the output structure -0147 if isfield(res,'full') -0148 solution.x=res.full; -0149 if minFlux<=1 -0150 if(isfield(res,'vbasis')) % gurobi uses vbasis and cbasis as hotstart -0151 hsSolOut.vbasis=res.vbasis; -0152 hsSolOut.cbasis=res.cbasis; -0153 end -0154 end -0155 solution.f=res.obj; -0156 if isfield(res,'dual') -0157 solution.sPrice=res.dual; -0158 else -0159 solution.sPrice=[]; -0160 end -0161 if isfield(res,'rcost') -0162 solution.rCost=res.rcost(1:numel(model.rxns)); -0163 else -0164 solution.rCost=[]; -0165 end -0166 -0167 %If either the square, the number, or the sum of fluxes should be minimized -0168 %then the objective function value should be fixed before another -0169 %optimization. It is not correct to fix the reactions which participate in -0170 %the objective function to their values in solution.x, as there can be -0171 %multiple solutions with the same objective function value. In addition, -0172 %this approach could result in numerical issues when several fluxes are -0173 %fixed. Instead a new "fake metabolite" is added to the problem. This -0174 %metabolite is produced by each reaction with the stoichiometry that -0175 %reaction has in the objective function. The equality constraint of that -0176 %"fake metabolite" is then set to be at least as good as the objective -0177 %function value. -0178 if minFlux~=0 -0179 model.S=[model.S;model.c']; -0180 model.mets=[model.mets;'TEMP']; -0181 -0182 %If the constraint on the objective function value is exact there is a -0183 %larger risk of numerical errors. However for the quadratic fitting -0184 %intervals are not allowed -0185 if minFlux~=2 -0186 if size(model.b,2)==1 -0187 model.b=[model.b model.b]; -0188 end -0189 if solution.f>0 -0190 model.b=[model.b;[solution.f*0.999999 inf]]; -0191 else -0192 model.b=[model.b;[solution.f*1.000001 inf]]; -0193 end -0194 else -0195 model.b=[model.b;ones(1,size(model.b,2))*solution.f]; -0196 end -0197 -0198 switch minFlux -0199 %The sum of fluxes should be minimized -0200 case 1 -0201 %Convert the model to the irreversible format -0202 revRxns=find(model.rev); -0203 if ~isempty(revRxns) -0204 iModel=convertToIrrev(model); -0205 else -0206 iModel=model; -0207 end -0208 %Minimize all fluxes -0209 iModel.c(:)=-1; -0210 sol=solveLP(iModel,0,params); -0211 -0212 %Map back to reversible fluxes -0213 if sol.stat>=0 -0214 solution.x=sol.x(1:numel(model.c)); -0215 solution.x(revRxns)=solution.x(revRxns)-sol.x(numel(model.c)+1:end); -0216 else -0217 EM='Could not solve the problem of minimizing the sum of fluxes. Uses output from original problem'; -0218 warning('RAVEN:warning', '%s', EM); -0219 solution.stat=-2; -0220 end -0221 -0222 case 2 -0223 error('%Minimization of square of fluxes is deprecated') -0224 -0225 %The number of fluxes should be minimized -0226 case 3 -0227 [qx,I]=getMinNrFluxes(model,model.rxns,params); -0228 qx(~I)=0; -0229 if any(I) -0230 solution.x=qx; -0231 else -0232 EM='Could not solve the problem of minimizing the number of fluxes. Uses output from linear program'; -0233 warning('RAVEN:warning', '%s', EM); -0234 solution.stat=-2; -0235 end -0236 end -0237 solution=rmfield(solution,{'sPrice','rCost'}); -0238 end -0239 end

    +0050 % Set only if minFlux is 0 or 1, and only when the solver returns a +0051 % basis. +0052 % +0053 % Examples +0054 % -------- +0055 % [solution, hsSolOut] = solveLP(model, minFlux, params, hsSol); +0056 +0057 p=parseRAVENargs(varargin, {'minFlux',0; 'params',[]; 'hsSol',[]}); +0058 minFlux=p.minFlux; +0059 params=p.params; +0060 if isempty(params) +0061 params.relGap=0.4; +0062 end +0063 hsSol=p.hsSol; +0064 +0065 %Default return values +0066 hsSolOut=[]; +0067 solution.x=[]; +0068 solution.f=[]; +0069 solution.stat=-1; +0070 +0071 %Check for valid lb and ub +0072 if ~isnumeric([model.lb,model.ub]) +0073 invalidBound = true(numel(model.lb),1); +0074 else +0075 invalidBound = false(numel(model.lb),1); +0076 end +0077 invalidBound = invalidBound | model.lb>model.ub; +0078 invalidBound = invalidBound | logical(sum(isnan([model.lb,model.ub]),2)); +0079 if any(invalidBound) +0080 error(['Invalid set of bounds for reaction(s): ', strjoin(model.rxns(invalidBound),', '), '.']) +0081 end +0082 %Check for valid objective +0083 if ~isnumeric(model.c) || any(isnan(model.c)) +0084 error('Invalid defintion of objective function in model.c.') +0085 end +0086 %Check for valid S-matrix +0087 if ~allfinite(model.S) +0088 invalidS = ~isfinite(model.S); +0089 error(['Invalid coefficients defined for reaction(s): ', strjoin(model.rxns(any(invalidS)),', '), '.']) +0090 end +0091 +0092 %Ignore the hot-start if the previous solution was not feasible +0093 if isfield(hsSol,'stat') +0094 if hsSol.stat<1 +0095 hsSol=[]; +0096 end +0097 end +0098 +0099 % Setup the linear problem for glpk +0100 prob = []; +0101 prob.c = [model.c*-1;zeros(size(model.S,1),1)]; +0102 prob.b = zeros(size(model.S,1), 1); +0103 prob.lb = [model.lb; model.b(:,1)]; +0104 prob.ub = [model.ub; model.b(:,min(size(model.b,2),2))]; +0105 prob.csense = repmat('E', 1, length(prob.b)); % Equality constraints +0106 prob.osense = 1; +0107 prob.vartype = repmat('C', 1, length(prob.c)); % Fluxes are continuous values +0108 prob.A = [model.S -speye(size(model.S,1))]; +0109 prob.a = model.S; +0110 +0111 %If hot-start should be used +0112 if ~isempty(hsSol) +0113 prob.vbasis=hsSol.vbasis; +0114 prob.cbasis=hsSol.cbasis; +0115 end +0116 +0117 % Parse the problem to the LP solver +0118 res = optimizeProb(prob,params); +0119 % Swap the sign of the obj-value +0120 res.obj = -res.obj; +0121 +0122 %Check if the problem was feasible and that the solution was optimal +0123 [isFeasible, isOptimal]=checkSolution(res); +0124 +0125 %If the problem was infeasible using hot-start it is often possible to +0126 %re-solve it without hot-start and get a feasible solution +0127 if ~isFeasible && ~isempty(hsSol) +0128 prob=rmfield(prob,{'vbasis','cbasis'}); +0129 res=optimizeProb(prob,params); +0130 [isFeasible, isOptimal]=checkSolution(res); +0131 end +0132 +0133 %Return without solution if the problem was infeasible +0134 if ~isFeasible +0135 solution.msg='The problem is infeasible'; +0136 return; +0137 end +0138 if ~isOptimal +0139 solution.msg='The problem is feasible, but not necessarily optimal'; +0140 solution.stat=0; +0141 else +0142 %All is well +0143 solution.stat=1; +0144 solution.msg='Optimal solution found'; +0145 end +0146 +0147 %Construct the output structure +0148 if isfield(res,'full') +0149 solution.x=res.full; +0150 if minFlux<=1 +0151 if(isfield(res,'vbasis')) % gurobi uses vbasis and cbasis as hotstart +0152 hsSolOut.vbasis=res.vbasis; +0153 hsSolOut.cbasis=res.cbasis; +0154 end +0155 end +0156 solution.f=res.obj; +0157 if isfield(res,'dual') +0158 solution.sPrice=res.dual; +0159 else +0160 solution.sPrice=[]; +0161 end +0162 if isfield(res,'rcost') +0163 solution.rCost=res.rcost(1:numel(model.rxns)); +0164 else +0165 solution.rCost=[]; +0166 end +0167 +0168 %If either the square, the number, or the sum of fluxes should be minimized +0169 %then the objective function value should be fixed before another +0170 %optimization. It is not correct to fix the reactions which participate in +0171 %the objective function to their values in solution.x, as there can be +0172 %multiple solutions with the same objective function value. In addition, +0173 %this approach could result in numerical issues when several fluxes are +0174 %fixed. Instead a new "fake metabolite" is added to the problem. This +0175 %metabolite is produced by each reaction with the stoichiometry that +0176 %reaction has in the objective function. The equality constraint of that +0177 %"fake metabolite" is then set to be at least as good as the objective +0178 %function value. +0179 if minFlux~=0 +0180 model.S=[model.S;model.c']; +0181 model.mets=[model.mets;'TEMP']; +0182 +0183 %If the constraint on the objective function value is exact there is a +0184 %larger risk of numerical errors. However for the quadratic fitting +0185 %intervals are not allowed +0186 if minFlux~=2 +0187 if size(model.b,2)==1 +0188 model.b=[model.b model.b]; +0189 end +0190 if solution.f>0 +0191 model.b=[model.b;[solution.f*0.999999 inf]]; +0192 else +0193 model.b=[model.b;[solution.f*1.000001 inf]]; +0194 end +0195 else +0196 model.b=[model.b;ones(1,size(model.b,2))*solution.f]; +0197 end +0198 +0199 switch minFlux +0200 %The sum of fluxes should be minimized +0201 case 1 +0202 %Convert the model to the irreversible format +0203 revRxns=find(model.rev); +0204 if ~isempty(revRxns) +0205 iModel=convertToIrrev(model); +0206 else +0207 iModel=model; +0208 end +0209 %Minimize all fluxes +0210 iModel.c(:)=-1; +0211 sol=solveLP(iModel,0,params); +0212 +0213 %Map back to reversible fluxes +0214 if sol.stat>=0 +0215 solution.x=sol.x(1:numel(model.c)); +0216 solution.x(revRxns)=solution.x(revRxns)-sol.x(numel(model.c)+1:end); +0217 else +0218 EM='Could not solve the problem of minimizing the sum of fluxes. Uses output from original problem'; +0219 warning('RAVEN:warning', '%s', EM); +0220 solution.stat=-2; +0221 end +0222 +0223 case 2 +0224 error('%Minimization of square of fluxes is deprecated') +0225 +0226 %The number of fluxes should be minimized +0227 case 3 +0228 [qx,I]=getMinNrFluxes(model,model.rxns,params); +0229 qx(~I)=0; +0230 if any(I) +0231 solution.x=qx; +0232 else +0233 EM='Could not solve the problem of minimizing the number of fluxes. Uses output from linear program'; +0234 warning('RAVEN:warning', '%s', EM); +0235 solution.stat=-2; +0236 end +0237 end +0238 solution=rmfield(solution,{'sPrice','rCost'}); +0239 end +0240 end

    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/solver/splitProbForConditioning.html b/docs/html/solver/splitProbForConditioning.html index a5db5270..d0deaaa2 100644 --- a/docs/html/solver/splitProbForConditioning.html +++ b/docs/html/solver/splitProbForConditioning.html @@ -32,18 +32,20 @@

    DESCRIPTION ^DESCRIPTION ^CROSS-REFERENCE INFORMATION ^
 </ul>
 This function is called by:
 <ul style= -
  • optimizeProb optimizeProb Optimize an LP or MILP formulated in COBRA terms.
  • +
  • optimizeProb optimizeProb Optimise an LP or MILP formulated in COBRA terms.
  • @@ -96,143 +99,146 @@

    SOURCE CODE ^% Reformulates an LP problem to improve its numerical conditioning by 0005 % splitting columns whose coefficients span more than a given ratio. In a 0006 % metabolic model a column corresponds to a reaction, and a wide spread of -0007 % stoichiometric coefficients within one reaction (e.g. a biomass reaction -0008 % with both major substrates and trace cofactors) cannot be removed by the -0009 % diagonal row/column scaling that LP solvers apply, and may lead to +0007 % stoichiometric coefficients within one reaction (for example a biomass reaction +0008 % with both large and small coefficients) cannot be removed by the +0009 % diagonal row and column scaling that LP solvers apply, and may lead to 0010 % unreliable solutions or spurious infeasibility. 0011 % -0012 % Splitting is done via auxiliary metabolites and variables that preserve +0012 % Splitting is done with auxiliary metabolites and variables that preserve 0013 % the feasible region of the original problem exactly, so the values of all -0014 % original variables are unchanged. -0015 % -0016 % The function is meant to be called inside optimizeProb, on the COBRA-style -0017 % problem struct, immediately before the problem is passed to the solver. -0018 % Auxiliary rows/columns are appended after the original ones, so the -0019 % augmentation can be stripped from the solver output afterwards (see -0020 % condInfo). -0021 % -0022 % Parameters -0023 % ---------- -0024 % prob : struct -0025 % COBRA-style LP problem struct, with at least the fields A, b, c, lb, -0026 % ub, csense and vartype. -0027 % maxRatio : double -0028 % maximum allowed ratio between the largest and smallest absolute -0029 % coefficient within any single column. -0030 % -0031 % Returns -0032 % ------- -0033 % prob : struct -0034 % the (possibly) augmented problem struct. Identical to the input if no -0035 % column needed splitting. -0036 % condInfo : struct -0037 % struct describing the augmentation, with fields: -0038 % -0039 % - applied : true if any column was split -0040 % - nSplits : number of auxiliary metabolite/conversion pairs added -0041 % - nOrigRows : number of constraint rows before augmentation -0042 % - nOrigCols : number of variables (columns) before augmentation -0043 % -0044 % Examples -0045 % -------- -0046 % [prob, condInfo] = splitProbForConditioning(prob, maxRatio); -0047 % -0048 % See also -0049 % -------- -0050 % optimizeProb -0051 -0052 [nOrigRows, nOrigCols] = size(prob.A); -0053 condInfo.applied = false; -0054 condInfo.nSplits = 0; -0055 condInfo.nOrigRows = nOrigRows; -0056 condInfo.nOrigCols = nOrigCols; -0057 -0058 %Detection uses a small slack so that reactions that are only marginally over -0059 %the threshold are left alone. This also makes the transformation idempotent: -0060 %a freshly split column sits exactly at maxRatio and is not split again. -0061 detectRatio = maxRatio * 1.01; -0062 -0063 %Work on triplets to avoid repeated resizing of the sparse matrix -0064 [I, J, V] = find(prob.A); -0065 absV = abs(V); -0066 -0067 %Largest/smallest absolute coefficient and number of nonzeros per column -0068 cMax = accumarray(J, absV, [nOrigCols 1], @max, 0); -0069 cMin = accumarray(J, absV, [nOrigCols 1], @min, inf); -0070 nnzCol = accumarray(J, 1, [nOrigCols 1], @sum, 0); -0071 -0072 %Single-coefficient columns (e.g. exchange reactions or slack variables) have -0073 %an undefined ratio and are never split -0074 badCols = find(nnzCol >= 2 & (cMax ./ cMin) > detectRatio); -0075 if isempty(badCols) -0076 return -0077 end -0078 -0079 %Flag the entries that are too small within their (bad) column. These are -0080 %removed from the original matrix and re-introduced via the auxiliary chain. -0081 smallThresh = cMax(J) / maxRatio; -0082 removeEntry = ismember(J, badCols) & (absV < smallThresh); -0083 -0084 %Keep all entries that are not removed -0085 keep = ~removeEntry; -0086 newI = I(keep); -0087 newJ = J(keep); -0088 newV = V(keep); -0089 -0090 %Auxiliary entries are accumulated here -0091 addI = zeros(0,1); addJ = zeros(0,1); addV = zeros(0,1); -0092 convLb = zeros(0,1); convUb = zeros(0,1); -0093 nStar = 0; %auxiliary metabolite rows added so far -0094 nConv = 0; %conversion variable columns added so far -0095 -0096 remIdx = find(removeEntry); -0097 for k = 1:numel(remIdx) -0098 e = remIdx(k); -0099 metRow = I(e); -0100 parentCol = J(e); -0101 coef = V(e); -0102 curCmax = cMax(parentCol); -0103 %Deliver the (signed) coefficient "coef" of metabolite "metRow" into -0104 %column "parentCol", splitting recursively until it is within range -0105 while true -0106 small = curCmax / maxRatio; -0107 if abs(coef) >= small -0108 %Coefficient is acceptable; place it directly -0109 addI(end+1,1) = metRow; addJ(end+1,1) = parentCol; addV(end+1,1) = coef; %#ok<AGROW> -0110 break -0111 end -0112 %Otherwise introduce a star metabolite and a conversion variable -0113 beta = sign(coef) * small; -0114 nStar = nStar + 1; starRow = nOrigRows + nStar; -0115 nConv = nConv + 1; convCol = nOrigCols + nConv; -0116 %The parent column now consumes the star metabolite (coefficient beta) -0117 addI(end+1,1) = starRow; addJ(end+1,1) = parentCol; addV(end+1,1) = beta; %#ok<AGROW> -0118 %The conversion variable produces one unit of the star metabolite -0119 addI(end+1,1) = starRow; addJ(end+1,1) = convCol; addV(end+1,1) = 1; %#ok<AGROW> -0120 %Conversion variables are determined by the equality and left unbounded -0121 convLb(end+1,1) = -inf; convUb(end+1,1) = inf; %#ok<AGROW> -0122 %Deliver the residual coefficient into the new conversion column. Its -0123 %reference scale is 1 (the production coefficient of the star met). -0124 coef = -coef / beta; -0125 parentCol = convCol; -0126 curCmax = 1; -0127 end -0128 end -0129 -0130 condInfo.applied = true; -0131 condInfo.nSplits = nStar; +0014 % original variables are unchanged. A column is split when its ratio +0015 % exceeds maxRatio by more than 1 %. Columns with a single nonzero +0016 % coefficient are never split. +0017 % +0018 % optimizeProb calls this function on the COBRA-style problem struct of an +0019 % LP, immediately before the problem is passed to the solver. Auxiliary +0020 % rows and columns are appended after the original ones, so the +0021 % augmentation can be stripped from the solver output afterwards (see +0022 % condInfo). +0023 % +0024 % Parameters +0025 % ---------- +0026 % prob : struct +0027 % COBRA-style LP problem struct, with at least the fields A, b, c, lb, +0028 % ub, csense and vartype. +0029 % maxRatio : double +0030 % maximum allowed ratio between the largest and smallest absolute +0031 % coefficient within any single column. +0032 % +0033 % Returns +0034 % ------- +0035 % prob : struct +0036 % the augmented problem struct. Identical to the input if no column +0037 % needed splitting. +0038 % condInfo : struct +0039 % struct describing the augmentation, with fields: +0040 % +0041 % - applied : true if any column was split +0042 % - nSplits : number of pairs of an auxiliary metabolite and a conversion +0043 % variable added +0044 % - nOrigRows : number of constraint rows before augmentation +0045 % - nOrigCols : number of variables (columns) before augmentation +0046 % +0047 % Examples +0048 % -------- +0049 % [prob, condInfo] = splitProbForConditioning(prob, maxRatio); +0050 % +0051 % See also +0052 % -------- +0053 % optimizeProb +0054 +0055 [nOrigRows, nOrigCols] = size(prob.A); +0056 condInfo.applied = false; +0057 condInfo.nSplits = 0; +0058 condInfo.nOrigRows = nOrigRows; +0059 condInfo.nOrigCols = nOrigCols; +0060 +0061 %Detection uses a small slack so that reactions that are only marginally over +0062 %the threshold are left alone. This also makes the transformation idempotent: +0063 %a freshly split column sits exactly at maxRatio and is not split again. +0064 detectRatio = maxRatio * 1.01; +0065 +0066 %Work on triplets to avoid repeated resizing of the sparse matrix +0067 [I, J, V] = find(prob.A); +0068 absV = abs(V); +0069 +0070 %Largest/smallest absolute coefficient and number of nonzeros per column +0071 cMax = accumarray(J, absV, [nOrigCols 1], @max, 0); +0072 cMin = accumarray(J, absV, [nOrigCols 1], @min, inf); +0073 nnzCol = accumarray(J, 1, [nOrigCols 1], @sum, 0); +0074 +0075 %Single-coefficient columns (e.g. exchange reactions or slack variables) have +0076 %an undefined ratio and are never split +0077 badCols = find(nnzCol >= 2 & (cMax ./ cMin) > detectRatio); +0078 if isempty(badCols) +0079 return +0080 end +0081 +0082 %Flag the entries that are too small within their (bad) column. These are +0083 %removed from the original matrix and re-introduced via the auxiliary chain. +0084 smallThresh = cMax(J) / maxRatio; +0085 removeEntry = ismember(J, badCols) & (absV < smallThresh); +0086 +0087 %Keep all entries that are not removed +0088 keep = ~removeEntry; +0089 newI = I(keep); +0090 newJ = J(keep); +0091 newV = V(keep); +0092 +0093 %Auxiliary entries are accumulated here +0094 addI = zeros(0,1); addJ = zeros(0,1); addV = zeros(0,1); +0095 convLb = zeros(0,1); convUb = zeros(0,1); +0096 nStar = 0; %auxiliary metabolite rows added so far +0097 nConv = 0; %conversion variable columns added so far +0098 +0099 remIdx = find(removeEntry); +0100 for k = 1:numel(remIdx) +0101 e = remIdx(k); +0102 metRow = I(e); +0103 parentCol = J(e); +0104 coef = V(e); +0105 curCmax = cMax(parentCol); +0106 %Deliver the (signed) coefficient "coef" of metabolite "metRow" into +0107 %column "parentCol", splitting recursively until it is within range +0108 while true +0109 small = curCmax / maxRatio; +0110 if abs(coef) >= small +0111 %Coefficient is acceptable; place it directly +0112 addI(end+1,1) = metRow; addJ(end+1,1) = parentCol; addV(end+1,1) = coef; %#ok<AGROW> +0113 break +0114 end +0115 %Otherwise introduce a star metabolite and a conversion variable +0116 beta = sign(coef) * small; +0117 nStar = nStar + 1; starRow = nOrigRows + nStar; +0118 nConv = nConv + 1; convCol = nOrigCols + nConv; +0119 %The parent column now consumes the star metabolite (coefficient beta) +0120 addI(end+1,1) = starRow; addJ(end+1,1) = parentCol; addV(end+1,1) = beta; %#ok<AGROW> +0121 %The conversion variable produces one unit of the star metabolite +0122 addI(end+1,1) = starRow; addJ(end+1,1) = convCol; addV(end+1,1) = 1; %#ok<AGROW> +0123 %Conversion variables are determined by the equality and left unbounded +0124 convLb(end+1,1) = -inf; convUb(end+1,1) = inf; %#ok<AGROW> +0125 %Deliver the residual coefficient into the new conversion column. Its +0126 %reference scale is 1 (the production coefficient of the star met). +0127 coef = -coef / beta; +0128 parentCol = convCol; +0129 curCmax = 1; +0130 end +0131 end 0132 -0133 %Assemble the augmented matrix and extend the problem vectors. New rows are -0134 %appended at the bottom, new columns at the right. -0135 prob.A = sparse([newI; addI], [newJ; addJ], [newV; addV], ... -0136 nOrigRows + nStar, nOrigCols + nConv); -0137 prob.b = [prob.b(:); zeros(nStar,1)]; -0138 prob.csense = [prob.csense(:).', repmat('E',1,nStar)]; -0139 prob.c = [prob.c(:); zeros(nConv,1)]; -0140 prob.lb = [prob.lb(:); convLb]; -0141 prob.ub = [prob.ub(:); convUb]; -0142 prob.vartype = [prob.vartype(:).', repmat('C',1,nConv)]; -0143 end +0133 condInfo.applied = true; +0134 condInfo.nSplits = nStar; +0135 +0136 %Assemble the augmented matrix and extend the problem vectors. New rows are +0137 %appended at the bottom, new columns at the right. +0138 prob.A = sparse([newI; addI], [newJ; addJ], [newV; addV], ... +0139 nOrigRows + nStar, nOrigCols + nConv); +0140 prob.b = [prob.b(:); zeros(nStar,1)]; +0141 prob.csense = [prob.csense(:).', repmat('E',1,nStar)]; +0142 prob.c = [prob.c(:); zeros(nConv,1)]; +0143 prob.lb = [prob.lb(:); convLb]; +0144 prob.ub = [prob.ub(:); convUb]; +0145 prob.vartype = [prob.vartype(:).', repmat('C',1,nConv)]; +0146 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/tasks/checkTasks.html b/docs/html/tasks/checkTasks.html index b2418923..0d66fc72 100644 --- a/docs/html/tasks/checkTasks.html +++ b/docs/html/tasks/checkTasks.html @@ -29,12 +29,11 @@

    SYNOPSIS ^DESCRIPTION ^

     checkTasks  Perform a set of simulations as defined in a task file.
     
    - This function is used for defining a set of tasks for a model to perform.
    - The tasks are defined by defining constraints on the model, and if the
    - problem is feasible, then the task is considered successful. In general,
    - each row can contain one constraint on uptakes, one constraint on outputs,
    - one new equation, and one change of reaction bounds. If more bounds are
    - needed to define the task, then several rows can be used for each task.
    + The function runs a set of tasks for a model. Each task is defined by
    + constraints on the model, and the task is successful if the problem is
    + feasible. Each row can contain one constraint on uptakes, one constraint on
    + outputs, one new equation, and one change of reaction bounds. If more bounds
    + are needed to define the task, then several rows can be used for each task.
     
      The task inputs and outputs are applied to the boundary metabolites of
      the model. The model's own exchange reactions are not closed: they keep
    @@ -58,17 +57,18 @@ 

    DESCRIPTION ^DESCRIPTION ^SOURCE CODE ^
    0001 function [taskReport, essentialRxns, taskStructure, essentialFluxes]=checkTasks(model,inputFile,varargin)
     0002 % checkTasks  Perform a set of simulations as defined in a task file.
     0003 %
    -0004 % This function is used for defining a set of tasks for a model to perform.
    -0005 % The tasks are defined by defining constraints on the model, and if the
    -0006 % problem is feasible, then the task is considered successful. In general,
    -0007 % each row can contain one constraint on uptakes, one constraint on outputs,
    -0008 % one new equation, and one change of reaction bounds. If more bounds are
    -0009 % needed to define the task, then several rows can be used for each task.
    -0010 %
    -0011 % The task inputs and outputs are applied to the boundary metabolites of
    -0012 % the model. The model's own exchange reactions are not closed: they keep
    -0013 % their bounds, so in a model without boundary metabolites they stay open
    -0014 % during every task. To let the task define the whole boundary, run
    -0015 % checkTasks on closeModel(model).
    -0016 %
    -0017 % Parameters
    -0018 % ----------
    -0019 % model : struct
    -0020 %     a model structure.
    -0021 % inputFile : char
    -0022 %     a task list in Excel format. See the function parseTaskList for
    -0023 %     details (optional if taskStructure is supplied).
    -0024 %
    -0025 % Name-Value Arguments
    -0026 % --------------------
    -0027 % printOutput : logical
    -0028 %     true if the results of the test should be displayed (default true).
    -0029 % printOnlyFailed : logical
    -0030 %     true if only tasks that failed should be displayed (default false).
    -0031 % getEssential : logical
    -0032 %     true if the essential reactions should be calculated for all the
    -0033 %     tasks. This option is used with runINIT (default false).
    +0004 % The function runs a set of tasks for a model. Each task is defined by
    +0005 % constraints on the model, and the task is successful if the problem is
    +0006 % feasible. Each row can contain one constraint on uptakes, one constraint on
    +0007 % outputs, one new equation, and one change of reaction bounds. If more bounds
    +0008 % are needed to define the task, then several rows can be used for each task.
    +0009 %
    +0010 % The task inputs and outputs are applied to the boundary metabolites of
    +0011 % the model. The model's own exchange reactions are not closed: they keep
    +0012 % their bounds, so in a model without boundary metabolites they stay open
    +0013 % during every task. To let the task define the whole boundary, run
    +0014 % checkTasks on closeModel(model).
    +0015 %
    +0016 % Parameters
    +0017 % ----------
    +0018 % model : struct
    +0019 %     a model structure.
    +0020 % inputFile : char
    +0021 %     a task list in Excel format. See the function parseTaskList for
    +0022 %     details (optional if taskStructure is supplied).
    +0023 %
    +0024 % Name-Value Arguments
    +0025 % --------------------
    +0026 % printOutput : logical
    +0027 %     true if the results of the test should be displayed (default true).
    +0028 % printOnlyFailed : logical
    +0029 %     true if only tasks that failed should be displayed (default false).
    +0030 % getEssential : logical
    +0031 %     true if the essential reactions should be calculated for all the
    +0032 %     tasks. This option is used by prepINITModel (ftINIT) and getINITModel
    +0033 %     (tINIT) (default false).
     0034 % taskStructure : struct
     0035 %     structure with the tasks, as from parseTaskList. If this is supplied
     0036 %     then inputFile is ignored.
     0037 % runParallel : logical
    -0038 %     true to evaluate tasks in parallel workers, since each task is
    -0039 %     independent of the others (see parallelWorkersRAVEN). Gurobi is
    -0040 %     automatically pinned to one thread per worker by optimizeProb, so
    -0041 %     this does not oversubscribe cores. Default false, matching the
    -0042 %     previous (serial) behaviour, since checkTasks is often called on
    -0043 %     small task lists where starting a pool would only add overhead.
    +0038 %     true to evaluate tasks in parallel workers, as each task is
    +0039 %     independent of the others (see parallelWorkersRAVEN). In a worker,
    +0040 %     optimizeProb sets Gurobi to one thread, so the workers do not use
    +0041 %     more cores than assigned. With false the tasks are evaluated in
    +0042 %     series, which avoids the overhead of starting a pool for small task
    +0043 %     lists (default false).
     0044 %
     0045 % Returns
     0046 % -------
    @@ -170,278 +172,280 @@ 

    SOURCE CODE ^% taskStructure : struct 0060 % structure with the tasks, as from parseTaskList. 0061 % essentialFluxes : double -0062 % the fluxes of the essential rxns - same structure as essentialRxns. -0063 % -0064 % Examples -0065 % -------- -0066 % [taskReport, essentialRxns, taskStructure] = checkTasks(model, inputFile, ... -0067 % printOutput, printOnlyFailed, getEssential, taskStructure); -0068 -0069 p=parseRAVENargs(varargin, {'printOutput',true; 'printOnlyFailed',false; 'getEssential',false; 'taskStructure',[]; 'runParallel',false}); -0070 printOutput=p.printOutput; -0071 printOnlyFailed=p.printOnlyFailed; -0072 getEssential=p.getEssential; -0073 taskStructure=p.taskStructure; -0074 runParallel=p.runParallel; -0075 -0076 %Prepare the input model a little -0077 model.b=zeros(numel(model.mets),2); -0078 -0079 modelMets=upper(strcat(model.metNames,'[',model.comps(model.metComps),']')); -0080 if ~isfield(model,'unconstrained') -0081 EM='Exchange metabolites should normally not be removed from the model when using checkTasks. Inputs and outputs are defined in the task file instead. Use importModel(file,false) to import a model with exchange metabolites remaining'; -0082 warning('RAVEN:warning', '%s', EM); -0083 end -0084 -0085 %Parse the task file -0086 if isempty(taskStructure) -0087 taskStructure=parseTaskList(inputFile); -0088 end -0089 -0090 nTasks=numel(taskStructure); -0091 essentialRxns=false(numel(model.rxns),nTasks); -0092 essentialFluxes = NaN(numel(model.rxns),nTasks); -0093 -0094 %Each task is independent (tModel is rebuilt from model at the top of -0095 %every iteration, nothing carries over), so this loop can run in -0096 %parallel. ids/descriptions/ok are plain arrays sliced by i and only -0097 %assembled into taskReport once the loop is done, because parfor's -0098 %classifier rejects slicing a struct field that is itself indexed with -0099 %cell/array subscripts (taskReport.id{i,1}=... is not a supported sliced -0100 %form). runParallel=false (the default) makes this run exactly like the -0101 %previous for loop, in the client, serially. -0102 ids=cell(nTasks,1); -0103 descriptions=cell(nTasks,1); -0104 ok=false(nTasks,1); -0105 -0106 nW=parallelWorkersRAVEN(runParallel); -0107 parfor (i=1:nTasks, nW) -0108 tModel=model; -0109 ids{i}=taskStructure(i).id; -0110 descriptions{i}=taskStructure(i).description; -0111 %Set the inputs -0112 if ~isempty(taskStructure(i).inputs) -0113 [I, J]=ismember(upper(taskStructure(i).inputs),modelMets); -0114 J=J(I); %Only keep the ones with matches -0115 %Several model metabolites can share a name[comp]. Only the first -0116 %one is constrained, so tell the user when that happens -0117 dupMets=duplicateMetNames(taskStructure(i).inputs(I),modelMets); -0118 if ~isempty(dupMets) -0119 EM=['Several model metabolites match input(s) in "[' taskStructure(i).id '] ' taskStructure(i).description '": ' strjoin(dupMets,', ') '. Only the first match is constrained']; -0120 warning('RAVEN:warning', '%s', EM); -0121 end -0122 K=ismember(upper(taskStructure(i).inputs),'ALLMETS'); -0123 L=~cellfun('isempty',strfind(upper(taskStructure(i).inputs),'ALLMETSIN')); -0124 %Check that all metabolites are either real metabolites or -0125 %ALLMETS/ALLMETSIN -0126 if ~all(I|K|L) -0127 fprintf(['ERROR: Could not find all inputs in "[' taskStructure(i).id '] ' taskStructure(i).description '"\n']); -0128 ok(i)=false; -0129 continue; -0130 end -0131 if numel(J)~=numel(unique(J)) -0132 EM=['The constraints on some input(s) in "[' taskStructure(i).id '] ' taskStructure(i).description '" are defined more than one time']; -0133 error('RAVEN:badInput', '%s', EM); -0134 end -0135 %If all metabolites should be added -0136 if any(K) -0137 %Check if ALLMETS is the first metabolite. Otherwise print a -0138 %warning since it will write over any other constraints that -0139 %are set -0140 if K(1)==0 -0141 EM=['ALLMETS is used as an input in "[' taskStructure(i).id '] ' taskStructure(i).description '" but it it not the first metabolite in the list. Constraints defined for the metabolites before it will be over-written']; -0142 warning('RAVEN:warning', '%s', EM); -0143 end -0144 %Use the first match of ALLMETS. There should only be one, but -0145 %still.. -0146 tModel.b(:,1)=taskStructure(i).UBin(find(K,1))*-1; -0147 end -0148 %If metabolites in a specific compartment should be used -0149 if any(L) -0150 L=find(L); -0151 for j=1:numel(L) -0152 %The compartment defined -0153 compartment=upper(taskStructure(i).inputs{L(j)}(11:end-1)); -0154 %Check if it exists in the model -0155 C=find(ismember(upper(model.comps),compartment)); -0156 if any(C) -0157 %Match to metabolites -0158 tModel.b(model.metComps==C,1)=taskStructure(i).UBin(L(j))*-1; -0159 else -0160 EM=['The compartment defined for ALLMETSIN in "[' taskStructure(i).id '] ' taskStructure(i).description '" does not exist']; -0161 error('RAVEN:badInput', '%s', EM); -0162 end -0163 end -0164 end -0165 %Then add the normal constraints -0166 if any(J) -0167 tModel.b(J,1)=taskStructure(i).UBin(I)*-1; -0168 tModel.b(J,2)=taskStructure(i).LBin(I)*-1; -0169 end -0170 end -0171 %Set the outputs -0172 if ~isempty(taskStructure(i).outputs) -0173 [I, J]=ismember(upper(taskStructure(i).outputs),modelMets); -0174 J=J(I); %Only keep the ones with matches -0175 %Several model metabolites can share a name[comp]. Only the first -0176 %one is constrained, so tell the user when that happens -0177 dupMets=duplicateMetNames(taskStructure(i).outputs(I),modelMets); -0178 if ~isempty(dupMets) -0179 EM=['Several model metabolites match output(s) in "[' taskStructure(i).id '] ' taskStructure(i).description '": ' strjoin(dupMets,', ') '. Only the first match is constrained']; -0180 warning('RAVEN:warning', '%s', EM); -0181 end -0182 K=ismember(upper(taskStructure(i).outputs),'ALLMETS'); -0183 L=~cellfun('isempty',strfind(upper(taskStructure(i).outputs),'ALLMETSIN')); -0184 %Check that all metabolites are either real metabolites or -0185 %ALLMETS/ALLMETSIN -0186 if ~all(I|K|L) -0187 fprintf(['ERROR: Could not find all outputs in "[' taskStructure(i).id '] ' taskStructure(i).description '"\n']); -0188 ok(i)=false; -0189 continue; -0190 end -0191 if numel(J)~=numel(unique(J)) -0192 EM=['The constraints on some output(s) in "[' taskStructure(i).id '] ' taskStructure(i).description '" are defined more than one time']; -0193 error('RAVEN:badInput', '%s', EM); -0194 end -0195 %If all metabolites should be added -0196 if any(K) -0197 %Check if ALLMETS is the first metabolite. Otherwise print a -0198 %warning since it will write over any other constraints that -0199 %are set -0200 if K(1)==0 -0201 EM=['ALLMETS is used as an output in "[' taskStructure(i).id '] ' taskStructure(i).description '" but it it not the first metabolite in the list. Constraints defined for the metabolites before it will be over-written']; -0202 warning('RAVEN:warning', '%s', EM); -0203 end -0204 %Use the first match of ALLMETS. There should only be one, but -0205 %still.. -0206 tModel.b(:,2)=taskStructure(i).UBout(find(K,1)); -0207 end -0208 %If metabolites in a specific compartment should be used -0209 if any(L) -0210 L=find(L); -0211 for j=1:numel(L) -0212 %The compartment defined -0213 compartment=upper(taskStructure(i).outputs{L(j)}(11:end-1)); -0214 %Check if it exists in the model -0215 C=find(ismember(upper(model.comps),compartment)); -0216 if any(C) -0217 %Match to metabolites -0218 tModel.b(model.metComps==C,2)=taskStructure(i).UBout(L(j)); -0219 else -0220 EM=['The compartment defined for ALLMETSIN in "[' taskStructure(i).id '] ' taskStructure(i).description '" does not exist']; -0221 error('RAVEN:badInput', '%s', EM); -0222 end -0223 end -0224 end -0225 %Then add the normal constraints -0226 if any(J) -0227 %Verify that IN and OUT bounds are consistent. Cannot require -0228 %that a metabolite is simultaneously input AND output at some -0229 %nonzero flux. -0230 I = find(I); % otherwise indexing becomes confusing -0231 nonzero_LBin = tModel.b(J,2) < 0; -0232 nonzero_LBout = taskStructure(i).LBout(I) > 0; -0233 if any(nonzero_LBin & nonzero_LBout) -0234 EM=['The IN LB and OUT LB in "[' taskStructure(i).id '] ' taskStructure(i).description '" cannot be nonzero for the same metabolite']; -0235 error('RAVEN:badInput', '%s', EM); -0236 end -0237 tModel.b(J(nonzero_LBout),1)=taskStructure(i).LBout(I(nonzero_LBout)); -0238 tModel.b(J,2)=taskStructure(i).UBout(I); -0239 end -0240 end -0241 %Add new rxns -0242 if ~isempty(taskStructure(i).equations) -0243 % num2str on the whole column right-aligns every row to a common -0244 % width, embedding a leading space in "TEMPORARY_ 1" once any id -0245 % reaches two digits ("TEMPORARY_10"); format each one independently. -0246 rxnIds=arrayfun(@(x) sprintf('TEMPORARY_%d',x), ... -0247 (1:numel(taskStructure(i).equations))', 'UniformOutput', false); -0248 % Built as a single struct() call, not incremental rxn.field=... -0249 % assignments: parfor's variable classifier cannot establish that a -0250 % variable built up via several dot-indexed assignments is a -0251 % same-shape temporary in every iteration, and rejects it outright. -0252 rxn=struct('equations',{taskStructure(i).equations}, ... -0253 'lb',taskStructure(i).LBequ, 'ub',taskStructure(i).UBequ, ... -0254 'rxns',{rxnIds}); -0255 %Allow for new metabolites to be added. This is because it should -0256 %be possible to add, say, a whole new pathway -0257 tModel=addRxns(tModel,rxn,3,[],true); -0258 end -0259 %Add changed bounds -0260 if ~isempty(taskStructure(i).changed) -0261 %NaN bounds mean "leave unchanged" -0262 lbSet=~isnan(taskStructure(i).LBrxn); -0263 ubSet=~isnan(taskStructure(i).UBrxn); -0264 tModel=setParam(tModel,'lb',taskStructure(i).changed(lbSet),taskStructure(i).LBrxn(lbSet)); -0265 tModel=setParam(tModel,'ub',taskStructure(i).changed(ubSet),taskStructure(i).UBrxn(ubSet)); -0266 end -0267 -0268 %Solve and print -0269 sol=solveLP(tModel); -0270 if ~isempty(sol.x) -0271 %assign the fluxes -0272 essentialFluxes(:,i) = sol.x(1:numel(model.rxns)); -0273 -0274 if ~taskStructure(i).shouldFail -0275 ok(i)=true; -0276 if printOnlyFailed==false && printOutput==true -0277 fprintf(['PASS: [' taskStructure(i).id '] ' taskStructure(i).description '\n']); -0278 end -0279 %Calculate the essential reactions -0280 if getEssential==true -0281 [~, taskEssential]=getEssentialRxns(tModel); -0282 %This is because there could be more reactions in tModel -0283 %than in model. Built as a full local column and written -0284 %with a plain ':' row index: parfor only accepts a sliced -0285 %output write when every subscript besides the loop -0286 %variable is ':', not a data-dependent index vector. -0287 essentialCol=false(numel(model.rxns),1); -0288 essentialCol(taskEssential(taskEssential<=numel(model.rxns)))=true; -0289 essentialRxns(:,i)=essentialCol; -0290 end -0291 else -0292 ok(i)=false; -0293 if printOutput==true -0294 fprintf(['PASS (should fail): [' taskStructure(i).id '] ' taskStructure(i).description '\n']); -0295 end -0296 end -0297 else -0298 if ~taskStructure(i).shouldFail -0299 ok(i)=false; -0300 if printOutput==true -0301 fprintf(['FAIL: [' taskStructure(i).id '] ' taskStructure(i).description '\n']); -0302 end -0303 else -0304 ok(i)=true; -0305 if printOnlyFailed==false && printOutput==true -0306 fprintf(['FAIL (should fail): [' taskStructure(i).id '] ' taskStructure(i).description '\n']); -0307 end -0308 end -0309 end -0310 if taskStructure(i).printFluxes && ~isempty(sol.x) -0311 sol=solveLP(tModel,1); -0312 if ~isempty(sol.x) -0313 printFluxes(tModel,sol.x,false,10^-6,[],'%rxnID (%eqn):%flux\n'); -0314 fprintf('\n'); -0315 end -0316 end -0317 end -0318 -0319 taskReport.id=ids; -0320 taskReport.description=descriptions; -0321 taskReport.ok=ok; -0322 -0323 end +0062 % the fluxes of the model reactions in the solution of each task (NaN +0063 % for a task without a solution), with the same dimensions as +0064 % essentialRxns. +0065 % +0066 % Examples +0067 % -------- +0068 % [taskReport, essentialRxns, taskStructure] = checkTasks(model, inputFile, ... +0069 % printOutput, printOnlyFailed, getEssential, taskStructure); +0070 +0071 p=parseRAVENargs(varargin, {'printOutput',true; 'printOnlyFailed',false; 'getEssential',false; 'taskStructure',[]; 'runParallel',false}); +0072 printOutput=p.printOutput; +0073 printOnlyFailed=p.printOnlyFailed; +0074 getEssential=p.getEssential; +0075 taskStructure=p.taskStructure; +0076 runParallel=p.runParallel; +0077 +0078 %Prepare the input model a little +0079 model.b=zeros(numel(model.mets),2); +0080 +0081 modelMets=upper(strcat(model.metNames,'[',model.comps(model.metComps),']')); +0082 if ~isfield(model,'unconstrained') +0083 EM='Exchange metabolites should normally not be removed from the model when using checkTasks. Inputs and outputs are defined in the task file instead. Use importModel(file,false) to import a model with exchange metabolites remaining'; +0084 warning('RAVEN:warning', '%s', EM); +0085 end +0086 +0087 %Parse the task file +0088 if isempty(taskStructure) +0089 taskStructure=parseTaskList(inputFile); +0090 end +0091 +0092 nTasks=numel(taskStructure); +0093 essentialRxns=false(numel(model.rxns),nTasks); +0094 essentialFluxes = NaN(numel(model.rxns),nTasks); +0095 +0096 %Each task is independent (tModel is rebuilt from model at the top of +0097 %every iteration, nothing carries over), so this loop can run in +0098 %parallel. ids/descriptions/ok are plain arrays sliced by i and only +0099 %assembled into taskReport once the loop is done, because parfor's +0100 %classifier rejects slicing a struct field that is itself indexed with +0101 %cell/array subscripts (taskReport.id{i,1}=... is not a supported sliced +0102 %form). runParallel=false (the default) makes this run exactly like the +0103 %previous for loop, in the client, serially. +0104 ids=cell(nTasks,1); +0105 descriptions=cell(nTasks,1); +0106 ok=false(nTasks,1); +0107 +0108 nW=parallelWorkersRAVEN(runParallel); +0109 parfor (i=1:nTasks, nW) +0110 tModel=model; +0111 ids{i}=taskStructure(i).id; +0112 descriptions{i}=taskStructure(i).description; +0113 %Set the inputs +0114 if ~isempty(taskStructure(i).inputs) +0115 [I, J]=ismember(upper(taskStructure(i).inputs),modelMets); +0116 J=J(I); %Only keep the ones with matches +0117 %Several model metabolites can share a name[comp]. Only the first +0118 %one is constrained, so tell the user when that happens +0119 dupMets=duplicateMetNames(taskStructure(i).inputs(I),modelMets); +0120 if ~isempty(dupMets) +0121 EM=['Several model metabolites match input(s) in "[' taskStructure(i).id '] ' taskStructure(i).description '": ' strjoin(dupMets,', ') '. Only the first match is constrained']; +0122 warning('RAVEN:warning', '%s', EM); +0123 end +0124 K=ismember(upper(taskStructure(i).inputs),'ALLMETS'); +0125 L=~cellfun('isempty',strfind(upper(taskStructure(i).inputs),'ALLMETSIN')); +0126 %Check that all metabolites are either real metabolites or +0127 %ALLMETS/ALLMETSIN +0128 if ~all(I|K|L) +0129 fprintf(['ERROR: Could not find all inputs in "[' taskStructure(i).id '] ' taskStructure(i).description '"\n']); +0130 ok(i)=false; +0131 continue; +0132 end +0133 if numel(J)~=numel(unique(J)) +0134 EM=['The constraints on some input(s) in "[' taskStructure(i).id '] ' taskStructure(i).description '" are defined more than one time']; +0135 error('RAVEN:badInput', '%s', EM); +0136 end +0137 %If all metabolites should be added +0138 if any(K) +0139 %Check if ALLMETS is the first metabolite. Otherwise print a +0140 %warning since it will write over any other constraints that +0141 %are set +0142 if K(1)==0 +0143 EM=['ALLMETS is used as an input in "[' taskStructure(i).id '] ' taskStructure(i).description '" but it it not the first metabolite in the list. Constraints defined for the metabolites before it will be over-written']; +0144 warning('RAVEN:warning', '%s', EM); +0145 end +0146 %Use the first match of ALLMETS. There should only be one, but +0147 %still.. +0148 tModel.b(:,1)=taskStructure(i).UBin(find(K,1))*-1; +0149 end +0150 %If metabolites in a specific compartment should be used +0151 if any(L) +0152 L=find(L); +0153 for j=1:numel(L) +0154 %The compartment defined +0155 compartment=upper(taskStructure(i).inputs{L(j)}(11:end-1)); +0156 %Check if it exists in the model +0157 C=find(ismember(upper(model.comps),compartment)); +0158 if any(C) +0159 %Match to metabolites +0160 tModel.b(model.metComps==C,1)=taskStructure(i).UBin(L(j))*-1; +0161 else +0162 EM=['The compartment defined for ALLMETSIN in "[' taskStructure(i).id '] ' taskStructure(i).description '" does not exist']; +0163 error('RAVEN:badInput', '%s', EM); +0164 end +0165 end +0166 end +0167 %Then add the normal constraints +0168 if any(J) +0169 tModel.b(J,1)=taskStructure(i).UBin(I)*-1; +0170 tModel.b(J,2)=taskStructure(i).LBin(I)*-1; +0171 end +0172 end +0173 %Set the outputs +0174 if ~isempty(taskStructure(i).outputs) +0175 [I, J]=ismember(upper(taskStructure(i).outputs),modelMets); +0176 J=J(I); %Only keep the ones with matches +0177 %Several model metabolites can share a name[comp]. Only the first +0178 %one is constrained, so tell the user when that happens +0179 dupMets=duplicateMetNames(taskStructure(i).outputs(I),modelMets); +0180 if ~isempty(dupMets) +0181 EM=['Several model metabolites match output(s) in "[' taskStructure(i).id '] ' taskStructure(i).description '": ' strjoin(dupMets,', ') '. Only the first match is constrained']; +0182 warning('RAVEN:warning', '%s', EM); +0183 end +0184 K=ismember(upper(taskStructure(i).outputs),'ALLMETS'); +0185 L=~cellfun('isempty',strfind(upper(taskStructure(i).outputs),'ALLMETSIN')); +0186 %Check that all metabolites are either real metabolites or +0187 %ALLMETS/ALLMETSIN +0188 if ~all(I|K|L) +0189 fprintf(['ERROR: Could not find all outputs in "[' taskStructure(i).id '] ' taskStructure(i).description '"\n']); +0190 ok(i)=false; +0191 continue; +0192 end +0193 if numel(J)~=numel(unique(J)) +0194 EM=['The constraints on some output(s) in "[' taskStructure(i).id '] ' taskStructure(i).description '" are defined more than one time']; +0195 error('RAVEN:badInput', '%s', EM); +0196 end +0197 %If all metabolites should be added +0198 if any(K) +0199 %Check if ALLMETS is the first metabolite. Otherwise print a +0200 %warning since it will write over any other constraints that +0201 %are set +0202 if K(1)==0 +0203 EM=['ALLMETS is used as an output in "[' taskStructure(i).id '] ' taskStructure(i).description '" but it it not the first metabolite in the list. Constraints defined for the metabolites before it will be over-written']; +0204 warning('RAVEN:warning', '%s', EM); +0205 end +0206 %Use the first match of ALLMETS. There should only be one, but +0207 %still.. +0208 tModel.b(:,2)=taskStructure(i).UBout(find(K,1)); +0209 end +0210 %If metabolites in a specific compartment should be used +0211 if any(L) +0212 L=find(L); +0213 for j=1:numel(L) +0214 %The compartment defined +0215 compartment=upper(taskStructure(i).outputs{L(j)}(11:end-1)); +0216 %Check if it exists in the model +0217 C=find(ismember(upper(model.comps),compartment)); +0218 if any(C) +0219 %Match to metabolites +0220 tModel.b(model.metComps==C,2)=taskStructure(i).UBout(L(j)); +0221 else +0222 EM=['The compartment defined for ALLMETSIN in "[' taskStructure(i).id '] ' taskStructure(i).description '" does not exist']; +0223 error('RAVEN:badInput', '%s', EM); +0224 end +0225 end +0226 end +0227 %Then add the normal constraints +0228 if any(J) +0229 %Verify that IN and OUT bounds are consistent. Cannot require +0230 %that a metabolite is simultaneously input AND output at some +0231 %nonzero flux. +0232 I = find(I); % otherwise indexing becomes confusing +0233 nonzero_LBin = tModel.b(J,2) < 0; +0234 nonzero_LBout = taskStructure(i).LBout(I) > 0; +0235 if any(nonzero_LBin & nonzero_LBout) +0236 EM=['The IN LB and OUT LB in "[' taskStructure(i).id '] ' taskStructure(i).description '" cannot be nonzero for the same metabolite']; +0237 error('RAVEN:badInput', '%s', EM); +0238 end +0239 tModel.b(J(nonzero_LBout),1)=taskStructure(i).LBout(I(nonzero_LBout)); +0240 tModel.b(J,2)=taskStructure(i).UBout(I); +0241 end +0242 end +0243 %Add new rxns +0244 if ~isempty(taskStructure(i).equations) +0245 % num2str on the whole column right-aligns every row to a common +0246 % width, embedding a leading space in "TEMPORARY_ 1" once any id +0247 % reaches two digits ("TEMPORARY_10"); format each one independently. +0248 rxnIds=arrayfun(@(x) sprintf('TEMPORARY_%d',x), ... +0249 (1:numel(taskStructure(i).equations))', 'UniformOutput', false); +0250 % Built as a single struct() call, not incremental rxn.field=... +0251 % assignments: parfor's variable classifier cannot establish that a +0252 % variable built up via several dot-indexed assignments is a +0253 % same-shape temporary in every iteration, and rejects it outright. +0254 rxn=struct('equations',{taskStructure(i).equations}, ... +0255 'lb',taskStructure(i).LBequ, 'ub',taskStructure(i).UBequ, ... +0256 'rxns',{rxnIds}); +0257 %Allow for new metabolites to be added. This is because it should +0258 %be possible to add, say, a whole new pathway +0259 tModel=addRxns(tModel,rxn,3,[],true); +0260 end +0261 %Add changed bounds +0262 if ~isempty(taskStructure(i).changed) +0263 %NaN bounds mean "leave unchanged" +0264 lbSet=~isnan(taskStructure(i).LBrxn); +0265 ubSet=~isnan(taskStructure(i).UBrxn); +0266 tModel=setParam(tModel,'lb',taskStructure(i).changed(lbSet),taskStructure(i).LBrxn(lbSet)); +0267 tModel=setParam(tModel,'ub',taskStructure(i).changed(ubSet),taskStructure(i).UBrxn(ubSet)); +0268 end +0269 +0270 %Solve and print +0271 sol=solveLP(tModel); +0272 if ~isempty(sol.x) +0273 %assign the fluxes +0274 essentialFluxes(:,i) = sol.x(1:numel(model.rxns)); +0275 +0276 if ~taskStructure(i).shouldFail +0277 ok(i)=true; +0278 if printOnlyFailed==false && printOutput==true +0279 fprintf(['PASS: [' taskStructure(i).id '] ' taskStructure(i).description '\n']); +0280 end +0281 %Calculate the essential reactions +0282 if getEssential==true +0283 [~, taskEssential]=getEssentialRxns(tModel); +0284 %This is because there could be more reactions in tModel +0285 %than in model. Built as a full local column and written +0286 %with a plain ':' row index: parfor only accepts a sliced +0287 %output write when every subscript besides the loop +0288 %variable is ':', not a data-dependent index vector. +0289 essentialCol=false(numel(model.rxns),1); +0290 essentialCol(taskEssential(taskEssential<=numel(model.rxns)))=true; +0291 essentialRxns(:,i)=essentialCol; +0292 end +0293 else +0294 ok(i)=false; +0295 if printOutput==true +0296 fprintf(['PASS (should fail): [' taskStructure(i).id '] ' taskStructure(i).description '\n']); +0297 end +0298 end +0299 else +0300 if ~taskStructure(i).shouldFail +0301 ok(i)=false; +0302 if printOutput==true +0303 fprintf(['FAIL: [' taskStructure(i).id '] ' taskStructure(i).description '\n']); +0304 end +0305 else +0306 ok(i)=true; +0307 if printOnlyFailed==false && printOutput==true +0308 fprintf(['FAIL (should fail): [' taskStructure(i).id '] ' taskStructure(i).description '\n']); +0309 end +0310 end +0311 end +0312 if taskStructure(i).printFluxes && ~isempty(sol.x) +0313 sol=solveLP(tModel,1); +0314 if ~isempty(sol.x) +0315 printFluxes(tModel,sol.x,false,10^-6,[],'%rxnID (%eqn):%flux\n'); +0316 fprintf('\n'); +0317 end +0318 end +0319 end +0320 +0321 taskReport.id=ids; +0322 taskReport.description=descriptions; +0323 taskReport.ok=ok; 0324 -0325 function dupMets = duplicateMetNames(names,modelMets) -0326 % Return the (upper-case) names that match more than one model metabolite. -0327 dupMets = unique(upper(names)); -0328 isDup = false(size(dupMets)); -0329 for k = 1:numel(dupMets) -0330 isDup(k) = sum(strcmp(modelMets,dupMets{k})) > 1; -0331 end -0332 dupMets = dupMets(isDup); -0333 end

    +0325 end +0326 +0327 function dupMets = duplicateMetNames(names,modelMets) +0328 % Return the (upper-case) names that match more than one model metabolite. +0329 dupMets = unique(upper(names)); +0330 isDup = false(size(dupMets)); +0331 for k = 1:numel(dupMets) +0332 isDup(k) = sum(strcmp(modelMets,dupMets{k})) > 1; +0333 end +0334 dupMets = dupMets(isDup); +0335 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/tasks/parseTaskList.html b/docs/html/tasks/parseTaskList.html index 3b104358..bc0475f7 100644 --- a/docs/html/tasks/parseTaskList.html +++ b/docs/html/tasks/parseTaskList.html @@ -33,36 +33,33 @@

    DESCRIPTION ^DESCRIPTION ^DESCRIPTION ^SOURCE CODE ^% ---------- 0006 % inputFile : char 0007 % a task list in either Excel (*.xlsx, with a sheet named TASKS with -0008 % all relevant content) or tab-delimited (*.txt) format. The old binary +0008 % all relevant content) or tab-delimited (*.txt) format. The binary 0009 % *.xls format is not supported. The file may contain the following -0010 % column headers (note, all rows starting with a non-empty cell are -0011 % removed; the first row after that is considered the headers; the -0012 % header row itself must therefore start with an empty cell): -0013 % -0014 % - ID : the only required header (string or numeric). Ids may repeat, -0015 % for example as category codes. A task starts at a row with a -0016 % non-empty id and can span multiple rows; only the first row of a task -0017 % should have an id -0018 % - DESCRIPTION : description of the task -0019 % - IN : allowed input(s) for the task. Metabolite names should be on -0020 % the form "model.metName[model.comps]". Several inputs can be -0021 % delimited by ";". If so, then the same bounds are used for all -0022 % inputs. If that is not wanted, then use several rows for the task -0023 % - IN LB : lower bound for the uptake of the metabolites in the row -0024 % (optional, default 0 which corresponds to a minimal uptake of 0 -0025 % units) +0010 % column headers. Rows that start with a non-empty cell are comments +0011 % and are removed. The first row that starts with an empty cell is the +0012 % header row, so the header row and the task rows must start with an +0013 % empty cell: +0014 % +0015 % - ID : the only required header (string or numeric). Ids may repeat, +0016 % for example as category codes. A task starts at a row with a +0017 % non-empty id and can span multiple rows; only the first row of a task +0018 % should have an id +0019 % - DESCRIPTION : description of the task +0020 % - IN : allowed input(s) for the task. Metabolite names are written in +0021 % the form "model.metName[model.comps]". Several inputs can be +0022 % delimited by ";". If so, then the same bounds are used for all +0023 % inputs. If that is not wanted, then use several rows for the task +0024 % - IN LB : lower bound for the uptake of the metabolites in the row +0025 % (optional, default 0) 0026 % - IN UB : upper bound for the uptake of the metabolites in the row -0027 % (optional, default 1000 which corresponds to a maximal uptake of -0028 % 1000 units) -0029 % - OUT : allowed output(s) for the task (see IN) -0030 % - OUT LB : lower bound for the production of the metabolites in the -0031 % row (optional, default 0 which corresponds to a minimal production -0032 % of 0 units) -0033 % - OUT UB : upper bound for the production of the metabolites in the -0034 % row (optional, default 1000 which corresponds to a maximal -0035 % production of 1000 units) -0036 % - EQU : equation to add. The equation should be on the form -0037 % "0.4 A + 2 B <=> (or =>) C" and the metabolites should be on the -0038 % form "model.metName[model.comps]" (optional) -0039 % - EQU LB : lower bound for the equation (optional, default -1000 for -0040 % reversible and 0 for irreversible) -0041 % - EQU UB : upper bound for the equation (optional, default 1000) -0042 % - CHANGED RXN : reaction ID for which to change the bounds for. -0043 % Several IDs can be delimited by ";". If so, then the same bounds -0044 % are used for all reactions. If that is not wanted, then use several -0045 % rows for the task -0046 % - CHANGED LB : lower bound for the reaction. If empty, the lower bound -0047 % of the reaction is left as it is -0048 % - CHANGED UB : upper bound for the reaction. If empty, the upper bound -0049 % of the reaction is left as it is -0050 % - SHOULD FAIL : true if the correct behavior of the model is to not -0051 % have a feasible solution given the constraints. Any non-empty value -0052 % except 0, false and no (any case) counts as true (optional, default -0053 % false) -0054 % - PRINT FLUX : true if the function should print the corresponding -0055 % flux distribution for a task. Can be useful for testing. Read in -0056 % the same way as SHOULD FAIL (optional, default false) -0057 % -0058 % Headers are matched ignoring case and surrounding whitespace. In the -0059 % ";"-delimited cells, items are trimmed and empty items are dropped. -0060 % -0061 % Returns -0062 % ------- -0063 % taskStruct : struct -0064 % array of structures with the following fields: -0065 % -0066 % - id : the id of the task -0067 % - description : the description of the task -0068 % - shouldFail : true if the task should fail -0069 % - printFluxes : true if the fluxes should be printed -0070 % - comments : string with comments -0071 % - inputs : cell array with input metabolites (in the form metName[comps]) -0072 % - LBin : array with lower bounds on inputs (default, 0) -0073 % - UBin : array with upper bounds on inputs (default, 1000) -0074 % - outputs : cell array with output metabolites (in the form metName[comps]) -0075 % - LBout : array with lower bounds on outputs (default, 0) -0076 % - UBout : array with upper bounds on outputs (default, 1000) -0077 % - equations : cell array with equations (with mets in the form metName[comps]) -0078 % - LBequ : array with lower bounds on equations (default, -1000 for -0079 % reversible and 0 for irreversible) -0080 % - UBequ : array with upper bounds on equations (default, 1000) -0081 % - changed : cell array with reactions to change bounds for -0082 % - LBrxn : array with lower bounds on changed reactions (NaN if empty, -0083 % meaning the bound is left unchanged) -0084 % - UBrxn : array with upper bounds on changed reactions (NaN if empty, -0085 % meaning the bound is left unchanged) -0086 % -0087 % Examples -0088 % -------- -0089 % taskStruct = parseTaskList(inputFile); -0090 % -0091 % Notes -0092 % ----- -0093 % This function is used for defining a set of tasks for a model to perform. -0094 % The tasks are defined by defining constraints on the model, and if the -0095 % problem is feasible, then the task is considered successful. In general, -0096 % each row can contain one constraint on uptakes, one constraint on -0097 % outputs, one new equation, and one change of reaction bounds. If more -0098 % bounds are needed to define the task, then several rows can be used for -0099 % each task. To perform the task use checkTasks or fitTasks. -0100 % -0101 % The general metabolites "ALLMETS" and "ALLMETSIN[comps]" can be used as -0102 % inputs or outputs in the similar manner to normal metabolites. This is a -0103 % convenient way to, for example, allow excretion of all metabolites to -0104 % check whether it is the synthesis of some metabolite that is limiting or -0105 % whether it is the degradation of some byproduct. One important difference -0106 % is that only the upper bounds are used for these general metabolites. -0107 % That is, you can only say that uptake or excretion is allowed, not that -0108 % it is required. This is to avoid conflicts where the constraints for the -0109 % general metabolites overwrite those of the real ones. -0110 % -0111 % See also -0112 % -------- -0113 % checkTasks, fitTasks +0027 % (optional, default 1000) +0028 % - OUT : allowed output(s) for the task (see IN) +0029 % - OUT LB : lower bound for the production of the metabolites in the +0030 % row (optional, default 0) +0031 % - OUT UB : upper bound for the production of the metabolites in the +0032 % row (optional, default 1000) +0033 % - EQU : equation to add. The equation is written in the form +0034 % "0.4 A + 2 B <=> (or =>) C" and the metabolites in the +0035 % form "model.metName[model.comps]" (optional) +0036 % - EQU LB : lower bound for the equation (optional, default -1000 for +0037 % reversible and 0 for irreversible) +0038 % - EQU UB : upper bound for the equation (optional, default 1000) +0039 % - CHANGED RXN : reaction ID for which to change the bounds for. +0040 % Several IDs can be delimited by ";". If so, then the same bounds +0041 % are used for all reactions. If that is not wanted, then use several +0042 % rows for the task +0043 % - CHANGED LB : lower bound for the reaction. If empty, the lower bound +0044 % of the reaction is left as it is +0045 % - CHANGED UB : upper bound for the reaction. If empty, the upper bound +0046 % of the reaction is left as it is +0047 % - SHOULD FAIL : true if the correct behaviour of the model is to not +0048 % have a feasible solution given the constraints. Any non-empty value +0049 % except 0, false and no (any case) counts as true (optional, default +0050 % false) +0051 % - PRINT FLUX : true if checkTasks should print the corresponding +0052 % flux distribution for a task. Read in the same way as SHOULD FAIL +0053 % (optional, default false) +0054 % - COMMENTS : comments on the task, stored in the field comments +0055 % (optional) +0056 % +0057 % Headers are matched ignoring case and surrounding whitespace. In the +0058 % ";"-delimited cells, items are trimmed and empty items are dropped. +0059 % +0060 % Returns +0061 % ------- +0062 % taskStruct : struct +0063 % array of structures with the following fields: +0064 % +0065 % - id : the id of the task +0066 % - description : the description of the task +0067 % - shouldFail : true if the task should fail +0068 % - printFluxes : true if the fluxes should be printed +0069 % - comments : string with comments +0070 % - inputs : cell array with input metabolites (in the form metName[comps]) +0071 % - LBin : array with lower bounds on inputs (default, 0) +0072 % - UBin : array with upper bounds on inputs (default, 1000) +0073 % - outputs : cell array with output metabolites (in the form metName[comps]) +0074 % - LBout : array with lower bounds on outputs (default, 0) +0075 % - UBout : array with upper bounds on outputs (default, 1000) +0076 % - equations : cell array with equations (with mets in the form metName[comps]) +0077 % - LBequ : array with lower bounds on equations (default, -1000 for +0078 % reversible and 0 for irreversible) +0079 % - UBequ : array with upper bounds on equations (default, 1000) +0080 % - changed : cell array with reactions to change bounds for +0081 % - LBrxn : array with lower bounds on changed reactions (NaN if empty, +0082 % meaning the bound is left unchanged) +0083 % - UBrxn : array with upper bounds on changed reactions (NaN if empty, +0084 % meaning the bound is left unchanged) +0085 % +0086 % Examples +0087 % -------- +0088 % taskStruct = parseTaskList(inputFile); +0089 % +0090 % Notes +0091 % ----- +0092 % The task list defines a set of tasks for a model to perform. A task is +0093 % defined by constraints on the model, and the task is successful if the +0094 % problem is feasible. Each row can contain one constraint on uptakes, one +0095 % constraint on outputs, one new equation, and one change of reaction bounds. +0096 % If more bounds are needed to define the task, then several rows can be used +0097 % for each task. To perform the task use checkTasks or fitTasks. +0098 % +0099 % The general metabolites "ALLMETS" and "ALLMETSIN[comps]" can be used as +0100 % inputs or outputs in the same manner as normal metabolites. They allow, +0101 % for example, excretion of all metabolites, to check whether the +0102 % synthesis of some metabolite or the degradation of some byproduct is +0103 % limiting. Only the upper bounds are used for these general metabolites, +0104 % so uptake or excretion can be allowed but not required. A lower bound +0105 % would overwrite the constraints of the individual metabolites. +0106 % +0107 % See also +0108 % -------- +0109 % checkTasks, fitTasks +0110 +0111 if ~isfile(inputFile) +0112 error('Task list %s cannot be found',string(inputFile)); +0113 end 0114 -0115 if ~isfile(inputFile) -0116 error('Task list %s cannot be found',string(inputFile)); -0117 end -0118 -0119 %Load the tasks file -0120 if strcmp(extractAfter(inputFile,strlength(inputFile) - 4), '.txt') -0121 %load from tab delimited text file -0122 fid = fopen(inputFile); -0123 %Need to read numeric columns as strings, this is converted further -0124 %down. If not, the titles would be lost. -0125 C = textscan(fid,'%q%q%q%q%q%q%q%q%q%q%q%q%q%q%q%q%q%q%q%q%*[^\n]', 'Delimiter', '\t'); -0126 fclose(fid); -0127 raw = [C{:}];%unnest the cell array of cell arrays into a 2-dim cell array -0128 else -0129 if endsWith(lower(char(inputFile)),'.xls') -0130 error('RAVEN:badInput', '%s', [inputFile ': the old binary .xls format is not supported. Save the task list as .xlsx (sheet ''TASKS'') or as a tab-delimited .txt file.']); -0131 end -0132 %Read the TASKS sheet from an Excel file -0133 try -0134 raw=readcell(inputFile,'Sheet','TASKS'); -0135 catch -0136 error('RAVEN:badInput', '%s', ['Could not load sheet "TASKS" from ' inputFile]); -0137 end -0138 %Normalise blank cells (which readcell returns as "missing") to [], as -0139 %expected by the downstream cleanSheet/parsing. -0140 raw(cellfun(@(x) all(ismissing(x)),raw))={[]}; -0141 end -0142 -0143 %Rows without any content carry no information (a header must not be -0144 %mistaken for one of them) -0145 if iscell(raw) -0146 emptyRow=all(cellfun(@(x) isempty(x) || (ischar(x) && isempty(strtrim(x))),raw),2); -0147 raw=raw(~emptyRow,:); -0148 end -0149 -0150 %Leading rows that start with a non-empty cell are comments; the first row -0151 %with an empty first cell is the header row -0152 if iscell(raw) -0153 if isempty(raw) -0154 error('RAVEN:badInput', '%s', 'The TASKS sheet must have a column named ID'); -0155 end -0156 firstBlank=cellfun(@(x) isempty(x) || (ischar(x) && isempty(strtrim(x))),raw(:,1)); -0157 first=find(firstBlank,1); -0158 if isempty(first) -0159 error('RAVEN:badInput', '%s', 'The TASKS sheet must have a column named ID'); -0160 end -0161 raw=raw(first:end,:); -0162 end +0115 %Load the tasks file +0116 if strcmp(extractAfter(inputFile,strlength(inputFile) - 4), '.txt') +0117 %load from tab delimited text file +0118 fid = fopen(inputFile); +0119 %Need to read numeric columns as strings, this is converted further +0120 %down. If not, the titles would be lost. +0121 C = textscan(fid,'%q%q%q%q%q%q%q%q%q%q%q%q%q%q%q%q%q%q%q%q%*[^\n]', 'Delimiter', '\t'); +0122 fclose(fid); +0123 raw = [C{:}];%unnest the cell array of cell arrays into a 2-dim cell array +0124 else +0125 if endsWith(lower(char(inputFile)),'.xls') +0126 error('RAVEN:badInput', '%s', [inputFile ': the old binary .xls format is not supported. Save the task list as .xlsx (sheet ''TASKS'') or as a tab-delimited .txt file.']); +0127 end +0128 %Read the TASKS sheet from an Excel file +0129 try +0130 raw=readcell(inputFile,'Sheet','TASKS'); +0131 catch +0132 error('RAVEN:badInput', '%s', ['Could not load sheet "TASKS" from ' inputFile]); +0133 end +0134 %Normalise blank cells (which readcell returns as "missing") to [], as +0135 %expected by the downstream cleanSheet/parsing. +0136 raw(cellfun(@(x) all(ismissing(x)),raw))={[]}; +0137 end +0138 +0139 %Rows without any content carry no information (a header must not be +0140 %mistaken for one of them) +0141 if iscell(raw) +0142 emptyRow=all(cellfun(@(x) isempty(x) || (ischar(x) && isempty(strtrim(x))),raw),2); +0143 raw=raw(~emptyRow,:); +0144 end +0145 +0146 %Leading rows that start with a non-empty cell are comments; the first row +0147 %with an empty first cell is the header row +0148 if iscell(raw) +0149 if isempty(raw) +0150 error('RAVEN:badInput', '%s', 'The TASKS sheet must have a column named ID'); +0151 end +0152 firstBlank=cellfun(@(x) isempty(x) || (ischar(x) && isempty(strtrim(x))),raw(:,1)); +0153 first=find(firstBlank,1); +0154 if isempty(first) +0155 error('RAVEN:badInput', '%s', 'The TASKS sheet must have a column named ID'); +0156 end +0157 raw=raw(first:end,:); +0158 end +0159 +0160 %Remove all lines starting with "#" (or actually any character) and all +0161 %empty columns +0162 raw=cleanSheet(raw); 0163 -0164 %Remove all lines starting with "#" (or actually any character) and all -0165 %empty columns -0166 raw=cleanSheet(raw); -0167 -0168 %Captions -0169 columns={'ID';'DESCRIPTION';'IN';'IN LB';'IN UB';'OUT';'OUT LB';'OUT UB';'EQU';'EQU LB';'EQU UB';'CHANGED RXN';'CHANGED LB';'CHANGED UB';'SHOULD FAIL';'PRINT FLUX';'COMMENTS'}; -0170 %Match the columns. Headers are matched ignoring case and surrounding -0171 %whitespace, so "id" and " IN LB " are accepted. -0172 header=raw(1,:); -0173 isText=cellfun(@ischar,header); -0174 header(isText)=upper(strtrim(header(isText))); -0175 [I, colI]=ismember(columns,header); -0176 -0177 %Only ID is required. Columns that are absent get index 0 from ismember, which -0178 %cannot be used to index raw, so point each of them at its own spare empty -0179 %column instead: every optional field then reads as empty and its default -0180 %applies. -0181 absent=find(colI==0); -0182 nOld=size(raw,2); -0183 raw(:,nOld+(1:numel(absent)))={[]}; -0184 colI(absent)=nOld+(1:numel(absent)); -0185 -0186 %Text cells (every cell of a text file, and numbers typed as text in Excel) -0187 %are converted to numbers; text that is not a number stays and is refused -0188 %below -0189 numericColumns = [0 0 0 1 1 0 1 1 0 1 1 0 1 1 0 0 0] == 1; -0190 cols = colI(numericColumns); -0191 numeric = cellfun(@isnumeric,raw(:,cols)); -0192 %trick to avoid messing up the title row: -0193 numeric(1,:) = 1; -0194 for colind = 1:numel(cols) -0195 col = cols(colind); -0196 for r = find(~numeric(:,colind))' -0197 x = raw{r,col}; -0198 if ischar(x) && isempty(strtrim(x)) -0199 raw{r,col} = []; -0200 elseif ischar(x) -0201 %Only plain numbers are accepted; str2double alone would also read -0202 %'1,5' as 15, '1,000' as 1000 and '2i' as a complex number -0203 if ~isempty(regexpi(strtrim(x),'^([+-]?(\d+\.?\d*|\.\d+)(e[+-]?\d+)?|[+-]?inf|nan)$','once')) -0204 raw{r,col} = str2double(x); -0205 end -0206 end -0207 end -0208 end -0209 -0210 %Every bound that is given must be a number -0211 numericNames = columns([4 5 7 8 10 11 13 14]); -0212 numericCols = colI([4 5 7 8 10 11 13 14]); -0213 for k = 1:numel(numericCols) -0214 for r = 2:size(raw,1) -0215 x = raw{r,numericCols(k)}; -0216 if ~isempty(x) && ~(isnumeric(x) && isscalar(x)) -0217 error('RAVEN:badInput', 'The value in column %s of task list data row %d is not a number (rows are counted after the header, comment rows and empty rows are removed)', numericNames{k}, r-1); +0164 %Captions +0165 columns={'ID';'DESCRIPTION';'IN';'IN LB';'IN UB';'OUT';'OUT LB';'OUT UB';'EQU';'EQU LB';'EQU UB';'CHANGED RXN';'CHANGED LB';'CHANGED UB';'SHOULD FAIL';'PRINT FLUX';'COMMENTS'}; +0166 %Match the columns. Headers are matched ignoring case and surrounding +0167 %whitespace, so "id" and " IN LB " are accepted. +0168 header=raw(1,:); +0169 isText=cellfun(@ischar,header); +0170 header(isText)=upper(strtrim(header(isText))); +0171 [I, colI]=ismember(columns,header); +0172 +0173 %Only ID is required. Columns that are absent get index 0 from ismember, which +0174 %cannot be used to index raw, so point each of them at its own spare empty +0175 %column instead: every optional field then reads as empty and its default +0176 %applies. +0177 absent=find(colI==0); +0178 nOld=size(raw,2); +0179 raw(:,nOld+(1:numel(absent)))={[]}; +0180 colI(absent)=nOld+(1:numel(absent)); +0181 +0182 %Text cells (every cell of a text file, and numbers typed as text in Excel) +0183 %are converted to numbers; text that is not a number stays and is refused +0184 %below +0185 numericColumns = [0 0 0 1 1 0 1 1 0 1 1 0 1 1 0 0 0] == 1; +0186 cols = colI(numericColumns); +0187 numeric = cellfun(@isnumeric,raw(:,cols)); +0188 %trick to avoid messing up the title row: +0189 numeric(1,:) = 1; +0190 for colind = 1:numel(cols) +0191 col = cols(colind); +0192 for r = find(~numeric(:,colind))' +0193 x = raw{r,col}; +0194 if ischar(x) && isempty(strtrim(x)) +0195 raw{r,col} = []; +0196 elseif ischar(x) +0197 %Only plain numbers are accepted; str2double alone would also read +0198 %'1,5' as 15, '1,000' as 1000 and '2i' as a complex number +0199 if ~isempty(regexpi(strtrim(x),'^([+-]?(\d+\.?\d*|\.\d+)(e[+-]?\d+)?|[+-]?inf|nan)$','once')) +0200 raw{r,col} = str2double(x); +0201 end +0202 end +0203 end +0204 end +0205 +0206 %Every bound that is given must be a number +0207 numericNames = columns([4 5 7 8 10 11 13 14]); +0208 numericCols = colI([4 5 7 8 10 11 13 14]); +0209 for k = 1:numel(numericCols) +0210 for r = 2:size(raw,1) +0211 x = raw{r,numericCols(k)}; +0212 if ~isempty(x) && ~(isnumeric(x) && isscalar(x)) +0213 error('RAVEN:badInput', 'The value in column %s of task list data row %d is not a number (rows are counted after the header, comment rows and empty rows are removed)', numericNames{k}, r-1); +0214 end +0215 %NaN means "leave unchanged" only in the bounds of changed reactions +0216 if k <= 6 && isnumeric(x) && isscalar(x) && isnan(x) +0217 error('RAVEN:badInput', 'The value in column %s of task list data row %d is NaN, which is only accepted in the bounds of changed reactions (rows are counted after the header, comment rows and empty rows are removed)', numericNames{k}, r-1); 0218 end -0219 %NaN means "leave unchanged" only in the bounds of changed reactions -0220 if k <= 6 && isnumeric(x) && isscalar(x) && isnan(x) -0221 error('RAVEN:badInput', 'The value in column %s of task list data row %d is NaN, which is only accepted in the bounds of changed reactions (rows are counted after the header, comment rows and empty rows are removed)', numericNames{k}, r-1); -0222 end -0223 end -0224 end -0225 -0226 %Check that the ID field is present -0227 if I(1)==0 -0228 EM='The TASKS sheet must have a column named ID'; -0229 error('RAVEN:badInput', '%s', EM); -0230 end -0231 -0232 %Rows before the first task id belong to no task -0233 firstId=find(~cellfun(@isempty,raw(2:end,colI(1))),1)+1; -0234 if isempty(firstId) -0235 firstId=size(raw,1)+1; +0219 end +0220 end +0221 +0222 %Check that the ID field is present +0223 if I(1)==0 +0224 EM='The TASKS sheet must have a column named ID'; +0225 error('RAVEN:badInput', '%s', EM); +0226 end +0227 +0228 %Rows before the first task id belong to no task +0229 firstId=find(~cellfun(@isempty,raw(2:end,colI(1))),1)+1; +0230 if isempty(firstId) +0231 firstId=size(raw,1)+1; +0232 end +0233 if firstId>2 +0234 warning('RAVEN:warning', 'Skipping %d row(s) before the first task id', firstId-2); +0235 raw(2:firstId-1,:)=[]; 0236 end -0237 if firstId>2 -0238 warning('RAVEN:warning', 'Skipping %d row(s) before the first task id', firstId-2); -0239 raw(2:firstId-1,:)=[]; +0237 if size(raw,1)<2 +0238 taskStruct=[]; +0239 return; 0240 end -0241 if size(raw,1)<2 -0242 taskStruct=[]; -0243 return; -0244 end -0245 -0246 %Add default bounds where needed -0247 for i=[4 5 7 8] -0248 I=cellfun(@isempty,raw(:,colI(i))); -0249 if i==5 || i==8 -0250 raw(I,colI(i))={1000}; -0251 else -0252 raw(I,colI(i))={0}; -0253 end -0254 end -0255 -0256 %Create an empty task structure -0257 eTask.id=''; -0258 eTask.description=''; -0259 eTask.shouldFail=false; -0260 eTask.printFluxes=false; -0261 eTask.comments=''; -0262 eTask.inputs={}; -0263 eTask.LBin=[]; -0264 eTask.UBin=[]; -0265 eTask.outputs={}; -0266 eTask.LBout=[]; -0267 eTask.UBout=[]; -0268 eTask.equations={}; -0269 eTask.LBequ=[]; -0270 eTask.UBequ=[]; -0271 eTask.changed={}; -0272 eTask.LBrxn=[]; -0273 eTask.UBrxn=[]; -0274 -0275 %Main loop -0276 taskStruct=[]; -0277 task=eTask; -0278 if isnumeric(raw{2,colI(1)}) -0279 task.id=num2str(raw{2,colI(1)}); -0280 else -0281 task.id=raw{2,colI(1)}; +0241 +0242 %Add default bounds where needed +0243 for i=[4 5 7 8] +0244 I=cellfun(@isempty,raw(:,colI(i))); +0245 if i==5 || i==8 +0246 raw(I,colI(i))={1000}; +0247 else +0248 raw(I,colI(i))={0}; +0249 end +0250 end +0251 +0252 %Create an empty task structure +0253 eTask.id=''; +0254 eTask.description=''; +0255 eTask.shouldFail=false; +0256 eTask.printFluxes=false; +0257 eTask.comments=''; +0258 eTask.inputs={}; +0259 eTask.LBin=[]; +0260 eTask.UBin=[]; +0261 eTask.outputs={}; +0262 eTask.LBout=[]; +0263 eTask.UBout=[]; +0264 eTask.equations={}; +0265 eTask.LBequ=[]; +0266 eTask.UBequ=[]; +0267 eTask.changed={}; +0268 eTask.LBrxn=[]; +0269 eTask.UBrxn=[]; +0270 +0271 %Main loop +0272 taskStruct=[]; +0273 task=eTask; +0274 if isnumeric(raw{2,colI(1)}) +0275 task.id=num2str(raw{2,colI(1)}); +0276 else +0277 task.id=raw{2,colI(1)}; +0278 end +0279 task.description=raw{2,colI(2)}; +0280 if isTruthy(raw{2,colI(15)}) +0281 task.shouldFail=true; 0282 end -0283 task.description=raw{2,colI(2)}; -0284 if isTruthy(raw{2,colI(15)}) -0285 task.shouldFail=true; -0286 end -0287 if isTruthy(raw{2,colI(16)}) -0288 task.printFluxes=true; -0289 end -0290 if ~isempty(raw{2,colI(17)}) -0291 task.comments=raw{2,colI(17)}; -0292 end -0293 -0294 for i=2:size(raw,1) -0295 %Set the inputs -0296 if ischar(raw{i,colI(3)}) -0297 inputs=splitList(raw{i,colI(3)}); -0298 task.inputs=[task.inputs;inputs(:)]; -0299 task.LBin=[task.LBin;ones(numel(inputs),1)*raw{i,colI(4)}]; -0300 task.UBin=[task.UBin;ones(numel(inputs),1)*raw{i,colI(5)}]; -0301 end -0302 %Set the outputs -0303 if ischar(raw{i,colI(6)}) -0304 outputs=splitList(raw{i,colI(6)}); -0305 task.outputs=[task.outputs;outputs(:)]; -0306 task.LBout=[task.LBout;ones(numel(outputs),1)*raw{i,colI(7)}]; -0307 task.UBout=[task.UBout;ones(numel(outputs),1)*raw{i,colI(8)}]; -0308 end -0309 %Add new rxns -0310 if ischar(raw{i,colI(9)}) -0311 task.equations=[task.equations;raw{i,colI(9)}]; -0312 if ~isempty(raw{i,colI(10)}) -0313 task.LBequ=[task.LBequ;raw{i,colI(10)}]; -0314 else -0315 if any(strfind(raw{i,colI(9)},'<=>')) -0316 task.LBequ=[task.LBequ;-1000]; -0317 else -0318 task.LBequ=[task.LBequ;0]; -0319 end -0320 end -0321 if ~isempty(raw{i,colI(11)}) -0322 task.UBequ=[task.UBequ;raw{i,colI(11)}]; -0323 else -0324 task.UBequ=[task.UBequ;1000]; -0325 end -0326 end -0327 %Add changed bounds -0328 if ischar(raw{i,colI(12)}) -0329 changed=splitList(raw{i,colI(12)}); -0330 task.changed=[task.changed;changed(:)]; -0331 %An empty bound means "leave this bound as it is", stored as NaN -0332 task.LBrxn=[task.LBrxn;ones(numel(changed),1)*boundOrNaN(raw{i,colI(13)})]; -0333 task.UBrxn=[task.UBrxn;ones(numel(changed),1)*boundOrNaN(raw{i,colI(14)})]; -0334 end -0335 -0336 %Check if it should add more constraints -0337 if i<size(raw,1) -0338 if isempty(raw{i+1,colI(1)}) -0339 continue; -0340 end -0341 end -0342 -0343 taskStruct=[taskStruct;task]; -0344 task=eTask; -0345 if i<size(raw,1) -0346 if isnumeric(raw{i+1,colI(1)}) -0347 task.id=num2str(raw{i+1,colI(1)}); -0348 else -0349 task.id=raw{i+1,colI(1)}; +0283 if isTruthy(raw{2,colI(16)}) +0284 task.printFluxes=true; +0285 end +0286 if ~isempty(raw{2,colI(17)}) +0287 task.comments=raw{2,colI(17)}; +0288 end +0289 +0290 for i=2:size(raw,1) +0291 %Set the inputs +0292 if ischar(raw{i,colI(3)}) +0293 inputs=splitList(raw{i,colI(3)}); +0294 task.inputs=[task.inputs;inputs(:)]; +0295 task.LBin=[task.LBin;ones(numel(inputs),1)*raw{i,colI(4)}]; +0296 task.UBin=[task.UBin;ones(numel(inputs),1)*raw{i,colI(5)}]; +0297 end +0298 %Set the outputs +0299 if ischar(raw{i,colI(6)}) +0300 outputs=splitList(raw{i,colI(6)}); +0301 task.outputs=[task.outputs;outputs(:)]; +0302 task.LBout=[task.LBout;ones(numel(outputs),1)*raw{i,colI(7)}]; +0303 task.UBout=[task.UBout;ones(numel(outputs),1)*raw{i,colI(8)}]; +0304 end +0305 %Add new rxns +0306 if ischar(raw{i,colI(9)}) +0307 task.equations=[task.equations;raw{i,colI(9)}]; +0308 if ~isempty(raw{i,colI(10)}) +0309 task.LBequ=[task.LBequ;raw{i,colI(10)}]; +0310 else +0311 if any(strfind(raw{i,colI(9)},'<=>')) +0312 task.LBequ=[task.LBequ;-1000]; +0313 else +0314 task.LBequ=[task.LBequ;0]; +0315 end +0316 end +0317 if ~isempty(raw{i,colI(11)}) +0318 task.UBequ=[task.UBequ;raw{i,colI(11)}]; +0319 else +0320 task.UBequ=[task.UBequ;1000]; +0321 end +0322 end +0323 %Add changed bounds +0324 if ischar(raw{i,colI(12)}) +0325 changed=splitList(raw{i,colI(12)}); +0326 task.changed=[task.changed;changed(:)]; +0327 %An empty bound means "leave this bound as it is", stored as NaN +0328 task.LBrxn=[task.LBrxn;ones(numel(changed),1)*boundOrNaN(raw{i,colI(13)})]; +0329 task.UBrxn=[task.UBrxn;ones(numel(changed),1)*boundOrNaN(raw{i,colI(14)})]; +0330 end +0331 +0332 %Check if it should add more constraints +0333 if i<size(raw,1) +0334 if isempty(raw{i+1,colI(1)}) +0335 continue; +0336 end +0337 end +0338 +0339 taskStruct=[taskStruct;task]; +0340 task=eTask; +0341 if i<size(raw,1) +0342 if isnumeric(raw{i+1,colI(1)}) +0343 task.id=num2str(raw{i+1,colI(1)}); +0344 else +0345 task.id=raw{i+1,colI(1)}; +0346 end +0347 task.description=raw{i+1,colI(2)}; +0348 if isTruthy(raw{i+1,colI(15)}) +0349 task.shouldFail=true; 0350 end -0351 task.description=raw{i+1,colI(2)}; -0352 if isTruthy(raw{i+1,colI(15)}) -0353 task.shouldFail=true; -0354 end -0355 if isTruthy(raw{i+1,colI(16)}) -0356 task.printFluxes=true; -0357 end -0358 if ~isempty(raw{i+1,colI(17)}) -0359 task.comments=raw{i+1,colI(17)}; -0360 end -0361 end +0351 if isTruthy(raw{i+1,colI(16)}) +0352 task.printFluxes=true; +0353 end +0354 if ~isempty(raw{i+1,colI(17)}) +0355 task.comments=raw{i+1,colI(17)}; +0356 end +0357 end +0358 end +0359 +0360 %Should add more checks, such as unique IDs and missing headers +0361 0362 end 0363 -0364 %Should add more checks, such as unique IDs and missing headers -0365 -0366 end -0367 -0368 function tf = isTruthy(x) -0369 % A flag cell is true unless it is empty, 0, or one of the texts "0", "false" -0370 % and "no" (any case). -0371 if isempty(x) -0372 tf = false; -0373 elseif ischar(x) || isstring(x) -0374 tf = ~ismember(lower(strtrim(char(x))),{'','0','false','no'}); -0375 elseif isnumeric(x) || islogical(x) -0376 tf = any(x(:) ~= 0); -0377 else -0378 tf = true; -0379 end -0380 end -0381 -0382 function items = splitList(str) -0383 % Split a ";"-delimited cell on ";", trim each item and drop empty ones, so -0384 % that "A; B;" gives {'A';'B'}. -0385 items = strtrim(regexp(str,';','split')); -0386 items = items(~cellfun(@isempty,items)); -0387 end -0388 -0389 function b = boundOrNaN(x) -0390 % Bound for a changed reaction: NaN (leave unchanged) when the cell is empty. -0391 if isempty(x) -0392 b = NaN; -0393 else -0394 b = x; -0395 end -0396 end +0364 function tf = isTruthy(x) +0365 % A flag cell is true unless it is empty, 0, or one of the texts "0", "false" +0366 % and "no" (any case). +0367 if isempty(x) +0368 tf = false; +0369 elseif ischar(x) || isstring(x) +0370 tf = ~ismember(lower(strtrim(char(x))),{'','0','false','no'}); +0371 elseif isnumeric(x) || islogical(x) +0372 tf = any(x(:) ~= 0); +0373 else +0374 tf = true; +0375 end +0376 end +0377 +0378 function items = splitList(str) +0379 % Split a ";"-delimited cell on ";", trim each item and drop empty ones, so +0380 % that "A; B;" gives {'A';'B'}. +0381 items = strtrim(regexp(str,';','split')); +0382 items = items(~cellfun(@isempty,items)); +0383 end +0384 +0385 function b = boundOrNaN(x) +0386 % Bound for a changed reaction: NaN (leave unchanged) when the cell is empty. +0387 if isempty(x) +0388 b = NaN; +0389 else +0390 b = x; +0391 end +0392 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/utils/convertCharArray.html b/docs/html/utils/convertCharArray.html index 06ebe848..a60a2f5e 100644 --- a/docs/html/utils/convertCharArray.html +++ b/docs/html/utils/convertCharArray.html @@ -29,9 +29,9 @@

    SYNOPSIS ^DESCRIPTION ^

     convertCharArray  Ensure input is a cell array of character vectors.
     
    - Converts input to make sure it is a cell array of character vectors.
    - String arrays are transformed into character vectors. Output is always a
    - cell array, also if only one character vector is given as input.
    + Converts the input to a cell array of character vectors. String arrays
    + are transformed into character vectors. A single character vector gives
    + a cell array with one element. An empty input gives [].
     
      Parameters
      ----------
    @@ -41,7 +41,7 @@ 

    DESCRIPTION ^CROSS-REFERENCE INFORMATION ^
 </ul>
 This function is called by:
 <ul style= -
  • ravenList ravenList Format a header and item list for warning() or error().
  • +
  • ravenList ravenList Format a header and a list of items for warning() or error().
  • @@ -63,9 +63,9 @@

    SOURCE CODE ^
    0001 function inputConverted = convertCharArray(funcInput)
     0002 % convertCharArray  Ensure input is a cell array of character vectors.
     0003 %
    -0004 % Converts input to make sure it is a cell array of character vectors.
    -0005 % String arrays are transformed into character vectors. Output is always a
    -0006 % cell array, also if only one character vector is given as input.
    +0004 % Converts the input to a cell array of character vectors. String arrays
    +0005 % are transformed into character vectors. A single character vector gives
    +0006 % a cell array with one element. An empty input gives [].
     0007 %
     0008 % Parameters
     0009 % ----------
    @@ -75,7 +75,7 @@ 

    SOURCE CODE ^% Returns 0014 % ------- 0015 % inputConverted : cell -0016 % cell array of character vectors. +0016 % cell array of character vectors, or [] if funcInput is empty. 0017 % 0018 % Examples 0019 % -------- diff --git a/docs/html/utils/deprecationWarning.html b/docs/html/utils/deprecationWarning.html index 16276a7a..beb0d492 100644 --- a/docs/html/utils/deprecationWarning.html +++ b/docs/html/utils/deprecationWarning.html @@ -30,16 +30,16 @@

    DESCRIPTION ^
     deprecationWarning  Warn once per session that a function is deprecated.
     
      Used by the wrappers in the deprecated/ folder. Each function warns the
    - first time it is called in a MATLAB session and stays quiet afterwards,
    - so a loop over thousands of reactions does not bury the console. Call
    - "clear functions" to reset.
    + first time it is called in a MATLAB session and does not warn again, also
    + when it is called repeatedly in a loop. "clear functions" resets the
    + record of functions that have warned.
     
      Parameters
      ----------
      oldFunction : char
          name of the deprecated function.
      replacement : char
    -     what to call instead, written as it would be typed, e.g.
    +     what to call instead, written as it would be typed, for example
          'findLeakMetabolite(model,''produce'',...)'.
     
      Examples
    @@ -63,16 +63,16 @@ 

    SOURCE CODE ^% deprecationWarning Warn once per session that a function is deprecated. 0003 % 0004 % Used by the wrappers in the deprecated/ folder. Each function warns the -0005 % first time it is called in a MATLAB session and stays quiet afterwards, -0006 % so a loop over thousands of reactions does not bury the console. Call -0007 % "clear functions" to reset. +0005 % first time it is called in a MATLAB session and does not warn again, also +0006 % when it is called repeatedly in a loop. "clear functions" resets the +0007 % record of functions that have warned. 0008 % 0009 % Parameters 0010 % ---------- 0011 % oldFunction : char 0012 % name of the deprecated function. 0013 % replacement : char -0014 % what to call instead, written as it would be typed, e.g. +0014 % what to call instead, written as it would be typed, for example 0015 % 'findLeakMetabolite(model,''produce'',...)'. 0016 % 0017 % Examples diff --git a/docs/html/utils/grRuleToDNF.html b/docs/html/utils/grRuleToDNF.html index 996d9722..ee1fd64e 100644 --- a/docs/html/utils/grRuleToDNF.html +++ b/docs/html/utils/grRuleToDNF.html @@ -37,7 +37,7 @@

    DESCRIPTION ^DESCRIPTION ^SOURCE CODE ^% ---------- 0010 % rule : char or string or struct 0011 % a grRule, or a tree from parseGrRule. A grRule is read by parseGrRule: -0012 % genes joined by "and"/"or" in any case or by "&", "&&", "|", "||", +0012 % genes joined by "and" or "or" in any case or by "&", "&&", "|", "||", 0013 % each operator separated from the gene ids by whitespace or a bracket, 0014 % with AND binding tighter than OR. 0015 % @@ -112,11 +112,11 @@

    SOURCE CODE ^% 0028 % Gene order within a clause follows the order the genes appear in the rule. 0029 % Duplicates are not removed: "G1 and G1" stays a two-element clause, since -0030 % collapsing it would silently rewrite the user's rule. +0030 % collapsing it would change the rule. 0031 % 0032 % The number of clauses is the product of the OR-arities across a complex, -0033 % so a pathological rule can expand combinatorially. Callers that build one -0034 % object per clause should check numel(clauses) first. +0033 % so the number of clauses can grow combinatorially with the rule. A caller +0034 % that builds one object per clause can check numel(clauses) first. 0035 % 0036 % Examples 0037 % -------- diff --git a/docs/html/utils/grRuleToString.html b/docs/html/utils/grRuleToString.html index 86e21a27..d0f1fa05 100644 --- a/docs/html/utils/grRuleToString.html +++ b/docs/html/utils/grRuleToString.html @@ -29,9 +29,10 @@

    SYNOPSIS ^DESCRIPTION ^

     grRuleToString  Render a grRule syntax tree back to a string.
     
    - Writes the standard RAVEN format: " and " / " or " as operators, with
    - parentheses added only where precedence requires them, so that
    - parseGrRule(grRuleToString(tree)) round-trips to the same structure.
    + Writes the standard RAVEN format, with " and " and " or " as operators. An
    + AND node inside an OR node and an OR node inside an AND node are placed in
    + parentheses. The result parses with parseGrRule to a rule with the same
    + logic.
     
      Parameters
      ----------
    @@ -67,60 +68,61 @@ 

    SOURCE CODE ^
    0001 function rule=grRuleToString(tree)
     0002 % grRuleToString  Render a grRule syntax tree back to a string.
     0003 %
    -0004 % Writes the standard RAVEN format: " and " / " or " as operators, with
    -0005 % parentheses added only where precedence requires them, so that
    -0006 % parseGrRule(grRuleToString(tree)) round-trips to the same structure.
    -0007 %
    -0008 % Parameters
    -0009 % ----------
    -0010 % tree : struct
    -0011 %     a tree from parseGrRule. An empty tree yields ''.
    -0012 %
    -0013 % Returns
    -0014 % -------
    -0015 % rule : char
    -0016 %     the rendered grRule.
    -0017 %
    -0018 % Examples
    -0019 % --------
    -0020 %     grRuleToString(parseGrRule('(G1 and G2) or G3'))   % '(G1 and G2) or G3'
    -0021 %
    -0022 % See also: parseGrRule, standardizeGrRules
    -0023 
    -0024 if isempty(tree)
    -0025     rule='';
    -0026     return
    -0027 end
    -0028 rule=nodeToString(tree);
    -0029 end
    -0030 
    +0004 % Writes the standard RAVEN format, with " and " and " or " as operators. An
    +0005 % AND node inside an OR node and an OR node inside an AND node are placed in
    +0006 % parentheses. The result parses with parseGrRule to a rule with the same
    +0007 % logic.
    +0008 %
    +0009 % Parameters
    +0010 % ----------
    +0011 % tree : struct
    +0012 %     a tree from parseGrRule. An empty tree yields ''.
    +0013 %
    +0014 % Returns
    +0015 % -------
    +0016 % rule : char
    +0017 %     the rendered grRule.
    +0018 %
    +0019 % Examples
    +0020 % --------
    +0021 %     grRuleToString(parseGrRule('(G1 and G2) or G3'))   % '(G1 and G2) or G3'
    +0022 %
    +0023 % See also: parseGrRule, standardizeGrRules
    +0024 
    +0025 if isempty(tree)
    +0026     rule='';
    +0027     return
    +0028 end
    +0029 rule=nodeToString(tree);
    +0030 end
     0031 
    -0032 function str=nodeToString(node)
    -0033 switch node.type
    -0034     case 'gene'
    -0035         str=node.id;
    -0036     case 'and'
    -0037         % An OR child inside an AND must be bracketed to keep precedence.
    -0038         parts=cellfun(@(c) bracketIf(c,'or'),node.children,'UniformOutput',false);
    -0039         str=strjoin(parts,' and ');
    -0040     case 'or'
    -0041         % An AND child inside an OR needs no brackets for correctness, but
    -0042         % RAVEN writes complexes bracketed, matching standardizeGrRules and
    -0043         % the joinOR helper in removeLowScoreGenes.
    -0044         parts=cellfun(@(c) bracketIf(c,'and'),node.children,'UniformOutput',false);
    -0045         str=strjoin(parts,' or ');
    -0046     otherwise
    -0047         error('RAVEN:badGrRule',['Unexpected grRule node type: ' node.type]);
    -0048 end
    +0032 
    +0033 function str=nodeToString(node)
    +0034 switch node.type
    +0035     case 'gene'
    +0036         str=node.id;
    +0037     case 'and'
    +0038         % An OR child inside an AND must be bracketed to keep precedence.
    +0039         parts=cellfun(@(c) bracketIf(c,'or'),node.children,'UniformOutput',false);
    +0040         str=strjoin(parts,' and ');
    +0041     case 'or'
    +0042         % An AND child inside an OR needs no brackets for correctness, but
    +0043         % RAVEN writes complexes bracketed, matching standardizeGrRules and
    +0044         % the joinOR helper in removeLowScoreGenes.
    +0045         parts=cellfun(@(c) bracketIf(c,'and'),node.children,'UniformOutput',false);
    +0046         str=strjoin(parts,' or ');
    +0047     otherwise
    +0048         error('RAVEN:badGrRule',['Unexpected grRule node type: ' node.type]);
     0049 end
    -0050 
    +0050 end
     0051 
    -0052 function str=bracketIf(node,type)
    -0053 str=nodeToString(node);
    -0054 if strcmp(node.type,type)
    -0055     str=['(' str ')'];
    -0056 end
    -0057 end

    +0052 +0053 function str=bracketIf(node,type) +0054 str=nodeToString(node); +0055 if strcmp(node.type,type) +0056 str=['(' str ')']; +0057 end +0058 end


    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/utils/index.html b/docs/html/utils/index.html index f4b0b191..2cb8129a 100644 --- a/docs/html/utils/index.html +++ b/docs/html/utils/index.html @@ -19,7 +19,7 @@

    Index for utils

    Matlab files in this directory:

    -
     convertCharArrayconvertCharArray Ensure input is a cell array of character vectors.
     deprecationWarningdeprecationWarning Warn once per session that a function is deprecated.
     grRuleToDNFgrRuleToDNF Convert a grRule to disjunctive normal form.
     grRuleToStringgrRuleToString Render a grRule syntax tree back to a string.
     isDnfGrRuleisDnfGrRule Test whether a grRule is in disjunctive normal form.
     legacyMethodNoticelegacyMethodNotice Point a caller at the recommended method, once.
     parallelWorkersRAVENparallelWorkersRAVEN Return the parfor worker count for a given setting.
     parseGrRuleparseGrRule Parse a grRule into a syntax tree.
     parseRAVENargsparseRAVENargs Resolve optional arguments passed positionally or by name.
     printOrangeprintOrange Print an orange-coloured string to the MATLAB Command Window.
     progressReport
     ravenListravenList Format a header and item list for warning() or error().
     ravenModelFieldsravenModelFields Declarative registry of all RAVEN model struct fields.
     runRAVENtestsrunRAVENtests Run all RAVEN function tests.
     setRavenProgresssetRavenProgress Set the RAVEN progress-reporting backend.
    + convertCharArrayconvertCharArray Ensure input is a cell array of character vectors.  deprecationWarningdeprecationWarning Warn once per session that a function is deprecated.  grRuleToDNFgrRuleToDNF Convert a grRule to disjunctive normal form.  grRuleToStringgrRuleToString Render a grRule syntax tree back to a string.  isDnfGrRuleisDnfGrRule Test whether a grRule is in disjunctive normal form.  legacyMethodNoticelegacyMethodNotice Warn once that a function has a recommended replacement.  parallelWorkersRAVENparallelWorkersRAVEN Return the parfor worker count for a given setting.  parseGrRuleparseGrRule Parse a grRule into a syntax tree.  parseRAVENargsparseRAVENargs Resolve optional arguments passed positionally or by name.  printOrangeprintOrange Print an orange-coloured string to the MATLAB Command Window.  progressReport  ravenListravenList Format a header and a list of items for warning() or error().  ravenModelFieldsravenModelFields Declarative registry of all RAVEN model struct fields.  runRAVENtestsrunRAVENtests Run all RAVEN function tests.  setRavenProgresssetRavenProgress Set the RAVEN progress-reporting backend. diff --git a/docs/html/utils/isDnfGrRule.html b/docs/html/utils/isDnfGrRule.html index 823f918a..b7bee886 100644 --- a/docs/html/utils/isDnfGrRule.html +++ b/docs/html/utils/isDnfGrRule.html @@ -29,11 +29,11 @@

    SYNOPSIS ^DESCRIPTION ^

     isDnfGrRule  Test whether a grRule is in disjunctive normal form.
     
    - DNF ("OR of AND-complexes") means no AND operator has an OR anywhere
    - beneath it, i.e. the rule is a single gene, a pure AND-complex, or an OR
    - of those. Standard RAVEN format requires DNF: subunits that must all be
    - present are ANDed into a complex, and the reaction is catalysed by at
    - least one such complex.
    + A rule in disjunctive normal form (DNF, an OR of AND-complexes) has no OR
    + operator beneath an AND operator. It is a single gene, an AND-complex, or an
    + OR of genes and AND-complexes. The standard RAVEN format requires DNF: the
    + subunits that must all be present are joined by AND into a complex, and the
    + reaction is catalysed by at least one such complex.
     
      Parameters
      ----------
    @@ -70,11 +70,11 @@ 

    SOURCE CODE ^
    0001 function tf=isDnfGrRule(rule)
     0002 % isDnfGrRule  Test whether a grRule is in disjunctive normal form.
     0003 %
    -0004 % DNF ("OR of AND-complexes") means no AND operator has an OR anywhere
    -0005 % beneath it, i.e. the rule is a single gene, a pure AND-complex, or an OR
    -0006 % of those. Standard RAVEN format requires DNF: subunits that must all be
    -0007 % present are ANDed into a complex, and the reaction is catalysed by at
    -0008 % least one such complex.
    +0004 % A rule in disjunctive normal form (DNF, an OR of AND-complexes) has no OR
    +0005 % operator beneath an AND operator. It is a single gene, an AND-complex, or an
    +0006 % OR of genes and AND-complexes. The standard RAVEN format requires DNF: the
    +0007 % subunits that must all be present are joined by AND into a complex, and the
    +0008 % reaction is catalysed by at least one such complex.
     0009 %
     0010 % Parameters
     0011 % ----------
    diff --git a/docs/html/utils/legacyMethodNotice.html b/docs/html/utils/legacyMethodNotice.html
    index 2c9eb287..5aabd841 100644
    --- a/docs/html/utils/legacyMethodNotice.html
    +++ b/docs/html/utils/legacyMethodNotice.html
    @@ -4,7 +4,7 @@
     
       Description of legacyMethodNotice
       
    -  
    +  
       
       
       
    @@ -21,37 +21,36 @@ 

    legacyMethodNotice

    PURPOSE ^

    -
    legacyMethodNotice Point a caller at the recommended method, once.
    +
    legacyMethodNotice Warn once that a function has a recommended replacement.

    SYNOPSIS ^

    function legacyMethodNotice(oldFunction, preferred)

    DESCRIPTION ^

    -
     legacyMethodNotice  Point a caller at the recommended method, once.
    +
     legacyMethodNotice  Warn once that a function has a recommended replacement.
     
    - For functions that remain supported but are no longer the recommended way
    - to do the job. Unlike deprecationWarning, this promises no removal: the
    - function keeps working and keeps being maintained. It is a signpost, not a
    - countdown.
    + For functions that remain supported and maintained but have a recommended
    + replacement for new work. Unlike deprecationWarning, the notice announces no
    + removal.
     
    - The notice is issued the first time the function is called in a MATLAB
    - session and stays quiet afterwards, so a script that calls it in a loop
    - does not bury the console. Call "clear functions" to reset. A caller that
    - wants the legacy method deliberately can silence it with
    - warning('off','RAVEN:legacyMethod').
    + The warning RAVEN:legacyMethod is issued at the first call of each
    + oldFunction in a MATLAB session; later calls with the same oldFunction issue
    + nothing. "clear functions" resets this. warning('off','RAVEN:legacyMethod')
    + silences the notice.
     
      Parameters
      ----------
      oldFunction : char
    -     name of the function being called.
    +     name of the function being called. The notice is issued once per name.
      preferred : char
    -     what to use instead for new work, written as it would be typed.
    +     name of the recommended replacement for new work, written as in the
    +     warning text.
     
      Examples
      --------
          legacyMethodNotice('getINITModel','ftINIT');
     
    - See Also
    + See also
      --------
      deprecationWarning
    @@ -69,51 +68,50 @@

    CROSS-REFERENCE INFORMATION ^
 
 <h2><a name=SOURCE CODE ^

    0001 function legacyMethodNotice(oldFunction, preferred)
    -0002 % legacyMethodNotice  Point a caller at the recommended method, once.
    +0002 % legacyMethodNotice  Warn once that a function has a recommended replacement.
     0003 %
    -0004 % For functions that remain supported but are no longer the recommended way
    -0005 % to do the job. Unlike deprecationWarning, this promises no removal: the
    -0006 % function keeps working and keeps being maintained. It is a signpost, not a
    -0007 % countdown.
    -0008 %
    -0009 % The notice is issued the first time the function is called in a MATLAB
    -0010 % session and stays quiet afterwards, so a script that calls it in a loop
    -0011 % does not bury the console. Call "clear functions" to reset. A caller that
    -0012 % wants the legacy method deliberately can silence it with
    -0013 % warning('off','RAVEN:legacyMethod').
    -0014 %
    -0015 % Parameters
    -0016 % ----------
    -0017 % oldFunction : char
    -0018 %     name of the function being called.
    -0019 % preferred : char
    -0020 %     what to use instead for new work, written as it would be typed.
    -0021 %
    -0022 % Examples
    -0023 % --------
    -0024 %     legacyMethodNotice('getINITModel','ftINIT');
    -0025 %
    -0026 % See Also
    -0027 % --------
    -0028 % deprecationWarning
    -0029 
    -0030 persistent alreadyNoticed
    -0031 
    -0032 if isempty(alreadyNoticed)
    -0033     alreadyNoticed=containers.Map('KeyType','char','ValueType','logical');
    -0034 end
    -0035 
    -0036 oldFunction=char(oldFunction);
    -0037 if isKey(alreadyNoticed,oldFunction)
    -0038     return
    -0039 end
    -0040 alreadyNoticed(oldFunction)=true;
    -0041 
    -0042 warning('RAVEN:legacyMethod', ...
    -0043     ['%s is the legacy implementation and remains supported, but %s is the ' ...
    -0044      'recommended method for new work. Silence this with ' ...
    -0045      'warning(''off'',''RAVEN:legacyMethod'').'], oldFunction, char(preferred));
    -0046 end
    +0004 % For functions that remain supported and maintained but have a recommended +0005 % replacement for new work. Unlike deprecationWarning, the notice announces no +0006 % removal. +0007 % +0008 % The warning RAVEN:legacyMethod is issued at the first call of each +0009 % oldFunction in a MATLAB session; later calls with the same oldFunction issue +0010 % nothing. "clear functions" resets this. warning('off','RAVEN:legacyMethod') +0011 % silences the notice. +0012 % +0013 % Parameters +0014 % ---------- +0015 % oldFunction : char +0016 % name of the function being called. The notice is issued once per name. +0017 % preferred : char +0018 % name of the recommended replacement for new work, written as in the +0019 % warning text. +0020 % +0021 % Examples +0022 % -------- +0023 % legacyMethodNotice('getINITModel','ftINIT'); +0024 % +0025 % See also +0026 % -------- +0027 % deprecationWarning +0028 +0029 persistent alreadyNoticed +0030 +0031 if isempty(alreadyNoticed) +0032 alreadyNoticed=containers.Map('KeyType','char','ValueType','logical'); +0033 end +0034 +0035 oldFunction=char(oldFunction); +0036 if isKey(alreadyNoticed,oldFunction) +0037 return +0038 end +0039 alreadyNoticed(oldFunction)=true; +0040 +0041 warning('RAVEN:legacyMethod', ... +0042 ['%s is the legacy implementation and remains supported, but %s is the ' ... +0043 'recommended method for new work. Silence this with ' ... +0044 'warning(''off'',''RAVEN:legacyMethod'').'], oldFunction, char(preferred)); +0045 end

    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/utils/parallelWorkersRAVEN.html b/docs/html/utils/parallelWorkersRAVEN.html index 1eb425ac..f5b2cb78 100644 --- a/docs/html/utils/parallelWorkersRAVEN.html +++ b/docs/html/utils/parallelWorkersRAVEN.html @@ -29,29 +29,31 @@

    SYNOPSIS ^DESCRIPTION ^

     parallelWorkersRAVEN  Return the parfor worker count for a given setting.
     
    - Returns a value for use with the parfor worker-count syntax:
    + Returns the value for the maximum-workers argument of parfor:
     
          nW = parallelWorkersRAVEN(runParallel);
          parfor (i = 1:N, nW)
              ...
          end
     
    - This avoids mutating global parallel-pool state: nW == 0 forces parfor
    - to run as a plain for loop; nW == Inf lets the Parallel Computing Toolbox
    - manage the pool size and auto-creation.
    + The function leaves the global parallel pool unchanged. With nW == 0, parfor
    + runs as a plain for loop. With nW == Inf, the Parallel Computing Toolbox
    + manages the pool size and creates a pool when none exists. When
    + runParallel is true but the Parallel Computing Toolbox is not installed, the
    + function prints a message and returns 0.
     
      Parameters
      ----------
      runParallel : logical
    -     whether the caller should use parallel workers.
    +     true to use parallel workers, false for sequential execution.
     
      Returns
      -------
      nW : double
    -     0   to force sequential execution (parfor behaves as a for loop,
    -         no pool required or created).
    -     Inf to use available pool workers (PCT manages pool size and
    -         auto-creation).
    + 0 sequential execution (parfor behaves as a for loop, no pool is + required or created). + Inf available pool workers (the Parallel Computing Toolbox manages + the pool size and creates a pool when none exists).

    CROSS-REFERENCE INFORMATION ^

    @@ -69,41 +71,43 @@

    SOURCE CODE ^
    0001 function nW = parallelWorkersRAVEN(runParallel)
     0002 % parallelWorkersRAVEN  Return the parfor worker count for a given setting.
     0003 %
    -0004 % Returns a value for use with the parfor worker-count syntax:
    +0004 % Returns the value for the maximum-workers argument of parfor:
     0005 %
     0006 %     nW = parallelWorkersRAVEN(runParallel);
     0007 %     parfor (i = 1:N, nW)
     0008 %         ...
     0009 %     end
     0010 %
    -0011 % This avoids mutating global parallel-pool state: nW == 0 forces parfor
    -0012 % to run as a plain for loop; nW == Inf lets the Parallel Computing Toolbox
    -0013 % manage the pool size and auto-creation.
    -0014 %
    -0015 % Parameters
    -0016 % ----------
    -0017 % runParallel : logical
    -0018 %     whether the caller should use parallel workers.
    -0019 %
    -0020 % Returns
    -0021 % -------
    -0022 % nW : double
    -0023 %     0   to force sequential execution (parfor behaves as a for loop,
    -0024 %         no pool required or created).
    -0025 %     Inf to use available pool workers (PCT manages pool size and
    -0026 %         auto-creation).
    -0027 
    -0028 if ~runParallel
    -0029     nW = 0;
    -0030     return
    -0031 end
    -0032 if isempty(ver('parallel'))
    -0033     disp('Cannot find MATLAB Parallel Computing Toolbox, process is not parallelized.')
    -0034     nW = 0;
    -0035 else
    -0036     nW = Inf;
    -0037 end
    -0038 end
    +0011 % The function leaves the global parallel pool unchanged. With nW == 0, parfor +0012 % runs as a plain for loop. With nW == Inf, the Parallel Computing Toolbox +0013 % manages the pool size and creates a pool when none exists. When +0014 % runParallel is true but the Parallel Computing Toolbox is not installed, the +0015 % function prints a message and returns 0. +0016 % +0017 % Parameters +0018 % ---------- +0019 % runParallel : logical +0020 % true to use parallel workers, false for sequential execution. +0021 % +0022 % Returns +0023 % ------- +0024 % nW : double +0025 % 0 sequential execution (parfor behaves as a for loop, no pool is +0026 % required or created). +0027 % Inf available pool workers (the Parallel Computing Toolbox manages +0028 % the pool size and creates a pool when none exists). +0029 +0030 if ~runParallel +0031 nW = 0; +0032 return +0033 end +0034 if isempty(ver('parallel')) +0035 disp('Cannot find MATLAB Parallel Computing Toolbox, process is not parallelized.') +0036 nW = 0; +0037 else +0038 nW = Inf; +0039 end +0040 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/utils/parseGrRule.html b/docs/html/utils/parseGrRule.html index e16cbdf1..49402413 100644 --- a/docs/html/utils/parseGrRule.html +++ b/docs/html/utils/parseGrRule.html @@ -29,16 +29,15 @@

    SYNOPSIS ^DESCRIPTION ^

     parseGrRule  Parse a grRule into a syntax tree.
     
    - Parses a gene-protein-reaction rule into a tree of AND/OR/gene nodes, so
    - that callers can reason about its structure instead of searching the raw
    - string. Gene IDs are matched as whole tokens, so IDs that contain "and" or
    - "or" as a substring (RAND1), or that are prefixes of one another (10 and
    - 100), are never confused with operators or with each other.
    + Parses a gene-protein-reaction rule into a tree of AND, OR and gene nodes.
    + Gene IDs are matched as whole tokens, so IDs that contain "and" or "or" as a
    + substring (RAND1), or that are prefixes of one another (10 and 100), are
    + distinct from operators and from each other.
     
      Parameters
      ----------
      rule : char or string
    -     a grRule, e.g. "(G1 and G2) or G3". Both the word forms ("and", "or",
    +     a grRule, for example "(G1 and G2) or G3". Both the word forms ("and", "or",
          any case) and the symbol forms ("&", "&&", "|", "||") are accepted.
          An empty rule yields an empty tree.
     
    @@ -47,23 +46,22 @@ 

    DESCRIPTION ^SOURCE CODE ^
    0001 function tree=parseGrRule(rule)
     0002 % parseGrRule  Parse a grRule into a syntax tree.
     0003 %
    -0004 % Parses a gene-protein-reaction rule into a tree of AND/OR/gene nodes, so
    -0005 % that callers can reason about its structure instead of searching the raw
    -0006 % string. Gene IDs are matched as whole tokens, so IDs that contain "and" or
    -0007 % "or" as a substring (RAND1), or that are prefixes of one another (10 and
    -0008 % 100), are never confused with operators or with each other.
    -0009 %
    -0010 % Parameters
    -0011 % ----------
    -0012 % rule : char or string
    -0013 %     a grRule, e.g. "(G1 and G2) or G3". Both the word forms ("and", "or",
    -0014 %     any case) and the symbol forms ("&", "&&", "|", "||") are accepted.
    -0015 %     An empty rule yields an empty tree.
    -0016 %
    -0017 % Returns
    -0018 % -------
    -0019 % tree : struct
    -0020 %     a scalar struct with fields:
    -0021 %
    -0022 %     - type     : char — 'gene', 'and' or 'or'.
    -0023 %     - id       : char — the gene ID; '' for 'and'/'or' nodes.
    -0024 %     - children : cell — child nodes; {} for 'gene' nodes.
    -0025 %
    -0026 %     An empty rule returns a 0x0 struct array, for which isempty is true.
    -0027 %
    -0028 % Notes
    -0029 % -----
    -0030 % The grammar is the usual precedence, with AND binding tighter than OR:
    -0031 %
    -0032 %     expr   := term ( OR term )*
    -0033 %     term   := factor ( AND factor )*
    -0034 %     factor := gene | '(' expr ')'
    -0035 %
    -0036 % Malformed rules raise RAVEN:badGrRule rather than being silently
    -0037 % misparsed. Callers that lint user input (findPotentialErrors) catch this
    -0038 % and report it instead of failing.
    -0039 %
    -0040 % Examples
    -0041 % --------
    -0042 %     tree = parseGrRule('(G1 and G2) or G3');
    -0043 %     tree.type            % 'or'
    -0044 %     numel(tree.children) % 2
    -0045 %
    -0046 % See also: isDnfGrRule, grRuleToDNF, grRuleToString
    -0047 
    -0048 if nargin<1 || isempty(rule)
    -0049     tree=emptyTree();
    -0050     return
    -0051 end
    -0052 rule=char(rule);
    -0053 if all(isspace(rule))
    -0054     tree=emptyTree();
    -0055     return
    -0056 end
    -0057 
    -0058 toks=tokenizeGrRule(rule);
    -0059 if isempty(toks)
    -0060     tree=emptyTree();
    -0061     return
    -0062 end
    -0063 
    -0064 [tree,pos]=parseExpr(toks,1,rule);
    -0065 if pos<=numel(toks)
    -0066     error('RAVEN:badGrRule',['Unexpected "' tokenText(toks{pos}) '" in grRule: ' rule]);
    -0067 end
    -0068 end
    -0069 
    -0070 
    -0071 function tree=emptyTree()
    -0072 tree=struct('type',{},'id',{},'children',{});
    -0073 end
    -0074 
    -0075 
    -0076 function toks=tokenizeGrRule(rule)
    -0077 % Split into '(' , ')' , 'AND' , 'OR' and gene tokens. Operators are matched
    -0078 % as whole words, which is what keeps gene IDs containing "and"/"or" intact.
    -0079 toks={};
    -0080 i=1;
    -0081 n=numel(rule);
    -0082 while i<=n
    -0083     c=rule(i);
    -0084     if isspace(c)
    -0085         i=i+1;
    -0086         continue
    -0087     end
    -0088     if c=='(' || c==')'
    -0089         toks{end+1}=c; %#ok<AGROW>
    -0090         i=i+1;
    -0091         continue
    -0092     end
    -0093     % Read a word: everything up to whitespace or a bracket.
    -0094     j=i;
    -0095     while j<=n && ~isspace(rule(j)) && rule(j)~='(' && rule(j)~=')'
    -0096         j=j+1;
    -0097     end
    -0098     word=rule(i:j-1);
    -0099     switch lower(word)
    -0100         case {'and','&','&&'}
    -0101             toks{end+1}='AND'; %#ok<AGROW>
    -0102         case {'or','|','||'}
    -0103             toks{end+1}='OR'; %#ok<AGROW>
    -0104         otherwise
    -0105             toks{end+1}=struct('gene',word); %#ok<AGROW>
    -0106     end
    -0107     i=j;
    -0108 end
    -0109 end
    -0110 
    -0111 
    -0112 function [node,pos]=parseExpr(toks,pos,rule)
    -0113 % expr := term ( OR term )*
    -0114 [node,pos]=parseTerm(toks,pos,rule);
    -0115 if pos>numel(toks) || ~isOp(toks{pos},'OR')
    -0116     return
    -0117 end
    -0118 children={node};
    -0119 while pos<=numel(toks) && isOp(toks{pos},'OR')
    -0120     [child,pos]=parseTerm(toks,pos+1,rule);
    -0121     children{end+1}=child; %#ok<AGROW>
    +0004 % Parses a gene-protein-reaction rule into a tree of AND, OR and gene nodes.
    +0005 % Gene IDs are matched as whole tokens, so IDs that contain "and" or "or" as a
    +0006 % substring (RAND1), or that are prefixes of one another (10 and 100), are
    +0007 % distinct from operators and from each other.
    +0008 %
    +0009 % Parameters
    +0010 % ----------
    +0011 % rule : char or string
    +0012 %     a grRule, for example "(G1 and G2) or G3". Both the word forms ("and", "or",
    +0013 %     any case) and the symbol forms ("&", "&&", "|", "||") are accepted.
    +0014 %     An empty rule yields an empty tree.
    +0015 %
    +0016 % Returns
    +0017 % -------
    +0018 % tree : struct
    +0019 %     a scalar struct with fields:
    +0020 %
    +0021 %     - type     : char, 'gene', 'and' or 'or'.
    +0022 %     - id       : char, the gene ID; '' for 'and' and 'or' nodes.
    +0023 %     - children : cell, the child nodes; {} for 'gene' nodes.
    +0024 %
    +0025 %     An empty rule returns a 0x0 struct array, for which isempty is true.
    +0026 %
    +0027 % Notes
    +0028 % -----
    +0029 % AND binds tighter than OR. The grammar is:
    +0030 %
    +0031 %     expr   := term ( OR term )*
    +0032 %     term   := factor ( AND factor )*
    +0033 %     factor := gene | '(' expr ')'
    +0034 %
    +0035 % Malformed rules raise the error RAVEN:badGrRule. findPotentialErrors catches
    +0036 % this error and reports the rule.
    +0037 %
    +0038 % Examples
    +0039 % --------
    +0040 %     tree = parseGrRule('(G1 and G2) or G3');
    +0041 %     tree.type            % 'or'
    +0042 %     numel(tree.children) % 2
    +0043 %
    +0044 % See also: isDnfGrRule, grRuleToDNF, grRuleToString
    +0045 
    +0046 if nargin<1 || isempty(rule)
    +0047     tree=emptyTree();
    +0048     return
    +0049 end
    +0050 rule=char(rule);
    +0051 if all(isspace(rule))
    +0052     tree=emptyTree();
    +0053     return
    +0054 end
    +0055 
    +0056 toks=tokenizeGrRule(rule);
    +0057 if isempty(toks)
    +0058     tree=emptyTree();
    +0059     return
    +0060 end
    +0061 
    +0062 [tree,pos]=parseExpr(toks,1,rule);
    +0063 if pos<=numel(toks)
    +0064     error('RAVEN:badGrRule',['Unexpected "' tokenText(toks{pos}) '" in grRule: ' rule]);
    +0065 end
    +0066 end
    +0067 
    +0068 
    +0069 function tree=emptyTree()
    +0070 tree=struct('type',{},'id',{},'children',{});
    +0071 end
    +0072 
    +0073 
    +0074 function toks=tokenizeGrRule(rule)
    +0075 % Split into '(' , ')' , 'AND' , 'OR' and gene tokens. Operators are matched
    +0076 % as whole words, which is what keeps gene IDs containing "and"/"or" intact.
    +0077 toks={};
    +0078 i=1;
    +0079 n=numel(rule);
    +0080 while i<=n
    +0081     c=rule(i);
    +0082     if isspace(c)
    +0083         i=i+1;
    +0084         continue
    +0085     end
    +0086     if c=='(' || c==')'
    +0087         toks{end+1}=c; %#ok<AGROW>
    +0088         i=i+1;
    +0089         continue
    +0090     end
    +0091     % Read a word: everything up to whitespace or a bracket.
    +0092     j=i;
    +0093     while j<=n && ~isspace(rule(j)) && rule(j)~='(' && rule(j)~=')'
    +0094         j=j+1;
    +0095     end
    +0096     word=rule(i:j-1);
    +0097     switch lower(word)
    +0098         case {'and','&','&&'}
    +0099             toks{end+1}='AND'; %#ok<AGROW>
    +0100         case {'or','|','||'}
    +0101             toks{end+1}='OR'; %#ok<AGROW>
    +0102         otherwise
    +0103             toks{end+1}=struct('gene',word); %#ok<AGROW>
    +0104     end
    +0105     i=j;
    +0106 end
    +0107 end
    +0108 
    +0109 
    +0110 function [node,pos]=parseExpr(toks,pos,rule)
    +0111 % expr := term ( OR term )*
    +0112 [node,pos]=parseTerm(toks,pos,rule);
    +0113 if pos>numel(toks) || ~isOp(toks{pos},'OR')
    +0114     return
    +0115 end
    +0116 children={node};
    +0117 while pos<=numel(toks) && isOp(toks{pos},'OR')
    +0118     [child,pos]=parseTerm(toks,pos+1,rule);
    +0119     children{end+1}=child; %#ok<AGROW>
    +0120 end
    +0121 node=struct('type','or','id','','children',{children});
     0122 end
    -0123 node=struct('type','or','id','','children',{children});
    -0124 end
    -0125 
    -0126 
    -0127 function [node,pos]=parseTerm(toks,pos,rule)
    -0128 % term := factor ( AND factor )*
    -0129 [node,pos]=parseFactor(toks,pos,rule);
    -0130 if pos>numel(toks) || ~isOp(toks{pos},'AND')
    -0131     return
    -0132 end
    -0133 children={node};
    -0134 while pos<=numel(toks) && isOp(toks{pos},'AND')
    -0135     [child,pos]=parseFactor(toks,pos+1,rule);
    -0136     children{end+1}=child; %#ok<AGROW>
    +0123 
    +0124 
    +0125 function [node,pos]=parseTerm(toks,pos,rule)
    +0126 % term := factor ( AND factor )*
    +0127 [node,pos]=parseFactor(toks,pos,rule);
    +0128 if pos>numel(toks) || ~isOp(toks{pos},'AND')
    +0129     return
    +0130 end
    +0131 children={node};
    +0132 while pos<=numel(toks) && isOp(toks{pos},'AND')
    +0133     [child,pos]=parseFactor(toks,pos+1,rule);
    +0134     children{end+1}=child; %#ok<AGROW>
    +0135 end
    +0136 node=struct('type','and','id','','children',{children});
     0137 end
    -0138 node=struct('type','and','id','','children',{children});
    -0139 end
    -0140 
    -0141 
    -0142 function [node,pos]=parseFactor(toks,pos,rule)
    -0143 % factor := gene | '(' expr ')'
    -0144 if pos>numel(toks)
    -0145     error('RAVEN:badGrRule',['grRule ends after an operator: ' rule]);
    -0146 end
    -0147 tok=toks{pos};
    -0148 if isOp(tok,'(')
    -0149     [node,pos]=parseExpr(toks,pos+1,rule);
    -0150     if pos>numel(toks) || ~isOp(toks{pos},')')
    -0151         error('RAVEN:badGrRule',['Unbalanced parentheses in grRule: ' rule]);
    -0152     end
    -0153     pos=pos+1;
    -0154     return
    -0155 end
    -0156 if isstruct(tok)
    -0157     node=struct('type','gene','id',tok.gene,'children',{{}});
    -0158     pos=pos+1;
    -0159     return
    +0138 
    +0139 
    +0140 function [node,pos]=parseFactor(toks,pos,rule)
    +0141 % factor := gene | '(' expr ')'
    +0142 if pos>numel(toks)
    +0143     error('RAVEN:badGrRule',['grRule ends after an operator: ' rule]);
    +0144 end
    +0145 tok=toks{pos};
    +0146 if isOp(tok,'(')
    +0147     [node,pos]=parseExpr(toks,pos+1,rule);
    +0148     if pos>numel(toks) || ~isOp(toks{pos},')')
    +0149         error('RAVEN:badGrRule',['Unbalanced parentheses in grRule: ' rule]);
    +0150     end
    +0151     pos=pos+1;
    +0152     return
    +0153 end
    +0154 if isstruct(tok)
    +0155     node=struct('type','gene','id',tok.gene,'children',{{}});
    +0156     pos=pos+1;
    +0157     return
    +0158 end
    +0159 error('RAVEN:badGrRule',['Expected a gene but found "' tokenText(tok) '" in grRule: ' rule]);
     0160 end
    -0161 error('RAVEN:badGrRule',['Expected a gene but found "' tokenText(tok) '" in grRule: ' rule]);
    -0162 end
    -0163 
    -0164 
    -0165 function tf=isOp(tok,op)
    -0166 tf=ischar(tok) && strcmp(tok,op);
    -0167 end
    -0168 
    -0169 
    -0170 function txt=tokenText(tok)
    -0171 if isstruct(tok)
    -0172     txt=tok.gene;
    -0173 else
    -0174     txt=tok;
    -0175 end
    -0176 end

    +0161 +0162 +0163 function tf=isOp(tok,op) +0164 tf=ischar(tok) && strcmp(tok,op); +0165 end +0166 +0167 +0168 function txt=tokenText(tok) +0169 if isstruct(tok) +0170 txt=tok.gene; +0171 else +0172 txt=tok; +0173 end +0174 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/utils/parseRAVENargs.html b/docs/html/utils/parseRAVENargs.html index a01e130c..70ae9426 100644 --- a/docs/html/utils/parseRAVENargs.html +++ b/docs/html/utils/parseRAVENargs.html @@ -29,18 +29,17 @@

    SYNOPSIS ^DESCRIPTION ^

     parseRAVENargs  Resolve optional arguments passed positionally or by name.
     
    - Helper that lets a RAVEN function accept its optional arguments either in
    - the traditional positional order, as name-value pairs, or as a mix of
    - leading positional values followed by name-value pairs. The required
    - leading arguments stay explicit in the function signature; the optional
    - tail is collected into varargin and passed here together with a
    - specification of the optional parameters.
    + Helper for RAVEN functions that accept optional arguments in positional
    + order, as name-value pairs, or as leading positional values followed by
    + name-value pairs. The required leading arguments are explicit in the function
    + signature. The optional arguments are collected in varargin and passed to
    + this function together with a specification of the optional parameters.
     
      Parameters
      ----------
      rawArgs : cell
    -     the varargin of the function, i.e. whatever was supplied after the required
    -     positional arguments.
    +     the varargin of the function, holding the arguments supplied after the
    +     required positional arguments.
      spec : cell
          an N-by-2 or N-by-3 cell array describing the optional parameters, one
          per row:
    @@ -48,9 +47,9 @@ 

    DESCRIPTION ^DESCRIPTION ^DESCRIPTION ^SOURCE CODE ^
    0001 function args = parseRAVENargs(rawArgs, spec)
     0002 % parseRAVENargs  Resolve optional arguments passed positionally or by name.
     0003 %
    -0004 % Helper that lets a RAVEN function accept its optional arguments either in
    -0005 % the traditional positional order, as name-value pairs, or as a mix of
    -0006 % leading positional values followed by name-value pairs. The required
    -0007 % leading arguments stay explicit in the function signature; the optional
    -0008 % tail is collected into varargin and passed here together with a
    -0009 % specification of the optional parameters.
    -0010 %
    -0011 % Parameters
    -0012 % ----------
    -0013 % rawArgs : cell
    -0014 %     the varargin of the function, i.e. whatever was supplied after the required
    -0015 %     positional arguments.
    -0016 % spec : cell
    -0017 %     an N-by-2 or N-by-3 cell array describing the optional parameters, one
    -0018 %     per row:
    -0019 %
    -0020 %     - column 1 : parameter name (char).
    -0021 %     - column 2 : default value, used when the parameter is not supplied.
    -0022 %     - column 3 : optional validator, a function handle that is called with
    -0023 %       the resolved value (including the default, when the parameter was not
    -0024 %       supplied) and should error on an invalid value, e.g. @mustBeNumeric.
    -0025 %       Use [] for no validation.
    -0026 %
    -0027 % Returns
    -0028 % -------
    -0029 % args : struct
    -0030 %     a struct with one field per parameter name, holding the resolved value.
    -0031 %
    -0032 % Notes
    -0033 % -----
    -0034 % Calling convention is resolved as follows:
    -0035 %
    -0036 %   1. If rawArgs is empty, all parameters take their defaults.
    -0037 %   2. The first element in rawArgs that is a string matching a known
    -0038 %      parameter name marks position k. Everything before k is treated
    -0039 %      as positional (assigned left-to-right by the order in spec).
    -0040 %      Everything from k onward must form valid name-value pairs: an even
    -0041 %      count where every name-position element is a known parameter name.
    -0042 %   3. If no such position exists (no element is a matching string), or the
    -0043 %      trailing section from k does not form valid name-value pairs, the
    -0044 %      entire rawArgs is treated as positional.
    +0004 % Helper for RAVEN functions that accept optional arguments in positional
    +0005 % order, as name-value pairs, or as leading positional values followed by
    +0006 % name-value pairs. The required leading arguments are explicit in the function
    +0007 % signature. The optional arguments are collected in varargin and passed to
    +0008 % this function together with a specification of the optional parameters.
    +0009 %
    +0010 % Parameters
    +0011 % ----------
    +0012 % rawArgs : cell
    +0013 %     the varargin of the function, holding the arguments supplied after the
    +0014 %     required positional arguments.
    +0015 % spec : cell
    +0016 %     an N-by-2 or N-by-3 cell array describing the optional parameters, one
    +0017 %     per row:
    +0018 %
    +0019 %     - column 1 : parameter name (char).
    +0020 %     - column 2 : default value, used when the parameter is not supplied.
    +0021 %     - column 3 : optional validator, a function handle that is called with
    +0022 %       the resolved value (the default when the parameter was not supplied)
    +0023 %       and should error on an invalid value, for example @mustBeNumeric. [] means no
    +0024 %       validation.
    +0025 %
    +0026 % Returns
    +0027 % -------
    +0028 % args : struct
    +0029 %     a struct with one field per parameter name, holding the resolved value.
    +0030 %
    +0031 % Notes
    +0032 % -----
    +0033 % The calling convention is resolved in these steps:
    +0034 %
    +0035 %   1. If rawArgs is empty, all parameters take their defaults.
    +0036 %   2. The first element in rawArgs that is a string matching a parameter
    +0037 %      name (case-insensitive) marks position k. The elements before k are
    +0038 %      positional and are assigned in the order of spec. The elements from k
    +0039 %      onward must form valid name-value pairs: an even count in which every
    +0040 %      name-position element is a parameter name.
    +0041 %   3. If no element is a matching string, or the elements from k do not form
    +0042 %      valid name-value pairs, all of rawArgs is positional.
    +0043 %
    +0044 % Three styles are accepted:
     0045 %
    -0046 % This means three styles are accepted interchangeably:
    -0047 %
    -0048 %     f(model, v1, v2)                   % purely positional
    -0049 %     f(model, "p1", v1, "p2", v2)       % purely named
    -0050 %     f(model, v1, "p2", v2, "p3", v3)   % hybrid: v1 positional, rest named
    -0051 %
    -0052 % When a positional value could itself be a string that equals a parameter
    -0053 % name, use the explicit name-value form for that argument to avoid
    -0054 % ambiguity.
    -0055 %
    -0056 % Examples
    -0057 % --------
    -0058 %     % inside exportToExcelFormat(model, varargin):
    -0059 %     p = parseRAVENargs(varargin, { ...
    -0060 %         "fileName", []; ...
    -0061 %         "sortIds",  false});
    -0062 %     fileName = p.fileName;
    -0063 %     sortIds  = p.sortIds;
    -0064 %
    -0065 %     % the caller may use any of these:
    -0066 %     exportToExcelFormat(model, "model.xlsx", true)              % positional
    -0067 %     exportToExcelFormat(model, "sortIds", true)                 % named
    -0068 %     exportToExcelFormat(model, "model.xlsx", "sortIds", true)   % hybrid
    -0069 
    -0070 if nargin < 2 || isempty(spec)
    -0071     error('parseRAVENargs:noSpec', 'A parameter specification is required.');
    -0072 end
    -0073 
    -0074 names = spec(:, 1);
    -0075 defaults = spec(:, 2);
    -0076 hasValidators = size(spec, 2) >= 3;
    -0077 
    -0078 % Seed every parameter with its default.
    -0079 args = struct();
    -0080 for i = 1:numel(names)
    -0081     args.(names{i}) = defaults{i};
    -0082 end
    -0083 
    -0084 if ~isempty(rawArgs)
    -0085     % Find which elements are strings matching a known parameter name.
    -0086     isParamName = cellfun(@(x) (ischar(x) || isstring(x)) && isscalar(string(x)) && ...
    -0087                           any(strcmpi(x, names)), rawArgs);
    -0088 
    -0089     % Position of the first parameter-name string — where name-value begins.
    -0090     nvStart = find(isParamName, 1);
    -0091 
    -0092     nvOK = false;
    -0093     if ~isempty(nvStart)
    -0094         nv = rawArgs(nvStart:end);
    -0095         % Valid name-value section: even count, all name-position elements
    -0096         % are known parameter names.
    -0097         nvOK = mod(numel(nv), 2) == 0 && ...
    -0098                all(cellfun(@(x) (ischar(x) || isstring(x)) && isscalar(string(x)) && ...
    -0099                    any(strcmpi(x, names)), nv(1:2:end)));
    -0100     end
    -0101 
    -0102     if ~isempty(nvStart) && nvOK
    -0103         % Assign leading positional arguments (may be zero when nvStart == 1).
    -0104         nPos = nvStart - 1;
    -0105         if nPos > numel(names)
    -0106             error('parseRAVENargs:tooManyArgs', ...
    -0107                 'Too many positional arguments: expected at most %d, got %d.', ...
    -0108                 numel(names), nPos);
    -0109         end
    -0110         for k = 1:nPos
    -0111             args.(names{k}) = rawArgs{k};
    -0112         end
    -0113         % Assign name-value pairs.
    -0114         nv = rawArgs(nvStart:end);
    -0115         for k = 1:2:numel(nv)
    -0116             args.(names{strcmpi(nv{k}, names)}) = nv{k + 1};
    -0117         end
    -0118     else
    -0119         % Purely positional.
    -0120         if numel(rawArgs) > numel(names)
    -0121             error('parseRAVENargs:tooManyArgs', ...
    -0122                 'Too many positional arguments: expected at most %d, got %d.', ...
    -0123                 numel(names), numel(rawArgs));
    -0124         end
    -0125         for k = 1:numel(rawArgs)
    -0126             args.(names{k}) = rawArgs{k};
    -0127         end
    -0128     end
    -0129 end
    -0130 
    -0131 % Apply optional validators.
    -0132 if hasValidators
    -0133     for i = 1:numel(names)
    -0134         validator = spec{i, 3};
    -0135         if ~isempty(validator)
    -0136             validator(args.(names{i}));
    -0137         end
    -0138     end
    -0139 end
    -0140 end

    +0046 % f(model, v1, v2) % purely positional +0047 % f(model, "p1", v1, "p2", v2) % purely named +0048 % f(model, v1, "p2", v2, "p3", v3) % hybrid: v1 positional, rest named +0049 % +0050 % A positional value that is a string equal to a parameter name is read as a +0051 % parameter name only when the arguments from there on form valid name-value +0052 % pairs; otherwise all arguments are positional. The name-value form passes +0053 % such a value unambiguously. +0054 % +0055 % Examples +0056 % -------- +0057 % % inside exportToExcelFormat(model, varargin): +0058 % p = parseRAVENargs(varargin, { ... +0059 % "fileName", []; ... +0060 % "sortIds", false}); +0061 % fileName = p.fileName; +0062 % sortIds = p.sortIds; +0063 % +0064 % % the caller can use any of these: +0065 % exportToExcelFormat(model, "model.xlsx", true) % positional +0066 % exportToExcelFormat(model, "sortIds", true) % named +0067 % exportToExcelFormat(model, "model.xlsx", "sortIds", true) % hybrid +0068 +0069 if nargin < 2 || isempty(spec) +0070 error('parseRAVENargs:noSpec', 'A parameter specification is required.'); +0071 end +0072 +0073 names = spec(:, 1); +0074 defaults = spec(:, 2); +0075 hasValidators = size(spec, 2) >= 3; +0076 +0077 % Seed every parameter with its default. +0078 args = struct(); +0079 for i = 1:numel(names) +0080 args.(names{i}) = defaults{i}; +0081 end +0082 +0083 if ~isempty(rawArgs) +0084 % Find which elements are strings matching a known parameter name. +0085 isParamName = cellfun(@(x) (ischar(x) || isstring(x)) && isscalar(string(x)) && ... +0086 any(strcmpi(x, names)), rawArgs); +0087 +0088 % Position of the first parameter-name string — where name-value begins. +0089 nvStart = find(isParamName, 1); +0090 +0091 nvOK = false; +0092 if ~isempty(nvStart) +0093 nv = rawArgs(nvStart:end); +0094 % Valid name-value section: even count, all name-position elements +0095 % are known parameter names. +0096 nvOK = mod(numel(nv), 2) == 0 && ... +0097 all(cellfun(@(x) (ischar(x) || isstring(x)) && isscalar(string(x)) && ... +0098 any(strcmpi(x, names)), nv(1:2:end))); +0099 end +0100 +0101 if ~isempty(nvStart) && nvOK +0102 % Assign leading positional arguments (may be zero when nvStart == 1). +0103 nPos = nvStart - 1; +0104 if nPos > numel(names) +0105 error('parseRAVENargs:tooManyArgs', ... +0106 'Too many positional arguments: expected at most %d, got %d.', ... +0107 numel(names), nPos); +0108 end +0109 for k = 1:nPos +0110 args.(names{k}) = rawArgs{k}; +0111 end +0112 % Assign name-value pairs. +0113 nv = rawArgs(nvStart:end); +0114 for k = 1:2:numel(nv) +0115 args.(names{strcmpi(nv{k}, names)}) = nv{k + 1}; +0116 end +0117 else +0118 % Purely positional. +0119 if numel(rawArgs) > numel(names) +0120 error('parseRAVENargs:tooManyArgs', ... +0121 'Too many positional arguments: expected at most %d, got %d.', ... +0122 numel(names), numel(rawArgs)); +0123 end +0124 for k = 1:numel(rawArgs) +0125 args.(names{k}) = rawArgs{k}; +0126 end +0127 end +0128 end +0129 +0130 % Apply optional validators. +0131 if hasValidators +0132 for i = 1:numel(names) +0133 validator = spec{i, 3}; +0134 if ~isempty(validator) +0135 validator(args.(names{i})); +0136 end +0137 end +0138 end +0139 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/utils/printOrange.html b/docs/html/utils/printOrange.html index dec51b6c..1cf5f7fd 100644 --- a/docs/html/utils/printOrange.html +++ b/docs/html/utils/printOrange.html @@ -29,14 +29,16 @@

    SYNOPSIS ^DESCRIPTION ^

     printOrange  Print an orange-coloured string to the MATLAB Command Window.
     
    - Prints stringToPrint in orange colour to the MATLAB Command Window. Only
    - works if MATLAB is open with a GUI; it does not work with command-line
    - MATLAB.
    + Prints stringToPrint in orange colour to the MATLAB Command Window. The
    + colour requires the MATLAB desktop; in command-line MATLAB the text is
    + printed without colour. When called without an output argument, the text is
    + wrapped to the width of the Command Window and printed. With an output
    + argument, nothing is printed.
     
      Parameters
      ----------
      stringToPrint : char
    -     string that should be printed in orange colour. "\n" marks a line
    +     string to print in orange colour. "\n" marks a line
          break, as in fprintf; no other escape sequence or format directive
          is interpreted, so text containing "%" prints as-is.
     
    @@ -65,48 +67,50 @@ 

    SOURCE CODE ^
    0001 function orangeString = printOrange(stringToPrint)
     0002 % printOrange  Print an orange-coloured string to the MATLAB Command Window.
     0003 %
    -0004 % Prints stringToPrint in orange colour to the MATLAB Command Window. Only
    -0005 % works if MATLAB is open with a GUI; it does not work with command-line
    -0006 % MATLAB.
    -0007 %
    -0008 % Parameters
    -0009 % ----------
    -0010 % stringToPrint : char
    -0011 %     string that should be printed in orange colour. "\n" marks a line
    -0012 %     break, as in fprintf; no other escape sequence or format directive
    -0013 %     is interpreted, so text containing "%" prints as-is.
    -0014 %
    -0015 % Returns
    -0016 % -------
    -0017 % orangeString : char
    -0018 %     the input string wrapped with the formatting codes for orange colour.
    -0019 %
    -0020 % Examples
    -0021 % --------
    -0022 %     printOrange(stringToPrint);
    -0023 
    -0024 try useDesktop = usejava('desktop'); catch, useDesktop = false; end
    -0025 if useDesktop
    -0026     orangeString = ['[\b' stringToPrint,']\b'];
    -0027 else
    -0028     orangeString = stringToPrint;
    -0029 end
    -0030 if nargout < 1
    -0031     % Wrap text to command window size
    -0032     sz = get(0, 'CommandWindowSize');
    -0033     orangeString = textwrap({orangeString},sz(1));
    -0034     orangeString = strjoin(orangeString,newline);
    -0035     %stringToPrint is arbitrary text (e.g. a model id) and may contain a
    -0036     %literal "%", which fprintf would otherwise reinterpret as a format
    -0037     %directive; the "\n" line breaks and the desktop "[\b ... ]\b"
    -0038     %highlight markers still need interpreting, so expand those explicitly
    -0039     %instead of using orangeString itself as the format string.
    -0040     orangeString = strrep(orangeString,'\n',newline);
    -0041     orangeString = strrep(orangeString,'[\b',sprintf('[\b'));
    -0042     orangeString = strrep(orangeString,']\b',sprintf(']\b'));
    -0043     fprintf('%s',orangeString);
    -0044 end
    -0045 end

    +0004 % Prints stringToPrint in orange colour to the MATLAB Command Window. The +0005 % colour requires the MATLAB desktop; in command-line MATLAB the text is +0006 % printed without colour. When called without an output argument, the text is +0007 % wrapped to the width of the Command Window and printed. With an output +0008 % argument, nothing is printed. +0009 % +0010 % Parameters +0011 % ---------- +0012 % stringToPrint : char +0013 % string to print in orange colour. "\n" marks a line +0014 % break, as in fprintf; no other escape sequence or format directive +0015 % is interpreted, so text containing "%" prints as-is. +0016 % +0017 % Returns +0018 % ------- +0019 % orangeString : char +0020 % the input string wrapped with the formatting codes for orange colour. +0021 % +0022 % Examples +0023 % -------- +0024 % printOrange(stringToPrint); +0025 +0026 try useDesktop = usejava('desktop'); catch, useDesktop = false; end +0027 if useDesktop +0028 orangeString = ['[\b' stringToPrint,']\b']; +0029 else +0030 orangeString = stringToPrint; +0031 end +0032 if nargout < 1 +0033 % Wrap text to command window size +0034 sz = get(0, 'CommandWindowSize'); +0035 orangeString = textwrap({orangeString},sz(1)); +0036 orangeString = strjoin(orangeString,newline); +0037 %stringToPrint is arbitrary text (e.g. a model id) and may contain a +0038 %literal "%", which fprintf would otherwise reinterpret as a format +0039 %directive; the "\n" line breaks and the desktop "[\b ... ]\b" +0040 %highlight markers still need interpreting, so expand those explicitly +0041 %instead of using orangeString itself as the format string. +0042 orangeString = strrep(orangeString,'\n',newline); +0043 orangeString = strrep(orangeString,'[\b',sprintf('[\b')); +0044 orangeString = strrep(orangeString,']\b',sprintf(']\b')); +0045 fprintf('%s',orangeString); +0046 end +0047 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/utils/progressReport.html b/docs/html/utils/progressReport.html index ff1c8f4b..87d8715b 100644 --- a/docs/html/utils/progressReport.html +++ b/docs/html/utils/progressReport.html @@ -47,314 +47,316 @@

    SOURCE CODE ^
    0001 classdef progressReport < handle
     0002 % progressReport  Universal progress reporting for RAVEN functions.
     0003 %
    -0004 % A single progress-reporting API used across RAVEN. The rendering backend
    -0005 % is selected automatically so that the same call works on an interactive
    -0006 % desktop, a plain command-line session and a headless server, and is
    -0007 % silenced during unit tests:
    +0004 % Progress-reporting class used by RAVEN functions. The rendering backend is
    +0005 % selected automatically. The same calls work on an interactive desktop, in a
    +0006 % command-line session and on a headless server. During unit tests no output
    +0007 % is produced. The backends are:
     0008 %
     0009 %   - 'gui'  : MATLAB waitbar (interactive desktop).
     0010 %   - 'cli'  : animated command-line bar (interactive terminal).
    -0011 %   - 'log'  : plain milestone lines, e.g. "Running BLAST: 30%". Contains no
    -0012 %              control characters, so it stays readable in redirected logs
    -0013 %              and on headless / -batch servers.
    -0014 %   - 'none' : no output (during tests, or when disabled).
    -0015 %
    -0016 % The backend can be forced with setRavenProgress, e.g. setRavenProgress('log')
    -0017 % on a compute cluster, or setRavenProgress('off') to silence all progress
    -0018 % reporting.
    -0019 %
    -0020 % Usage
    -0021 % -----
    -0022 % For loops with one cheap-to-expensive iteration each, count one per pass:
    -0023 %
    -0024 %     PB = progressReport(N, 'Task name');   % before the loop
    -0025 %     for i = 1:N
    -0026 %         ...                                % loop body
    -0027 %         PB.count;                          % or count(PB), also in parfor
    -0028 %     end
    -0029 %     PB.done;                               % optional; cleans up on early exit
    -0030 %
    -0031 % For very long loops over cheap iterations, set the absolute position at
    -0032 % intervals to avoid per-iteration overhead:
    -0033 %
    -0034 %     PB = progressReport(N, 'Task name');
    -0035 %     for i = 1:N
    -0036 %         ...
    -0037 %         if rem(i-1, 1000) == 0
    -0038 %             PB.update(i);                  % or update(PB, i)
    -0039 %         end
    -0040 %     end
    -0041 %     PB.done;
    -0042 %
    -0043 % Parameters
    -0044 % ----------
    -0045 % N : double
    -0046 %     number of iterations the loop will perform (a positive integer).
    -0047 % taskName : char, optional
    -0048 %     label shown alongside the progress (default '').
    -0049 %
    -0050 % See also
    -0051 % --------
    -0052 % setRavenProgress
    -0053 
    -0054     properties (SetAccess = private)
    -0055         mode char            % resolved backend: 'gui', 'cli', 'log' or 'none'
    -0056         N    double
    -0057         taskName char
    -0058     end
    -0059     properties (Access = private, Transient)
    -0060         counter   = 0
    -0061         lastPct   = -1       % last percentage rendered
    -0062         logStep   = 10       % percent between 'log' lines
    -0063         barWidth  = 30
    -0064         prevLen   = 0        % characters on the current 'cli' line
    -0065         guiHandle = []
    -0066         Queue     = []       % DataQueue for parfor counting
    -0067         Listener  = []
    -0068         noPCT     = true
    -0069         finished  = false
    -0070     end
    -0071 
    -0072     methods
    -0073         function obj = progressReport(N, taskName)
    -0074             if nargin < 2 || isempty(taskName)
    -0075                 taskName = '';
    -0076             end
    -0077             obj.N = double(N);
    -0078             obj.taskName = truncateName(char(taskName), 40);
    -0079             obj.mode = resolveProgressMode();
    -0080 
    -0081             % Nothing sensible to show for an empty or unknown-length loop.
    -0082             if ~(isscalar(obj.N) && isfinite(obj.N) && obj.N >= 1)
    -0083                 obj.mode = 'none';
    -0084             end
    -0085             if strcmp(obj.mode,'none')
    -0086                 return
    -0087             end
    -0088 
    -0089             obj.noPCT = isempty(ver('parallel'));
    -0090             if ~obj.noPCT
    -0091                 obj.Queue = parallel.pool.DataQueue;
    -0092                 obj.Listener = afterEach(obj.Queue, @(v) localUpdate(obj, v));
    -0093             end
    -0094 
    -0095             % Initial render.
    -0096             switch obj.mode
    -0097                 case 'gui'
    -0098                     name = obj.taskName;
    -0099                     if isempty(name); name = 'Progress'; end
    -0100                     try
    -0101                         obj.guiHandle = waitbar(0, '0%', 'Name', name);
    -0102                     catch
    -0103                         obj.mode = 'log';   % no display available
    -0104                         emitLog(obj, 0);
    -0105                     end
    -0106                     obj.lastPct = 0;
    -0107                 case 'cli'
    -0108                     drawCli(obj, 0);
    -0109                     obj.lastPct = 0;
    -0110                 case 'log'
    -0111                     emitLog(obj, 0);
    -0112                     obj.lastPct = 0;
    -0113             end
    -0114         end
    -0115 
    -0116         function count(obj)
    -0117             % Register one completed iteration.
    -0118             if strcmp(obj.mode,'none')
    -0119                 return
    -0120             end
    -0121             if obj.noPCT || isempty(getCurrentJob)
    -0122                 localUpdate(obj, -1);       % serial execution
    -0123             else
    -0124                 send(obj.Queue, -1);        % parfor worker -> client
    -0125             end
    -0126         end
    -0127 
    -0128         function update(obj, n)
    -0129             % Set the absolute number of completed iterations to n.
    -0130             if strcmp(obj.mode,'none')
    -0131                 return
    -0132             end
    -0133             if obj.noPCT || isempty(getCurrentJob)
    -0134                 localUpdate(obj, n);        % serial execution
    -0135             else
    -0136                 send(obj.Queue, n);         % parfor worker -> client
    -0137             end
    -0138         end
    -0139 
    -0140         function done(obj)
    -0141             % Finalise the bar (call when a loop may exit before reaching N).
    -0142             finishUp(obj);
    -0143         end
    -0144     end
    -0145 
    -0146     methods (Hidden = true)
    -0147         function delete(obj)
    -0148             if ~isempty(obj.Queue)
    -0149                 delete(obj.Queue);
    -0150             end
    -0151             finishUp(obj);
    -0152         end
    -0153     end
    -0154 
    -0155     methods (Access = private)
    -0156         function localUpdate(obj, v)
    -0157             if v < 0
    -0158                 obj.counter = obj.counter + 1;
    -0159             else
    -0160                 obj.counter = max(obj.counter, v);
    -0161             end
    -0162             render(obj);
    -0163         end
    -0164 
    -0165         function render(obj)
    -0166             if obj.finished
    -0167                 return
    -0168             end
    -0169             pct = min(100, floor(100 * obj.counter / obj.N));
    -0170             switch obj.mode
    -0171                 case 'gui'
    -0172                     if pct ~= obj.lastPct
    -0173                         obj.lastPct = pct;
    -0174                         if ~isempty(obj.guiHandle) && isvalid(obj.guiHandle)
    -0175                             waitbar(pct/100, obj.guiHandle, sprintf('%d%%', pct));
    -0176                         end
    -0177                     end
    -0178                     if obj.counter >= obj.N; finishUp(obj); end
    -0179                 case 'cli'
    -0180                     if pct ~= obj.lastPct
    -0181                         obj.lastPct = pct;
    -0182                         drawCli(obj, pct);
    -0183                     end
    -0184                     if obj.counter >= obj.N; finishUp(obj); end
    -0185                 case 'log'
    -0186                     if pct >= obj.lastPct + obj.logStep ...
    -0187                             || (obj.counter >= obj.N && obj.lastPct < 100)
    -0188                         if obj.counter >= obj.N; pct = 100; end
    -0189                         obj.lastPct = pct;
    -0190                         emitLog(obj, pct);
    -0191                     end
    -0192             end
    -0193         end
    -0194 
    -0195         function drawCli(obj, pct)
    -0196             nfill = round(obj.barWidth * pct / 100);
    -0197             bar = [repmat('#',1,nfill), repmat('-',1,obj.barWidth-nfill)];
    -0198             if isempty(obj.taskName)
    -0199                 s = sprintf('[%s] %3d%%', bar, pct);
    -0200             else
    -0201                 s = sprintf('%s [%s] %3d%%', obj.taskName, bar, pct);
    -0202             end
    -0203             fprintf('%s%s', repmat(sprintf('\b'),1,obj.prevLen), s);
    -0204             obj.prevLen = numel(s);
    -0205         end
    -0206 
    -0207         function emitLog(obj, pct)
    -0208             if isempty(obj.taskName)
    -0209                 fprintf('Progress: %d%%\n', pct);
    -0210             else
    -0211                 fprintf('%s: %d%%\n', obj.taskName, pct);
    -0212             end
    -0213         end
    -0214 
    -0215         function finishUp(obj)
    -0216             if obj.finished
    -0217                 return
    -0218             end
    -0219             switch obj.mode
    -0220                 case 'gui'
    -0221                     if ~isempty(obj.guiHandle) && isvalid(obj.guiHandle)
    -0222                         delete(obj.guiHandle);
    -0223                     end
    -0224                 case 'cli'
    -0225                     if obj.lastPct < 100
    -0226                         drawCli(obj, 100);  % show completion
    -0227                     end
    -0228                     if obj.prevLen > 0
    -0229                         fprintf('\n');      % move off the progress line
    -0230                     end
    -0231                 case 'log'
    -0232                     if obj.lastPct < 100
    -0233                         emitLog(obj, 100);
    -0234                     end
    -0235             end
    -0236             obj.finished = true;
    -0237         end
    -0238     end
    -0239 end
    -0240 
    -0241 % ---------------------------- Local functions ----------------------------
    -0242 function mode = resolveProgressMode()
    -0243 % Decide the backend from the RAVEN preference, the test context and the
    -0244 % runtime environment.
    -0245 mode = 'auto';
    -0246 try
    -0247     mode = char(getpref('RAVEN','progressBar','auto'));
    -0248 catch
    -0249 end
    -0250 if strcmpi(mode,'off')
    -0251     mode = 'none';
    -0252 end
    -0253 if ~strcmpi(mode,'auto')
    -0254     if ~ismember(lower(mode),{'gui','cli','log','none'})
    -0255         mode = 'cli';
    -0256     end
    -0257     mode = lower(mode);
    -0258     return
    -0259 end
    -0260 
    -0261 % 'auto': silence inside the test framework first.
    -0262 if inTestContext()
    -0263     mode = 'none';
    -0264     return
    -0265 end
    -0266 
    -0267 % Headless / deployed / -batch -> clean log lines.
    -0268 isBatch = false;
    -0269 try
    -0270     isBatch = batchStartupOptionUsed;
    -0271 catch
    -0272 end
    -0273 if isBatch || isdeployed
    -0274     mode = 'log';
    -0275     return
    -0276 end
    -0277 
    -0278 % Desktop available -> GUI, otherwise an interactive terminal -> CLI.
    -0279 hasDesktop = false;
    -0280 try
    -0281     hasDesktop = usejava('desktop');
    -0282 catch
    -0283 end
    -0284 if hasDesktop
    -0285     mode = 'gui';
    -0286 else
    -0287     mode = 'cli';
    -0288 end
    -0289 end
    -0290 
    -0291 function tf = inTestContext()
    -0292 % Best-effort detection of the matlab.unittest framework on the call stack.
    -0293 % RavenTestCase also forces the preference to 'none', so the maintained
    -0294 % suite is silenced even if this heuristic misses a frame.
    -0295 tf = false;
    -0296 st = dbstack('-completenames');
    -0297 for i = 1:numel(st)
    -0298     if contains(st(i).name,'matlab.unittest','IgnoreCase',true) ...
    -0299             || contains(st(i).name,'TestRunner','IgnoreCase',true) ...
    -0300             || contains(st(i).name,'runtests','IgnoreCase',true)
    -0301         tf = true;
    -0302         return
    -0303     end
    -0304 end
    -0305 end
    -0306 
    -0307 function name = truncateName(name, maxLen)
    -0308 if numel(name) > maxLen
    -0309     name = name(1:maxLen);
    -0310 end
    -0311 end
    +0011 % - 'log' : plain milestone lines, for example "Running BLAST: 30%", printed +0012 % every 10%. The lines contain no control characters, so they +0013 % are readable in redirected logs and on headless and -batch +0014 % servers. +0015 % - 'none' : no output (during tests, or when disabled). +0016 % +0017 % setRavenProgress sets the backend: setRavenProgress('log') selects plain +0018 % lines, for example on a compute cluster, and setRavenProgress('off') +0019 % disables all progress reporting. +0020 % +0021 % Usage +0022 % ----- +0023 % To register each iteration, call count once per pass: +0024 % +0025 % PB = progressReport(N, 'Task name'); % before the loop +0026 % for i = 1:N +0027 % ... % loop body +0028 % PB.count; % or count(PB), also in parfor +0029 % end +0030 % PB.done; % optional; finalises the bar on early exit +0031 % +0032 % To avoid the per-iteration overhead in long loops of short iterations, set +0033 % the absolute position at intervals: +0034 % +0035 % PB = progressReport(N, 'Task name'); +0036 % for i = 1:N +0037 % ... +0038 % if rem(i-1, 1000) == 0 +0039 % PB.update(i); % or update(PB, i) +0040 % end +0041 % end +0042 % PB.done; +0043 % +0044 % Parameters +0045 % ---------- +0046 % N : double +0047 % number of iterations the loop performs (a positive integer). For any other +0048 % value, no progress is shown. +0049 % taskName : char, optional +0050 % label shown alongside the progress (default ''). +0051 % +0052 % See also +0053 % -------- +0054 % setRavenProgress +0055 +0056 properties (SetAccess = private) +0057 mode char % resolved backend: 'gui', 'cli', 'log' or 'none' +0058 N double +0059 taskName char +0060 end +0061 properties (Access = private, Transient) +0062 counter = 0 +0063 lastPct = -1 % last percentage rendered +0064 logStep = 10 % percent between 'log' lines +0065 barWidth = 30 +0066 prevLen = 0 % characters on the current 'cli' line +0067 guiHandle = [] +0068 Queue = [] % DataQueue for parfor counting +0069 Listener = [] +0070 noPCT = true +0071 finished = false +0072 end +0073 +0074 methods +0075 function obj = progressReport(N, taskName) +0076 if nargin < 2 || isempty(taskName) +0077 taskName = ''; +0078 end +0079 obj.N = double(N); +0080 obj.taskName = truncateName(char(taskName), 40); +0081 obj.mode = resolveProgressMode(); +0082 +0083 % Nothing sensible to show for an empty or unknown-length loop. +0084 if ~(isscalar(obj.N) && isfinite(obj.N) && obj.N >= 1) +0085 obj.mode = 'none'; +0086 end +0087 if strcmp(obj.mode,'none') +0088 return +0089 end +0090 +0091 obj.noPCT = isempty(ver('parallel')); +0092 if ~obj.noPCT +0093 obj.Queue = parallel.pool.DataQueue; +0094 obj.Listener = afterEach(obj.Queue, @(v) localUpdate(obj, v)); +0095 end +0096 +0097 % Initial render. +0098 switch obj.mode +0099 case 'gui' +0100 name = obj.taskName; +0101 if isempty(name); name = 'Progress'; end +0102 try +0103 obj.guiHandle = waitbar(0, '0%', 'Name', name); +0104 catch +0105 obj.mode = 'log'; % no display available +0106 emitLog(obj, 0); +0107 end +0108 obj.lastPct = 0; +0109 case 'cli' +0110 drawCli(obj, 0); +0111 obj.lastPct = 0; +0112 case 'log' +0113 emitLog(obj, 0); +0114 obj.lastPct = 0; +0115 end +0116 end +0117 +0118 function count(obj) +0119 % Register one completed iteration. +0120 if strcmp(obj.mode,'none') +0121 return +0122 end +0123 if obj.noPCT || isempty(getCurrentJob) +0124 localUpdate(obj, -1); % serial execution +0125 else +0126 send(obj.Queue, -1); % parfor worker -> client +0127 end +0128 end +0129 +0130 function update(obj, n) +0131 % Set the absolute number of completed iterations to n. +0132 if strcmp(obj.mode,'none') +0133 return +0134 end +0135 if obj.noPCT || isempty(getCurrentJob) +0136 localUpdate(obj, n); % serial execution +0137 else +0138 send(obj.Queue, n); % parfor worker -> client +0139 end +0140 end +0141 +0142 function done(obj) +0143 % Finalise the bar (call when a loop may exit before reaching N). +0144 finishUp(obj); +0145 end +0146 end +0147 +0148 methods (Hidden = true) +0149 function delete(obj) +0150 if ~isempty(obj.Queue) +0151 delete(obj.Queue); +0152 end +0153 finishUp(obj); +0154 end +0155 end +0156 +0157 methods (Access = private) +0158 function localUpdate(obj, v) +0159 if v < 0 +0160 obj.counter = obj.counter + 1; +0161 else +0162 obj.counter = max(obj.counter, v); +0163 end +0164 render(obj); +0165 end +0166 +0167 function render(obj) +0168 if obj.finished +0169 return +0170 end +0171 pct = min(100, floor(100 * obj.counter / obj.N)); +0172 switch obj.mode +0173 case 'gui' +0174 if pct ~= obj.lastPct +0175 obj.lastPct = pct; +0176 if ~isempty(obj.guiHandle) && isvalid(obj.guiHandle) +0177 waitbar(pct/100, obj.guiHandle, sprintf('%d%%', pct)); +0178 end +0179 end +0180 if obj.counter >= obj.N; finishUp(obj); end +0181 case 'cli' +0182 if pct ~= obj.lastPct +0183 obj.lastPct = pct; +0184 drawCli(obj, pct); +0185 end +0186 if obj.counter >= obj.N; finishUp(obj); end +0187 case 'log' +0188 if pct >= obj.lastPct + obj.logStep ... +0189 || (obj.counter >= obj.N && obj.lastPct < 100) +0190 if obj.counter >= obj.N; pct = 100; end +0191 obj.lastPct = pct; +0192 emitLog(obj, pct); +0193 end +0194 end +0195 end +0196 +0197 function drawCli(obj, pct) +0198 nfill = round(obj.barWidth * pct / 100); +0199 bar = [repmat('#',1,nfill), repmat('-',1,obj.barWidth-nfill)]; +0200 if isempty(obj.taskName) +0201 s = sprintf('[%s] %3d%%', bar, pct); +0202 else +0203 s = sprintf('%s [%s] %3d%%', obj.taskName, bar, pct); +0204 end +0205 fprintf('%s%s', repmat(sprintf('\b'),1,obj.prevLen), s); +0206 obj.prevLen = numel(s); +0207 end +0208 +0209 function emitLog(obj, pct) +0210 if isempty(obj.taskName) +0211 fprintf('Progress: %d%%\n', pct); +0212 else +0213 fprintf('%s: %d%%\n', obj.taskName, pct); +0214 end +0215 end +0216 +0217 function finishUp(obj) +0218 if obj.finished +0219 return +0220 end +0221 switch obj.mode +0222 case 'gui' +0223 if ~isempty(obj.guiHandle) && isvalid(obj.guiHandle) +0224 delete(obj.guiHandle); +0225 end +0226 case 'cli' +0227 if obj.lastPct < 100 +0228 drawCli(obj, 100); % show completion +0229 end +0230 if obj.prevLen > 0 +0231 fprintf('\n'); % move off the progress line +0232 end +0233 case 'log' +0234 if obj.lastPct < 100 +0235 emitLog(obj, 100); +0236 end +0237 end +0238 obj.finished = true; +0239 end +0240 end +0241 end +0242 +0243 % ---------------------------- Local functions ---------------------------- +0244 function mode = resolveProgressMode() +0245 % Decide the backend from the RAVEN preference, the test context and the +0246 % runtime environment. +0247 mode = 'auto'; +0248 try +0249 mode = char(getpref('RAVEN','progressBar','auto')); +0250 catch +0251 end +0252 if strcmpi(mode,'off') +0253 mode = 'none'; +0254 end +0255 if ~strcmpi(mode,'auto') +0256 if ~ismember(lower(mode),{'gui','cli','log','none'}) +0257 mode = 'cli'; +0258 end +0259 mode = lower(mode); +0260 return +0261 end +0262 +0263 % 'auto': silence inside the test framework first. +0264 if inTestContext() +0265 mode = 'none'; +0266 return +0267 end +0268 +0269 % Headless / deployed / -batch -> clean log lines. +0270 isBatch = false; +0271 try +0272 isBatch = batchStartupOptionUsed; +0273 catch +0274 end +0275 if isBatch || isdeployed +0276 mode = 'log'; +0277 return +0278 end +0279 +0280 % Desktop available -> GUI, otherwise an interactive terminal -> CLI. +0281 hasDesktop = false; +0282 try +0283 hasDesktop = usejava('desktop'); +0284 catch +0285 end +0286 if hasDesktop +0287 mode = 'gui'; +0288 else +0289 mode = 'cli'; +0290 end +0291 end +0292 +0293 function tf = inTestContext() +0294 % Best-effort detection of the matlab.unittest framework on the call stack. +0295 % RavenTestCase also forces the preference to 'none', so the maintained +0296 % suite is silenced even if this heuristic misses a frame. +0297 tf = false; +0298 st = dbstack('-completenames'); +0299 for i = 1:numel(st) +0300 if contains(st(i).name,'matlab.unittest','IgnoreCase',true) ... +0301 || contains(st(i).name,'TestRunner','IgnoreCase',true) ... +0302 || contains(st(i).name,'runtests','IgnoreCase',true) +0303 tf = true; +0304 return +0305 end +0306 end +0307 end +0308 +0309 function name = truncateName(name, maxLen) +0310 if numel(name) > maxLen +0311 name = name(1:maxLen); +0312 end +0313 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/utils/ravenList.html b/docs/html/utils/ravenList.html index 64f92383..6ce5a11d 100644 --- a/docs/html/utils/ravenList.html +++ b/docs/html/utils/ravenList.html @@ -4,7 +4,7 @@ Description of ravenList - + @@ -21,28 +21,31 @@

    ravenList

    PURPOSE ^

    -
    ravenList Format a header and item list for warning() or error().
    +
    ravenList Format a header and a list of items for warning() or error().

    SYNOPSIS ^

    function msg = ravenList(header, items, trim)

    DESCRIPTION ^

    -
     ravenList  Format a header and item list for warning() or error().
    +
     ravenList  Format a header and a list of items for warning() or error().
     
      Parameters
      ----------
      header : char
          message header string.
      items : cell
    -     cell array of items to list below the header.
    +     cell array of items, listed one per line after the header.
      trim : logical
    -     cap the list at 10 items (default true).
    +     true to limit the list to 10 lines (default true). When the list has more
    +     than 10 items, the tenth line reads '...and N more' and replaces items
    +     10 to the end, with N the number of replaced items.
     
      Returns
      -------
      msg : char
    -     header + newline + tab-indented items, ready to pass to warning() or
    -     error() as the format string.
    +     header, a newline, and the items separated by newlines with a tab before
    +     each item. If items is empty, msg is header. Passed to warning() or
    +     error() as an argument after the format '%s'.
     
      Examples
      --------
    @@ -63,44 +66,47 @@ 

    CROSS-REFERENCE INFORMATION ^
 
 <h2><a name=SOURCE CODE ^

    0001 function msg = ravenList(header, items, trim)
    -0002 % ravenList  Format a header and item list for warning() or error().
    +0002 % ravenList  Format a header and a list of items for warning() or error().
     0003 %
     0004 % Parameters
     0005 % ----------
     0006 % header : char
     0007 %     message header string.
     0008 % items : cell
    -0009 %     cell array of items to list below the header.
    +0009 %     cell array of items, listed one per line after the header.
     0010 % trim : logical
    -0011 %     cap the list at 10 items (default true).
    -0012 %
    -0013 % Returns
    -0014 % -------
    -0015 % msg : char
    -0016 %     header + newline + tab-indented items, ready to pass to warning() or
    -0017 %     error() as the format string.
    -0018 %
    -0019 % Examples
    -0020 % --------
    -0021 %     warning("RAVEN:warning", "%s", ravenList("Missing metabolites:", badMets));
    -0022 %     error("RAVEN:badInput", "%s", ravenList("Invalid reactions:", badRxns, false));
    -0023 
    -0024 if nargin < 3; trim = true; end
    -0025 items = convertCharArray(items);
    -0026 if isempty(items)
    -0027     msg = header;
    -0028     return
    -0029 end
    -0030 if trim && numel(items) > 10
    -0031     items{10} = sprintf('...and %d more', numel(items)-9);
    -0032     items(11:end) = [];
    -0033 end
    -0034 % {char(9)} rather than char(9): strcat strips trailing whitespace from a
    -0035 % plain char argument, and a lone tab is entirely trailing whitespace, so
    -0036 % every item's indent was silently dropped. Wrapped in a cell, strcat
    -0037 % treats it as a cell-array input instead, which it does not trim.
    -0038 msg = [header, newline, strjoin(strcat({char(9)}, items), newline)];
    -0039 end
    +0011 % true to limit the list to 10 lines (default true). When the list has more +0012 % than 10 items, the tenth line reads '...and N more' and replaces items +0013 % 10 to the end, with N the number of replaced items. +0014 % +0015 % Returns +0016 % ------- +0017 % msg : char +0018 % header, a newline, and the items separated by newlines with a tab before +0019 % each item. If items is empty, msg is header. Passed to warning() or +0020 % error() as an argument after the format '%s'. +0021 % +0022 % Examples +0023 % -------- +0024 % warning("RAVEN:warning", "%s", ravenList("Missing metabolites:", badMets)); +0025 % error("RAVEN:badInput", "%s", ravenList("Invalid reactions:", badRxns, false)); +0026 +0027 if nargin < 3; trim = true; end +0028 items = convertCharArray(items); +0029 if isempty(items) +0030 msg = header; +0031 return +0032 end +0033 if trim && numel(items) > 10 +0034 items{10} = sprintf('...and %d more', numel(items)-9); +0035 items(11:end) = []; +0036 end +0037 % {char(9)} rather than char(9): strcat strips trailing whitespace from a +0038 % plain char argument, and a lone tab is entirely trailing whitespace, so +0039 % every item's indent was silently dropped. Wrapped in a cell, strcat +0040 % treats it as a cell-array input instead, which it does not trim. +0041 msg = [header, newline, strjoin(strcat({char(9)}, items), newline)]; +0042 end

    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/utils/ravenModelFields.html b/docs/html/utils/ravenModelFields.html index b7afad47..a7405242 100644 --- a/docs/html/utils/ravenModelFields.html +++ b/docs/html/utils/ravenModelFields.html @@ -29,20 +29,21 @@

    SYNOPSIS ^DESCRIPTION ^

     ravenModelFields  Declarative registry of all RAVEN model struct fields.
     
    - Returns a struct array. Each element describes one field:
    + Returns a struct array with one element per field. The elements have the
    + fields:
        .name    - field name (char)
    -   .type    - entity type: 'rxn', 'met', 'gene', or 'comp' (char)
    +   .type    - entity type: 'rxn', 'met', 'gene' or 'comp' (char)
        .default - default value when padding new entries
     
    - Only 1-D (vector-per-entity) fields are listed here. Fields that require
    - custom 2-D indexing or value-remapping are excluded and must be handled
    - explicitly by callers:
    + Only 1-D (vector-per-entity) fields are listed. Fields that need 2-D
    + indexing or value remapping are excluded, and callers handle them
    + explicitly:
     
        S          - sparse (mets x rxns); use S(:,perm) for rxns, S(perm,:) for mets
        rxnGeneMat - sparse (rxns x genes); use rxnGeneMat(perm,:) or rxnGeneMat(:,perm)
        b          - double (mets x N), may be 2-D; use b(perm,:)
    -   metComps, rxnComps, geneComps - when permuting comps, their VALUES must
    -       be remapped (not the arrays themselves permuted)
    + metComps, rxnComps, geneComps - when the compartments are permuted, the + values of these arrays are remapped; the arrays are not permuted

    CROSS-REFERENCE INFORMATION ^

    @@ -60,74 +61,75 @@

    SOURCE CODE ^
    0001 function reg = ravenModelFields()
     0002 % ravenModelFields  Declarative registry of all RAVEN model struct fields.
     0003 %
    -0004 % Returns a struct array. Each element describes one field:
    -0005 %   .name    - field name (char)
    -0006 %   .type    - entity type: 'rxn', 'met', 'gene', or 'comp' (char)
    -0007 %   .default - default value when padding new entries
    -0008 %
    -0009 % Only 1-D (vector-per-entity) fields are listed here. Fields that require
    -0010 % custom 2-D indexing or value-remapping are excluded and must be handled
    -0011 % explicitly by callers:
    -0012 %
    -0013 %   S          - sparse (mets x rxns); use S(:,perm) for rxns, S(perm,:) for mets
    -0014 %   rxnGeneMat - sparse (rxns x genes); use rxnGeneMat(perm,:) or rxnGeneMat(:,perm)
    -0015 %   b          - double (mets x N), may be 2-D; use b(perm,:)
    -0016 %   metComps, rxnComps, geneComps - when permuting comps, their VALUES must
    -0017 %       be remapped (not the arrays themselves permuted)
    -0018 
    -0019 rows = {
    -0020 % name                   type      default
    -0021   'rxns',                'rxn',    '';
    -0022   'lb',                  'rxn',    0;
    -0023   'ub',                  'rxn',    1000;
    -0024   'rev',                 'rxn',    false;
    -0025   'c',                   'rxn',    0;
    -0026   'grRules',             'rxn',    '';
    -0027   'rxnNames',            'rxn',    '';
    -0028   'eccodes',             'rxn',    '';
    -0029   'subSystems',          'rxn',    '';
    -0030   'rxnFrom',             'rxn',    '';
    -0031   'rxnNotes',            'rxn',    '';
    -0032   'rxnReferences',       'rxn',    '';
    -0033   'pwys',                'rxn',    '';
    -0034   'equations',           'rxn',    '';
    -0035   'rxnComps',            'rxn',    NaN;
    -0036   'rxnConfidenceScores', 'rxn',    NaN;
    -0037   'rxnDeltaG',           'rxn',    NaN;
    -0038   'rxnScores',           'rxn',    NaN;
    -0039   'spontaneous',         'rxn',    false;
    -0040   'rxnMiriams',          'rxn',    {};
    -0041   'mets',                'met',    '';
    -0042   'metComps',            'met',    0;
    -0043   'metNames',            'met',    '';
    -0044   'metFormulas',         'met',    '';
    -0045   'inchis',              'met',    '';
    -0046   'metSmiles',           'met',    '';
    -0047   'metNotes',            'met',    '';
    -0048   'metFrom',             'met',    '';
    -0049   'metCharges',          'met',    NaN;
    -0050   'metDeltaG',           'met',    NaN;
    -0051   'unconstrained',       'met',    0;
    -0052   'metMiriams',          'met',    {};
    -0053   'genes',               'gene',   '';
    -0054   'geneShortNames',      'gene',   '';
    -0055   'geneFrom',            'gene',   '';
    -0056   'proteins',            'gene',   '';
    -0057   'geneComps',           'gene',   NaN;
    -0058   'geneMiriams',         'gene',   {};
    -0059   'comps',               'comp',   '';
    -0060   'compNames',           'comp',   '';
    -0061   'compOutside',         'comp',   '';
    -0062   'compMiriams',         'comp',   {};
    -0063 };
    -0064 
    -0065 n = size(rows, 1);
    -0066 for i = 1:n
    -0067     reg(i).name    = rows{i,1};
    -0068     reg(i).type    = rows{i,2};
    -0069     reg(i).default = rows{i,3};
    -0070 end
    -0071 end
    +0004 % Returns a struct array with one element per field. The elements have the +0005 % fields: +0006 % .name - field name (char) +0007 % .type - entity type: 'rxn', 'met', 'gene' or 'comp' (char) +0008 % .default - default value when padding new entries +0009 % +0010 % Only 1-D (vector-per-entity) fields are listed. Fields that need 2-D +0011 % indexing or value remapping are excluded, and callers handle them +0012 % explicitly: +0013 % +0014 % S - sparse (mets x rxns); use S(:,perm) for rxns, S(perm,:) for mets +0015 % rxnGeneMat - sparse (rxns x genes); use rxnGeneMat(perm,:) or rxnGeneMat(:,perm) +0016 % b - double (mets x N), may be 2-D; use b(perm,:) +0017 % metComps, rxnComps, geneComps - when the compartments are permuted, the +0018 % values of these arrays are remapped; the arrays are not permuted +0019 +0020 rows = { +0021 % name type default +0022 'rxns', 'rxn', ''; +0023 'lb', 'rxn', 0; +0024 'ub', 'rxn', 1000; +0025 'rev', 'rxn', false; +0026 'c', 'rxn', 0; +0027 'grRules', 'rxn', ''; +0028 'rxnNames', 'rxn', ''; +0029 'eccodes', 'rxn', ''; +0030 'subSystems', 'rxn', ''; +0031 'rxnFrom', 'rxn', ''; +0032 'rxnNotes', 'rxn', ''; +0033 'rxnReferences', 'rxn', ''; +0034 'pwys', 'rxn', ''; +0035 'equations', 'rxn', ''; +0036 'rxnComps', 'rxn', NaN; +0037 'rxnConfidenceScores', 'rxn', NaN; +0038 'rxnDeltaG', 'rxn', NaN; +0039 'rxnScores', 'rxn', NaN; +0040 'spontaneous', 'rxn', false; +0041 'rxnMiriams', 'rxn', {}; +0042 'mets', 'met', ''; +0043 'metComps', 'met', 0; +0044 'metNames', 'met', ''; +0045 'metFormulas', 'met', ''; +0046 'inchis', 'met', ''; +0047 'metSmiles', 'met', ''; +0048 'metNotes', 'met', ''; +0049 'metFrom', 'met', ''; +0050 'metCharges', 'met', NaN; +0051 'metDeltaG', 'met', NaN; +0052 'unconstrained', 'met', 0; +0053 'metMiriams', 'met', {}; +0054 'genes', 'gene', ''; +0055 'geneShortNames', 'gene', ''; +0056 'geneFrom', 'gene', ''; +0057 'proteins', 'gene', ''; +0058 'geneComps', 'gene', NaN; +0059 'geneMiriams', 'gene', {}; +0060 'comps', 'comp', ''; +0061 'compNames', 'comp', ''; +0062 'compOutside', 'comp', ''; +0063 'compMiriams', 'comp', {}; +0064 }; +0065 +0066 n = size(rows, 1); +0067 for i = 1:n +0068 reg(i).name = rows{i,1}; +0069 reg(i).type = rows{i,2}; +0070 reg(i).default = rows{i,3}; +0071 end +0072 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/utils/runRAVENtests.html b/docs/html/utils/runRAVENtests.html index bc80ee9c..d6db3460 100644 --- a/docs/html/utils/runRAVENtests.html +++ b/docs/html/utils/runRAVENtests.html @@ -29,8 +29,9 @@

    SYNOPSIS ^DESCRIPTION ^

     runRAVENtests  Run all RAVEN function tests.
     
    - Runs all unit tests found at RAVEN/test/function_tests, and prints output in
    - the command window.
    + Runs all unit tests found in the folder tests/function_tests of the RAVEN
    + root folder, except the abstract base class RavenTestCase, and prints the
    + output in the command window.
     
      Returns
      -------
    @@ -57,30 +58,31 @@ 

    SOURCE CODE ^
    0001 function testResults = runRAVENtests
     0002 % runRAVENtests  Run all RAVEN function tests.
     0003 %
    -0004 % Runs all unit tests found at RAVEN/test/function_tests, and prints output in
    -0005 % the command window.
    -0006 %
    -0007 % Returns
    -0008 % -------
    -0009 % testResults : matlab.unittest.TestResult
    -0010 %     array of test results returned by runtests.
    -0011 %
    -0012 % Examples
    -0013 % --------
    -0014 %     testResults = runRAVENtests;
    -0015 
    -0016 ravenPath = findRAVENroot;
    -0017 curwd = pwd;
    -0018 cd(fullfile(ravenPath,'tests','function_tests'));
    -0019 
    -0020 % Collect the concrete test classes only. RavenTestCase is an abstract base
    -0021 % class that the test classes subclass, and runtests cannot build a suite
    -0022 % from an abstract class, so it must be excluded from the file list.
    -0023 testFiles = struct2table(dir('*.m')).name;
    -0024 testFiles(strcmp(testFiles,'RavenTestCase.m')) = [];
    -0025 testResults = runtests(testFiles);
    -0026 
    -0027 cd(curwd);

    +0004 % Runs all unit tests found in the folder tests/function_tests of the RAVEN +0005 % root folder, except the abstract base class RavenTestCase, and prints the +0006 % output in the command window. +0007 % +0008 % Returns +0009 % ------- +0010 % testResults : matlab.unittest.TestResult +0011 % array of test results returned by runtests. +0012 % +0013 % Examples +0014 % -------- +0015 % testResults = runRAVENtests; +0016 +0017 ravenPath = findRAVENroot; +0018 curwd = pwd; +0019 cd(fullfile(ravenPath,'tests','function_tests')); +0020 +0021 % Collect the concrete test classes only. RavenTestCase is an abstract base +0022 % class that the test classes subclass, and runtests cannot build a suite +0023 % from an abstract class, so it must be excluded from the file list. +0024 testFiles = struct2table(dir('*.m')).name; +0025 testFiles(strcmp(testFiles,'RavenTestCase.m')) = []; +0026 testResults = runtests(testFiles); +0027 +0028 cd(curwd);
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/utils/setRavenProgress.html b/docs/html/utils/setRavenProgress.html index 9f07ab3d..742cb612 100644 --- a/docs/html/utils/setRavenProgress.html +++ b/docs/html/utils/setRavenProgress.html @@ -30,25 +30,26 @@

    DESCRIPTION ^
     setRavenProgress  Set the RAVEN progress-reporting backend.
     
      Sets the preference that progressReport uses to decide how progress is
    - shown. The setting is persistent across MATLAB sessions.
    + shown. The setting is stored as the RAVEN preference progressBar and
    + persists across MATLAB sessions.
     
      Parameters
      ----------
      mode : char
          one of:
     
    -     - 'auto' : choose automatically (default). GUI on an interactive
    -       desktop, an animated bar in an interactive terminal, plain log lines
    -       on a headless / -batch server, and no output during unit tests.
    +     - 'auto' : choose automatically (default): 'gui' on the MATLAB desktop,
    +       'cli' in a terminal, 'log' on a -batch or deployed session, and no
    +       output during unit tests.
          - 'gui'  : always use a MATLAB waitbar.
          - 'cli'  : always use the animated command-line bar.
    -     - 'log'  : always print plain milestone lines (safe in redirected
    -       logs and on headless servers).
    -     - 'none' / 'off' : never show progress.
    +     - 'log'  : always print plain milestone lines, which are readable in
    +       redirected logs and on headless servers.
    +     - 'none' or 'off' : never show progress.
     
      Examples
      --------
    -     setRavenProgress('log');   % e.g. on a compute cluster
    +     setRavenProgress('log');   % for example on a compute cluster
          setRavenProgress('off');   % silence all progress reporting
          setRavenProgress('auto');  % restore the default behaviour
     
    @@ -73,44 +74,45 @@ 

    SOURCE CODE ^% setRavenProgress Set the RAVEN progress-reporting backend. 0003 % 0004 % Sets the preference that progressReport uses to decide how progress is -0005 % shown. The setting is persistent across MATLAB sessions. -0006 % -0007 % Parameters -0008 % ---------- -0009 % mode : char -0010 % one of: -0011 % -0012 % - 'auto' : choose automatically (default). GUI on an interactive -0013 % desktop, an animated bar in an interactive terminal, plain log lines -0014 % on a headless / -batch server, and no output during unit tests. -0015 % - 'gui' : always use a MATLAB waitbar. -0016 % - 'cli' : always use the animated command-line bar. -0017 % - 'log' : always print plain milestone lines (safe in redirected -0018 % logs and on headless servers). -0019 % - 'none' / 'off' : never show progress. -0020 % -0021 % Examples -0022 % -------- -0023 % setRavenProgress('log'); % e.g. on a compute cluster -0024 % setRavenProgress('off'); % silence all progress reporting -0025 % setRavenProgress('auto'); % restore the default behaviour -0026 % -0027 % See also -0028 % -------- -0029 % progressReport -0030 -0031 if nargin < 1 || isempty(mode) -0032 mode = 'auto'; -0033 end -0034 mode = lower(char(mode)); -0035 if strcmp(mode,'off') -0036 mode = 'none'; -0037 end -0038 if ~ismember(mode,{'auto','gui','cli','log','none'}) -0039 error('Unknown progress mode "%s". Use auto, gui, cli, log or off.', mode); -0040 end -0041 setpref('RAVEN','progressBar',mode); -0042 end

    +0005 % shown. The setting is stored as the RAVEN preference progressBar and +0006 % persists across MATLAB sessions. +0007 % +0008 % Parameters +0009 % ---------- +0010 % mode : char +0011 % one of: +0012 % +0013 % - 'auto' : choose automatically (default): 'gui' on the MATLAB desktop, +0014 % 'cli' in a terminal, 'log' on a -batch or deployed session, and no +0015 % output during unit tests. +0016 % - 'gui' : always use a MATLAB waitbar. +0017 % - 'cli' : always use the animated command-line bar. +0018 % - 'log' : always print plain milestone lines, which are readable in +0019 % redirected logs and on headless servers. +0020 % - 'none' or 'off' : never show progress. +0021 % +0022 % Examples +0023 % -------- +0024 % setRavenProgress('log'); % for example on a compute cluster +0025 % setRavenProgress('off'); % silence all progress reporting +0026 % setRavenProgress('auto'); % restore the default behaviour +0027 % +0028 % See also +0029 % -------- +0030 % progressReport +0031 +0032 if nargin < 1 || isempty(mode) +0033 mode = 'auto'; +0034 end +0035 mode = lower(char(mode)); +0036 if strcmp(mode,'off') +0037 mode = 'none'; +0038 end +0039 if ~ismember(mode,{'auto','gui','cli','log','none'}) +0040 error('Unknown progress mode "%s". Use auto, gui, cli, log or off.', mode); +0041 end +0042 setpref('RAVEN','progressBar',mode); +0043 end
    Generated by m2html © 2005
    \ No newline at end of file From 68a6e60147110f124899d57add7d5df8530684ae Mon Sep 17 00:00:00 2001 From: Eduard Kerkhoven Date: Sat, 31 Oct 2026 17:48:34 +0100 Subject: [PATCH 40/48] fix: four bugs found while rewriting the help blocks, corrected examples and comments (#17) - `renameModelGenes` reads the gene table from a file path (the file name was passed to `%s` as a table). - `getIndexes` with type `metnames` and `returnLogical` true raises `RAVEN:badInput` (each name can match several metabolites). - `checkProduction` returns a model with excretion reactions only when `checkNeededForProduction` is true. - `findGeneDeletions` returns `grRatioMuts` with one entry per gene pair for double deletions. - `checkRaven` names the offline bundle `RAVEN--full.zip`. The examples of `gapReport`, `fitTasks`, `printFluxes` and `predictLocalization` use the documented arguments. The comments of `simplifyModel`, `setExchangeBounds`, `closeModel` and `ftINIT` state the current behaviour, and the help of `getModelFromKEGG` states which reactions the keep options retain. Each fix has a function test. --- CHANGELOG.md | 25 ++++++++++++++++++++++ checkRaven.m | 2 +- src/INIT/ftINIT.m | 8 +++---- src/analysis/findGeneDeletions.m | 2 +- src/curation/renameModelGenes.m | 9 ++++---- src/gapfilling/checkProduction.m | 13 ++++++----- src/gapfilling/fitTasks.m | 4 ++-- src/gapfilling/gapReport.m | 5 +++-- src/localization/predictLocalization.m | 3 ++- src/manipulation/closeModel.m | 14 ++++-------- src/manipulation/setExchangeBounds.m | 6 ++---- src/manipulation/simplifyModel.m | 7 +++--- src/queries/getIndexes.m | 9 +++++++- src/queries/printFluxes.m | 4 ++-- src/reconstruction/kegg/getModelFromKEGG.m | 4 +++- tests/function_tests/tAnalysis.m | 18 ++++++++++++++++ tests/function_tests/tGapfilling.m | 22 +++++++++++++++++++ tests/function_tests/tGenomeData.m | 19 ++++++++++++++++ tests/function_tests/tQueries.m | 13 +++++++++++ 19 files changed, 146 insertions(+), 41 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb5d234a..3f3dddaf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,27 @@ * `gapFillTopological`: without `targets` and without an objective reaction it raises `RAVEN:badInput` (was: an error without identifier). Help states the default seeds and targets. +* `checkProduction` with `checkNeededForProduction`: the returned `model` holds excretion + reactions only (was: the excretion reaction of the last metabolite in `notProduced` was + returned as an uptake reaction). This matters for code that uses the fifth output. + +### Curation + +* `renameModelGenes` with a file path as `geneTable`: the table is read and the genes are + renamed (was: an error `MATLAB:invalidConversion` when the message that names the file + was printed). + +### Queries + +* `getIndexes` with type `'metnames'` and `returnLogical` true: raises `RAVEN:badInput` (was: + the error "Unable to use a value of type cell as an index"). A name can match several + metabolites, so no single logical vector is defined. + +### Analysis + +* `findGeneDeletions` with `'dgd'`: `grRatioMuts` has one element per gene pair, as the help + states (was: twice as many, the second half zero). This matters for code that indexes + `grRatioMuts` by the number of its elements. ### Context-specific models (ftINIT / tINIT) @@ -57,6 +78,10 @@ case, `&`, `&&`, `|`, `||`, operators as whole words, so `G1&G2` is one gene id), and that a malformed rule raises `RAVEN:badGrRule`. +* `checkRaven`: when the binaries cannot be downloaded, the message names the + `RAVEN--full.zip` bundle attached to each release for an offline install (was: + "the `*-binaries` RAVEN release"). + ### Documentation * Help blocks of 176 files are rewritten in impersonal, present-tense wording with British spelling; the code is unchanged. Statements that differed from the code are corrected, for example `simplifyModel` `deleteDuplicates` (first occurrence kept), `removeBadRxns` `refModel`, `replaceMets` outputs, `getMinNrFluxes` `params`, `checkRaven` `developMode` and the `downloadRavenBinaries` offline bundle. `reverseRxns` has the RAVEN 3 help structure. diff --git a/checkRaven.m b/checkRaven.m index 380c1e2f..d207bec2 100755 --- a/checkRaven.m +++ b/checkRaven.m @@ -257,7 +257,7 @@ downloadRavenBinaries({'blast+','diamond','hmmer'}); catch ME printOrange([' Could not download binaries from toolbox-assets: ' ME.message '\n' ... - ' (offline? fetch the "*-binaries" RAVEN release for an offline install)\n']); + ' (offline? install from the RAVEN--full.zip bundle attached to each RAVEN release)\n']); end % Run each bundled binary with a version/help flag to confirm it % executes on this system, rather than running the full test cases. diff --git a/src/INIT/ftINIT.m b/src/INIT/ftINIT.m index 6b5ba6e4..a81496c4 100644 --- a/src/INIT/ftINIT.m +++ b/src/INIT/ftINIT.m @@ -285,10 +285,10 @@ lastError = []; if ~isempty(proveAbsGap) %One solve proven to this fixed absolute gap, replacing the relative-gap - %escalation below. The escalation's final near-zero-objective run otherwise - %accepts an arbitrary within-gap incumbent -- measured on genome-scale - %Human-GEM to cost 2.0-4.0 objective units (raven-toolbox's prove_abs_gap, - %raven-gecko-parity#104). Pairs naturally with resolveTies. + %escalation below. The final near-zero-objective run of the escalation + %accepts an arbitrary incumbent within the gap; on genome-scale Human-GEM + %the step 1 objective of the escalation ended 4.0 to 12.0 units below the + %proven optimum (raven-toolbox's prove_abs_gap, raven-gecko-parity#104). if ~isempty(stp.MILPParams) && isfield(stp.MILPParams{1}, 'TimeLimit') params = struct('TimeLimit', stp.MILPParams{1}.TimeLimit); else diff --git a/src/analysis/findGeneDeletions.m b/src/analysis/findGeneDeletions.m index 9ad6d278..e944e2de 100755 --- a/src/analysis/findGeneDeletions.m +++ b/src/analysis/findGeneDeletions.m @@ -113,7 +113,7 @@ [~, I]=ismember(originalGenes(details==1),model.genes); genesToModify=nchoosek(I,2); genes=geneMapping(genesToModify); - grRatioMuts=zeros(1,numel(genes)); + grRatioMuts=zeros(1,size(genesToModify,1)); fluxes=sparse(numel(model.rxns),size(genesToModify,1)); for i=1:size(genesToModify,1) tempModel=removeGenes(model,genesToModify(i,:),false,false,false); diff --git a/src/curation/renameModelGenes.m b/src/curation/renameModelGenes.m index 9dc2a32f..12425541 100644 --- a/src/curation/renameModelGenes.m +++ b/src/curation/renameModelGenes.m @@ -80,14 +80,15 @@ % Load table if a file path was supplied if ischar(geneTable) || isstring(geneTable) - if ~isfile(geneTable) + tableFile = char(geneTable); + if ~isfile(tableFile) error('renameModelGenes:fileNotFound', ... - 'Table file not found: %s', geneTable); + 'Table file not found: %s', tableFile); end - geneTable = readtable(geneTable, 'Delimiter', '\t', 'FileType', 'text', ... + geneTable = readtable(tableFile, 'Delimiter', '\t', 'FileType', 'text', ... 'TextType', 'char'); fprintf('Loaded gene table from file: %s (%d rows)\n', ... - geneTable, height(geneTable)); + tableFile, height(geneTable)); end % Validate column names diff --git a/src/gapfilling/checkProduction.m b/src/gapfilling/checkProduction.m index 157bede6..0f5cec0c 100755 --- a/src/gapfilling/checkProduction.m +++ b/src/gapfilling/checkProduction.m @@ -52,11 +52,8 @@ % checkNeededForProduction is false. % model : struct % model structure with an excretion reaction added for each metabolite -% in the compartments of excretionFromCompartments. When -% checkNeededForProduction is true, the excretion reaction of the last -% metabolite in notProduced is returned as an uptake reaction, because -% the loop that flips each reaction into an uptake reaction does not -% reset the last one. +% in the compartments of excretionFromCompartments. All added reactions +% are excretion reactions, also when checkNeededForProduction is true. % % Examples % -------- @@ -104,6 +101,12 @@ %be produced now neededForProductionMat(i,:)=haveFlux(model,10^-5,addedRxns(notProduced)); end + if ~isempty(notProduced) + %Reset the last iteration, so that the returned model has excretion + %reactions only + lastCol=numel(model.rxns)-numel(addedRxns)+notProduced(end); + model.S(:,lastCol)=model.S(:,lastCol)*-1; + end %Calculate the smallest number of metabolites that must be connected to %make everything connected and return their names diff --git a/src/gapfilling/fitTasks.m b/src/gapfilling/fitTasks.m index b45beb94..6ea251a3 100755 --- a/src/gapfilling/fitTasks.m +++ b/src/gapfilling/fitTasks.m @@ -65,8 +65,8 @@ % % Examples % -------- -% [outModel, addedRxns]=fitTasks(model,refModel,inputFile,printOutput,... -% rxnScores,taskStructure,params); +% [outModel, addedRxns, failedTasks]=fitTasks(model,refModel,inputFile,... +% 'printOutput',false,'rxnScores',rxnScores,'params',params); p=parseRAVENargs(varargin, {'printOutput',true; ... 'rxnScores',[]; ... diff --git a/src/gapfilling/gapReport.m b/src/gapfilling/gapReport.m index f8a006d4..c5b1d1c8 100755 --- a/src/gapfilling/gapReport.m +++ b/src/gapfilling/gapReport.m @@ -69,8 +69,9 @@ % Examples % -------- % [noFluxRxns, noFluxRxnsRelaxed, subGraphs, notProducedMets, ... -% minToConnect, neededForProductionMat, connectedFromTemplates, ... -% addedFromTemplates] = gapReport(model, templateModels); +% minToConnect, neededForProductionMat, canProduceWithoutInput, ... +% canConsumeWithoutOutput, connectedFromTemplates, addedFromTemplates] = ... +% gapReport(model, 'templateModels', templateModels); p=parseRAVENargs(varargin, {'templateModels',[]}); templateModels=p.templateModels; diff --git a/src/localization/predictLocalization.m b/src/localization/predictLocalization.m index 05c1ca12..015360e2 100755 --- a/src/localization/predictLocalization.m +++ b/src/localization/predictLocalization.m @@ -64,7 +64,8 @@ % -------- % [outModel, geneLocalization, transportStruct, scores, removedRxns] = ... % predictLocalization(model, GSS, defaultCompartment, ... -% transportCost, maxTime, plotResults); +% 'transportCost', transportCost, 'maxTime', maxTime, ... +% 'plotResults', plotResults); p=parseRAVENargs(varargin, {'transportCost',[]; 'maxTime',[]; 'plotResults',[]}); transportCost=p.transportCost; diff --git a/src/manipulation/closeModel.m b/src/manipulation/closeModel.m index 5def2209..d3bea092 100755 --- a/src/manipulation/closeModel.m +++ b/src/manipulation/closeModel.m @@ -36,15 +36,9 @@ col=closedModel.S(:,i); %A boundary/exchange/sink/demand reaction has metabolites on only one %side, same definition as getExchangeRxns: no substrates or no - %products. The previous coefficient-magnitude rule (all nonzero - %coefficients summing to 1 in absolute value) missed a scaled - %single-metabolite sink such as "2 A =>" (sum 2) outright, and only - %matched a multi-metabolite one-sided reaction such as - %"0.5 A + 0.5 B =>" by coincidence, when its magnitudes summed to - %exactly 1 --- and even then mishandled it below, since find() on such - %a reaction returns more than one index and only the first metabolite - %was ever used to build the boundary metabolite, silently dropping the - %rest. + %products. The coefficient magnitudes do not matter, so a scaled sink + %such as "2 A =>" and a multi-metabolite one-sided reaction such as + %"0.5 A + 0.5 B =>" are both boundary reactions. if any(col>0) && any(col<0) continue end @@ -53,7 +47,7 @@ continue end %A single metabolite names and inherits properties from that - %metabolite, as before. Several metabolites on the one populated side + %metabolite. Several metabolites on the one populated side %have no single metabolite to name the boundary after or copy %properties from, so it is named after the reaction instead and left %without those properties. diff --git a/src/manipulation/setExchangeBounds.m b/src/manipulation/setExchangeBounds.m index dddc6d5c..a118a175 100755 --- a/src/manipulation/setExchangeBounds.m +++ b/src/manipulation/setExchangeBounds.m @@ -200,10 +200,8 @@ % check for any metabolites that are exchanged in more than one reaction. % metInd holds one entry per (metabolite,reaction) pair, so a metabolite -% involved in more than one exchange reaction is a repeated *value* in -% metInd, not a position past numel(unique(metInd)) -- comparing those two -% index spaces directly (as a previous version of this check did) compares -% positions in the unique list against indices into metInd itself. +% involved in more than one exchange reaction is a value that occurs more +% than once in metInd. The occurrences are counted per unique value. [uniqueMetInd,~,ic] = unique(metInd); counts = accumarray(ic,1); multiMetInd = exchMetInd(uniqueMetInd(counts>1)); diff --git a/src/manipulation/simplifyModel.m b/src/manipulation/simplifyModel.m index 464f4011..2031936b 100755 --- a/src/manipulation/simplifyModel.m +++ b/src/manipulation/simplifyModel.m @@ -90,9 +90,10 @@ end if deleteDuplicates==true - %Delete all but the last occurrence of duplicate reactions. The - %reactions must have the same bounds, reversibility, and objective - %coefficient to be regarded as duplicate + %Merge duplicate reactions into the first occurrence and delete the + %others. Reactions with the same stoichiometry and reversibility are + %duplicates; the merged reaction keeps the widest bounds and the largest + %objective coefficient (see contractModel) [reducedModel, rxnsToDelete, ~]=contractModel(reducedModel); deletedReactions=[deletedReactions; rxnsToDelete]; end diff --git a/src/queries/getIndexes.m b/src/queries/getIndexes.m index 61840547..71fb1c59 100755 --- a/src/queries/getIndexes.m +++ b/src/queries/getIndexes.m @@ -25,7 +25,8 @@ % -------------------- % returnLogical : logical % sets whether to return a logical array or an array with the indexes -% (default false). +% (default false). It cannot be true for type 'metnames', which raises +% the error RAVEN:badInput. % % Returns % ------- @@ -61,6 +62,12 @@ case 'genes' searchIn=model.genes; case 'metnames' + if returnLogical==true + error('RAVEN:badInput', ['The combination of type ''metnames'' and ' ... + 'returnLogical true is not supported, because each name can ' ... + 'match several metabolites. Use type ''mets'' or ''metcomps'' ' ... + 'for a logical vector.']); + end searchIn=model.metNames; case 'metcomps' % If provided as metaboliteName[comp], then index search is quite diff --git a/src/queries/printFluxes.m b/src/queries/printFluxes.m index fde3fb9a..41654d17 100755 --- a/src/queries/printFluxes.m +++ b/src/queries/printFluxes.m @@ -46,8 +46,8 @@ function printFluxes(model, fluxes, varargin) % % Examples % -------- -% printFluxes(model, fluxes, onlyExchange, cutOffFlux, outputFile, ... -% outputString, metaboliteList); +% printFluxes(model, fluxes, 'onlyExchange', false, 'cutOffFlux', 10^-6, ... +% 'outputFile', 'fluxes.txt', 'metaboliteList', {'glucose'}); p=parseRAVENargs(varargin, {'onlyExchange',true; 'cutOffFlux',10^-8; 'outputFile',[]; 'outputString',[]; 'metaboliteList',{}}); onlyExchange=p.onlyExchange; diff --git a/src/reconstruction/kegg/getModelFromKEGG.m b/src/reconstruction/kegg/getModelFromKEGG.m index 1b853173..f8712c21 100755 --- a/src/reconstruction/kegg/getModelFromKEGG.m +++ b/src/reconstruction/kegg/getModelFromKEGG.m @@ -24,7 +24,9 @@ % Returns % ------- % model : struct -% a model structure with all reactions and the metabolites used in them. +% a model structure with the reactions that the keep* options retain (by +% default all except those labelled as "general reaction") and the +% metabolites used in them. % KOModel : struct % a model structure representing KEGG Orthology ids and their associated % genes. KO ids are stored as reactions. diff --git a/tests/function_tests/tAnalysis.m b/tests/function_tests/tAnalysis.m index 9b49a086..e855d9e4 100644 --- a/tests/function_tests/tAnalysis.m +++ b/tests/function_tests/tAnalysis.m @@ -122,6 +122,24 @@ function findGeneDeletionsComputesGrRatio(testCase) testCase.verifyGreaterThan(max(grRatioMuts), 0); end + function findGeneDeletionsDoubleDeletionSizes(testCase) + % Three genes with solvable single deletions give three gene pairs. + m = struct(); + m.id = 'toy'; m.name = 'toy'; + m.rxns = {'EX_a'; 'R1'; 'R2'; 'R3'; 'BIO'; 'EX_p'}; m.rxnNames = m.rxns; + m.mets = {'a'; 'b'; 'p'}; m.metNames = m.mets; m.metComps = ones(3,1); + m.comps = {'c'}; m.compNames = {'c'}; + m.S = sparse([1 -1 -1 -1 0 0; 0 1 1 0 -1 0; 0 0 0 1 0 -1]); + m.lb = zeros(6,1); m.ub = [10; 1000; 1000; 1000; 1000; 1000]; + m.rev = zeros(6,1); m.c = [0; 0; 0; 0; 1; 0]; m.b = zeros(3,1); + m.genes = {'g1'; 'g2'; 'g3'}; m.grRules = {''; 'g1'; 'g2'; 'g3'; ''; ''}; + m.rxnGeneMat = sparse([0 0 0; 1 0 0; 0 1 0; 0 0 1; 0 0 0; 0 0 0]); + evalc('[genes, fluxes, ~, ~, grRatioMuts] = findGeneDeletions(m, ''dgd'');'); + testCase.verifySize(genes, [3 2]); + testCase.verifySize(fluxes, [numel(m.rxns) 3]); + testCase.verifySize(grRatioMuts, [1 3]); + end + function traceFluxPathReturnsCells(testCase) testCase.assumeSolver('solveLP'); sol = solveLP(testCase.model); diff --git a/tests/function_tests/tGapfilling.m b/tests/function_tests/tGapfilling.m index b4492fa6..04533ab8 100644 --- a/tests/function_tests/tGapfilling.m +++ b/tests/function_tests/tGapfilling.m @@ -26,6 +26,28 @@ function checkProductionReturnsIndices(testCase) testCase.verifyClass(notProduced, 'double'); end + function checkProductionReturnsExcretionReactionsOnly(testCase) + % Neither a nor c has a source, so b and d, and a and c, cannot be + % produced. The returned model holds an excretion reaction for + % each metabolite, with or without checkNeededForProduction. + m = struct(); + m.id = 'toy'; m.name = 'toy'; + m.rxns = {'R1'; 'R2'}; m.rxnNames = m.rxns; + m.mets = {'a'; 'b'; 'c'; 'd'}; m.metNames = m.mets; m.metComps = ones(4,1); + m.comps = {'c'}; m.compNames = {'c'}; + m.S = sparse([-1 0; 1 0; 0 -1; 0 1]); + m.lb = [0; 0]; m.ub = [1000; 1000]; m.rev = [0; 0]; m.c = [0; 0]; + m.b = zeros(4,1); + m.genes = {}; m.grRules = {''; ''}; m.rxnGeneMat = sparse(2,0); + + [~, ~, ~, ~, plain] = checkProduction(m, 'printDetails', false); + [notProduced, ~, ~, ~, checked] = checkProduction(m, ... + 'checkNeededForProduction', true, 'printDetails', false); + testCase.verifyEqual(notProduced, (1:4)'); + testCase.verifyEqual(full(checked.S), full(plain.S)); + testCase.verifyTrue(all(full(sum(checked.S(:, 3:end), 1)) < 0)); + end + function findLeakMetaboliteProduceReturnsSolution(testCase) evalc('[sol, metabolite] = findLeakMetabolite(testCase.model, ''produce'');'); testCase.verifyNotEmpty(sol); diff --git a/tests/function_tests/tGenomeData.m b/tests/function_tests/tGenomeData.m index 06855ece..d1dddff7 100644 --- a/tests/function_tests/tGenomeData.m +++ b/tests/function_tests/tGenomeData.m @@ -77,6 +77,25 @@ function renameModelGenesRenamesGenesAndGrRules(testCase) testCase.verifyEqual(size(m2.rxnGeneMat,2), numel(m2.genes)); end + function renameModelGenesReadsTableFromFilePath(testCase) + model.id = 'test'; + model.rxns = {'r1'; 'r2'}; + model.genes = {'LT1'; 'LT2'}; + model.grRules = {'LT1'; 'LT1 or LT2'}; + model.rxnGeneMat = sparse([1 0; 1 1]); + + T = table({'LT1'; 'LT2'}, {'AAA'; 'BBB'}, ... + 'VariableNames', {'locus_tag', 'gene_name'}); + tsvFile = [tempname '.tsv']; + writetable(T, tsvFile, 'FileType', 'text', 'Delimiter', '\t'); + testCase.addTeardown(@() delete(tsvFile)); + output = evalc('m2 = renameModelGenes(model, tsvFile, ''locus_tag'', ''gene_name'');'); + + testCase.verifySubstring(output, tsvFile); + testCase.verifyEqual(sort(m2.genes), {'AAA'; 'BBB'}); + testCase.verifyEqual(m2.grRules, {'AAA'; 'AAA or BBB'}); + end + function downloadGenomeDataDownloads(testCase) % Network test: download a small genome from NCBI Datasets. % Skipped when there is no internet connection. diff --git a/tests/function_tests/tQueries.m b/tests/function_tests/tQueries.m index 7e918050..7b146503 100644 --- a/tests/function_tests/tQueries.m +++ b/tests/function_tests/tQueries.m @@ -322,6 +322,19 @@ function getIndexesLogical(testCase) testCase.verifyEqual(find(lg), 1); end + function getIndexesMetnamesReturnLogicalErrors(testCase) + name = testCase.model.metNames(1); + testCase.verifyError( ... + @() getIndexes(testCase.model, name, 'metnames', 'returnLogical', true), ... + 'RAVEN:badInput'); + try + getIndexes(testCase.model, name, 'metnames', 'returnLogical', true); + catch e + testCase.verifySubstring(e.message, 'metnames'); + testCase.verifySubstring(e.message, 'returnLogical'); + end + end + function getIndexesLogicalMaskAllTrueReturnsIndices(testCase) % A logical all-true mask of length n must return numeric [1..n], % not be passed through as a logical array: this requires an From 874448cbfb4b9ee0c806a25eae6bb43ba8b38378 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 31 Oct 2026 16:50:35 +0000 Subject: [PATCH 41/48] doc: regenerate HTML documentation for 68a6e60 [skip ci] --- docs/html/INIT/ftINIT.html | 8 +- docs/html/analysis/findGeneDeletions.html | 2 +- docs/html/curation/renameModelGenes.html | 323 +-- docs/html/gapfilling/checkProduction.html | 250 +- docs/html/gapfilling/fitTasks.html | 8 +- docs/html/gapfilling/gapReport.html | 192 +- .../localization/predictLocalization.html | 2168 +++++++++-------- docs/html/manipulation/closeModel.html | 206 +- docs/html/manipulation/setExchangeBounds.html | 64 +- docs/html/manipulation/simplifyModel.html | 501 ++-- docs/html/queries/getIndexes.html | 256 +- docs/html/queries/printFluxes.html | 8 +- .../reconstruction/kegg/getModelFromKEGG.html | 102 +- 13 files changed, 2049 insertions(+), 2039 deletions(-) diff --git a/docs/html/INIT/ftINIT.html b/docs/html/INIT/ftINIT.html index 4ba1c3e0..3a85612f 100644 --- a/docs/html/INIT/ftINIT.html +++ b/docs/html/INIT/ftINIT.html @@ -331,10 +331,10 @@

    SOURCE CODE ^if ~isempty(proveAbsGap) 0287 %One solve proven to this fixed absolute gap, replacing the relative-gap -0288 %escalation below. The escalation's final near-zero-objective run otherwise -0289 %accepts an arbitrary within-gap incumbent -- measured on genome-scale -0290 %Human-GEM to cost 2.0-4.0 objective units (raven-toolbox's prove_abs_gap, -0291 %raven-gecko-parity#104). Pairs naturally with resolveTies. +0288 %escalation below. The final near-zero-objective run of the escalation +0289 %accepts an arbitrary incumbent within the gap; on genome-scale Human-GEM +0290 %the step 1 objective of the escalation ended 4.0 to 12.0 units below the +0291 %proven optimum (raven-toolbox's prove_abs_gap, raven-gecko-parity#104). 0292 if ~isempty(stp.MILPParams) && isfield(stp.MILPParams{1}, 'TimeLimit') 0293 params = struct('TimeLimit', stp.MILPParams{1}.TimeLimit); 0294 else diff --git a/docs/html/analysis/findGeneDeletions.html b/docs/html/analysis/findGeneDeletions.html index d100eed3..fa94b4df 100644 --- a/docs/html/analysis/findGeneDeletions.html +++ b/docs/html/analysis/findGeneDeletions.html @@ -211,7 +211,7 @@

    SOURCE CODE ^for i=1:size(genesToModify,1) 0119 tempModel=removeGenes(model,genesToModify(i,:),false,false,false); diff --git a/docs/html/curation/renameModelGenes.html b/docs/html/curation/renameModelGenes.html index 7fbd3bef..d6595a25 100644 --- a/docs/html/curation/renameModelGenes.html +++ b/docs/html/curation/renameModelGenes.html @@ -180,167 +180,168 @@

    SOURCE CODE ^% Load table if a file path was supplied 0082 if ischar(geneTable) || isstring(geneTable) -0083 if ~isfile(geneTable) -0084 error('renameModelGenes:fileNotFound', ... -0085 'Table file not found: %s', geneTable); -0086 end -0087 geneTable = readtable(geneTable, 'Delimiter', '\t', 'FileType', 'text', ... -0088 'TextType', 'char'); -0089 fprintf('Loaded gene table from file: %s (%d rows)\n', ... -0090 geneTable, height(geneTable)); -0091 end -0092 -0093 % Validate column names -0094 if ~ismember(fromCol, geneTable.Properties.VariableNames) -0095 error('renameModelGenes:badColumn', ... -0096 'Column "%s" not found in geneTable.\nAvailable columns: %s', ... -0097 fromCol, strjoin(geneTable.Properties.VariableNames, ', ')); -0098 end -0099 if ~ismember(toCol, geneTable.Properties.VariableNames) -0100 error('renameModelGenes:badColumn', ... -0101 'Column "%s" not found in geneTable.\nAvailable columns: %s', ... -0102 toCol, strjoin(geneTable.Properties.VariableNames, ', ')); -0103 end -0104 -0105 % Build mapping: oldName → newName -0106 % Use only rows where both fromCol and toCol are non-empty. If a fromCol value maps to -0107 % multiple toCol values (isoforms), take the first non-empty one — gene names are -0108 % per-gene, not per-isoform. -0109 -0110 fromVals = geneTable.(fromCol); -0111 toVals = geneTable.(toCol); -0112 -0113 % Convert to char cell if needed -0114 if ~iscell(fromVals), fromVals = cellstr(fromVals); end -0115 if ~iscell(toVals), toVals = cellstr(toVals); end -0116 -0117 % Build map (first occurrence wins for duplicates). Only genes with a -0118 % non-empty toCol value are mapped; the rest are left unchanged. -0119 nameMap = containers.Map('KeyType', 'char', 'ValueType', 'char'); -0120 for i = 1:numel(fromVals) -0121 k = strtrim(fromVals{i}); -0122 v = strtrim(toVals{i}); -0123 if ~isempty(k) && ~isempty(v) && ~isKey(nameMap, k) -0124 nameMap(k) = v; -0125 end -0126 end -0127 -0128 % Standardize grRules before renaming to ensure consistent formatting for accurate replacement. -0129 fprintf('Removing unnecessary outer parentheses ...\n'); -0130 model.grRules = removeUnnecessaryParentheses(model.grRules); -0131 -0132 fprintf('Standardizing grRules ...\n'); -0133 [model.grRules, ~, indexes2check] = standardizeGrRules(model, true); -0134 if ~isempty(indexes2check) -0135 warning('renameModelGenes:potentialErrors', ... -0136 '%d grRules have potential issues. Review them before proceeding.', ... -0137 numel(indexes2check)); -0138 end -0139 -0140 % Rename model.genes; genes without a mapping are left unchanged -0141 nGenes = numel(model.genes); -0142 notFound = {}; -0143 for i = 1:nGenes -0144 oldName = model.genes{i}; -0145 if isKey(nameMap, oldName) -0146 model.genes{i} = nameMap(oldName); -0147 else -0148 notFound{end+1} = oldName; %#ok<AGROW> -0149 end -0150 end -0151 -0152 % Report genes that had no mapping and were left unchanged -0153 if ~isempty(notFound) -0154 warning('renameModelGenes:noMapping', ... -0155 '%d gene(s) in the model had no mapping in column "%s" and were left unchanged:\n %s', ... -0156 numel(notFound), fromCol, strjoin(unique(notFound), ', ')); -0157 end -0158 -0159 % Rename genes inside grRules Sort keys longest-first to avoid partial substitutions -0160 % (e.g. replace 'gene10' before 'gene1'). -0161 allKeys = keys(nameMap); -0162 allVals = values(nameMap); -0163 [~, order] = sort(cellfun(@numel, allKeys), 'descend'); -0164 allKeys = allKeys(order); -0165 allVals = allVals(order); -0166 -0167 nRxns = numel(model.grRules); -0168 for i = 1:nRxns -0169 rule = model.grRules{i}; -0170 if isempty(rule) -0171 continue -0172 end -0173 for j = 1:numel(allKeys) -0174 oldG = allKeys{j}; -0175 newG = allVals{j}; -0176 % Use word-boundary replacement: only match oldG when surrounded -0177 % by non-word characters (spaces, parentheses, start/end of string) -0178 % to avoid replacing 'gene1' inside 'gene10'. -0179 rule = regexprep(rule, ['(?<![^\s()])' regexptranslate('escape', oldG) ... -0180 '(?![^\s()])'], newG); -0181 end -0182 model.grRules{i} = rule; -0183 end -0184 -0185 % Rebuild rxnGeneMat to match the updated gene list -0186 fprintf('Rebuilding rxnGeneMat ...\n'); -0187 [model.grRules, model.rxnGeneMat, ~] = standardizeGrRules(model, true); -0188 -0189 fprintf('Done. %d genes renamed, %d left unchanged.\n', ... -0190 nGenes - numel(notFound), numel(notFound)); -0191 end -0192 -0193 %-------------------------------------------------------------------------- -0194 % Helper functions -0195 -0196 function grRules = removeUnnecessaryParentheses(grRules) -0197 % Remove unnecessary outer parentheses from grRules. -0198 % e.g., '(A and (B or C))' → 'A and (B or C)' -0199 % '(A or B)' → 'A or B' -0200 % but preserves '(A and B) or C' (outer parens are needed here) -0201 % -0202 % Checks if the opening parenthesis at position 1 closes before the end -0203 % of the string. If yes, outer parens are necessary. If no, they can be -0204 % removed recursively. -0205 -0206 nRxns = numel(grRules); -0207 for i = 1:nRxns -0208 rule = grRules{i}; -0209 if isempty(rule) -0210 continue -0211 end -0212 -0213 % Recursively remove outer parentheses while they are unnecessary -0214 while length(rule) >= 2 && rule(1) == '(' && rule(end) == ')' -0215 % Count brackets to see when the opening paren closes -0216 parenCount = 0; -0217 closedEarly = false; -0218 -0219 for j = 1:length(rule) -0220 if rule(j) == '(' -0221 parenCount = parenCount + 1; -0222 elseif rule(j) == ')' -0223 parenCount = parenCount - 1; -0224 % If it reaches 0 before the end, outer parens are necessary -0225 if parenCount == 0 && j < length(rule) -0226 closedEarly = true; -0227 break -0228 end -0229 end -0230 end -0231 -0232 if closedEarly -0233 % Opening paren closes early → outer parens are needed -0234 break -0235 else -0236 % Opening paren only closes at the end → they're unnecessary -0237 rule = rule(2:end-1); -0238 end -0239 end -0240 -0241 grRules{i} = rule; -0242 end -0243 end +0083 tableFile = char(geneTable); +0084 if ~isfile(tableFile) +0085 error('renameModelGenes:fileNotFound', ... +0086 'Table file not found: %s', tableFile); +0087 end +0088 geneTable = readtable(tableFile, 'Delimiter', '\t', 'FileType', 'text', ... +0089 'TextType', 'char'); +0090 fprintf('Loaded gene table from file: %s (%d rows)\n', ... +0091 tableFile, height(geneTable)); +0092 end +0093 +0094 % Validate column names +0095 if ~ismember(fromCol, geneTable.Properties.VariableNames) +0096 error('renameModelGenes:badColumn', ... +0097 'Column "%s" not found in geneTable.\nAvailable columns: %s', ... +0098 fromCol, strjoin(geneTable.Properties.VariableNames, ', ')); +0099 end +0100 if ~ismember(toCol, geneTable.Properties.VariableNames) +0101 error('renameModelGenes:badColumn', ... +0102 'Column "%s" not found in geneTable.\nAvailable columns: %s', ... +0103 toCol, strjoin(geneTable.Properties.VariableNames, ', ')); +0104 end +0105 +0106 % Build mapping: oldName → newName +0107 % Use only rows where both fromCol and toCol are non-empty. If a fromCol value maps to +0108 % multiple toCol values (isoforms), take the first non-empty one — gene names are +0109 % per-gene, not per-isoform. +0110 +0111 fromVals = geneTable.(fromCol); +0112 toVals = geneTable.(toCol); +0113 +0114 % Convert to char cell if needed +0115 if ~iscell(fromVals), fromVals = cellstr(fromVals); end +0116 if ~iscell(toVals), toVals = cellstr(toVals); end +0117 +0118 % Build map (first occurrence wins for duplicates). Only genes with a +0119 % non-empty toCol value are mapped; the rest are left unchanged. +0120 nameMap = containers.Map('KeyType', 'char', 'ValueType', 'char'); +0121 for i = 1:numel(fromVals) +0122 k = strtrim(fromVals{i}); +0123 v = strtrim(toVals{i}); +0124 if ~isempty(k) && ~isempty(v) && ~isKey(nameMap, k) +0125 nameMap(k) = v; +0126 end +0127 end +0128 +0129 % Standardize grRules before renaming to ensure consistent formatting for accurate replacement. +0130 fprintf('Removing unnecessary outer parentheses ...\n'); +0131 model.grRules = removeUnnecessaryParentheses(model.grRules); +0132 +0133 fprintf('Standardizing grRules ...\n'); +0134 [model.grRules, ~, indexes2check] = standardizeGrRules(model, true); +0135 if ~isempty(indexes2check) +0136 warning('renameModelGenes:potentialErrors', ... +0137 '%d grRules have potential issues. Review them before proceeding.', ... +0138 numel(indexes2check)); +0139 end +0140 +0141 % Rename model.genes; genes without a mapping are left unchanged +0142 nGenes = numel(model.genes); +0143 notFound = {}; +0144 for i = 1:nGenes +0145 oldName = model.genes{i}; +0146 if isKey(nameMap, oldName) +0147 model.genes{i} = nameMap(oldName); +0148 else +0149 notFound{end+1} = oldName; %#ok<AGROW> +0150 end +0151 end +0152 +0153 % Report genes that had no mapping and were left unchanged +0154 if ~isempty(notFound) +0155 warning('renameModelGenes:noMapping', ... +0156 '%d gene(s) in the model had no mapping in column "%s" and were left unchanged:\n %s', ... +0157 numel(notFound), fromCol, strjoin(unique(notFound), ', ')); +0158 end +0159 +0160 % Rename genes inside grRules Sort keys longest-first to avoid partial substitutions +0161 % (e.g. replace 'gene10' before 'gene1'). +0162 allKeys = keys(nameMap); +0163 allVals = values(nameMap); +0164 [~, order] = sort(cellfun(@numel, allKeys), 'descend'); +0165 allKeys = allKeys(order); +0166 allVals = allVals(order); +0167 +0168 nRxns = numel(model.grRules); +0169 for i = 1:nRxns +0170 rule = model.grRules{i}; +0171 if isempty(rule) +0172 continue +0173 end +0174 for j = 1:numel(allKeys) +0175 oldG = allKeys{j}; +0176 newG = allVals{j}; +0177 % Use word-boundary replacement: only match oldG when surrounded +0178 % by non-word characters (spaces, parentheses, start/end of string) +0179 % to avoid replacing 'gene1' inside 'gene10'. +0180 rule = regexprep(rule, ['(?<![^\s()])' regexptranslate('escape', oldG) ... +0181 '(?![^\s()])'], newG); +0182 end +0183 model.grRules{i} = rule; +0184 end +0185 +0186 % Rebuild rxnGeneMat to match the updated gene list +0187 fprintf('Rebuilding rxnGeneMat ...\n'); +0188 [model.grRules, model.rxnGeneMat, ~] = standardizeGrRules(model, true); +0189 +0190 fprintf('Done. %d genes renamed, %d left unchanged.\n', ... +0191 nGenes - numel(notFound), numel(notFound)); +0192 end +0193 +0194 %-------------------------------------------------------------------------- +0195 % Helper functions +0196 +0197 function grRules = removeUnnecessaryParentheses(grRules) +0198 % Remove unnecessary outer parentheses from grRules. +0199 % e.g., '(A and (B or C))' → 'A and (B or C)' +0200 % '(A or B)' → 'A or B' +0201 % but preserves '(A and B) or C' (outer parens are needed here) +0202 % +0203 % Checks if the opening parenthesis at position 1 closes before the end +0204 % of the string. If yes, outer parens are necessary. If no, they can be +0205 % removed recursively. +0206 +0207 nRxns = numel(grRules); +0208 for i = 1:nRxns +0209 rule = grRules{i}; +0210 if isempty(rule) +0211 continue +0212 end +0213 +0214 % Recursively remove outer parentheses while they are unnecessary +0215 while length(rule) >= 2 && rule(1) == '(' && rule(end) == ')' +0216 % Count brackets to see when the opening paren closes +0217 parenCount = 0; +0218 closedEarly = false; +0219 +0220 for j = 1:length(rule) +0221 if rule(j) == '(' +0222 parenCount = parenCount + 1; +0223 elseif rule(j) == ')' +0224 parenCount = parenCount - 1; +0225 % If it reaches 0 before the end, outer parens are necessary +0226 if parenCount == 0 && j < length(rule) +0227 closedEarly = true; +0228 break +0229 end +0230 end +0231 end +0232 +0233 if closedEarly +0234 % Opening paren closes early → outer parens are needed +0235 break +0236 else +0237 % Opening paren only closes at the end → they're unnecessary +0238 rule = rule(2:end-1); +0239 end +0240 end +0241 +0242 grRules{i} = rule; +0243 end +0244 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/gapfilling/checkProduction.html b/docs/html/gapfilling/checkProduction.html index 62003a79..8f1b46ec 100644 --- a/docs/html/gapfilling/checkProduction.html +++ b/docs/html/gapfilling/checkProduction.html @@ -80,11 +80,8 @@

    DESCRIPTION ^SOURCE CODE ^% checkNeededForProduction is false. 0053 % model : struct 0054 % model structure with an excretion reaction added for each metabolite -0055 % in the compartments of excretionFromCompartments. When -0056 % checkNeededForProduction is true, the excretion reaction of the last -0057 % metabolite in notProduced is returned as an uptake reaction, because -0058 % the loop that flips each reaction into an uptake reaction does not -0059 % reset the last one. -0060 % -0061 % Examples -0062 % -------- -0063 % [notProduced, notProducedNames, neededForProductionMat, minToConnect, model] = ... -0064 % checkProduction(model, checkNeededForProduction, excretionFromCompartments, printDetails); -0065 -0066 p=parseRAVENargs(varargin, {'checkNeededForProduction',false; ... -0067 'excretionFromCompartments',[]; ... -0068 'printDetails',true}); -0069 checkNeededForProduction=p.checkNeededForProduction; -0070 excretionFromCompartments=p.excretionFromCompartments; -0071 printDetails=p.printDetails; -0072 -0073 if isempty(excretionFromCompartments) -0074 excretionFromCompartments=model.comps; -0075 else -0076 excretionFromCompartments=convertCharArray(excretionFromCompartments); -0077 end -0078 -0079 %Add an exchange reaction for each metabolite in the allowed compartments -0080 %and see if it can carry a flux -0081 allowedMetIds=ismember(model.comps(model.metComps),excretionFromCompartments); -0082 allowedMetIndexes=find(allowedMetIds); -0083 [model, addedRxns]=addExchangeRxns(model,'out',allowedMetIndexes); -0084 -0085 canProduce=haveFlux(model,10^-5,addedRxns); -0086 -0087 notProduced=find(~canProduce); -0088 minToConnect={}; -0089 if checkNeededForProduction==true -0090 %For each of the metabolites that couldn't be produced allow uptake and -0091 %check which of the other metabolites that couldn't be produced that -0092 %can be produced -0093 neededForProductionMat=false(numel(notProduced)); -0094 for i=1:numel(notProduced) -0095 %Add uptake for this metabolite -0096 if i>1 -0097 %Reset last iteration -0098 model.S(:,numel(model.rxns)-numel(addedRxns)+notProduced(i-1))=model.S(:,numel(model.rxns)-numel(addedRxns)+notProduced(i-1))*-1; -0099 end -0100 %Change the production reaction to an uptake reaction -0101 model.S(:,numel(model.rxns)-numel(addedRxns)+notProduced(i))=model.S(:,numel(model.rxns)-numel(addedRxns)+notProduced(i))*-1; -0102 -0103 %Test which of the metabolites that couldn't be produced that can -0104 %be produced now -0105 neededForProductionMat(i,:)=haveFlux(model,10^-5,addedRxns(notProduced)); -0106 end -0107 %Calculate the smallest number of metabolites that must be connected to -0108 %make everything connected and return their names -0109 -0110 %The algorithm is relatively straight forward. It finds the metabolite -0111 %that connects the most unconnected metabolites (iteratively), adds it -0112 %and removes the now connected metabolites until all are connected. -0113 %This is not guaranteed to find the global minimum -0114 neededForProdTemp=neededForProductionMat; -0115 while 1==1 -0116 %Get all metabolites a metabolite makes connected -0117 totalConnected=false(size(neededForProdTemp)); -0118 for i=1:numel(notProduced) -0119 totalConnected(i,:)=neededForProdTemp(i,:); -0120 -0121 lastIter=0; -0122 while 1==1 -0123 [~, a]=find(neededForProdTemp(totalConnected(i,:),:)); -0124 totalConnected(i,a)=true; -0125 if numel(a)==lastIter -0126 break; %No more connections were possible -0127 else -0128 lastIter=numel(a); -0129 end -0130 end -0131 end -0132 [connections, mostConnected]=max(sum(totalConnected,2)); -0133 -0134 if connections>0 -0135 %Add the most connected metabolite to the list and remove all -0136 %metabolites that it's connected to -0137 metID=allowedMetIndexes(notProduced(mostConnected)); -0138 entry=[model.metNames{metID},'[',model.comps{model.metComps(metID)},'] (connects ' num2str(connections) ' metabolites)']; -0139 minToConnect=[minToConnect;entry]; -0140 neededForProdTemp(totalConnected(mostConnected,:),:)=false; -0141 neededForProdTemp(:,totalConnected(mostConnected,:))=false; -0142 else -0143 break; -0144 end -0145 end -0146 else -0147 neededForProductionMat=[]; -0148 end -0149 -0150 notProducedNames=strcat(model.metNames(allowedMetIndexes(notProduced)),'[',model.comps(model.metComps(allowedMetIndexes(notProduced))),']'); -0151 -0152 if printDetails==true -0153 fprintf('The following metabolites could not be produced:\n'); -0154 [notProducedNamesTemp,perm]=sort(notProducedNames); -0155 -0156 if checkNeededForProduction==true -0157 neededForProdTemp=neededForProductionMat(:,perm); -0158 neededForProdTemp=neededForProdTemp(perm,:); -0159 fprintf('\tIf the production of a metabolite is dependent on some other metabolites then those are printed under the name\n\n'); -0160 end -0161 for i=1:numel(notProducedNamesTemp) -0162 fprintf([notProducedNamesTemp{i} '\n']); -0163 neededForProdTemp(i,i)=false; %Not neat to do this here. Prevent printing itself -0164 if checkNeededForProduction==true -0165 enablesProduction=find(neededForProdTemp(:,i)); -0166 if any(enablesProduction) -0167 for j=1:numel(enablesProduction) -0168 fprintf(['\t' notProducedNamesTemp{enablesProduction(j)} '\n']); -0169 end -0170 end -0171 end -0172 end -0173 end -0174 end +0055 % in the compartments of excretionFromCompartments. All added reactions +0056 % are excretion reactions, also when checkNeededForProduction is true. +0057 % +0058 % Examples +0059 % -------- +0060 % [notProduced, notProducedNames, neededForProductionMat, minToConnect, model] = ... +0061 % checkProduction(model, checkNeededForProduction, excretionFromCompartments, printDetails); +0062 +0063 p=parseRAVENargs(varargin, {'checkNeededForProduction',false; ... +0064 'excretionFromCompartments',[]; ... +0065 'printDetails',true}); +0066 checkNeededForProduction=p.checkNeededForProduction; +0067 excretionFromCompartments=p.excretionFromCompartments; +0068 printDetails=p.printDetails; +0069 +0070 if isempty(excretionFromCompartments) +0071 excretionFromCompartments=model.comps; +0072 else +0073 excretionFromCompartments=convertCharArray(excretionFromCompartments); +0074 end +0075 +0076 %Add an exchange reaction for each metabolite in the allowed compartments +0077 %and see if it can carry a flux +0078 allowedMetIds=ismember(model.comps(model.metComps),excretionFromCompartments); +0079 allowedMetIndexes=find(allowedMetIds); +0080 [model, addedRxns]=addExchangeRxns(model,'out',allowedMetIndexes); +0081 +0082 canProduce=haveFlux(model,10^-5,addedRxns); +0083 +0084 notProduced=find(~canProduce); +0085 minToConnect={}; +0086 if checkNeededForProduction==true +0087 %For each of the metabolites that couldn't be produced allow uptake and +0088 %check which of the other metabolites that couldn't be produced that +0089 %can be produced +0090 neededForProductionMat=false(numel(notProduced)); +0091 for i=1:numel(notProduced) +0092 %Add uptake for this metabolite +0093 if i>1 +0094 %Reset last iteration +0095 model.S(:,numel(model.rxns)-numel(addedRxns)+notProduced(i-1))=model.S(:,numel(model.rxns)-numel(addedRxns)+notProduced(i-1))*-1; +0096 end +0097 %Change the production reaction to an uptake reaction +0098 model.S(:,numel(model.rxns)-numel(addedRxns)+notProduced(i))=model.S(:,numel(model.rxns)-numel(addedRxns)+notProduced(i))*-1; +0099 +0100 %Test which of the metabolites that couldn't be produced that can +0101 %be produced now +0102 neededForProductionMat(i,:)=haveFlux(model,10^-5,addedRxns(notProduced)); +0103 end +0104 if ~isempty(notProduced) +0105 %Reset the last iteration, so that the returned model has excretion +0106 %reactions only +0107 lastCol=numel(model.rxns)-numel(addedRxns)+notProduced(end); +0108 model.S(:,lastCol)=model.S(:,lastCol)*-1; +0109 end +0110 %Calculate the smallest number of metabolites that must be connected to +0111 %make everything connected and return their names +0112 +0113 %The algorithm is relatively straight forward. It finds the metabolite +0114 %that connects the most unconnected metabolites (iteratively), adds it +0115 %and removes the now connected metabolites until all are connected. +0116 %This is not guaranteed to find the global minimum +0117 neededForProdTemp=neededForProductionMat; +0118 while 1==1 +0119 %Get all metabolites a metabolite makes connected +0120 totalConnected=false(size(neededForProdTemp)); +0121 for i=1:numel(notProduced) +0122 totalConnected(i,:)=neededForProdTemp(i,:); +0123 +0124 lastIter=0; +0125 while 1==1 +0126 [~, a]=find(neededForProdTemp(totalConnected(i,:),:)); +0127 totalConnected(i,a)=true; +0128 if numel(a)==lastIter +0129 break; %No more connections were possible +0130 else +0131 lastIter=numel(a); +0132 end +0133 end +0134 end +0135 [connections, mostConnected]=max(sum(totalConnected,2)); +0136 +0137 if connections>0 +0138 %Add the most connected metabolite to the list and remove all +0139 %metabolites that it's connected to +0140 metID=allowedMetIndexes(notProduced(mostConnected)); +0141 entry=[model.metNames{metID},'[',model.comps{model.metComps(metID)},'] (connects ' num2str(connections) ' metabolites)']; +0142 minToConnect=[minToConnect;entry]; +0143 neededForProdTemp(totalConnected(mostConnected,:),:)=false; +0144 neededForProdTemp(:,totalConnected(mostConnected,:))=false; +0145 else +0146 break; +0147 end +0148 end +0149 else +0150 neededForProductionMat=[]; +0151 end +0152 +0153 notProducedNames=strcat(model.metNames(allowedMetIndexes(notProduced)),'[',model.comps(model.metComps(allowedMetIndexes(notProduced))),']'); +0154 +0155 if printDetails==true +0156 fprintf('The following metabolites could not be produced:\n'); +0157 [notProducedNamesTemp,perm]=sort(notProducedNames); +0158 +0159 if checkNeededForProduction==true +0160 neededForProdTemp=neededForProductionMat(:,perm); +0161 neededForProdTemp=neededForProdTemp(perm,:); +0162 fprintf('\tIf the production of a metabolite is dependent on some other metabolites then those are printed under the name\n\n'); +0163 end +0164 for i=1:numel(notProducedNamesTemp) +0165 fprintf([notProducedNamesTemp{i} '\n']); +0166 neededForProdTemp(i,i)=false; %Not neat to do this here. Prevent printing itself +0167 if checkNeededForProduction==true +0168 enablesProduction=find(neededForProdTemp(:,i)); +0169 if any(enablesProduction) +0170 for j=1:numel(enablesProduction) +0171 fprintf(['\t' notProducedNamesTemp{enablesProduction(j)} '\n']); +0172 end +0173 end +0174 end +0175 end +0176 end +0177 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/gapfilling/fitTasks.html b/docs/html/gapfilling/fitTasks.html index 5ffc8264..0aab1552 100644 --- a/docs/html/gapfilling/fitTasks.html +++ b/docs/html/gapfilling/fitTasks.html @@ -93,8 +93,8 @@

    DESCRIPTION ^CROSS-REFERENCE INFORMATION ^

    @@ -178,8 +178,8 @@

    SOURCE CODE ^% 0066 % Examples 0067 % -------- -0068 % [outModel, addedRxns]=fitTasks(model,refModel,inputFile,printOutput,... -0069 % rxnScores,taskStructure,params); +0068 % [outModel, addedRxns, failedTasks]=fitTasks(model,refModel,inputFile,... +0069 % 'printOutput',false,'rxnScores',rxnScores,'params',params); 0070 0071 p=parseRAVENargs(varargin, {'printOutput',true; ... 0072 'rxnScores',[]; ... diff --git a/docs/html/gapfilling/gapReport.html b/docs/html/gapfilling/gapReport.html index 80d02416..a58ce0e9 100644 --- a/docs/html/gapfilling/gapReport.html +++ b/docs/html/gapfilling/gapReport.html @@ -95,8 +95,9 @@

    DESCRIPTION ^CROSS-REFERENCE INFORMATION ^

    @@ -184,100 +185,101 @@

    SOURCE CODE ^% Examples 0070 % -------- 0071 % [noFluxRxns, noFluxRxnsRelaxed, subGraphs, notProducedMets, ... -0072 % minToConnect, neededForProductionMat, connectedFromTemplates, ... -0073 % addedFromTemplates] = gapReport(model, templateModels); -0074 -0075 p=parseRAVENargs(varargin, {'templateModels',[]}); -0076 templateModels=p.templateModels; -0077 -0078 if isempty(templateModels) -0079 templateModels=[]; -0080 connectedFromTemplates=[]; -0081 addedFromTemplates=[]; -0082 end -0083 -0084 %Escapes model text before it is spliced into an fprintf template, since -0085 %a literal "%" in e.g. a model id or metabolite name would otherwise be -0086 %misread as a format directive and swallow the remainder of the line. -0087 esc=@(s) strrep(s,'%','%%'); -0088 -0089 fprintf(['Gap analysis for ' esc(model.id) ' - ' esc(model.name) '\n\n']); -0090 if isfield(model,'unconstrained') -0091 closedModel=model; -0092 model=simplifyModel(model); -0093 else -0094 closedModel=model; -0095 [~,boundary]=getExchangeRxns(closedModel); -0096 closedModel.lb(boundary)=0; -0097 closedModel.ub(boundary)=0; -0098 end -0099 -0100 requireFeasible(model,'under its own bounds'); -0101 requireFeasible(closedModel,'once its boundary reactions are closed'); -0102 -0103 model2=model; -0104 model2.b=[model2.b inf(numel(model2.mets),1)]; -0105 I=haveFlux(model); -0106 noFluxRxns=model.rxns(~I); -0107 J=haveFlux(model2); -0108 noFluxRxnsRelaxed=model2.rxns(~J); -0109 bModel=removeReactions(model,~I,true,true); -0110 cModel=removeReactions(model2,~J,true,true); -0111 fprintf('***Overview\n'); -0112 fprintf([num2str(numel(model.rxns)-sum(I)) ' out of ' num2str(numel(model.rxns))... -0113 ' reactions cannot carry flux (' num2str(numel(model.rxns)-sum(J)) ' if net production of all metabolites is allowed)\n']); -0114 fprintf([num2str(numel(model.mets)-numel(bModel.mets)) ' out of ' num2str(numel(model.mets))... -0115 ' metabolites are unreachable (' num2str(numel(model.mets)-numel(cModel.mets)) ' if net production of all metabolites is allowed)\n']); -0116 -0117 fprintf('\n***Isolated subnetworks\n'); -0118 subGraphs=getAllSubGraphs(model); -0119 fprintf(['A total of ' num2str(size(subGraphs,2)) ' isolated sub-networks are present in the model\n']); -0120 for i=1:size(subGraphs,2) -0121 fprintf(['\t' num2str(i) '. ' num2str(sum(subGraphs(:,i))) ' metabolites\n']); -0122 end -0123 -0124 fprintf('\n***Metabolite connectivity\n'); -0125 [notProducedMets, ~, neededForProductionMat,minToConnect]=checkProduction(model,true,model.comps,false); -0126 fprintf(['To enable net production of all metabolites, a total of ' num2str(numel(minToConnect)) ' metabolites must be connected\n']); -0127 fprintf('Top 10 metabolites to connect:\n'); -0128 for i=1:min(10,numel(minToConnect)) -0129 fprintf(['\t' num2str(i) '. ' esc(minToConnect{i}) '\n']); -0130 end -0131 -0132 fprintf('\n***Mass balancing\n'); -0133 produced=canExchange(closedModel,'produce'); -0134 canProduceWithoutInput=closedModel.mets(produced); -0135 consumed=canExchange(closedModel,'consume'); -0136 canConsumeWithoutOutput=closedModel.mets(consumed); -0137 fprintf([num2str(numel(canConsumeWithoutOutput)) ' metabolites could be consumed without any outputs\n' num2str(numel(canProduceWithoutInput)) ' metabolites could be produced without any inputs\n']); -0138 -0139 if ~isempty(templateModels) -0140 fprintf('\n***Automated gap-filling\n'); -0141 [connectedFromTemplates, ~, addedFromTemplates]=fillGaps(model,templateModels); -0142 t=esc(templateModels{1}.id); -0143 for i=2:numel(templateModels) -0144 t=[t ', ' esc(templateModels{i}.id)]; -0145 end -0146 fprintf([num2str(numel(connectedFromTemplates)) ' unconnected reactions can be connected by including ' num2str(numel(addedFromTemplates)) ' reactions from\n' t '\n']); -0147 end +0072 % minToConnect, neededForProductionMat, canProduceWithoutInput, ... +0073 % canConsumeWithoutOutput, connectedFromTemplates, addedFromTemplates] = ... +0074 % gapReport(model, 'templateModels', templateModels); +0075 +0076 p=parseRAVENargs(varargin, {'templateModels',[]}); +0077 templateModels=p.templateModels; +0078 +0079 if isempty(templateModels) +0080 templateModels=[]; +0081 connectedFromTemplates=[]; +0082 addedFromTemplates=[]; +0083 end +0084 +0085 %Escapes model text before it is spliced into an fprintf template, since +0086 %a literal "%" in e.g. a model id or metabolite name would otherwise be +0087 %misread as a format directive and swallow the remainder of the line. +0088 esc=@(s) strrep(s,'%','%%'); +0089 +0090 fprintf(['Gap analysis for ' esc(model.id) ' - ' esc(model.name) '\n\n']); +0091 if isfield(model,'unconstrained') +0092 closedModel=model; +0093 model=simplifyModel(model); +0094 else +0095 closedModel=model; +0096 [~,boundary]=getExchangeRxns(closedModel); +0097 closedModel.lb(boundary)=0; +0098 closedModel.ub(boundary)=0; +0099 end +0100 +0101 requireFeasible(model,'under its own bounds'); +0102 requireFeasible(closedModel,'once its boundary reactions are closed'); +0103 +0104 model2=model; +0105 model2.b=[model2.b inf(numel(model2.mets),1)]; +0106 I=haveFlux(model); +0107 noFluxRxns=model.rxns(~I); +0108 J=haveFlux(model2); +0109 noFluxRxnsRelaxed=model2.rxns(~J); +0110 bModel=removeReactions(model,~I,true,true); +0111 cModel=removeReactions(model2,~J,true,true); +0112 fprintf('***Overview\n'); +0113 fprintf([num2str(numel(model.rxns)-sum(I)) ' out of ' num2str(numel(model.rxns))... +0114 ' reactions cannot carry flux (' num2str(numel(model.rxns)-sum(J)) ' if net production of all metabolites is allowed)\n']); +0115 fprintf([num2str(numel(model.mets)-numel(bModel.mets)) ' out of ' num2str(numel(model.mets))... +0116 ' metabolites are unreachable (' num2str(numel(model.mets)-numel(cModel.mets)) ' if net production of all metabolites is allowed)\n']); +0117 +0118 fprintf('\n***Isolated subnetworks\n'); +0119 subGraphs=getAllSubGraphs(model); +0120 fprintf(['A total of ' num2str(size(subGraphs,2)) ' isolated sub-networks are present in the model\n']); +0121 for i=1:size(subGraphs,2) +0122 fprintf(['\t' num2str(i) '. ' num2str(sum(subGraphs(:,i))) ' metabolites\n']); +0123 end +0124 +0125 fprintf('\n***Metabolite connectivity\n'); +0126 [notProducedMets, ~, neededForProductionMat,minToConnect]=checkProduction(model,true,model.comps,false); +0127 fprintf(['To enable net production of all metabolites, a total of ' num2str(numel(minToConnect)) ' metabolites must be connected\n']); +0128 fprintf('Top 10 metabolites to connect:\n'); +0129 for i=1:min(10,numel(minToConnect)) +0130 fprintf(['\t' num2str(i) '. ' esc(minToConnect{i}) '\n']); +0131 end +0132 +0133 fprintf('\n***Mass balancing\n'); +0134 produced=canExchange(closedModel,'produce'); +0135 canProduceWithoutInput=closedModel.mets(produced); +0136 consumed=canExchange(closedModel,'consume'); +0137 canConsumeWithoutOutput=closedModel.mets(consumed); +0138 fprintf([num2str(numel(canConsumeWithoutOutput)) ' metabolites could be consumed without any outputs\n' num2str(numel(canProduceWithoutInput)) ' metabolites could be produced without any inputs\n']); +0139 +0140 if ~isempty(templateModels) +0141 fprintf('\n***Automated gap-filling\n'); +0142 [connectedFromTemplates, ~, addedFromTemplates]=fillGaps(model,templateModels); +0143 t=esc(templateModels{1}.id); +0144 for i=2:numel(templateModels) +0145 t=[t ', ' esc(templateModels{i}.id)]; +0146 end +0147 fprintf([num2str(numel(connectedFromTemplates)) ' unconnected reactions can be connected by including ' num2str(numel(addedFromTemplates)) ' reactions from\n' t '\n']); 0148 end -0149 -0150 function requireFeasible(model,condition) -0151 %Errors, naming the forced-flux reactions, if the model is infeasible -0152 model.c(:)=0; -0153 sol=solveLP(model); -0154 if sol.stat~=-1 -0155 return -0156 end -0157 forced=model.rxns(model.lb>0 | model.ub<0); -0158 hint=''; -0159 if ~isempty(forced) -0160 hint=[' Reactions with a forced flux: ' strjoin(forced,', ') '.']; -0161 end -0162 error('RAVEN:infeasibleModel',['The model is infeasible ' condition ... -0163 ', so gapReport cannot analyse it.' strrep(hint,'%','%%') ... -0164 ' Set the bounds of ATP maintenance and any other forced flux to zero first.']); -0165 end +0149 end +0150 +0151 function requireFeasible(model,condition) +0152 %Errors, naming the forced-flux reactions, if the model is infeasible +0153 model.c(:)=0; +0154 sol=solveLP(model); +0155 if sol.stat~=-1 +0156 return +0157 end +0158 forced=model.rxns(model.lb>0 | model.ub<0); +0159 hint=''; +0160 if ~isempty(forced) +0161 hint=[' Reactions with a forced flux: ' strjoin(forced,', ') '.']; +0162 end +0163 error('RAVEN:infeasibleModel',['The model is infeasible ' condition ... +0164 ', so gapReport cannot analyse it.' strrep(hint,'%','%%') ... +0165 ' Set the bounds of ATP maintenance and any other forced flux to zero first.']); +0166 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/localization/predictLocalization.html b/docs/html/localization/predictLocalization.html index 200fc1f3..f7192815 100644 --- a/docs/html/localization/predictLocalization.html +++ b/docs/html/localization/predictLocalization.html @@ -90,7 +90,8 @@

    DESCRIPTION ^CROSS-REFERENCE INFORMATION ^

    @@ -173,1089 +174,1090 @@

    SOURCE CODE ^% -------- 0065 % [outModel, geneLocalization, transportStruct, scores, removedRxns] = ... 0066 % predictLocalization(model, GSS, defaultCompartment, ... -0067 % transportCost, maxTime, plotResults); -0068 -0069 p=parseRAVENargs(varargin, {'transportCost',[]; 'maxTime',[]; 'plotResults',[]}); -0070 transportCost=p.transportCost; -0071 maxTime=p.maxTime; -0072 plotResults=p.plotResults; -0073 -0074 if isempty(transportCost) -0075 transportCost=ones(numel(model.mets),1)*0.5; -0076 end -0077 if numel(transportCost)==1 -0078 transportCost=ones(numel(model.mets),1)*transportCost; -0079 end -0080 transportCost=transportCost(:); -0081 -0082 if numel(transportCost)~=numel(model.mets) -0083 EM='The vector of transport costs must have the same dimension as model.mets'; -0084 error('RAVEN:badInput', '%s', EM); -0085 end -0086 if isempty(maxTime) -0087 maxTime=15; -0088 end -0089 if isempty(plotResults) -0090 plotResults=false; -0091 end -0092 -0093 if isfield(model,'rxnComps') -0094 model=rmfield(model,'rxnComps'); -0095 EM='The model structure contains information about reaction compartmentalization. This is not supported by this function. The rxnComps field has been deleted'; -0096 warning('RAVEN:warning', '%s', EM); -0097 end -0098 if isfield(model,'geneComps') -0099 model=rmfield(model,'geneComps'); -0100 EM='The model structure contains information about gene compartmentalization. This is not supported by this function. The geneComps field has been deleted'; -0101 warning('RAVEN:warning', '%s', EM); -0102 end -0103 -0104 defaultCompartment=char(defaultCompartment); -0105 I=ismember(defaultCompartment,GSS.compartments); -0106 if I==false -0107 error('RAVEN:badInput', ... -0108 ['defaultCompartment ''%s'' not found in GSS. Available compartments: %s. ' ... -0109 '(If you mapped scores to model ids via compartmentMap, pass an id such as ''c''; ' ... -0110 'otherwise pass the raw predictor label.)'], ... -0111 defaultCompartment, strjoin(GSS.compartments(:)', ', ')); -0112 end -0113 -0114 if numel(model.comps)>1 -0115 EM='The model has several compartments. All compartments will be merged'; -0116 warning('RAVEN:warning', '%s', EM); -0117 model=mergeCompartments(model,true,true); -0118 end -0119 -0120 noGenes = ismember(model.genes,GSS.genes); -0121 if ~all(noGenes) -0122 EM=['For ' num2str(numel(find(~noGenes))) ' of ' num2str(numel(model.genes)) ' model genes no data was found in GSS']; -0123 warning('RAVEN:warning', '%s', EM); -0124 end -0125 -0126 %***Begin formating the data structures -0127 -0128 %Expand the model so that iso-enzymes have different reactions -0129 model=expandModel(model); -0130 -0131 %Identify reactions that have to be deleted because the involved mets are -0132 %never produced. This is done in an iterative manner -0133 removedRxns={}; -0134 %This is to keep track of which metabolites are removed in this step. It is -0135 %needed to adjust the transport costs -0136 originalModelMets=model.mets; -0137 while 1 -0138 irrevModel=convertToIrrev(model); -0139 -0140 I=sum(irrevModel.S>0,2); -0141 -0142 %Pretend that the unconstrained metabolites are made enough -0143 if isfield(irrevModel,'unconstrained') -0144 I(irrevModel.unconstrained~=0)=2; -0145 end -0146 metsToDelete=false(numel(model.mets),1); -0147 -0148 %This is not very neat but iterate through each metabolite and check -0149 %whether it can be produced (without using only one isolated reversible -0150 %reaction) -0151 for i=1:numel(irrevModel.mets) -0152 %If something can be made in two reactions then everything is fine. -0153 %If it can be made in one reaction it is fine unless it is through -0154 %an isolated reversible reaction (which can act as a mini loop) -0155 if I(i)<2 -0156 if I(i)==1 -0157 %Find the reaction where this metabolite is produced -0158 [~, J]=find(irrevModel.S(i,:)>0); -0159 -0160 %Check the metabolites that are consumed in this reaction. -0161 %The problem is if any of them is only produced in the -0162 %opposite reversible reaction -0163 K=irrevModel.S(:,J)<0; -0164 check=find(K & I<=1); -0165 -0166 for j=1:numel(check) -0167 %Find the reactions where it participates -0168 [~, L]=find(irrevModel.S(check(j),:)>0); -0169 -0170 if ~isempty(L) -0171 rxn=irrevModel.rxns(J); -0172 rxnRev=irrevModel.rxns(L); -0173 if strcmp(strrep(rxn,'_REV',''),strrep(rxnRev,'_REV','')) -0174 metsToDelete(i)=true; -0175 end -0176 else -0177 %If the metabolite was never produced then do -0178 %nothing and deal with it when the loop gets there -0179 continue; -0180 end -0181 end -0182 else -0183 %Not made anywhere -0184 metsToDelete(i)=true; -0185 end -0186 end -0187 end -0188 -0189 if any(metsToDelete) -0190 %Delete any reactions involving any of the metsToDelete -0191 [~, I]=find(model.S(metsToDelete,:)); -0192 removedRxns=[removedRxns;model.rxns(I)]; -0193 model=removeReactions(model,I,true,true); -0194 else -0195 %All bad reactions deleted -0196 break; -0197 end -0198 end -0199 -0200 %Adjust the transport costs -0201 transportCost=transportCost(ismember(originalModelMets,model.mets)); -0202 -0203 %Assign fake genes to reactions without genes -0204 I=find(sum(model.rxnGeneMat,2)==0); -0205 for i=1:numel(I) -0206 model.genes=[model.genes;['&&FAKE&&' num2str(i)]]; -0207 if isfield(model,'geneShortNames') -0208 model.geneShortNames=[model.geneShortNames;{''}]; -0209 end -0210 if isfield(model,'geneMiriams') -0211 model.geneMiriams=[model.geneMiriams;{[]}]; -0212 end -0213 if isfield(model,'proteins') -0214 model.proteins=[model.proteins;{[]}]; -0215 end -0216 if isfield(model,'geneFrom') -0217 model.geneFrom=[model.geneFrom;{{'FAKE'}}]; -0218 end -0219 model.rxnGeneMat(I(i),numel(model.genes))=1; -0220 model.grRules{I(i)}=''; -0221 end -0222 -0223 %Update the GSS. All genes, fake or real, for which there is no evidence -0224 %gets a score 0.5 in all compartments -0225 I=setdiff(model.genes,GSS.genes); -0226 GSS.genes=[GSS.genes;I]; -0227 GSS.scores=[GSS.scores;ones(numel(I),numel(GSS.compartments))*0.5]; -0228 -0229 %Gene complexes should be moved together to be biologically -0230 %relevant. The average score for the genes is used for each compartment. -0231 %This is done by changing the model so that gene complexes are used as a -0232 %single gene name and then a score is calculated for that "gene". -0233 -0234 %Only "and"-relationships exist after expandModel -0235 genes=unique(model.grRules); -0236 nGenes=strrep(genes,'(',''); -0237 nGenes=strrep(nGenes,')',''); -0238 %nGenes=strrep(nGenes," and ","_and_"); -0239 complexes=setdiff(nGenes,model.genes); -0240 if ~isempty(complexes) -0241 if isempty(complexes{1}) %Empty grRules also come up here -0242 complexes(1)=[]; -0243 end -0244 end -0245 cScores=zeros(numel(complexes),numel(GSS.compartments)); -0246 for i=1:numel(complexes) -0247 genesInComplex=regexp(complexes{i},' and ','split'); -0248 -0249 %Find these genes in GSS -0250 [I, J]=ismember(genesInComplex,GSS.genes); -0251 -0252 if any(I) -0253 %Get the average of the genes that were found -0254 mScores=mean(GSS.scores(J(I),:)); -0255 -0256 %And add 0.5 for the genes that were not found, to be -0257 %consistent with non-complexes -0258 mScores=(mScores.*sum(I)+(numel(genesInComplex)-sum(I))*0.5)/numel(genesInComplex); -0259 else -0260 EM=['Could not parse grRule "' complexes{i} '". Assigning score 0.0 in all compartments']; -0261 warning('RAVEN:warning', '%s', EM); -0262 mScores=ones(1,numel(genesInComplex))*0.5; -0263 end -0264 cScores(i,:)=mScores; -0265 -0266 %Add this complex as a new gene -0267 model.genes=[model.genes;complexes{i}]; -0268 if isfield(model,'geneMiriams') -0269 model.geneMiriams=[model.geneMiriams;{[]}]; -0270 end -0271 if isfield(model,'geneShortNames') -0272 model.geneShortNames=[model.geneShortNames;{''}]; -0273 end -0274 if isfield(model,'proteins') -0275 model.proteins=[model.proteins;{''}]; -0276 end -0277 if isfield(model,'geneFrom') -0278 model.geneFrom=[model.geneFrom;{'COMPLEX'}]; -0279 end -0280 %Find the reactions which had the original complex and change them to -0281 %use the new "gene" -0282 I=ismember(model.grRules,['(' complexes{i} ')']); -0283 -0284 %Should check more carefully if there can be an error here -0285 if ~isempty(I) -0286 model.rxnGeneMat(I,:)=0; %Ok since the split on "or" was applied -0287 model.rxnGeneMat(I,numel(model.genes))=1; -0288 end -0289 end -0290 -0291 %Add the new "genes" -0292 GSS.genes=[GSS.genes;complexes]; -0293 GSS.scores=[GSS.scores;cScores]; -0294 -0295 %After merging the complexes it could happen that there are genes that are -0296 %no longer in use. Delete such genes -0297 model=removeReactions(model,{},false,true); -0298 -0299 %Exchange reactions, defined as involving an unconstrained metabolite, are -0300 %special in that they have to stay in the defaultCompartment. This means -0301 %that uptake/excretion of metabolites is always via the default -0302 %compartment. This is a small simplification, but should be valid in most -0303 %cases -0304 [~, I]=getExchangeRxns(model); -0305 -0306 %It will be easier later on if the same place. Put them in the beginning -0307 J=1:numel(model.rxns); -0308 J(I)=[]; -0309 model=permuteModel(model,[I;J'],'rxns'); -0310 -0311 %Number of exchange reactions -0312 nER=numel(I); -0313 -0314 %Also put the exchange metabolites in the beginning -0315 if isfield(model,'unconstrained') -0316 I=find(model.unconstrained); -0317 J=1:numel(model.mets); -0318 J(I)=[]; -0319 model=permuteModel(model,[I;J'],'mets'); -0320 %Also reorder the transport costs -0321 transportCost=transportCost([I;J']); -0322 %Number of exchange metabolites -0323 nEM=numel(I); -0324 else -0325 nEM=0; -0326 end -0327 -0328 %There is no point of having genes for exchange reactions, so delete them. -0329 %Also to make computations easier -0330 model.rxnGeneMat(1:nER,:)=0; -0331 model.grRules(1:nER)={''}; -0332 -0333 %Remove unused genes -0334 model=removeReactions(model,{},false,true); -0335 -0336 %Remove genes with no match to the model and reorder so that the genes are -0337 %in the same order as model.genes. Since the fake genes are already added -0338 %so that all genes in model exist in GSS it is fine to do like this -0339 [~, J]=ismember(model.genes,GSS.genes); -0340 GSS.genes=model.genes; -0341 GSS.scores=GSS.scores(J,:); -0342 -0343 %Reorder the GSS so that the first index corresponds to the default -0344 %compartment -0345 [~, J]=ismember(defaultCompartment,GSS.compartments); -0346 reorder=1:numel(GSS.compartments); -0347 reorder(J)=[]; -0348 reorder=[J reorder]; -0349 GSS.scores=GSS.scores(:,reorder); -0350 GSS.compartments=GSS.compartments(reorder); -0351 -0352 %Since it is only checked whether the metabolites can be synthesized, there -0353 %is no need to care about the stoichiometry. Change to -1/1 to simplify -0354 %later. Keep the S matrix for later though -0355 oldS=model.S; -0356 model.S(model.S>0)=1; -0357 model.S(model.S<0)=-1; -0358 -0359 %Here is a bit of a trick. To avoid the recurring calculation which -0360 %reactions are reversible, the reversible reactions have the coefficients -0361 %-10/10 instead of -1/1 -0362 model.S(:,model.rev==1)=model.S(:,model.rev==1).*10; -0363 -0364 %***Begin problem formulation -0365 -0366 %Some numbers that are good to have -0367 nRxns=numel(model.rxns)-nER; %Excluding exchange rxns -0368 nMets=numel(model.mets)-nEM; %Excluding exchange mets -0369 nGenes=numel(model.genes); -0370 nComps=numel(GSS.compartments); -0371 -0372 %Create a big stoichiometric matrix that will be the current model. In -0373 %order to have faster simulations the maximal model size is declared and -0374 %reactions are then moved within it. -0375 -0376 %First the original model (with the first nE being exchange rxns), then -0377 %reserve space for number of rxns minus exchange rxns for each non-default -0378 %compartment, then transport reactions for all non-exchange mets between -0379 %the default compartment and all others. -0380 %NOTE: Kept eye()*0 since eye() can be used to include all transport from -0381 %the beginning -0382 s=repmat(eye(nMets)*0,1,nComps-1); -0383 s=[zeros(numel(model.mets)-nMets,size(s,2));s]; -0384 S=[model.S sparse(numel(model.mets),nRxns*(nComps-1)) s]; -0385 s=[sparse(nMets*(nComps-1),numel(model.rxns)+nRxns*(nComps-1)) eye(nMets*(nComps-1))*0]; -0386 S=[S;s]; -0387 -0388 %Also replicate the transport costs -0389 transportCost=[transportCost(1:nEM);repmat(transportCost(nEM+1:end),nComps,1)]; -0390 -0391 %Create a binary matrix that says where the genes are in the current -0392 %solution -0393 g2c=false(nGenes,nComps); -0394 %All genes start in the default compartment -0395 g2c(:,1)=true; -0396 -0397 %Start of main optimization loop -0398 tic; -0399 bestScore=-inf; -0400 bestS=[]; -0401 bestg2c=[]; -0402 -0403 %Temp for testing -0404 plotScore=[]; -0405 nTrans=[]; -0406 totScore=[]; -0407 minScore=sum(min(GSS.scores,[],2)); -0408 maxScore=sum(max(GSS.scores,[],2)); -0409 -0410 while toc<maxTime*60 -0411 %Pick a random gene, weighted by it is current score minus the best -0412 %score for that gene (often 1.0, but can be 0.5 for no genes or average -0413 %for complexes). Genes with bad fits are more likely to be moved. This -0414 %formulation never moves a gene from its best compartment. Therefore a -0415 %small uniform weight is added -0416 [I, J]=find(g2c); -0417 geneToMove=randsample(nGenes,1,true,max(GSS.scores(I,:),[],2)-GSS.scores(sub2ind(size(g2c),I,J))+0.1); -0418 -0419 %Sample among possible compartments to move to. Add a larger weight to -0420 %even out the odds a little. Also a way of getting rid of loops where -0421 %the same set of genes are moved back and forth several times -0422 toComp=randsample(nComps,1,true,GSS.scores(geneToMove,:)+0.2); -0423 -0424 %Check that it moves to a new compartment -0425 if toComp==find(g2c(geneToMove,:)) -0426 continue; -0427 end -0428 -0429 %Moves the gene -0430 [newS, newg2c]=moveGene(S,model,g2c,geneToMove,toComp,nRxns,nMets); -0431 -0432 %Tries to connect the network. If this was not possible in 10 -0433 %iterations, then abort. If more than 20 modifications were needed then -0434 %it is unlikely that it will be a lower score -0435 wasConnected=false; -0436 for j=1:10 -0437 %Find the metabolites that are now unconnected -0438 unconnected=findUnconnected(newS,nEM); -0439 -0440 %Continue if there are still unconnected -0441 if any(unconnected) -0442 %For each gene find out how many of these could be connected if -0443 %the gene was moved and how many would be disconnected by -0444 %moving that gene -0445 [geneIndex, moveTo, deltaConnected, deltaScore]=selectGenes(newS,nEM,nMets,nER,nRxns,model,unconnected,g2c,GSS); -0446 -0447 %Score which gene would be the best to move. The highest -0448 %deltaScore is 1.0. It should be possible to move a gene from -0449 %worst to best compartment even if it disconnects, say, 1.5 -0450 %more metabolites -0451 [score, I]=max(1.5*deltaScore+deltaConnected); -0452 -0453 %Check if it has to add a transport or if there is a gene that -0454 %could be moved order to have a more connected network -0455 hasToAddTransport=true; -0456 if ~isempty(deltaConnected) -0457 if score>0 -0458 hasToAddTransport=false; -0459 end -0460 end -0461 -0462 %If it is possible to move any gene to have a more -0463 %connected network, then move the best one -0464 if hasToAddTransport==false -0465 [newS, newg2c]=moveGene(newS,model,g2c,geneIndex(I),moveTo(I),nRxns,nMets); -0466 else -0467 %Choose a random unconnected metabolite that should be -0468 %connected -0469 transMet=unconnected(randsample(numel(unconnected),1)); -0470 -0471 %First get where the metabolite is now -0472 comps=ceil((transMet-nEM)/((size(S,1)-nEM)/nComps)); -0473 -0474 %Find the corresponding metabolite index if it were in the -0475 %default compartment -0476 dcIndex=transMet-(comps-1)*nMets; -0477 -0478 %Then get the indexes of that metabolite in all -0479 %compartments -0480 allIndexes=dcIndex; -0481 for k=1:nComps-1 -0482 allIndexes=[allIndexes;dcIndex+nMets*k]; -0483 end -0484 -0485 %It could be that some of these are not used in any -0486 %reaction. Get only the ones which are -0487 I=sum(newS(allIndexes,:)~=0,2)>0; -0488 -0489 %Then get the ones that are used but not in unconnected. -0490 %These are metabolites that could potentially be -0491 %transported to connect transMet -0492 connectedUsed=setdiff(allIndexes(I),unconnected); -0493 -0494 %This may be an error but leave it for now. It seems to -0495 %happen if nothing can be connected in one step -0496 if isempty(connectedUsed) -0497 break; -0498 end -0499 -0500 %If transMet is in the default compartment then everything -0501 %is fine, just connect it to a random one -0502 if transMet==dcIndex -0503 newS=addTransportToS(newS,nRxns,nER,nMets,nEM,nComps,transMet,connectedUsed(randsample(numel(connectedUsed),1))); -0504 else -0505 %If one of the connectedUsed is in the default -0506 %compartment then connect to that one -0507 I=connectedUsed(connectedUsed<(nMets+nEM)); -0508 if any(I) -0509 newS=addTransportToS(newS,nRxns,nER,nMets,nEM,nComps,transMet,I(randsample(numel(I),1))); -0510 else -0511 %This is if the only way to connect it is by adding -0512 %two transport reactions, going via the default -0513 %compartment -0514 break; -0515 end -0516 end -0517 end -0518 else -0519 wasConnected=true; -0520 break; -0521 end -0522 end -0523 -0524 %If the network was connected in a new way, it is possible that some -0525 %transport reactions are no longer needed. They should be removed -0526 if wasConnected==true -0527 %These are the metabolites that are being transported -0528 activeTransport=find(sum(newS(:,nER+nRxns*nComps+1:end),2)); -0529 -0530 %Get the metabolites that are unconnected if transport was not used -0531 unconnected=findUnconnected(newS(:,1:nER+nRxns*nComps),nEM); -0532 -0533 %Find the transport reactions that are not needed and delete them -0534 I=setdiff(activeTransport,unconnected); -0535 -0536 %Since both metabolites in a transport rxns must be connected for -0537 %the reaction to be deleted, the sum over the colums should be 4 -0538 newS(:,find(sum(newS(I,nER+nRxns*nComps+1:end))==4)+nER+nRxns*nComps)=0; -0539 -0540 %Score the solution and determine whether to keep it as a new -0541 %solution -0542 [score, geneScore, trCost]=scoreLocalization(newS,newg2c,GSS,transportCost); -0543 -0544 %If it was the best solution so far, keep it -0545 if score>bestScore -0546 bestScore=score; -0547 bestS=newS; -0548 bestg2c=newg2c; -0549 end -0550 -0551 %This should not be steepest descent later -0552 if score>=bestScore% || exp((score-bestScore)*7)>rand() -0553 plotScore=[plotScore;geneScore]; -0554 nTrans=[nTrans;trCost]; -0555 totScore=[totScore;score]; -0556 S=newS; -0557 g2c=newg2c; -0558 -0559 if plotResults==true -0560 subplot(3,2,1); -0561 spy(S); -0562 subplot(3,2,2); -0563 plot(plotScore,'r'); -0564 xlabel('Gene score'); -0565 subplot(3,2,3); -0566 plot((plotScore-minScore)/(maxScore-minScore),'r'); -0567 xlabel('Gene score relative to predictions'); -0568 subplot(3,2,4); -0569 plot(nTrans,'g'); -0570 xlabel('Transport cost'); -0571 subplot(3,2,5); -0572 plot(totScore,'b'); -0573 xlabel('Total score'); -0574 subplot(3,2,6); -0575 pause(0.2); -0576 end -0577 end -0578 end -0579 end -0580 scores.totScore=score; -0581 scores.geneScore=geneScore; -0582 scores.transCost=trCost; -0583 -0584 %Find which metabolites are transported and to where -0585 [I, J]=find(bestS(nEM+1:nEM+nMets,end-nMets*(nComps-1)+1:end)); -0586 J=ceil(J/nMets+1); -0587 transportStruct.mets=model.metNames(I+nEM); -0588 transportStruct.toComp=GSS.compartments(J); -0589 -0590 [I, J]=find(bestg2c); -0591 geneLocalization.genes=GSS.genes(I); -0592 geneLocalization.comps=GSS.compartments(J); -0593 -0594 %Resort the gene names -0595 [~, I]=sort(geneLocalization.genes); -0596 geneLocalization.genes=geneLocalization.genes(I); -0597 geneLocalization.comps=geneLocalization.comps(I); -0598 -0599 %Remove the fake genes -0600 I=strncmp('&&FAKE&&',geneLocalization.genes,8); -0601 geneLocalization.genes(I)=[]; -0602 geneLocalization.comps(I)=[]; -0603 -0604 %Put together the model. This is done by first duplicating the S matrix -0605 %into the different compartments. Then the transport reactions are added -0606 %based on transportStruct. By now model.S should have the same size as the -0607 %S matrix used in the optimization, but with conserved stoichiometry. In -0608 %the final step all reactions and metabolites that are not used in the S -0609 %matrix from the optimization are deleted from the model -0610 outModel=model; -0611 outModel.S=oldS; -0612 -0613 %This is the S matrix without exchange rxns or metabolites -0614 copyPart=outModel.S(nEM+1:end,nER+1:end); -0615 -0616 %Replicate to give the rxnGeneMat for the full system -0617 copyRxnGeneMat=outModel.rxnGeneMat(nER+1:end,:); -0618 outModel.rxnGeneMat=[outModel.rxnGeneMat;repmat(copyRxnGeneMat,nComps-1,1)]; -0619 -0620 %First fix the compartments. The model is already ordered with the exchange -0621 %metabolites first. The original model may contain one or two compartments, -0622 %depending on whether any exchange metabolites are defined -0623 nStartComps=numel(outModel.comps); -0624 if nStartComps==1 -0625 outModel.comps={'1'}; -0626 outModel.compNames=GSS.compartments(1); -0627 else -0628 if model.metComps(1)==1 -0629 outModel.compNames(1)=GSS.compartments(1); -0630 else -0631 outModel.compNames(2)=GSS.compartments(1); -0632 end -0633 end -0634 outModel.compNames=[outModel.compNames;GSS.compartments(2:end)]; -0635 -0636 %Ugly little loop -0637 for i=1:numel(GSS.compartments)-1 -0638 outModel.comps=[outModel.comps;num2str(numel(outModel.comps)+1)]; -0639 end -0640 %This information is not known from the data, so empty fields are added -0641 outModel.compOutside=cell(numel(outModel.comps),1); -0642 outModel.compOutside(:)={''}; -0643 -0644 for i=1:nComps-1 -0645 outModel.S=[outModel.S sparse(size(outModel.S,1),nRxns)]; -0646 outModel.S=[outModel.S;[sparse(nMets,nRxns*i+nER) copyPart]]; -0647 outModel.rxns=[outModel.rxns;strcat(outModel.rxns(nER+1:nER+nRxns),'_',GSS.compartments{i+1})]; -0648 outModel.rxnNames=[outModel.rxnNames;strcat(outModel.rxnNames(nER+1:nER+nRxns),' (',GSS.compartments{i+1},')')]; -0649 outModel.lb=[outModel.lb;outModel.lb(nER+1:nER+nRxns)]; -0650 outModel.ub=[outModel.ub;outModel.ub(nER+1:nER+nRxns)]; -0651 outModel.rev=[outModel.rev;outModel.rev(nER+1:nER+nRxns)]; -0652 outModel.c=[outModel.c;outModel.c(nER+1:nER+nRxns)]; -0653 if isfield(outModel,'grRules') -0654 outModel.grRules=[outModel.grRules;outModel.grRules(nER+1:nER+nRxns)]; -0655 end -0656 if isfield(outModel,'subSystems') -0657 outModel.subSystems=[outModel.subSystems;outModel.subSystems(nER+1:nER+nRxns)]; -0658 end -0659 if isfield(outModel,'eccodes') -0660 outModel.eccodes=[outModel.eccodes;outModel.eccodes(nER+1:nER+nRxns)]; -0661 end -0662 if isfield(outModel,'rxnFrom') -0663 outModel.rxnFrom=[outModel.rxnFrom;outModel.rxnFrom(nER+1:nER+nRxns)]; -0664 end -0665 if isfield(outModel,'rxnMiriams') -0666 outModel.rxnMiriams=[outModel.rxnMiriams;outModel.rxnMiriams(nER+1:nER+nRxns)]; -0667 end -0668 if isfield(outModel,'rxnNotes') -0669 outModel.rxnNotes=[outModel.rxnNotes;outModel.rxnNotes(nER+1:nER+nRxns)]; -0670 end -0671 if isfield(outModel,'rxnReferences') -0672 outModel.rxnReferences=[outModel.rxnReferences;outModel.rxnReferences(nER+1:nER+nRxns)]; -0673 end -0674 if isfield(outModel,'rxnConfidenceScores') -0675 outModel.rxnConfidenceScores=[outModel.rxnConfidenceScores;outModel.rxnConfidenceScores(nER+1:nER+nRxns)]; -0676 end -0677 if isfield(outModel,'rxnDeltaG') -0678 outModel.rxnDeltaG=[outModel.rxnDeltaG;outModel.rxnDeltaG(nER+1:nER+nRxns)]; -0679 end -0680 outModel.mets=[outModel.mets;strcat(outModel.mets(nEM+1:nEM+nMets),'_',GSS.compartments{i+1})]; -0681 outModel.metNames=[outModel.metNames;outModel.metNames(nEM+1:nEM+nMets)]; -0682 outModel.b=[outModel.b;outModel.b(nEM+1:nEM+nMets,:)]; -0683 I=ones(nMets,1)*nStartComps+i; -0684 outModel.metComps=[outModel.metComps;I]; -0685 if isfield(outModel,'inchis') -0686 outModel.inchis=[outModel.inchis;outModel.inchis(nEM+1:nEM+nMets)]; -0687 end -0688 if isfield(outModel,'metSmiles') -0689 outModel.metSmiles=[outModel.metSmiles;outModel.metSmiles(nEM+1:nEM+nMets)]; -0690 end -0691 if isfield(outModel,'unconstrained') -0692 outModel.unconstrained=[outModel.unconstrained;outModel.unconstrained(nEM+1:nEM+nMets)]; -0693 end -0694 if isfield(outModel,'metMiriams') -0695 outModel.metMiriams=[outModel.metMiriams;outModel.metMiriams(nEM+1:nEM+nMets)]; -0696 end -0697 if isfield(outModel,'metFormulas') -0698 outModel.metFormulas=[outModel.metFormulas;outModel.metFormulas(nEM+1:nEM+nMets)]; -0699 end -0700 if isfield(outModel,'metFrom') -0701 outModel.metFrom=[outModel.metFrom;outModel.metFrom(nEM+1:nEM+nMets)]; -0702 end -0703 if isfield(outModel,'metCharges') -0704 outModel.metCharges=[outModel.metCharges;outModel.metCharges(nEM+1:nEM+nMets)]; -0705 end -0706 if isfield(outModel,'metDeltaG') -0707 outModel.metDeltaG=[outModel.metDeltaG;outModel.metDeltaG(nEM+1:nEM+nMets)]; -0708 end -0709 end -0710 -0711 %Add the transport reactions -0712 transS=bestS(:,numel(outModel.rxns)+1:end); -0713 J=sum(transS)>0; %Active rxns -0714 -0715 %Transport reactions are written in a different way compared to a "real" -0716 %stoichimetric matrix. This is to fix that -0717 transS(transS~=0)=1; -0718 transS(1:nEM+nMets,:)=transS(1:nEM+nMets,:)*-1; -0719 I=find(sum(transS>0,2)); -0720 nTransRxns=numel(I); -0721 outModel.S=[outModel.S transS(:,J)]; -0722 filler=ones(nTransRxns,1); -0723 outModel.lb=[outModel.lb;filler*-1000]; -0724 outModel.ub=[outModel.ub;filler*1000]; -0725 outModel.rev=[outModel.rev;filler]; -0726 outModel.c=[outModel.c;filler*0]; -0727 outModel.rxnGeneMat=[outModel.rxnGeneMat;sparse(nTransRxns,numel(outModel.genes))]; -0728 -0729 for i=1:numel(I) -0730 outModel.rxns=[outModel.rxns;strcat('transport',num2str(i))]; -0731 outModel.rxnNames=[outModel.rxnNames;['Transport of ',outModel.metNames{I(i)}]]; -0732 if isfield(outModel,'grRules') -0733 outModel.grRules=[outModel.grRules;{''}]; -0734 end -0735 if isfield(outModel,'rxnMiriams') -0736 outModel.rxnMiriams=[outModel.rxnMiriams;{[]}]; -0737 end -0738 if isfield(outModel,'subSystems') -0739 if any(cellfun(@(x) iscell(x), outModel.subSystems)) -0740 outModel.subSystems=[outModel.subSystems;{{'Inferred transport reactions'}}]; -0741 else -0742 outModel.subSystems=[outModel.subSystems;{'Inferred transport reactions'}]; -0743 end -0744 end -0745 if isfield(outModel,'eccodes') -0746 outModel.eccodes=[outModel.eccodes;{''}]; -0747 end -0748 if isfield(outModel,'rxnFrom') -0749 outModel.rxnFrom=[outModel.rxnFrom;{''}]; -0750 end -0751 if isfield(outModel,'rxnNotes') -0752 outModel.rxnNotes=[outModel.rxnNotes;{''}]; -0753 end -0754 if isfield(outModel,'rxnReferences') -0755 outModel.rxnReferences=[outModel.rxnReferences;{''}]; -0756 end -0757 if isfield(outModel,'rxnConfidenceScores') -0758 outModel.rxnConfidenceScores=[outModel.rxnConfidenceScores;NaN]; -0759 end -0760 if isfield(outModel,'rxnDeltaG') -0761 outModel.rxnDeltaG=[outModel.rxnDeltaG;NaN]; -0762 end -0763 end -0764 -0765 %Then remove all reactions and metabolites that are not used in the final -0766 %solution from the optimization -0767 [~, J]=find(bestS(:,1:nER+nComps*nRxns)); -0768 K=true(numel(outModel.rxns),1); -0769 K(J)=false; -0770 K(end-nTransRxns+1:end)=false; -0771 outModel=removeReactions(outModel,K,true); -0772 -0773 %Remove all fake genes -0774 I=strncmp('&&FAKE&&',outModel.genes,8); -0775 outModel.genes(I)=[]; -0776 if isfield(outModel,'geneMiriams') -0777 outModel.geneMiriams(I)=[]; -0778 end -0779 if isfield(outModel,'geneShortNames') -0780 outModel.geneShortNames(I)=[]; -0781 end -0782 if isfield(outModel,'proteins') -0783 outModel.proteins(I)=[]; -0784 end -0785 outModel.rxnGeneMat(:,I)=[]; -0786 -0787 %Fix grRules and reconstruct rxnGeneMat -0788 [grRules,rxnGeneMat] = standardizeGrRules(outModel,true); -0789 outModel.grRules = grRules; -0790 outModel.rxnGeneMat = rxnGeneMat; -0791 end -0792 -0793 %Moves a gene and all associated reactions from one compartment to another -0794 function [S, g2c]=moveGene(S,model,g2c,geneToMove,toComp,nRxns,nMets) -0795 %Find the current compartment and update to the new one -0796 currentComp=find(g2c(geneToMove,:)); -0797 g2c(geneToMove,:)=false; -0798 g2c(geneToMove,toComp)=true; -0799 -0800 %Find the reactions in the original model that the gene controls -0801 [I, ~]=find(model.rxnGeneMat(:,geneToMove)); -0802 -0803 %Calculate their current positions in the S matrix -0804 oldRxns=I+(currentComp-1)*nRxns; -0805 -0806 %And their new positions -0807 newRxns=I+(toComp-1)*nRxns; -0808 -0809 %The metabolite ids also have to be changed to match the new -0810 %compartment -0811 metChange=nMets*(toComp-currentComp); -0812 -0813 %Update the reactions -0814 [I, J, K]=find(S(:,oldRxns)); -0815 I=I+metChange; -0816 -0817 %Move the reactions -0818 S(:,oldRxns)=0; -0819 S(sub2ind(size(S),I,newRxns(J)))=K; -0820 end -0821 -0822 %Finds which metabolites are unconnected, in the sense that they are never -0823 %a product or only a product in a reversible reaction where one reactant is -0824 %only a product in the opposite direction of that reaction. This function -0825 %ignores exchange metabolites. Returns a vector of metabolite indexes. -0826 %metsToCheck is an array of metabolite indexes to check for connectivity. -0827 %If not supplied then all metabolites are checked -0828 function unconnected=findUnconnected(S,nEM,metsToCheck) -0829 if nargin>2 -0830 %Do this by deleting everything from the network that is not in -0831 %metsToCheck and that is not exchange metabolites -0832 I=false(size(S,1),1); -0833 I(1:nEM)=true; -0834 I(metsToCheck)=true; -0835 S=S(I,:); -0836 end -0837 -0838 em=false(size(S,1),1); -0839 em(1:nEM)=true; -0840 -0841 %Construct a matrix in which the reversible reactions are inverted -0842 I=sum(S>2,1) | sum(S>2,1); -0843 revS=S; -0844 revS(:,I)=revS(:,I)*-1; -0845 -0846 %First calculate the ones that are ok -0847 %Produced in 2 rxns, is exchange, is not used at all, is produced in -0848 %non-reversible, involved in more than 1 reversible reactions -0849 connected=sum(S>0,2)>1 | em | sum(S~=0,2)==0 | sum(S(:,~I)>0,2)>0 | sum(S(:,I)~=0,2)>1; -0850 -0851 %Then get the ones that are unconnected because they are never produced -0852 unconnected=sum(S>0 | revS>0,2)==0 & connected==false; -0853 -0854 %Then get the ones that are potentially unconnected -0855 maybeUnconnected=~connected & ~unconnected; -0856 %maybeUnconnected=find(maybeUnconnectedS); -0857 -0858 %The metabolites in maybeUnconnected are involved in one reversible -0859 %reaction and not produced in any other reaction. This means that the -0860 %reactions which have at least one met in maybeUnconnected as reactant and -0861 %one as product are unconnected. The metabolites in maybeUnconnected that -0862 %are present in those reactions are then dead ends -0863 deadRxns=any(S(maybeUnconnected,:)>0) & any(S(maybeUnconnected,:)<0); -0864 -0865 %Get the mets involved in any of those reactions -0866 problematic=any(S(:,deadRxns)~=0,2); -0867 -0868 %If any of these are in the maybeUnconnected list then the metabolite is -0869 %unconnected -0870 unconnected(problematic & maybeUnconnected)=true; -0871 -0872 %Map back to metsToCheck -0873 if nargin>2 -0874 unconnected=metsToCheck(unconnected(nEM+1:end)); -0875 else -0876 unconnected=find(unconnected); -0877 end +0067 % 'transportCost', transportCost, 'maxTime', maxTime, ... +0068 % 'plotResults', plotResults); +0069 +0070 p=parseRAVENargs(varargin, {'transportCost',[]; 'maxTime',[]; 'plotResults',[]}); +0071 transportCost=p.transportCost; +0072 maxTime=p.maxTime; +0073 plotResults=p.plotResults; +0074 +0075 if isempty(transportCost) +0076 transportCost=ones(numel(model.mets),1)*0.5; +0077 end +0078 if numel(transportCost)==1 +0079 transportCost=ones(numel(model.mets),1)*transportCost; +0080 end +0081 transportCost=transportCost(:); +0082 +0083 if numel(transportCost)~=numel(model.mets) +0084 EM='The vector of transport costs must have the same dimension as model.mets'; +0085 error('RAVEN:badInput', '%s', EM); +0086 end +0087 if isempty(maxTime) +0088 maxTime=15; +0089 end +0090 if isempty(plotResults) +0091 plotResults=false; +0092 end +0093 +0094 if isfield(model,'rxnComps') +0095 model=rmfield(model,'rxnComps'); +0096 EM='The model structure contains information about reaction compartmentalization. This is not supported by this function. The rxnComps field has been deleted'; +0097 warning('RAVEN:warning', '%s', EM); +0098 end +0099 if isfield(model,'geneComps') +0100 model=rmfield(model,'geneComps'); +0101 EM='The model structure contains information about gene compartmentalization. This is not supported by this function. The geneComps field has been deleted'; +0102 warning('RAVEN:warning', '%s', EM); +0103 end +0104 +0105 defaultCompartment=char(defaultCompartment); +0106 I=ismember(defaultCompartment,GSS.compartments); +0107 if I==false +0108 error('RAVEN:badInput', ... +0109 ['defaultCompartment ''%s'' not found in GSS. Available compartments: %s. ' ... +0110 '(If you mapped scores to model ids via compartmentMap, pass an id such as ''c''; ' ... +0111 'otherwise pass the raw predictor label.)'], ... +0112 defaultCompartment, strjoin(GSS.compartments(:)', ', ')); +0113 end +0114 +0115 if numel(model.comps)>1 +0116 EM='The model has several compartments. All compartments will be merged'; +0117 warning('RAVEN:warning', '%s', EM); +0118 model=mergeCompartments(model,true,true); +0119 end +0120 +0121 noGenes = ismember(model.genes,GSS.genes); +0122 if ~all(noGenes) +0123 EM=['For ' num2str(numel(find(~noGenes))) ' of ' num2str(numel(model.genes)) ' model genes no data was found in GSS']; +0124 warning('RAVEN:warning', '%s', EM); +0125 end +0126 +0127 %***Begin formating the data structures +0128 +0129 %Expand the model so that iso-enzymes have different reactions +0130 model=expandModel(model); +0131 +0132 %Identify reactions that have to be deleted because the involved mets are +0133 %never produced. This is done in an iterative manner +0134 removedRxns={}; +0135 %This is to keep track of which metabolites are removed in this step. It is +0136 %needed to adjust the transport costs +0137 originalModelMets=model.mets; +0138 while 1 +0139 irrevModel=convertToIrrev(model); +0140 +0141 I=sum(irrevModel.S>0,2); +0142 +0143 %Pretend that the unconstrained metabolites are made enough +0144 if isfield(irrevModel,'unconstrained') +0145 I(irrevModel.unconstrained~=0)=2; +0146 end +0147 metsToDelete=false(numel(model.mets),1); +0148 +0149 %This is not very neat but iterate through each metabolite and check +0150 %whether it can be produced (without using only one isolated reversible +0151 %reaction) +0152 for i=1:numel(irrevModel.mets) +0153 %If something can be made in two reactions then everything is fine. +0154 %If it can be made in one reaction it is fine unless it is through +0155 %an isolated reversible reaction (which can act as a mini loop) +0156 if I(i)<2 +0157 if I(i)==1 +0158 %Find the reaction where this metabolite is produced +0159 [~, J]=find(irrevModel.S(i,:)>0); +0160 +0161 %Check the metabolites that are consumed in this reaction. +0162 %The problem is if any of them is only produced in the +0163 %opposite reversible reaction +0164 K=irrevModel.S(:,J)<0; +0165 check=find(K & I<=1); +0166 +0167 for j=1:numel(check) +0168 %Find the reactions where it participates +0169 [~, L]=find(irrevModel.S(check(j),:)>0); +0170 +0171 if ~isempty(L) +0172 rxn=irrevModel.rxns(J); +0173 rxnRev=irrevModel.rxns(L); +0174 if strcmp(strrep(rxn,'_REV',''),strrep(rxnRev,'_REV','')) +0175 metsToDelete(i)=true; +0176 end +0177 else +0178 %If the metabolite was never produced then do +0179 %nothing and deal with it when the loop gets there +0180 continue; +0181 end +0182 end +0183 else +0184 %Not made anywhere +0185 metsToDelete(i)=true; +0186 end +0187 end +0188 end +0189 +0190 if any(metsToDelete) +0191 %Delete any reactions involving any of the metsToDelete +0192 [~, I]=find(model.S(metsToDelete,:)); +0193 removedRxns=[removedRxns;model.rxns(I)]; +0194 model=removeReactions(model,I,true,true); +0195 else +0196 %All bad reactions deleted +0197 break; +0198 end +0199 end +0200 +0201 %Adjust the transport costs +0202 transportCost=transportCost(ismember(originalModelMets,model.mets)); +0203 +0204 %Assign fake genes to reactions without genes +0205 I=find(sum(model.rxnGeneMat,2)==0); +0206 for i=1:numel(I) +0207 model.genes=[model.genes;['&&FAKE&&' num2str(i)]]; +0208 if isfield(model,'geneShortNames') +0209 model.geneShortNames=[model.geneShortNames;{''}]; +0210 end +0211 if isfield(model,'geneMiriams') +0212 model.geneMiriams=[model.geneMiriams;{[]}]; +0213 end +0214 if isfield(model,'proteins') +0215 model.proteins=[model.proteins;{[]}]; +0216 end +0217 if isfield(model,'geneFrom') +0218 model.geneFrom=[model.geneFrom;{{'FAKE'}}]; +0219 end +0220 model.rxnGeneMat(I(i),numel(model.genes))=1; +0221 model.grRules{I(i)}=''; +0222 end +0223 +0224 %Update the GSS. All genes, fake or real, for which there is no evidence +0225 %gets a score 0.5 in all compartments +0226 I=setdiff(model.genes,GSS.genes); +0227 GSS.genes=[GSS.genes;I]; +0228 GSS.scores=[GSS.scores;ones(numel(I),numel(GSS.compartments))*0.5]; +0229 +0230 %Gene complexes should be moved together to be biologically +0231 %relevant. The average score for the genes is used for each compartment. +0232 %This is done by changing the model so that gene complexes are used as a +0233 %single gene name and then a score is calculated for that "gene". +0234 +0235 %Only "and"-relationships exist after expandModel +0236 genes=unique(model.grRules); +0237 nGenes=strrep(genes,'(',''); +0238 nGenes=strrep(nGenes,')',''); +0239 %nGenes=strrep(nGenes," and ","_and_"); +0240 complexes=setdiff(nGenes,model.genes); +0241 if ~isempty(complexes) +0242 if isempty(complexes{1}) %Empty grRules also come up here +0243 complexes(1)=[]; +0244 end +0245 end +0246 cScores=zeros(numel(complexes),numel(GSS.compartments)); +0247 for i=1:numel(complexes) +0248 genesInComplex=regexp(complexes{i},' and ','split'); +0249 +0250 %Find these genes in GSS +0251 [I, J]=ismember(genesInComplex,GSS.genes); +0252 +0253 if any(I) +0254 %Get the average of the genes that were found +0255 mScores=mean(GSS.scores(J(I),:)); +0256 +0257 %And add 0.5 for the genes that were not found, to be +0258 %consistent with non-complexes +0259 mScores=(mScores.*sum(I)+(numel(genesInComplex)-sum(I))*0.5)/numel(genesInComplex); +0260 else +0261 EM=['Could not parse grRule "' complexes{i} '". Assigning score 0.0 in all compartments']; +0262 warning('RAVEN:warning', '%s', EM); +0263 mScores=ones(1,numel(genesInComplex))*0.5; +0264 end +0265 cScores(i,:)=mScores; +0266 +0267 %Add this complex as a new gene +0268 model.genes=[model.genes;complexes{i}]; +0269 if isfield(model,'geneMiriams') +0270 model.geneMiriams=[model.geneMiriams;{[]}]; +0271 end +0272 if isfield(model,'geneShortNames') +0273 model.geneShortNames=[model.geneShortNames;{''}]; +0274 end +0275 if isfield(model,'proteins') +0276 model.proteins=[model.proteins;{''}]; +0277 end +0278 if isfield(model,'geneFrom') +0279 model.geneFrom=[model.geneFrom;{'COMPLEX'}]; +0280 end +0281 %Find the reactions which had the original complex and change them to +0282 %use the new "gene" +0283 I=ismember(model.grRules,['(' complexes{i} ')']); +0284 +0285 %Should check more carefully if there can be an error here +0286 if ~isempty(I) +0287 model.rxnGeneMat(I,:)=0; %Ok since the split on "or" was applied +0288 model.rxnGeneMat(I,numel(model.genes))=1; +0289 end +0290 end +0291 +0292 %Add the new "genes" +0293 GSS.genes=[GSS.genes;complexes]; +0294 GSS.scores=[GSS.scores;cScores]; +0295 +0296 %After merging the complexes it could happen that there are genes that are +0297 %no longer in use. Delete such genes +0298 model=removeReactions(model,{},false,true); +0299 +0300 %Exchange reactions, defined as involving an unconstrained metabolite, are +0301 %special in that they have to stay in the defaultCompartment. This means +0302 %that uptake/excretion of metabolites is always via the default +0303 %compartment. This is a small simplification, but should be valid in most +0304 %cases +0305 [~, I]=getExchangeRxns(model); +0306 +0307 %It will be easier later on if the same place. Put them in the beginning +0308 J=1:numel(model.rxns); +0309 J(I)=[]; +0310 model=permuteModel(model,[I;J'],'rxns'); +0311 +0312 %Number of exchange reactions +0313 nER=numel(I); +0314 +0315 %Also put the exchange metabolites in the beginning +0316 if isfield(model,'unconstrained') +0317 I=find(model.unconstrained); +0318 J=1:numel(model.mets); +0319 J(I)=[]; +0320 model=permuteModel(model,[I;J'],'mets'); +0321 %Also reorder the transport costs +0322 transportCost=transportCost([I;J']); +0323 %Number of exchange metabolites +0324 nEM=numel(I); +0325 else +0326 nEM=0; +0327 end +0328 +0329 %There is no point of having genes for exchange reactions, so delete them. +0330 %Also to make computations easier +0331 model.rxnGeneMat(1:nER,:)=0; +0332 model.grRules(1:nER)={''}; +0333 +0334 %Remove unused genes +0335 model=removeReactions(model,{},false,true); +0336 +0337 %Remove genes with no match to the model and reorder so that the genes are +0338 %in the same order as model.genes. Since the fake genes are already added +0339 %so that all genes in model exist in GSS it is fine to do like this +0340 [~, J]=ismember(model.genes,GSS.genes); +0341 GSS.genes=model.genes; +0342 GSS.scores=GSS.scores(J,:); +0343 +0344 %Reorder the GSS so that the first index corresponds to the default +0345 %compartment +0346 [~, J]=ismember(defaultCompartment,GSS.compartments); +0347 reorder=1:numel(GSS.compartments); +0348 reorder(J)=[]; +0349 reorder=[J reorder]; +0350 GSS.scores=GSS.scores(:,reorder); +0351 GSS.compartments=GSS.compartments(reorder); +0352 +0353 %Since it is only checked whether the metabolites can be synthesized, there +0354 %is no need to care about the stoichiometry. Change to -1/1 to simplify +0355 %later. Keep the S matrix for later though +0356 oldS=model.S; +0357 model.S(model.S>0)=1; +0358 model.S(model.S<0)=-1; +0359 +0360 %Here is a bit of a trick. To avoid the recurring calculation which +0361 %reactions are reversible, the reversible reactions have the coefficients +0362 %-10/10 instead of -1/1 +0363 model.S(:,model.rev==1)=model.S(:,model.rev==1).*10; +0364 +0365 %***Begin problem formulation +0366 +0367 %Some numbers that are good to have +0368 nRxns=numel(model.rxns)-nER; %Excluding exchange rxns +0369 nMets=numel(model.mets)-nEM; %Excluding exchange mets +0370 nGenes=numel(model.genes); +0371 nComps=numel(GSS.compartments); +0372 +0373 %Create a big stoichiometric matrix that will be the current model. In +0374 %order to have faster simulations the maximal model size is declared and +0375 %reactions are then moved within it. +0376 +0377 %First the original model (with the first nE being exchange rxns), then +0378 %reserve space for number of rxns minus exchange rxns for each non-default +0379 %compartment, then transport reactions for all non-exchange mets between +0380 %the default compartment and all others. +0381 %NOTE: Kept eye()*0 since eye() can be used to include all transport from +0382 %the beginning +0383 s=repmat(eye(nMets)*0,1,nComps-1); +0384 s=[zeros(numel(model.mets)-nMets,size(s,2));s]; +0385 S=[model.S sparse(numel(model.mets),nRxns*(nComps-1)) s]; +0386 s=[sparse(nMets*(nComps-1),numel(model.rxns)+nRxns*(nComps-1)) eye(nMets*(nComps-1))*0]; +0387 S=[S;s]; +0388 +0389 %Also replicate the transport costs +0390 transportCost=[transportCost(1:nEM);repmat(transportCost(nEM+1:end),nComps,1)]; +0391 +0392 %Create a binary matrix that says where the genes are in the current +0393 %solution +0394 g2c=false(nGenes,nComps); +0395 %All genes start in the default compartment +0396 g2c(:,1)=true; +0397 +0398 %Start of main optimization loop +0399 tic; +0400 bestScore=-inf; +0401 bestS=[]; +0402 bestg2c=[]; +0403 +0404 %Temp for testing +0405 plotScore=[]; +0406 nTrans=[]; +0407 totScore=[]; +0408 minScore=sum(min(GSS.scores,[],2)); +0409 maxScore=sum(max(GSS.scores,[],2)); +0410 +0411 while toc<maxTime*60 +0412 %Pick a random gene, weighted by it is current score minus the best +0413 %score for that gene (often 1.0, but can be 0.5 for no genes or average +0414 %for complexes). Genes with bad fits are more likely to be moved. This +0415 %formulation never moves a gene from its best compartment. Therefore a +0416 %small uniform weight is added +0417 [I, J]=find(g2c); +0418 geneToMove=randsample(nGenes,1,true,max(GSS.scores(I,:),[],2)-GSS.scores(sub2ind(size(g2c),I,J))+0.1); +0419 +0420 %Sample among possible compartments to move to. Add a larger weight to +0421 %even out the odds a little. Also a way of getting rid of loops where +0422 %the same set of genes are moved back and forth several times +0423 toComp=randsample(nComps,1,true,GSS.scores(geneToMove,:)+0.2); +0424 +0425 %Check that it moves to a new compartment +0426 if toComp==find(g2c(geneToMove,:)) +0427 continue; +0428 end +0429 +0430 %Moves the gene +0431 [newS, newg2c]=moveGene(S,model,g2c,geneToMove,toComp,nRxns,nMets); +0432 +0433 %Tries to connect the network. If this was not possible in 10 +0434 %iterations, then abort. If more than 20 modifications were needed then +0435 %it is unlikely that it will be a lower score +0436 wasConnected=false; +0437 for j=1:10 +0438 %Find the metabolites that are now unconnected +0439 unconnected=findUnconnected(newS,nEM); +0440 +0441 %Continue if there are still unconnected +0442 if any(unconnected) +0443 %For each gene find out how many of these could be connected if +0444 %the gene was moved and how many would be disconnected by +0445 %moving that gene +0446 [geneIndex, moveTo, deltaConnected, deltaScore]=selectGenes(newS,nEM,nMets,nER,nRxns,model,unconnected,g2c,GSS); +0447 +0448 %Score which gene would be the best to move. The highest +0449 %deltaScore is 1.0. It should be possible to move a gene from +0450 %worst to best compartment even if it disconnects, say, 1.5 +0451 %more metabolites +0452 [score, I]=max(1.5*deltaScore+deltaConnected); +0453 +0454 %Check if it has to add a transport or if there is a gene that +0455 %could be moved order to have a more connected network +0456 hasToAddTransport=true; +0457 if ~isempty(deltaConnected) +0458 if score>0 +0459 hasToAddTransport=false; +0460 end +0461 end +0462 +0463 %If it is possible to move any gene to have a more +0464 %connected network, then move the best one +0465 if hasToAddTransport==false +0466 [newS, newg2c]=moveGene(newS,model,g2c,geneIndex(I),moveTo(I),nRxns,nMets); +0467 else +0468 %Choose a random unconnected metabolite that should be +0469 %connected +0470 transMet=unconnected(randsample(numel(unconnected),1)); +0471 +0472 %First get where the metabolite is now +0473 comps=ceil((transMet-nEM)/((size(S,1)-nEM)/nComps)); +0474 +0475 %Find the corresponding metabolite index if it were in the +0476 %default compartment +0477 dcIndex=transMet-(comps-1)*nMets; +0478 +0479 %Then get the indexes of that metabolite in all +0480 %compartments +0481 allIndexes=dcIndex; +0482 for k=1:nComps-1 +0483 allIndexes=[allIndexes;dcIndex+nMets*k]; +0484 end +0485 +0486 %It could be that some of these are not used in any +0487 %reaction. Get only the ones which are +0488 I=sum(newS(allIndexes,:)~=0,2)>0; +0489 +0490 %Then get the ones that are used but not in unconnected. +0491 %These are metabolites that could potentially be +0492 %transported to connect transMet +0493 connectedUsed=setdiff(allIndexes(I),unconnected); +0494 +0495 %This may be an error but leave it for now. It seems to +0496 %happen if nothing can be connected in one step +0497 if isempty(connectedUsed) +0498 break; +0499 end +0500 +0501 %If transMet is in the default compartment then everything +0502 %is fine, just connect it to a random one +0503 if transMet==dcIndex +0504 newS=addTransportToS(newS,nRxns,nER,nMets,nEM,nComps,transMet,connectedUsed(randsample(numel(connectedUsed),1))); +0505 else +0506 %If one of the connectedUsed is in the default +0507 %compartment then connect to that one +0508 I=connectedUsed(connectedUsed<(nMets+nEM)); +0509 if any(I) +0510 newS=addTransportToS(newS,nRxns,nER,nMets,nEM,nComps,transMet,I(randsample(numel(I),1))); +0511 else +0512 %This is if the only way to connect it is by adding +0513 %two transport reactions, going via the default +0514 %compartment +0515 break; +0516 end +0517 end +0518 end +0519 else +0520 wasConnected=true; +0521 break; +0522 end +0523 end +0524 +0525 %If the network was connected in a new way, it is possible that some +0526 %transport reactions are no longer needed. They should be removed +0527 if wasConnected==true +0528 %These are the metabolites that are being transported +0529 activeTransport=find(sum(newS(:,nER+nRxns*nComps+1:end),2)); +0530 +0531 %Get the metabolites that are unconnected if transport was not used +0532 unconnected=findUnconnected(newS(:,1:nER+nRxns*nComps),nEM); +0533 +0534 %Find the transport reactions that are not needed and delete them +0535 I=setdiff(activeTransport,unconnected); +0536 +0537 %Since both metabolites in a transport rxns must be connected for +0538 %the reaction to be deleted, the sum over the colums should be 4 +0539 newS(:,find(sum(newS(I,nER+nRxns*nComps+1:end))==4)+nER+nRxns*nComps)=0; +0540 +0541 %Score the solution and determine whether to keep it as a new +0542 %solution +0543 [score, geneScore, trCost]=scoreLocalization(newS,newg2c,GSS,transportCost); +0544 +0545 %If it was the best solution so far, keep it +0546 if score>bestScore +0547 bestScore=score; +0548 bestS=newS; +0549 bestg2c=newg2c; +0550 end +0551 +0552 %This should not be steepest descent later +0553 if score>=bestScore% || exp((score-bestScore)*7)>rand() +0554 plotScore=[plotScore;geneScore]; +0555 nTrans=[nTrans;trCost]; +0556 totScore=[totScore;score]; +0557 S=newS; +0558 g2c=newg2c; +0559 +0560 if plotResults==true +0561 subplot(3,2,1); +0562 spy(S); +0563 subplot(3,2,2); +0564 plot(plotScore,'r'); +0565 xlabel('Gene score'); +0566 subplot(3,2,3); +0567 plot((plotScore-minScore)/(maxScore-minScore),'r'); +0568 xlabel('Gene score relative to predictions'); +0569 subplot(3,2,4); +0570 plot(nTrans,'g'); +0571 xlabel('Transport cost'); +0572 subplot(3,2,5); +0573 plot(totScore,'b'); +0574 xlabel('Total score'); +0575 subplot(3,2,6); +0576 pause(0.2); +0577 end +0578 end +0579 end +0580 end +0581 scores.totScore=score; +0582 scores.geneScore=geneScore; +0583 scores.transCost=trCost; +0584 +0585 %Find which metabolites are transported and to where +0586 [I, J]=find(bestS(nEM+1:nEM+nMets,end-nMets*(nComps-1)+1:end)); +0587 J=ceil(J/nMets+1); +0588 transportStruct.mets=model.metNames(I+nEM); +0589 transportStruct.toComp=GSS.compartments(J); +0590 +0591 [I, J]=find(bestg2c); +0592 geneLocalization.genes=GSS.genes(I); +0593 geneLocalization.comps=GSS.compartments(J); +0594 +0595 %Resort the gene names +0596 [~, I]=sort(geneLocalization.genes); +0597 geneLocalization.genes=geneLocalization.genes(I); +0598 geneLocalization.comps=geneLocalization.comps(I); +0599 +0600 %Remove the fake genes +0601 I=strncmp('&&FAKE&&',geneLocalization.genes,8); +0602 geneLocalization.genes(I)=[]; +0603 geneLocalization.comps(I)=[]; +0604 +0605 %Put together the model. This is done by first duplicating the S matrix +0606 %into the different compartments. Then the transport reactions are added +0607 %based on transportStruct. By now model.S should have the same size as the +0608 %S matrix used in the optimization, but with conserved stoichiometry. In +0609 %the final step all reactions and metabolites that are not used in the S +0610 %matrix from the optimization are deleted from the model +0611 outModel=model; +0612 outModel.S=oldS; +0613 +0614 %This is the S matrix without exchange rxns or metabolites +0615 copyPart=outModel.S(nEM+1:end,nER+1:end); +0616 +0617 %Replicate to give the rxnGeneMat for the full system +0618 copyRxnGeneMat=outModel.rxnGeneMat(nER+1:end,:); +0619 outModel.rxnGeneMat=[outModel.rxnGeneMat;repmat(copyRxnGeneMat,nComps-1,1)]; +0620 +0621 %First fix the compartments. The model is already ordered with the exchange +0622 %metabolites first. The original model may contain one or two compartments, +0623 %depending on whether any exchange metabolites are defined +0624 nStartComps=numel(outModel.comps); +0625 if nStartComps==1 +0626 outModel.comps={'1'}; +0627 outModel.compNames=GSS.compartments(1); +0628 else +0629 if model.metComps(1)==1 +0630 outModel.compNames(1)=GSS.compartments(1); +0631 else +0632 outModel.compNames(2)=GSS.compartments(1); +0633 end +0634 end +0635 outModel.compNames=[outModel.compNames;GSS.compartments(2:end)]; +0636 +0637 %Ugly little loop +0638 for i=1:numel(GSS.compartments)-1 +0639 outModel.comps=[outModel.comps;num2str(numel(outModel.comps)+1)]; +0640 end +0641 %This information is not known from the data, so empty fields are added +0642 outModel.compOutside=cell(numel(outModel.comps),1); +0643 outModel.compOutside(:)={''}; +0644 +0645 for i=1:nComps-1 +0646 outModel.S=[outModel.S sparse(size(outModel.S,1),nRxns)]; +0647 outModel.S=[outModel.S;[sparse(nMets,nRxns*i+nER) copyPart]]; +0648 outModel.rxns=[outModel.rxns;strcat(outModel.rxns(nER+1:nER+nRxns),'_',GSS.compartments{i+1})]; +0649 outModel.rxnNames=[outModel.rxnNames;strcat(outModel.rxnNames(nER+1:nER+nRxns),' (',GSS.compartments{i+1},')')]; +0650 outModel.lb=[outModel.lb;outModel.lb(nER+1:nER+nRxns)]; +0651 outModel.ub=[outModel.ub;outModel.ub(nER+1:nER+nRxns)]; +0652 outModel.rev=[outModel.rev;outModel.rev(nER+1:nER+nRxns)]; +0653 outModel.c=[outModel.c;outModel.c(nER+1:nER+nRxns)]; +0654 if isfield(outModel,'grRules') +0655 outModel.grRules=[outModel.grRules;outModel.grRules(nER+1:nER+nRxns)]; +0656 end +0657 if isfield(outModel,'subSystems') +0658 outModel.subSystems=[outModel.subSystems;outModel.subSystems(nER+1:nER+nRxns)]; +0659 end +0660 if isfield(outModel,'eccodes') +0661 outModel.eccodes=[outModel.eccodes;outModel.eccodes(nER+1:nER+nRxns)]; +0662 end +0663 if isfield(outModel,'rxnFrom') +0664 outModel.rxnFrom=[outModel.rxnFrom;outModel.rxnFrom(nER+1:nER+nRxns)]; +0665 end +0666 if isfield(outModel,'rxnMiriams') +0667 outModel.rxnMiriams=[outModel.rxnMiriams;outModel.rxnMiriams(nER+1:nER+nRxns)]; +0668 end +0669 if isfield(outModel,'rxnNotes') +0670 outModel.rxnNotes=[outModel.rxnNotes;outModel.rxnNotes(nER+1:nER+nRxns)]; +0671 end +0672 if isfield(outModel,'rxnReferences') +0673 outModel.rxnReferences=[outModel.rxnReferences;outModel.rxnReferences(nER+1:nER+nRxns)]; +0674 end +0675 if isfield(outModel,'rxnConfidenceScores') +0676 outModel.rxnConfidenceScores=[outModel.rxnConfidenceScores;outModel.rxnConfidenceScores(nER+1:nER+nRxns)]; +0677 end +0678 if isfield(outModel,'rxnDeltaG') +0679 outModel.rxnDeltaG=[outModel.rxnDeltaG;outModel.rxnDeltaG(nER+1:nER+nRxns)]; +0680 end +0681 outModel.mets=[outModel.mets;strcat(outModel.mets(nEM+1:nEM+nMets),'_',GSS.compartments{i+1})]; +0682 outModel.metNames=[outModel.metNames;outModel.metNames(nEM+1:nEM+nMets)]; +0683 outModel.b=[outModel.b;outModel.b(nEM+1:nEM+nMets,:)]; +0684 I=ones(nMets,1)*nStartComps+i; +0685 outModel.metComps=[outModel.metComps;I]; +0686 if isfield(outModel,'inchis') +0687 outModel.inchis=[outModel.inchis;outModel.inchis(nEM+1:nEM+nMets)]; +0688 end +0689 if isfield(outModel,'metSmiles') +0690 outModel.metSmiles=[outModel.metSmiles;outModel.metSmiles(nEM+1:nEM+nMets)]; +0691 end +0692 if isfield(outModel,'unconstrained') +0693 outModel.unconstrained=[outModel.unconstrained;outModel.unconstrained(nEM+1:nEM+nMets)]; +0694 end +0695 if isfield(outModel,'metMiriams') +0696 outModel.metMiriams=[outModel.metMiriams;outModel.metMiriams(nEM+1:nEM+nMets)]; +0697 end +0698 if isfield(outModel,'metFormulas') +0699 outModel.metFormulas=[outModel.metFormulas;outModel.metFormulas(nEM+1:nEM+nMets)]; +0700 end +0701 if isfield(outModel,'metFrom') +0702 outModel.metFrom=[outModel.metFrom;outModel.metFrom(nEM+1:nEM+nMets)]; +0703 end +0704 if isfield(outModel,'metCharges') +0705 outModel.metCharges=[outModel.metCharges;outModel.metCharges(nEM+1:nEM+nMets)]; +0706 end +0707 if isfield(outModel,'metDeltaG') +0708 outModel.metDeltaG=[outModel.metDeltaG;outModel.metDeltaG(nEM+1:nEM+nMets)]; +0709 end +0710 end +0711 +0712 %Add the transport reactions +0713 transS=bestS(:,numel(outModel.rxns)+1:end); +0714 J=sum(transS)>0; %Active rxns +0715 +0716 %Transport reactions are written in a different way compared to a "real" +0717 %stoichimetric matrix. This is to fix that +0718 transS(transS~=0)=1; +0719 transS(1:nEM+nMets,:)=transS(1:nEM+nMets,:)*-1; +0720 I=find(sum(transS>0,2)); +0721 nTransRxns=numel(I); +0722 outModel.S=[outModel.S transS(:,J)]; +0723 filler=ones(nTransRxns,1); +0724 outModel.lb=[outModel.lb;filler*-1000]; +0725 outModel.ub=[outModel.ub;filler*1000]; +0726 outModel.rev=[outModel.rev;filler]; +0727 outModel.c=[outModel.c;filler*0]; +0728 outModel.rxnGeneMat=[outModel.rxnGeneMat;sparse(nTransRxns,numel(outModel.genes))]; +0729 +0730 for i=1:numel(I) +0731 outModel.rxns=[outModel.rxns;strcat('transport',num2str(i))]; +0732 outModel.rxnNames=[outModel.rxnNames;['Transport of ',outModel.metNames{I(i)}]]; +0733 if isfield(outModel,'grRules') +0734 outModel.grRules=[outModel.grRules;{''}]; +0735 end +0736 if isfield(outModel,'rxnMiriams') +0737 outModel.rxnMiriams=[outModel.rxnMiriams;{[]}]; +0738 end +0739 if isfield(outModel,'subSystems') +0740 if any(cellfun(@(x) iscell(x), outModel.subSystems)) +0741 outModel.subSystems=[outModel.subSystems;{{'Inferred transport reactions'}}]; +0742 else +0743 outModel.subSystems=[outModel.subSystems;{'Inferred transport reactions'}]; +0744 end +0745 end +0746 if isfield(outModel,'eccodes') +0747 outModel.eccodes=[outModel.eccodes;{''}]; +0748 end +0749 if isfield(outModel,'rxnFrom') +0750 outModel.rxnFrom=[outModel.rxnFrom;{''}]; +0751 end +0752 if isfield(outModel,'rxnNotes') +0753 outModel.rxnNotes=[outModel.rxnNotes;{''}]; +0754 end +0755 if isfield(outModel,'rxnReferences') +0756 outModel.rxnReferences=[outModel.rxnReferences;{''}]; +0757 end +0758 if isfield(outModel,'rxnConfidenceScores') +0759 outModel.rxnConfidenceScores=[outModel.rxnConfidenceScores;NaN]; +0760 end +0761 if isfield(outModel,'rxnDeltaG') +0762 outModel.rxnDeltaG=[outModel.rxnDeltaG;NaN]; +0763 end +0764 end +0765 +0766 %Then remove all reactions and metabolites that are not used in the final +0767 %solution from the optimization +0768 [~, J]=find(bestS(:,1:nER+nComps*nRxns)); +0769 K=true(numel(outModel.rxns),1); +0770 K(J)=false; +0771 K(end-nTransRxns+1:end)=false; +0772 outModel=removeReactions(outModel,K,true); +0773 +0774 %Remove all fake genes +0775 I=strncmp('&&FAKE&&',outModel.genes,8); +0776 outModel.genes(I)=[]; +0777 if isfield(outModel,'geneMiriams') +0778 outModel.geneMiriams(I)=[]; +0779 end +0780 if isfield(outModel,'geneShortNames') +0781 outModel.geneShortNames(I)=[]; +0782 end +0783 if isfield(outModel,'proteins') +0784 outModel.proteins(I)=[]; +0785 end +0786 outModel.rxnGeneMat(:,I)=[]; +0787 +0788 %Fix grRules and reconstruct rxnGeneMat +0789 [grRules,rxnGeneMat] = standardizeGrRules(outModel,true); +0790 outModel.grRules = grRules; +0791 outModel.rxnGeneMat = rxnGeneMat; +0792 end +0793 +0794 %Moves a gene and all associated reactions from one compartment to another +0795 function [S, g2c]=moveGene(S,model,g2c,geneToMove,toComp,nRxns,nMets) +0796 %Find the current compartment and update to the new one +0797 currentComp=find(g2c(geneToMove,:)); +0798 g2c(geneToMove,:)=false; +0799 g2c(geneToMove,toComp)=true; +0800 +0801 %Find the reactions in the original model that the gene controls +0802 [I, ~]=find(model.rxnGeneMat(:,geneToMove)); +0803 +0804 %Calculate their current positions in the S matrix +0805 oldRxns=I+(currentComp-1)*nRxns; +0806 +0807 %And their new positions +0808 newRxns=I+(toComp-1)*nRxns; +0809 +0810 %The metabolite ids also have to be changed to match the new +0811 %compartment +0812 metChange=nMets*(toComp-currentComp); +0813 +0814 %Update the reactions +0815 [I, J, K]=find(S(:,oldRxns)); +0816 I=I+metChange; +0817 +0818 %Move the reactions +0819 S(:,oldRxns)=0; +0820 S(sub2ind(size(S),I,newRxns(J)))=K; +0821 end +0822 +0823 %Finds which metabolites are unconnected, in the sense that they are never +0824 %a product or only a product in a reversible reaction where one reactant is +0825 %only a product in the opposite direction of that reaction. This function +0826 %ignores exchange metabolites. Returns a vector of metabolite indexes. +0827 %metsToCheck is an array of metabolite indexes to check for connectivity. +0828 %If not supplied then all metabolites are checked +0829 function unconnected=findUnconnected(S,nEM,metsToCheck) +0830 if nargin>2 +0831 %Do this by deleting everything from the network that is not in +0832 %metsToCheck and that is not exchange metabolites +0833 I=false(size(S,1),1); +0834 I(1:nEM)=true; +0835 I(metsToCheck)=true; +0836 S=S(I,:); +0837 end +0838 +0839 em=false(size(S,1),1); +0840 em(1:nEM)=true; +0841 +0842 %Construct a matrix in which the reversible reactions are inverted +0843 I=sum(S>2,1) | sum(S>2,1); +0844 revS=S; +0845 revS(:,I)=revS(:,I)*-1; +0846 +0847 %First calculate the ones that are ok +0848 %Produced in 2 rxns, is exchange, is not used at all, is produced in +0849 %non-reversible, involved in more than 1 reversible reactions +0850 connected=sum(S>0,2)>1 | em | sum(S~=0,2)==0 | sum(S(:,~I)>0,2)>0 | sum(S(:,I)~=0,2)>1; +0851 +0852 %Then get the ones that are unconnected because they are never produced +0853 unconnected=sum(S>0 | revS>0,2)==0 & connected==false; +0854 +0855 %Then get the ones that are potentially unconnected +0856 maybeUnconnected=~connected & ~unconnected; +0857 %maybeUnconnected=find(maybeUnconnectedS); +0858 +0859 %The metabolites in maybeUnconnected are involved in one reversible +0860 %reaction and not produced in any other reaction. This means that the +0861 %reactions which have at least one met in maybeUnconnected as reactant and +0862 %one as product are unconnected. The metabolites in maybeUnconnected that +0863 %are present in those reactions are then dead ends +0864 deadRxns=any(S(maybeUnconnected,:)>0) & any(S(maybeUnconnected,:)<0); +0865 +0866 %Get the mets involved in any of those reactions +0867 problematic=any(S(:,deadRxns)~=0,2); +0868 +0869 %If any of these are in the maybeUnconnected list then the metabolite is +0870 %unconnected +0871 unconnected(problematic & maybeUnconnected)=true; +0872 +0873 %Map back to metsToCheck +0874 if nargin>2 +0875 unconnected=metsToCheck(unconnected(nEM+1:end)); +0876 else +0877 unconnected=find(unconnected); 0878 end -0879 -0880 %Given a set of unconnected metabolites, this function tries to move each -0881 %gene that could connect any of them, calculates the number of newly -0882 %connected metabolites minus the number of newly disconnected metabolites. -0883 %As some metabolites are very connected, only 25 genes are checked. Genes -0884 %that have a low score in their current compartment are more likely to be -0885 %moved -0886 function [geneIndex, moveTo, deltaConnected, deltaScore]=selectGenes(S,nEM,nMets,nER,nRxns,model,unconnected,g2c,GSS) -0887 %If moveTo is 0 then the gene cannot connect any of the metabolites -0888 moveTo=zeros(numel(model.genes),1); -0889 deltaConnected=zeros(numel(model.genes),1); -0890 -0891 %First get where the metabolites are now -0892 nComps=size(g2c,2); -0893 comps=ceil((unconnected-nEM)/((size(S,1)-nEM)/nComps)); -0894 -0895 %Find the corresponding metabolite indexes if they all were in the default -0896 %compartment -0897 dcIndexes=unique(unconnected-(comps-1)*nMets); -0898 -0899 %Then find them if they were in any other compartment -0900 allIndexes=dcIndexes; -0901 for i=1:nComps-1 -0902 allIndexes=[allIndexes;dcIndexes+nMets*i]; -0903 end -0904 -0905 %Also check which reversible reactions that could be used -0906 I=sum(S>2,1) | sum(S>2,1); -0907 revS=S; -0908 revS(:,I)=revS(:,I)*-1; -0909 -0910 %Find all reactions that could make any of the unconnected metabolites in -0911 %some other compartment -0912 newMets=setdiff(allIndexes,unconnected); -0913 [~, potential]=find(S(newMets,:)>0 | revS(newMets,:)>0); -0914 potential(potential<=nER | potential>nER+nRxns*nComps)=[]; %No exchange rxns or transport rxns -0915 -0916 %Map J to the real metabolic reactions in model -0917 rxnComps=ceil((potential-nER)/(nRxns)); -0918 -0919 %Find the corresponding reaction indexes if they all were in the default -0920 %compartment -0921 dcRxnIndexes=potential-(rxnComps-1)*nRxns; -0922 -0923 %Get the genes for those reactions -0924 genes=find(sum(model.rxnGeneMat(dcRxnIndexes,:)>0,1)); -0925 -0926 %For some cases there can be very many reactions to connect something. This -0927 %is in particular true in the beginning of the optimization if, say, ATP is -0928 %unconnected. Therefore limit the number of genes to be checked to 25. -0929 %Weigh so that genes with bad scores in their current compartment are more -0930 %likely to be moved. -0931 -0932 %Get scores for these genes -0933 [~, J]=find(g2c(genes,:)); -0934 -0935 %Add a small weight so that genes in their best compartment could be moved -0936 %as well -0937 geneScores=GSS.scores(sub2ind(size(g2c),genes(:),J)); -0938 modGeneScores=1.1-geneScores; -0939 if numel(genes)>25 -0940 rGenes=genes(randsample(numel(genes),min(numel(genes),25),true,modGeneScores)); -0941 -0942 %The sampling with weights could give duplicates -0943 rGenes=unique(rGenes); -0944 -0945 %Reorder the geneScores to match -0946 [~, I]=ismember(rGenes,genes); -0947 geneScores=geneScores(I); -0948 genes=rGenes; -0949 end -0950 for i=1:numel(genes) -0951 %Since one gene is moved at a time, only metabolites involved in any of -0952 %the reactions for that gene can become unconnected. This helps to -0953 %speed up the algorithm. First get all involved reactions in the -0954 %default compartment -0955 rxns=find(model.rxnGeneMat(:,genes(i))); -0956 -0957 %Then get their mets -0958 mets=find(sum(model.S(:,rxns)~=0,2)>0); -0959 -0960 %Then get their indexes in all compartments -0961 allIndexes=mets; -0962 for j=1:nComps-1 -0963 allIndexes=[allIndexes;mets+nMets*j]; -0964 end -0965 -0966 %Check which of the unconnected metabolites that these reactions -0967 %correspond to. This could have been done earlier, but it is fast. The -0968 %reversibility check is skipped because it is unlikely to be an issue -0969 %here. Worst case is that the gene is tested once to much -0970 [I, ~]=find(model.S(:,rxns)); -0971 moveToComps=unique(comps(ismember(dcIndexes,I))); -0972 -0973 %Try to move the gene to each of the compartments -0974 bestMove=-inf; -0975 bestComp=[]; -0976 for j=1:numel(moveToComps) -0977 newS=moveGene(S,model,g2c,genes(i),moveToComps(j),nRxns,nMets); -0978 -0979 %Check how many metabolites that are unconnected after moving the -0980 %gene -0981 dConnected=numel(unconnected)-numel(findUnconnected(newS,nEM,[allIndexes;unconnected])); -0982 if dConnected>bestMove -0983 bestMove=dConnected; -0984 bestComp=moveToComps(j); -0985 end -0986 end -0987 -0988 %Add the difference in connectivity and where the genes should be moved -0989 moveTo(genes(i))=bestComp; -0990 deltaConnected(genes(i))=bestMove; -0991 end -0992 -0993 %Finish up -0994 geneIndex=genes(:); -0995 moveTo=moveTo(geneIndex); -0996 deltaConnected=deltaConnected(geneIndex); -0997 deltaScore=GSS.scores(sub2ind(size(g2c),geneIndex(:),moveTo))-geneScores; -0998 end -0999 -1000 %Small function to add a transport reactions between two metabolites. -1001 %Transport reactions are written as having a coefficient 2.0 for both -1002 %reactant and product. This is not a "real" reaction, but since all normal -1003 %reactions have coefficient -1/1 or -10/10 it is a compact way of writing -1004 %it -1005 function S=addTransportToS(S,nRxns,nER,nMets,nEM,nComps,metA,metB) -1006 mets=[metA;metB]; -1007 %Find the current compartments for the metabolites -1008 comps=ceil((mets-nEM)/((size(S,1)-nEM)/nComps)); -1009 -1010 if sum(comps==1)~=1 -1011 EM='Tried to create a transport reaction from a non-default compartment'; -1012 error('RAVEN:badInput', '%s', EM); -1013 end -1014 -1015 %Calculate the reaction index -1016 rIndex=(nER+nRxns*nComps)+mets(comps~=1)-nEM-nMets; -1017 -1018 S(mets,rIndex)=2; -1019 end -1020 -1021 %Scores a network based on the localization of the genes and the number of -1022 %transporter reactions used -1023 function [score, geneScore, transportCost]=scoreLocalization(S,g2c,GSS,transportCost) -1024 [I, J]=find(g2c); -1025 geneScore=sum(GSS.scores(sub2ind(size(g2c),I,J))); -1026 [I, ~]=find(S==2); -1027 I=unique(I); -1028 transportCost=sum(transportCost(I)); -1029 score=geneScore-transportCost; -1030 end -1031 -1032 % To avoid dependency on stats toolbox, use this alternative implementation -1033 % of randsample, source: -1034 % https://github.com/gpeyre/numerical-tours/blob/dacee30081c04ef5f67b26b387ead85f2b193af9/matlab/toolbox_signal/randsample.m -1035 function y = randsample(n, k, replace, w) -1036 %RANDSAMPLE Random sample, with or without replacement. -1037 % Y = RANDSAMPLE(N,K) returns Y as a vector of K values sampled uniformly -1038 % at random, without replacement, from the integers 1:N. -1039 % -1040 % Y = RANDSAMPLE(POPULATION,K) returns K values sampled uniformly at -1041 % random, without replacement, from the values in the vector POPULATION. -1042 % -1043 % Y = RANDSAMPLE(...,REPLACE) returns a sample taken with replacement if -1044 % REPLACE is true, or without replacement if REPLACE is false (the default). -1045 % -1046 % Y = RANDSAMPLE(...,true,W) returns a weighted sample, using positive -1047 % weights W, taken with replacement. W is often a vector of probabilities. -1048 % This function does not support weighted sampling without replacement. -1049 % -1050 % Example: Generate a random sequence of the characters ACGT, with -1051 % replacement, according to specified probabilities. -1052 % -1053 % R = randsample("ACGT",48,true,[0.15 0.35 0.35 0.15]) -1054 % -1055 % See also RAND, RANDPERM. -1056 -1057 % Copyright 1993-2008 The MathWorks, Inc. -1058 % $Revision: 1.1.4.3 $ $Date: 2008/12/01 08:09:34 $ -1059 -1060 if nargin < 2 -1061 error('stats:randsample:TooFewInputs','Requires two input arguments.'); -1062 elseif numel(n) == 1 -1063 population = []; -1064 else -1065 population = n; -1066 n = numel(population); -1067 if length(population)~=n -1068 error('stats:randsample:BadPopulation','POPULATION must be a vector.'); -1069 end -1070 end -1071 -1072 if nargin < 3 -1073 replace = false; -1074 end -1075 -1076 if nargin < 4 -1077 w = []; -1078 elseif ~isempty(w) -1079 if length(w) ~= n -1080 if isempty(population) -1081 error('stats:randsample:InputSizeMismatch',... -1082 'W must have length equal to N.'); -1083 else -1084 error('stats:randsample:InputSizeMismatch',... -1085 'W must have the same length as the population.'); -1086 end -1087 else -1088 p = w(:)' / sum(w); -1089 end -1090 end -1091 -1092 switch replace -1093 -1094 % Sample with replacement -1095 case {true, 'true', 1} -1096 if isempty(w) -1097 y = ceil(n .* rand(k,1)); -1098 else -1099 [dum, y] = histc(rand(k,1),[0 cumsum(p)]); -1100 end -1101 -1102 % Sample without replacement -1103 case {false, 'false', 0} -1104 if k > n -1105 if isempty(population) -1106 error('stats:randsample:SampleTooLarge',... -1107 'K must be less than or equal to N for sampling without replacement.'); -1108 else -1109 error('stats:randsample:SampleTooLarge',... -1110 'K must be less than or equal to the population size.'); -1111 end -1112 end -1113 -1114 if isempty(w) -1115 % If the sample is a sizeable fraction of the population, -1116 % just randomize the whole population (which involves a full -1117 % sort of n random values), and take the first k. -1118 if 4*k > n -1119 rp = randperm(n); -1120 y = rp(1:k); -1121 -1122 % If the sample is a small fraction of the population, a full sort -1123 % is wasteful. Repeatedly sample with replacement until there are -1124 % k unique values. -1125 else -1126 x = zeros(1,n); % flags -1127 sumx = 0; -1128 while sumx < k -1129 x(ceil(n * rand(1,k-sumx))) = 1; % sample w/replacement -1130 sumx = sum(x); % count how many unique elements so far -1131 end -1132 y = find(x > 0); -1133 y = y(randperm(k)); -1134 end -1135 else -1136 error('stats:randsample:NoWeighting',... -1137 'Weighted sampling without replacement is not supported.'); -1138 end -1139 otherwise -1140 error('stats:randsample:BadReplaceValue',... -1141 'REPLACE must be either true or false.'); -1142 end -1143 -1144 if ~isempty(population) -1145 y = population(y); -1146 else -1147 y = y(:); -1148 end -1149 end +0879 end +0880 +0881 %Given a set of unconnected metabolites, this function tries to move each +0882 %gene that could connect any of them, calculates the number of newly +0883 %connected metabolites minus the number of newly disconnected metabolites. +0884 %As some metabolites are very connected, only 25 genes are checked. Genes +0885 %that have a low score in their current compartment are more likely to be +0886 %moved +0887 function [geneIndex, moveTo, deltaConnected, deltaScore]=selectGenes(S,nEM,nMets,nER,nRxns,model,unconnected,g2c,GSS) +0888 %If moveTo is 0 then the gene cannot connect any of the metabolites +0889 moveTo=zeros(numel(model.genes),1); +0890 deltaConnected=zeros(numel(model.genes),1); +0891 +0892 %First get where the metabolites are now +0893 nComps=size(g2c,2); +0894 comps=ceil((unconnected-nEM)/((size(S,1)-nEM)/nComps)); +0895 +0896 %Find the corresponding metabolite indexes if they all were in the default +0897 %compartment +0898 dcIndexes=unique(unconnected-(comps-1)*nMets); +0899 +0900 %Then find them if they were in any other compartment +0901 allIndexes=dcIndexes; +0902 for i=1:nComps-1 +0903 allIndexes=[allIndexes;dcIndexes+nMets*i]; +0904 end +0905 +0906 %Also check which reversible reactions that could be used +0907 I=sum(S>2,1) | sum(S>2,1); +0908 revS=S; +0909 revS(:,I)=revS(:,I)*-1; +0910 +0911 %Find all reactions that could make any of the unconnected metabolites in +0912 %some other compartment +0913 newMets=setdiff(allIndexes,unconnected); +0914 [~, potential]=find(S(newMets,:)>0 | revS(newMets,:)>0); +0915 potential(potential<=nER | potential>nER+nRxns*nComps)=[]; %No exchange rxns or transport rxns +0916 +0917 %Map J to the real metabolic reactions in model +0918 rxnComps=ceil((potential-nER)/(nRxns)); +0919 +0920 %Find the corresponding reaction indexes if they all were in the default +0921 %compartment +0922 dcRxnIndexes=potential-(rxnComps-1)*nRxns; +0923 +0924 %Get the genes for those reactions +0925 genes=find(sum(model.rxnGeneMat(dcRxnIndexes,:)>0,1)); +0926 +0927 %For some cases there can be very many reactions to connect something. This +0928 %is in particular true in the beginning of the optimization if, say, ATP is +0929 %unconnected. Therefore limit the number of genes to be checked to 25. +0930 %Weigh so that genes with bad scores in their current compartment are more +0931 %likely to be moved. +0932 +0933 %Get scores for these genes +0934 [~, J]=find(g2c(genes,:)); +0935 +0936 %Add a small weight so that genes in their best compartment could be moved +0937 %as well +0938 geneScores=GSS.scores(sub2ind(size(g2c),genes(:),J)); +0939 modGeneScores=1.1-geneScores; +0940 if numel(genes)>25 +0941 rGenes=genes(randsample(numel(genes),min(numel(genes),25),true,modGeneScores)); +0942 +0943 %The sampling with weights could give duplicates +0944 rGenes=unique(rGenes); +0945 +0946 %Reorder the geneScores to match +0947 [~, I]=ismember(rGenes,genes); +0948 geneScores=geneScores(I); +0949 genes=rGenes; +0950 end +0951 for i=1:numel(genes) +0952 %Since one gene is moved at a time, only metabolites involved in any of +0953 %the reactions for that gene can become unconnected. This helps to +0954 %speed up the algorithm. First get all involved reactions in the +0955 %default compartment +0956 rxns=find(model.rxnGeneMat(:,genes(i))); +0957 +0958 %Then get their mets +0959 mets=find(sum(model.S(:,rxns)~=0,2)>0); +0960 +0961 %Then get their indexes in all compartments +0962 allIndexes=mets; +0963 for j=1:nComps-1 +0964 allIndexes=[allIndexes;mets+nMets*j]; +0965 end +0966 +0967 %Check which of the unconnected metabolites that these reactions +0968 %correspond to. This could have been done earlier, but it is fast. The +0969 %reversibility check is skipped because it is unlikely to be an issue +0970 %here. Worst case is that the gene is tested once to much +0971 [I, ~]=find(model.S(:,rxns)); +0972 moveToComps=unique(comps(ismember(dcIndexes,I))); +0973 +0974 %Try to move the gene to each of the compartments +0975 bestMove=-inf; +0976 bestComp=[]; +0977 for j=1:numel(moveToComps) +0978 newS=moveGene(S,model,g2c,genes(i),moveToComps(j),nRxns,nMets); +0979 +0980 %Check how many metabolites that are unconnected after moving the +0981 %gene +0982 dConnected=numel(unconnected)-numel(findUnconnected(newS,nEM,[allIndexes;unconnected])); +0983 if dConnected>bestMove +0984 bestMove=dConnected; +0985 bestComp=moveToComps(j); +0986 end +0987 end +0988 +0989 %Add the difference in connectivity and where the genes should be moved +0990 moveTo(genes(i))=bestComp; +0991 deltaConnected(genes(i))=bestMove; +0992 end +0993 +0994 %Finish up +0995 geneIndex=genes(:); +0996 moveTo=moveTo(geneIndex); +0997 deltaConnected=deltaConnected(geneIndex); +0998 deltaScore=GSS.scores(sub2ind(size(g2c),geneIndex(:),moveTo))-geneScores; +0999 end +1000 +1001 %Small function to add a transport reactions between two metabolites. +1002 %Transport reactions are written as having a coefficient 2.0 for both +1003 %reactant and product. This is not a "real" reaction, but since all normal +1004 %reactions have coefficient -1/1 or -10/10 it is a compact way of writing +1005 %it +1006 function S=addTransportToS(S,nRxns,nER,nMets,nEM,nComps,metA,metB) +1007 mets=[metA;metB]; +1008 %Find the current compartments for the metabolites +1009 comps=ceil((mets-nEM)/((size(S,1)-nEM)/nComps)); +1010 +1011 if sum(comps==1)~=1 +1012 EM='Tried to create a transport reaction from a non-default compartment'; +1013 error('RAVEN:badInput', '%s', EM); +1014 end +1015 +1016 %Calculate the reaction index +1017 rIndex=(nER+nRxns*nComps)+mets(comps~=1)-nEM-nMets; +1018 +1019 S(mets,rIndex)=2; +1020 end +1021 +1022 %Scores a network based on the localization of the genes and the number of +1023 %transporter reactions used +1024 function [score, geneScore, transportCost]=scoreLocalization(S,g2c,GSS,transportCost) +1025 [I, J]=find(g2c); +1026 geneScore=sum(GSS.scores(sub2ind(size(g2c),I,J))); +1027 [I, ~]=find(S==2); +1028 I=unique(I); +1029 transportCost=sum(transportCost(I)); +1030 score=geneScore-transportCost; +1031 end +1032 +1033 % To avoid dependency on stats toolbox, use this alternative implementation +1034 % of randsample, source: +1035 % https://github.com/gpeyre/numerical-tours/blob/dacee30081c04ef5f67b26b387ead85f2b193af9/matlab/toolbox_signal/randsample.m +1036 function y = randsample(n, k, replace, w) +1037 %RANDSAMPLE Random sample, with or without replacement. +1038 % Y = RANDSAMPLE(N,K) returns Y as a vector of K values sampled uniformly +1039 % at random, without replacement, from the integers 1:N. +1040 % +1041 % Y = RANDSAMPLE(POPULATION,K) returns K values sampled uniformly at +1042 % random, without replacement, from the values in the vector POPULATION. +1043 % +1044 % Y = RANDSAMPLE(...,REPLACE) returns a sample taken with replacement if +1045 % REPLACE is true, or without replacement if REPLACE is false (the default). +1046 % +1047 % Y = RANDSAMPLE(...,true,W) returns a weighted sample, using positive +1048 % weights W, taken with replacement. W is often a vector of probabilities. +1049 % This function does not support weighted sampling without replacement. +1050 % +1051 % Example: Generate a random sequence of the characters ACGT, with +1052 % replacement, according to specified probabilities. +1053 % +1054 % R = randsample("ACGT",48,true,[0.15 0.35 0.35 0.15]) +1055 % +1056 % See also RAND, RANDPERM. +1057 +1058 % Copyright 1993-2008 The MathWorks, Inc. +1059 % $Revision: 1.1.4.3 $ $Date: 2008/12/01 08:09:34 $ +1060 +1061 if nargin < 2 +1062 error('stats:randsample:TooFewInputs','Requires two input arguments.'); +1063 elseif numel(n) == 1 +1064 population = []; +1065 else +1066 population = n; +1067 n = numel(population); +1068 if length(population)~=n +1069 error('stats:randsample:BadPopulation','POPULATION must be a vector.'); +1070 end +1071 end +1072 +1073 if nargin < 3 +1074 replace = false; +1075 end +1076 +1077 if nargin < 4 +1078 w = []; +1079 elseif ~isempty(w) +1080 if length(w) ~= n +1081 if isempty(population) +1082 error('stats:randsample:InputSizeMismatch',... +1083 'W must have length equal to N.'); +1084 else +1085 error('stats:randsample:InputSizeMismatch',... +1086 'W must have the same length as the population.'); +1087 end +1088 else +1089 p = w(:)' / sum(w); +1090 end +1091 end +1092 +1093 switch replace +1094 +1095 % Sample with replacement +1096 case {true, 'true', 1} +1097 if isempty(w) +1098 y = ceil(n .* rand(k,1)); +1099 else +1100 [dum, y] = histc(rand(k,1),[0 cumsum(p)]); +1101 end +1102 +1103 % Sample without replacement +1104 case {false, 'false', 0} +1105 if k > n +1106 if isempty(population) +1107 error('stats:randsample:SampleTooLarge',... +1108 'K must be less than or equal to N for sampling without replacement.'); +1109 else +1110 error('stats:randsample:SampleTooLarge',... +1111 'K must be less than or equal to the population size.'); +1112 end +1113 end +1114 +1115 if isempty(w) +1116 % If the sample is a sizeable fraction of the population, +1117 % just randomize the whole population (which involves a full +1118 % sort of n random values), and take the first k. +1119 if 4*k > n +1120 rp = randperm(n); +1121 y = rp(1:k); +1122 +1123 % If the sample is a small fraction of the population, a full sort +1124 % is wasteful. Repeatedly sample with replacement until there are +1125 % k unique values. +1126 else +1127 x = zeros(1,n); % flags +1128 sumx = 0; +1129 while sumx < k +1130 x(ceil(n * rand(1,k-sumx))) = 1; % sample w/replacement +1131 sumx = sum(x); % count how many unique elements so far +1132 end +1133 y = find(x > 0); +1134 y = y(randperm(k)); +1135 end +1136 else +1137 error('stats:randsample:NoWeighting',... +1138 'Weighted sampling without replacement is not supported.'); +1139 end +1140 otherwise +1141 error('stats:randsample:BadReplaceValue',... +1142 'REPLACE must be either true or false.'); +1143 end +1144 +1145 if ~isempty(population) +1146 y = population(y); +1147 else +1148 y = y(:); +1149 end +1150 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/manipulation/closeModel.html b/docs/html/manipulation/closeModel.html index cfaee767..189f0511 100644 --- a/docs/html/manipulation/closeModel.html +++ b/docs/html/manipulation/closeModel.html @@ -102,112 +102,106 @@

    SOURCE CODE ^%A boundary/exchange/sink/demand reaction has metabolites on only one 0038 %side, same definition as getExchangeRxns: no substrates or no -0039 %products. The previous coefficient-magnitude rule (all nonzero -0040 %coefficients summing to 1 in absolute value) missed a scaled -0041 %single-metabolite sink such as "2 A =>" (sum 2) outright, and only -0042 %matched a multi-metabolite one-sided reaction such as -0043 %"0.5 A + 0.5 B =>" by coincidence, when its magnitudes summed to -0044 %exactly 1 --- and even then mishandled it below, since find() on such -0045 %a reaction returns more than one index and only the first metabolite -0046 %was ever used to build the boundary metabolite, silently dropping the -0047 %rest. -0048 if any(col>0) && any(col<0) -0049 continue -0050 end -0051 metIdx=find(col); -0052 if isempty(metIdx) -0053 continue -0054 end -0055 %A single metabolite names and inherits properties from that -0056 %metabolite, as before. Several metabolites on the one populated side -0057 %have no single metabolite to name the boundary after or copy -0058 %properties from, so it is named after the reaction instead and left -0059 %without those properties. -0060 oneMet=isscalar(metIdx); -0061 if oneMet -0062 newMetId=strcat(closedModel.mets{metIdx},'_b'); -0063 else -0064 newMetId=strcat(closedModel.rxns{i},'_b'); -0065 end -0066 closedModel.mets{numel(closedModel.mets)+1}=newMetId; -0067 if isfield(closedModel,'metNames') -0068 if oneMet -0069 closedModel.metNames{numel(closedModel.metNames)+1}=closedModel.metNames{metIdx}; -0070 else -0071 closedModel.metNames{numel(closedModel.metNames)+1}=newMetId; -0072 end -0073 end -0074 closedModel.metComps(numel(closedModel.metComps)+1)=numel(closedModel.comps); -0075 if isfield(closedModel,'inchis') -0076 if oneMet -0077 closedModel.inchis{numel(closedModel.inchis)+1}=closedModel.inchis{metIdx}; -0078 else -0079 closedModel.inchis{numel(closedModel.inchis)+1}=''; -0080 end -0081 end -0082 if isfield(closedModel,'metSmiles') -0083 if oneMet -0084 closedModel.metSmiles{numel(closedModel.metSmiles)+1}=closedModel.metSmiles{metIdx}; -0085 else -0086 closedModel.metSmiles{numel(closedModel.metSmiles)+1}=''; -0087 end -0088 end -0089 if isfield(closedModel,'metFormulas') -0090 if oneMet -0091 closedModel.metFormulas{numel(closedModel.metFormulas)+1}=closedModel.metFormulas{metIdx}; -0092 else -0093 closedModel.metFormulas{numel(closedModel.metFormulas)+1}=''; -0094 end -0095 end -0096 if isfield(closedModel,'metMiriams') -0097 if oneMet -0098 closedModel.metMiriams{numel(closedModel.metMiriams)+1}=closedModel.metMiriams{metIdx}; -0099 else -0100 closedModel.metMiriams{numel(closedModel.metMiriams)+1}=[]; -0101 end -0102 end -0103 if isfield(closedModel,'metFrom') -0104 closedModel.metFrom{numel(closedModel.metFrom)+1}=''; -0105 end -0106 if isfield(closedModel,'metNotes') -0107 if oneMet -0108 closedModel.metNotes{numel(closedModel.metNotes)+1}=closedModel.metNotes{metIdx}; -0109 else -0110 closedModel.metNotes{numel(closedModel.metNotes)+1}=''; -0111 end -0112 end -0113 if isfield(closedModel,'metCharges') -0114 if oneMet -0115 closedModel.metCharges(numel(closedModel.metCharges)+1)=closedModel.metCharges(metIdx); -0116 else -0117 closedModel.metCharges(numel(closedModel.metCharges)+1)=0; -0118 end -0119 end -0120 if isfield(closedModel,'metDeltaG') -0121 if oneMet -0122 closedModel.metDeltaG(numel(closedModel.metDeltaG)+1)=closedModel.metDeltaG(metIdx); -0123 else -0124 closedModel.metDeltaG(numel(closedModel.metDeltaG)+1)=NaN; -0125 end -0126 end -0127 closedModel.unconstrained(numel(closedModel.unconstrained)+1)=1; -0128 % (end+1,:), not numel(b)+1: a linear index past the end of an N-by-2 -0129 % b (net-production bounds) does not append a row, it errors ("grow -0130 % array along ambiguous dimension"). -0131 closedModel.b(end+1,:)=0; -0132 closedModel.S=[closedModel.S;sparse(1,size(closedModel.S,2))]; -0133 %Any nonzero value blocks the reaction once its slack is pinned to 0 by -0134 %model.b above, regardless of magnitude; the sign is chosen only so the -0135 %boundary row reads the same way a human would expect (produced by vs. -0136 %consumed by the reaction). -0137 if sum(col)>0 -0138 closedModel.S(numel(closedModel.mets),i)=-1; -0139 else -0140 closedModel.S(numel(closedModel.mets),i)=1; -0141 end -0142 end -0143 -0144 end +0039 %products. The coefficient magnitudes do not matter, so a scaled sink +0040 %such as "2 A =>" and a multi-metabolite one-sided reaction such as +0041 %"0.5 A + 0.5 B =>" are both boundary reactions. +0042 if any(col>0) && any(col<0) +0043 continue +0044 end +0045 metIdx=find(col); +0046 if isempty(metIdx) +0047 continue +0048 end +0049 %A single metabolite names and inherits properties from that +0050 %metabolite. Several metabolites on the one populated side +0051 %have no single metabolite to name the boundary after or copy +0052 %properties from, so it is named after the reaction instead and left +0053 %without those properties. +0054 oneMet=isscalar(metIdx); +0055 if oneMet +0056 newMetId=strcat(closedModel.mets{metIdx},'_b'); +0057 else +0058 newMetId=strcat(closedModel.rxns{i},'_b'); +0059 end +0060 closedModel.mets{numel(closedModel.mets)+1}=newMetId; +0061 if isfield(closedModel,'metNames') +0062 if oneMet +0063 closedModel.metNames{numel(closedModel.metNames)+1}=closedModel.metNames{metIdx}; +0064 else +0065 closedModel.metNames{numel(closedModel.metNames)+1}=newMetId; +0066 end +0067 end +0068 closedModel.metComps(numel(closedModel.metComps)+1)=numel(closedModel.comps); +0069 if isfield(closedModel,'inchis') +0070 if oneMet +0071 closedModel.inchis{numel(closedModel.inchis)+1}=closedModel.inchis{metIdx}; +0072 else +0073 closedModel.inchis{numel(closedModel.inchis)+1}=''; +0074 end +0075 end +0076 if isfield(closedModel,'metSmiles') +0077 if oneMet +0078 closedModel.metSmiles{numel(closedModel.metSmiles)+1}=closedModel.metSmiles{metIdx}; +0079 else +0080 closedModel.metSmiles{numel(closedModel.metSmiles)+1}=''; +0081 end +0082 end +0083 if isfield(closedModel,'metFormulas') +0084 if oneMet +0085 closedModel.metFormulas{numel(closedModel.metFormulas)+1}=closedModel.metFormulas{metIdx}; +0086 else +0087 closedModel.metFormulas{numel(closedModel.metFormulas)+1}=''; +0088 end +0089 end +0090 if isfield(closedModel,'metMiriams') +0091 if oneMet +0092 closedModel.metMiriams{numel(closedModel.metMiriams)+1}=closedModel.metMiriams{metIdx}; +0093 else +0094 closedModel.metMiriams{numel(closedModel.metMiriams)+1}=[]; +0095 end +0096 end +0097 if isfield(closedModel,'metFrom') +0098 closedModel.metFrom{numel(closedModel.metFrom)+1}=''; +0099 end +0100 if isfield(closedModel,'metNotes') +0101 if oneMet +0102 closedModel.metNotes{numel(closedModel.metNotes)+1}=closedModel.metNotes{metIdx}; +0103 else +0104 closedModel.metNotes{numel(closedModel.metNotes)+1}=''; +0105 end +0106 end +0107 if isfield(closedModel,'metCharges') +0108 if oneMet +0109 closedModel.metCharges(numel(closedModel.metCharges)+1)=closedModel.metCharges(metIdx); +0110 else +0111 closedModel.metCharges(numel(closedModel.metCharges)+1)=0; +0112 end +0113 end +0114 if isfield(closedModel,'metDeltaG') +0115 if oneMet +0116 closedModel.metDeltaG(numel(closedModel.metDeltaG)+1)=closedModel.metDeltaG(metIdx); +0117 else +0118 closedModel.metDeltaG(numel(closedModel.metDeltaG)+1)=NaN; +0119 end +0120 end +0121 closedModel.unconstrained(numel(closedModel.unconstrained)+1)=1; +0122 % (end+1,:), not numel(b)+1: a linear index past the end of an N-by-2 +0123 % b (net-production bounds) does not append a row, it errors ("grow +0124 % array along ambiguous dimension"). +0125 closedModel.b(end+1,:)=0; +0126 closedModel.S=[closedModel.S;sparse(1,size(closedModel.S,2))]; +0127 %Any nonzero value blocks the reaction once its slack is pinned to 0 by +0128 %model.b above, regardless of magnitude; the sign is chosen only so the +0129 %boundary row reads the same way a human would expect (produced by vs. +0130 %consumed by the reaction). +0131 if sum(col)>0 +0132 closedModel.S(numel(closedModel.mets),i)=-1; +0133 else +0134 closedModel.S(numel(closedModel.mets),i)=1; +0135 end +0136 end +0137 +0138 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/manipulation/setExchangeBounds.html b/docs/html/manipulation/setExchangeBounds.html index 83cbac29..9b00c3bd 100644 --- a/docs/html/manipulation/setExchangeBounds.html +++ b/docs/html/manipulation/setExchangeBounds.html @@ -300,39 +300,37 @@

    SOURCE CODE ^% check for any metabolites that are exchanged in more than one reaction. 0202 % metInd holds one entry per (metabolite,reaction) pair, so a metabolite -0203 % involved in more than one exchange reaction is a repeated *value* in -0204 % metInd, not a position past numel(unique(metInd)) -- comparing those two -0205 % index spaces directly (as a previous version of this check did) compares -0206 % positions in the unique list against indices into metInd itself. -0207 [uniqueMetInd,~,ic] = unique(metInd); -0208 counts = accumarray(ic,1); -0209 multiMetInd = exchMetInd(uniqueMetInd(counts>1)); -0210 if ~isempty(multiMetInd) -0211 fprintf('WARNING: The following metabolites are involved in more than one exchange reaction:\n'); -0212 fprintf('\t%s\n',model.metNames{multiMetInd(1:min(numel(multiMetInd),10))}); -0213 if numel(multiMetInd) > 10 -0214 fprintf('\t... and %u more.\n',numel(multiMetInd)-10); -0215 end -0216 end -0217 -0218 % set exchange reaction bounds -0219 model = setParam(model,'lb',exchRxnInd(rxnInd),lb(metInd)); -0220 model = setParam(model,'ub',exchRxnInd(rxnInd),ub(metInd)); -0221 -0222 if closeOthers -0223 % constrain import of all other exchange reactions to zero -0224 constrainInd = setdiff(exchRxnInd,exchRxnInd(rxnInd)); -0225 if strcmp(importDir,'backward') -0226 model = setParam(model,'lb',constrainInd,0); -0227 else -0228 model = setParam(model,'ub',constrainInd,0); -0229 end -0230 end -0231 -0232 % assign output model -0233 exchModel = model; -0234 -0235 +0203 % involved in more than one exchange reaction is a value that occurs more +0204 % than once in metInd. The occurrences are counted per unique value. +0205 [uniqueMetInd,~,ic] = unique(metInd); +0206 counts = accumarray(ic,1); +0207 multiMetInd = exchMetInd(uniqueMetInd(counts>1)); +0208 if ~isempty(multiMetInd) +0209 fprintf('WARNING: The following metabolites are involved in more than one exchange reaction:\n'); +0210 fprintf('\t%s\n',model.metNames{multiMetInd(1:min(numel(multiMetInd),10))}); +0211 if numel(multiMetInd) > 10 +0212 fprintf('\t... and %u more.\n',numel(multiMetInd)-10); +0213 end +0214 end +0215 +0216 % set exchange reaction bounds +0217 model = setParam(model,'lb',exchRxnInd(rxnInd),lb(metInd)); +0218 model = setParam(model,'ub',exchRxnInd(rxnInd),ub(metInd)); +0219 +0220 if closeOthers +0221 % constrain import of all other exchange reactions to zero +0222 constrainInd = setdiff(exchRxnInd,exchRxnInd(rxnInd)); +0223 if strcmp(importDir,'backward') +0224 model = setParam(model,'lb',constrainInd,0); +0225 else +0226 model = setParam(model,'ub',constrainInd,0); +0227 end +0228 end +0229 +0230 % assign output model +0231 exchModel = model; +0232 +0233
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/manipulation/simplifyModel.html b/docs/html/manipulation/simplifyModel.html index d2f17e55..cf83096e 100644 --- a/docs/html/manipulation/simplifyModel.html +++ b/docs/html/manipulation/simplifyModel.html @@ -197,257 +197,258 @@

    SOURCE CODE ^end 0091 0092 if deleteDuplicates==true -0093 %Delete all but the last occurrence of duplicate reactions. The -0094 %reactions must have the same bounds, reversibility, and objective -0095 %coefficient to be regarded as duplicate -0096 [reducedModel, rxnsToDelete, ~]=contractModel(reducedModel); -0097 deletedReactions=[deletedReactions; rxnsToDelete]; -0098 end -0099 -0100 if deleteZeroInterval==true -0101 %Find all reactions where both LB and UB are 0 -0102 zeroIntervalReactions=and(reducedModel.lb==0,reducedModel.ub==0); -0103 -0104 rxnsToDelete=setdiff(reducedModel.rxns(zeroIntervalReactions),reservedRxns); -0105 deletedReactions=[deletedReactions; rxnsToDelete]; -0106 -0107 %Remove reactions -0108 reducedModel=removeReactions(reducedModel,rxnsToDelete); -0109 -0110 %Find metabolites that no longer are used and delete them -0111 notInUse=orphanedMets(reducedModel); -0112 deletedMetabolites=[deletedMetabolites;reducedModel.mets(notInUse)]; -0113 -0114 %Remove metabolites -0115 reducedModel=removeMets(reducedModel,notInUse); -0116 end -0117 -0118 if deleteInaccessible==true -0119 %Print a warning if exchange metabolites have not been deleted yet. This -0120 %often means that the only allowed products are the metabolites that -0121 %are taken up be the system -0122 if isfield(reducedModel,'unconstrained') && suppressWarnings==false -0123 EM='Removing dead-end reactions before removing exchange metabolites'; -0124 warning('RAVEN:warning', '%s', EM); -0125 end -0126 -0127 while true -0128 %Find all metabolites that are only reactants or only products. -0129 %Delete those metabolites and reactions. This is done until no more -0130 %such reactions are found -0131 -0132 %Construct a stoichiometric matrix where all reactions are written -0133 %as reversible -0134 -0135 %Add fake exchange reactions if model.b~=0 -0136 in=any(reducedModel.b<0,2); -0137 out=any(reducedModel.b>0,2); -0138 I=speye(numel(reducedModel.mets)); -0139 revS=[reducedModel.S,reducedModel.S(:,reducedModel.lb<0)*-1 I(:,in) I(:,out)*-1]; -0140 -0141 metUsage=sum(abs(revS')>0); -0142 onlyProducts=sum(revS'>0) == metUsage; -0143 onlyReactants=sum(revS'<0) == metUsage; -0144 -0145 %Also remove metabolites that only participate in one reversible -0146 %reaction Do not remove the ones in one reversible reaction if is -0147 %also has a non-zero coefficient in model.b -0148 notInUse=onlyProducts | onlyReactants | (sum(abs(reducedModel.S')>0)<=1 & (~in & ~out)'); -0149 deletedRxn=false; -0150 if any(notInUse) -0151 %Find their corresponding reactions -0152 rxnsNotInUse=sum(abs(reducedModel.S(notInUse,:))>0,1)>0; -0153 rxnsToDelete=setdiff(reducedModel.rxns(rxnsNotInUse),reservedRxns); -0154 deletedReactions=[deletedReactions; rxnsToDelete]; -0155 -0156 %Remove reactions -0157 reducedModel=removeReactions(reducedModel,rxnsToDelete); -0158 -0159 %Remove metabolites. Recalculate since it could be that some -0160 %cannot be deleted due to reserved rxns -0161 notInUse=orphanedMets(reducedModel); -0162 deletedMetabolites=[deletedMetabolites; reducedModel.mets(notInUse)]; -0163 reducedModel=removeMets(reducedModel,notInUse); -0164 -0165 %It could happen that it just finds reserved reactions and gets -0166 %stuck -0167 if ~isempty(rxnsToDelete) -0168 deletedRxn=true; -0169 end -0170 else -0171 break; -0172 end -0173 if deletedRxn==false -0174 break; -0175 end -0176 end -0177 end -0178 -0179 if deleteMinMax==true -0180 %Get reactions that cannot carry fluxes. This should be done -0181 %algebraically if possible -0182 I=~haveFlux(reducedModel); -0183 -0184 %Remove reactions -0185 rxnsToDelete=setdiff(reducedModel.rxns(I),reservedRxns); -0186 deletedReactions=[deletedReactions; rxnsToDelete]; -0187 reducedModel=removeReactions(reducedModel,rxnsToDelete); -0188 -0189 %Remove metabolites -0190 notInUse=orphanedMets(reducedModel); -0191 deletedMetabolites=[deletedMetabolites; reducedModel.mets(notInUse)]; -0192 reducedModel=removeMets(reducedModel,notInUse); -0193 end -0194 -0195 if constrainReversible==true -0196 revs=find(reducedModel.rev); -0197 %getAllowedBounds treats an empty reaction list as all reactions -0198 if ~isempty(revs) -0199 [I,J]=getAllowedBounds(reducedModel,revs); -0200 -0201 I=abs(I); -0202 J=abs(J); -0203 -0204 %Get the "small" values -0205 K=I<10^-10; -0206 L=J<10^-10; -0207 -0208 %Keep the values where only one direction is zero (small) -0209 I(K==L)=[]; -0210 J(K==L)=[]; -0211 revs(K==L)=[]; -0212 -0213 %Change the reversibility of the remaining reactions -0214 reducedModel.rev(revs(J>10^-10))=0; %Ignore reverse direction -0215 reducedModel.lb(revs(J>10^-10))=0; -0216 -0217 toSwitch=revs(I>10^-10); -0218 reducedModel.rev(toSwitch)=0; %Change directionality -0219 reducedModel.ub(toSwitch)=reducedModel.lb(toSwitch)*-1; -0220 reducedModel.lb(toSwitch)=0; -0221 reducedModel.S(:,toSwitch)=reducedModel.S(:,toSwitch).*-1; -0222 reducedModel.c(toSwitch)=reducedModel.c(toSwitch)*-1; -0223 end -0224 end -0225 if groupLinear==true -0226 if suppressWarnings==false -0227 fprintf('NOTE: You have chosen to group linear reactions. This option does not keep track of gene/reaction associations when reactions are merged. Deleting all gene information\n'); -0228 end -0229 -0230 reducedModel.genes={}; -0231 reducedModel.rxnGeneMat=sparse(numel(reducedModel.rxns),0); -0232 reducedModel.grRules(:)={''}; -0233 -0234 if isfield(reducedModel,'geneShortNames') -0235 reducedModel.geneShortNames={}; -0236 end -0237 if isfield(reducedModel,'proteins') -0238 reducedModel.proteins={}; -0239 end -0240 if isfield(reducedModel,'geneMiriams') -0241 reducedModel.geneMiriams={}; -0242 end -0243 if isfield(reducedModel,'geneComps') -0244 reducedModel.geneComps=[]; -0245 end -0246 -0247 %Convert the model to irreversible -0248 irrevModel=convertToIrrev(reducedModel); -0249 -0250 %Loop through and iteratively group linear reactions -0251 while 1 -0252 %Get the banned reaction indexes. Note that the indexes will change -0253 %in each iteration, but the names will not as they will not be merged -0254 %with any other reaction -0255 bannedIndexes=getIndexes(irrevModel,reservedRxns,'rxns'); -0256 -0257 %Select all metabolites that are only present as reactants/products -0258 %in one reaction -0259 singleNegative=find(sum(irrevModel.S'<0)==1); -0260 singlePositive=find(sum(irrevModel.S'>0)==1); -0261 -0262 %Retrieve the common metabolites -0263 common=intersect(singleNegative,singlePositive); -0264 -0265 mergedSome=false; -0266 -0267 %Loop through each of them and see if the reactions should be -0268 %merged -0269 for i=1:numel(common) -0270 involvedRxns=find(irrevModel.S(common(i),:)); -0271 -0272 %Check so that one or both of the reactions have not been merged -0273 %already -0274 if numel(involvedRxns)==2 && isempty(intersect(bannedIndexes,involvedRxns)) -0275 %Calculate how many times the second reaction has to be -0276 %multiplied before being merged with the first -0277 stoichRatio=abs(irrevModel.S(common(i),involvedRxns(1))/irrevModel.S(common(i),involvedRxns(2))); -0278 -0279 %Add the second to the first -0280 irrevModel.S(:,involvedRxns(1))=irrevModel.S(:,involvedRxns(1))+irrevModel.S(:,involvedRxns(2))*stoichRatio; -0281 -0282 %Clear the second reaction -0283 irrevModel.S(:,involvedRxns(2))=0; -0284 -0285 %This is to prevent numerical issues. It should be 0 -0286 %already -0287 irrevModel.S(common(i),involvedRxns(1))=0; -0288 -0289 %At this point the second reaction is certain to be deleted -0290 %in a later step and can therefore be ignored -0291 -0292 %Recalculate the bounds for the new reaction. This can be -0293 %problematic since the scale of the bounds may change -0294 %dramatically. Let the most constraining reaction determine -0295 %the new bound -0296 lb1=irrevModel.lb(involvedRxns(1)); -0297 lb2=irrevModel.lb(involvedRxns(2)); -0298 ub1=irrevModel.ub(involvedRxns(1)); -0299 ub2=irrevModel.ub(involvedRxns(2)); -0300 -0301 if lb2~=-inf -0302 irrevModel.lb(involvedRxns(1))=max(lb1,lb2/stoichRatio); -0303 end -0304 if ub2~=inf -0305 irrevModel.ub(involvedRxns(1))=min(ub1,ub2/stoichRatio); -0306 end -0307 -0308 %Then recalculate the objective coefficient. The resulting -0309 %coefficient is the weighted sum of the previous -0310 irrevModel.c(involvedRxns(1))=irrevModel.c(involvedRxns(1))+irrevModel.c(involvedRxns(2))*stoichRatio; -0311 -0312 %Iterate again -0313 mergedSome=true; -0314 end -0315 end -0316 -0317 %All possible reactions merged -0318 if mergedSome==false -0319 break; -0320 end -0321 -0322 %Now delete all reactions that involve no metabolites -0323 I=find(sum(irrevModel.S~=0)==0); -0324 -0325 %Remove reactions -0326 irrevModel=removeReactions(irrevModel,I); -0327 -0328 %Remove metabolites -0329 notInUse=orphanedMets(irrevModel); -0330 irrevModel=removeMets(irrevModel,notInUse); -0331 end -0332 -0333 reducedModel=irrevModel; -0334 end +0093 %Merge duplicate reactions into the first occurrence and delete the +0094 %others. Reactions with the same stoichiometry and reversibility are +0095 %duplicates; the merged reaction keeps the widest bounds and the largest +0096 %objective coefficient (see contractModel) +0097 [reducedModel, rxnsToDelete, ~]=contractModel(reducedModel); +0098 deletedReactions=[deletedReactions; rxnsToDelete]; +0099 end +0100 +0101 if deleteZeroInterval==true +0102 %Find all reactions where both LB and UB are 0 +0103 zeroIntervalReactions=and(reducedModel.lb==0,reducedModel.ub==0); +0104 +0105 rxnsToDelete=setdiff(reducedModel.rxns(zeroIntervalReactions),reservedRxns); +0106 deletedReactions=[deletedReactions; rxnsToDelete]; +0107 +0108 %Remove reactions +0109 reducedModel=removeReactions(reducedModel,rxnsToDelete); +0110 +0111 %Find metabolites that no longer are used and delete them +0112 notInUse=orphanedMets(reducedModel); +0113 deletedMetabolites=[deletedMetabolites;reducedModel.mets(notInUse)]; +0114 +0115 %Remove metabolites +0116 reducedModel=removeMets(reducedModel,notInUse); +0117 end +0118 +0119 if deleteInaccessible==true +0120 %Print a warning if exchange metabolites have not been deleted yet. This +0121 %often means that the only allowed products are the metabolites that +0122 %are taken up be the system +0123 if isfield(reducedModel,'unconstrained') && suppressWarnings==false +0124 EM='Removing dead-end reactions before removing exchange metabolites'; +0125 warning('RAVEN:warning', '%s', EM); +0126 end +0127 +0128 while true +0129 %Find all metabolites that are only reactants or only products. +0130 %Delete those metabolites and reactions. This is done until no more +0131 %such reactions are found +0132 +0133 %Construct a stoichiometric matrix where all reactions are written +0134 %as reversible +0135 +0136 %Add fake exchange reactions if model.b~=0 +0137 in=any(reducedModel.b<0,2); +0138 out=any(reducedModel.b>0,2); +0139 I=speye(numel(reducedModel.mets)); +0140 revS=[reducedModel.S,reducedModel.S(:,reducedModel.lb<0)*-1 I(:,in) I(:,out)*-1]; +0141 +0142 metUsage=sum(abs(revS')>0); +0143 onlyProducts=sum(revS'>0) == metUsage; +0144 onlyReactants=sum(revS'<0) == metUsage; +0145 +0146 %Also remove metabolites that only participate in one reversible +0147 %reaction Do not remove the ones in one reversible reaction if is +0148 %also has a non-zero coefficient in model.b +0149 notInUse=onlyProducts | onlyReactants | (sum(abs(reducedModel.S')>0)<=1 & (~in & ~out)'); +0150 deletedRxn=false; +0151 if any(notInUse) +0152 %Find their corresponding reactions +0153 rxnsNotInUse=sum(abs(reducedModel.S(notInUse,:))>0,1)>0; +0154 rxnsToDelete=setdiff(reducedModel.rxns(rxnsNotInUse),reservedRxns); +0155 deletedReactions=[deletedReactions; rxnsToDelete]; +0156 +0157 %Remove reactions +0158 reducedModel=removeReactions(reducedModel,rxnsToDelete); +0159 +0160 %Remove metabolites. Recalculate since it could be that some +0161 %cannot be deleted due to reserved rxns +0162 notInUse=orphanedMets(reducedModel); +0163 deletedMetabolites=[deletedMetabolites; reducedModel.mets(notInUse)]; +0164 reducedModel=removeMets(reducedModel,notInUse); +0165 +0166 %It could happen that it just finds reserved reactions and gets +0167 %stuck +0168 if ~isempty(rxnsToDelete) +0169 deletedRxn=true; +0170 end +0171 else +0172 break; +0173 end +0174 if deletedRxn==false +0175 break; +0176 end +0177 end +0178 end +0179 +0180 if deleteMinMax==true +0181 %Get reactions that cannot carry fluxes. This should be done +0182 %algebraically if possible +0183 I=~haveFlux(reducedModel); +0184 +0185 %Remove reactions +0186 rxnsToDelete=setdiff(reducedModel.rxns(I),reservedRxns); +0187 deletedReactions=[deletedReactions; rxnsToDelete]; +0188 reducedModel=removeReactions(reducedModel,rxnsToDelete); +0189 +0190 %Remove metabolites +0191 notInUse=orphanedMets(reducedModel); +0192 deletedMetabolites=[deletedMetabolites; reducedModel.mets(notInUse)]; +0193 reducedModel=removeMets(reducedModel,notInUse); +0194 end +0195 +0196 if constrainReversible==true +0197 revs=find(reducedModel.rev); +0198 %getAllowedBounds treats an empty reaction list as all reactions +0199 if ~isempty(revs) +0200 [I,J]=getAllowedBounds(reducedModel,revs); +0201 +0202 I=abs(I); +0203 J=abs(J); +0204 +0205 %Get the "small" values +0206 K=I<10^-10; +0207 L=J<10^-10; +0208 +0209 %Keep the values where only one direction is zero (small) +0210 I(K==L)=[]; +0211 J(K==L)=[]; +0212 revs(K==L)=[]; +0213 +0214 %Change the reversibility of the remaining reactions +0215 reducedModel.rev(revs(J>10^-10))=0; %Ignore reverse direction +0216 reducedModel.lb(revs(J>10^-10))=0; +0217 +0218 toSwitch=revs(I>10^-10); +0219 reducedModel.rev(toSwitch)=0; %Change directionality +0220 reducedModel.ub(toSwitch)=reducedModel.lb(toSwitch)*-1; +0221 reducedModel.lb(toSwitch)=0; +0222 reducedModel.S(:,toSwitch)=reducedModel.S(:,toSwitch).*-1; +0223 reducedModel.c(toSwitch)=reducedModel.c(toSwitch)*-1; +0224 end +0225 end +0226 if groupLinear==true +0227 if suppressWarnings==false +0228 fprintf('NOTE: You have chosen to group linear reactions. This option does not keep track of gene/reaction associations when reactions are merged. Deleting all gene information\n'); +0229 end +0230 +0231 reducedModel.genes={}; +0232 reducedModel.rxnGeneMat=sparse(numel(reducedModel.rxns),0); +0233 reducedModel.grRules(:)={''}; +0234 +0235 if isfield(reducedModel,'geneShortNames') +0236 reducedModel.geneShortNames={}; +0237 end +0238 if isfield(reducedModel,'proteins') +0239 reducedModel.proteins={}; +0240 end +0241 if isfield(reducedModel,'geneMiriams') +0242 reducedModel.geneMiriams={}; +0243 end +0244 if isfield(reducedModel,'geneComps') +0245 reducedModel.geneComps=[]; +0246 end +0247 +0248 %Convert the model to irreversible +0249 irrevModel=convertToIrrev(reducedModel); +0250 +0251 %Loop through and iteratively group linear reactions +0252 while 1 +0253 %Get the banned reaction indexes. Note that the indexes will change +0254 %in each iteration, but the names will not as they will not be merged +0255 %with any other reaction +0256 bannedIndexes=getIndexes(irrevModel,reservedRxns,'rxns'); +0257 +0258 %Select all metabolites that are only present as reactants/products +0259 %in one reaction +0260 singleNegative=find(sum(irrevModel.S'<0)==1); +0261 singlePositive=find(sum(irrevModel.S'>0)==1); +0262 +0263 %Retrieve the common metabolites +0264 common=intersect(singleNegative,singlePositive); +0265 +0266 mergedSome=false; +0267 +0268 %Loop through each of them and see if the reactions should be +0269 %merged +0270 for i=1:numel(common) +0271 involvedRxns=find(irrevModel.S(common(i),:)); +0272 +0273 %Check so that one or both of the reactions have not been merged +0274 %already +0275 if numel(involvedRxns)==2 && isempty(intersect(bannedIndexes,involvedRxns)) +0276 %Calculate how many times the second reaction has to be +0277 %multiplied before being merged with the first +0278 stoichRatio=abs(irrevModel.S(common(i),involvedRxns(1))/irrevModel.S(common(i),involvedRxns(2))); +0279 +0280 %Add the second to the first +0281 irrevModel.S(:,involvedRxns(1))=irrevModel.S(:,involvedRxns(1))+irrevModel.S(:,involvedRxns(2))*stoichRatio; +0282 +0283 %Clear the second reaction +0284 irrevModel.S(:,involvedRxns(2))=0; +0285 +0286 %This is to prevent numerical issues. It should be 0 +0287 %already +0288 irrevModel.S(common(i),involvedRxns(1))=0; +0289 +0290 %At this point the second reaction is certain to be deleted +0291 %in a later step and can therefore be ignored +0292 +0293 %Recalculate the bounds for the new reaction. This can be +0294 %problematic since the scale of the bounds may change +0295 %dramatically. Let the most constraining reaction determine +0296 %the new bound +0297 lb1=irrevModel.lb(involvedRxns(1)); +0298 lb2=irrevModel.lb(involvedRxns(2)); +0299 ub1=irrevModel.ub(involvedRxns(1)); +0300 ub2=irrevModel.ub(involvedRxns(2)); +0301 +0302 if lb2~=-inf +0303 irrevModel.lb(involvedRxns(1))=max(lb1,lb2/stoichRatio); +0304 end +0305 if ub2~=inf +0306 irrevModel.ub(involvedRxns(1))=min(ub1,ub2/stoichRatio); +0307 end +0308 +0309 %Then recalculate the objective coefficient. The resulting +0310 %coefficient is the weighted sum of the previous +0311 irrevModel.c(involvedRxns(1))=irrevModel.c(involvedRxns(1))+irrevModel.c(involvedRxns(2))*stoichRatio; +0312 +0313 %Iterate again +0314 mergedSome=true; +0315 end +0316 end +0317 +0318 %All possible reactions merged +0319 if mergedSome==false +0320 break; +0321 end +0322 +0323 %Now delete all reactions that involve no metabolites +0324 I=find(sum(irrevModel.S~=0)==0); +0325 +0326 %Remove reactions +0327 irrevModel=removeReactions(irrevModel,I); +0328 +0329 %Remove metabolites +0330 notInUse=orphanedMets(irrevModel); +0331 irrevModel=removeMets(irrevModel,notInUse); +0332 end +0333 +0334 reducedModel=irrevModel; 0335 end -0336 -0337 function I=orphanedMets(model) -0338 %Metabolites that take part in no reaction, and can therefore be removed -0339 %without changing what the model can do. Metabolites with a non-zero -0340 %model.b are kept: b is a boundary condition on the metabolite, so removing -0341 %the row would drop that constraint along with it. -0342 I=sum(model.S~=0,2)==0 & ~any(model.b~=0,2); -0343 end +0336 end +0337 +0338 function I=orphanedMets(model) +0339 %Metabolites that take part in no reaction, and can therefore be removed +0340 %without changing what the model can do. Metabolites with a non-zero +0341 %model.b are kept: b is a boundary condition on the metabolite, so removing +0342 %the row would drop that constraint along with it. +0343 I=sum(model.S~=0,2)==0 & ~any(model.b~=0,2); +0344 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/queries/getIndexes.html b/docs/html/queries/getIndexes.html index 6d651b27..ef64d889 100644 --- a/docs/html/queries/getIndexes.html +++ b/docs/html/queries/getIndexes.html @@ -53,7 +53,8 @@

    DESCRIPTION ^SOURCE CODE ^% -------------------- 0026 % returnLogical : logical 0027 % sets whether to return a logical array or an array with the indexes -0028 % (default false). -0029 % -0030 % Returns -0031 % ------- -0032 % indexes : logical or double -0033 % a logical array if returnLogical is true, otherwise a double array. For -0034 % 'metnames' with several names, a cell array of double vectors. -0035 % -0036 % Notes -0037 % ----- -0038 % If 'ecenzymes', 'ecrxns' or 'ecgenes' are used with a GECKO 3 model, then -0039 % the indexes are from the model.ec.enzymes, model.ec.rxns or model.ec.genes -0040 % fields, respectively. -0041 % -0042 % Examples -0043 % -------- -0044 % indexes = getIndexes(model, objects, type, returnLogical); -0045 -0046 p=parseRAVENargs(varargin, {'returnLogical', false}); -0047 returnLogical=p.returnLogical; -0048 -0049 if ~islogical(objects) && ~isnumeric(objects) -0050 objects=convertCharArray(objects); -0051 end -0052 type=char(type); -0053 -0054 indexes=[]; -0055 -0056 switch type -0057 case 'rxns' -0058 searchIn=model.rxns; -0059 case 'mets' -0060 searchIn=model.mets; -0061 case 'genes' -0062 searchIn=model.genes; -0063 case 'metnames' -0064 searchIn=model.metNames; -0065 case 'metcomps' -0066 % If provided as metaboliteName[comp], then index search is quite -0067 % different from general approach, and therefore coded separately. -0068 mets=regexprep(objects,'(^.+)\[(.+)\]$','$1'); -0069 comps=regexprep(objects,'(^.+)\[(.+)\]$','$2'); -0070 indexes=zeros(1,numel(objects)); -0071 for i=1:numel(objects) -0072 index=find(strcmp(mets(i),model.metNames)); -0073 index=index(strcmp(comps(i),model.comps(model.metComps(index)))); -0074 if isscalar(index) -0075 indexes(i)=index; -0076 elseif isempty(index) -0077 error(['Could not find object ''' objects{i} ''' in the model']); -0078 else -0079 error(['''' objects{i} ''' is ambiguous: it matches the ' ... -0080 'metabolites ' strjoin(model.mets(index),', ') ... -0081 '. Use metabolite ids (type ''mets'') instead.']); -0082 end -0083 end -0084 indexes=indexes(:); -0085 if returnLogical==true -0086 tempIndexes=false(numel(model.mets),1); -0087 tempIndexes(indexes)=true; -0088 indexes=tempIndexes; -0089 end -0090 return %None of the remaining function needs to run if metcomps -0091 case 'ecrxns' -0092 if ~isfield(model,'ec') -0093 error('Type %s cannot be used if no model.ec structure is present.',type) -0094 end -0095 searchIn=model.ec.rxns; -0096 case 'ecenzymes' -0097 if ~isfield(model,'ec') -0098 error('Type %s cannot be used if no model.ec structure is present.',type) -0099 end -0100 searchIn=model.ec.enzymes; -0101 case 'ecgenes' -0102 if ~isfield(model,'ec') -0103 error('Type %s cannot be used if no model.ec structure is present.',type) -0104 end -0105 searchIn=model.ec.genes; -0106 otherwise -0107 if contains(objects,{'rxns','mets','metnames','metcomps','genes','ecgenes','ecenzymes','ecrxns'}) -0108 error('The second and third parameter provided to run getIndexes are likely switched. Note that the second parameter is the object to find the index for, while the third parameter is the object type (''rxns'', ''mets'', ''metnames'', ''metcomps'', ''genes'', ''ecgenes'', ''ecenzymes'' or ''ecrxns'').') -0109 else -0110 error('Incorrect value of the ''type'' parameter. Allowed values are ''rxns'', ''mets'', ''metnames'', ''metcomps'', ''genes'', ''ecgenes'', ''ecenzymes'' or ''ecrxns''.'); -0111 end -0112 end -0113 -0114 if iscell(objects) -0115 if strcmpi(type,'metnames') -0116 % metnames allows multiple matches per name (same metabolite in different -0117 % compartments), so return a cell array of index vectors. -0118 for i=1:numel(objects) -0119 indexes{i}=find(strcmp(objects(i),searchIn)); -0120 end -0121 else -0122 % Build a hash map once (O(m)) for O(1) per-object lookup instead of -0123 % O(m) strcmp per object. -0124 searchMap = containers.Map(searchIn, 1:numel(searchIn)); +0028 % (default false). It cannot be true for type 'metnames', which raises +0029 % the error RAVEN:badInput. +0030 % +0031 % Returns +0032 % ------- +0033 % indexes : logical or double +0034 % a logical array if returnLogical is true, otherwise a double array. For +0035 % 'metnames' with several names, a cell array of double vectors. +0036 % +0037 % Notes +0038 % ----- +0039 % If 'ecenzymes', 'ecrxns' or 'ecgenes' are used with a GECKO 3 model, then +0040 % the indexes are from the model.ec.enzymes, model.ec.rxns or model.ec.genes +0041 % fields, respectively. +0042 % +0043 % Examples +0044 % -------- +0045 % indexes = getIndexes(model, objects, type, returnLogical); +0046 +0047 p=parseRAVENargs(varargin, {'returnLogical', false}); +0048 returnLogical=p.returnLogical; +0049 +0050 if ~islogical(objects) && ~isnumeric(objects) +0051 objects=convertCharArray(objects); +0052 end +0053 type=char(type); +0054 +0055 indexes=[]; +0056 +0057 switch type +0058 case 'rxns' +0059 searchIn=model.rxns; +0060 case 'mets' +0061 searchIn=model.mets; +0062 case 'genes' +0063 searchIn=model.genes; +0064 case 'metnames' +0065 if returnLogical==true +0066 error('RAVEN:badInput', ['The combination of type ''metnames'' and ' ... +0067 'returnLogical true is not supported, because each name can ' ... +0068 'match several metabolites. Use type ''mets'' or ''metcomps'' ' ... +0069 'for a logical vector.']); +0070 end +0071 searchIn=model.metNames; +0072 case 'metcomps' +0073 % If provided as metaboliteName[comp], then index search is quite +0074 % different from general approach, and therefore coded separately. +0075 mets=regexprep(objects,'(^.+)\[(.+)\]$','$1'); +0076 comps=regexprep(objects,'(^.+)\[(.+)\]$','$2'); +0077 indexes=zeros(1,numel(objects)); +0078 for i=1:numel(objects) +0079 index=find(strcmp(mets(i),model.metNames)); +0080 index=index(strcmp(comps(i),model.comps(model.metComps(index)))); +0081 if isscalar(index) +0082 indexes(i)=index; +0083 elseif isempty(index) +0084 error(['Could not find object ''' objects{i} ''' in the model']); +0085 else +0086 error(['''' objects{i} ''' is ambiguous: it matches the ' ... +0087 'metabolites ' strjoin(model.mets(index),', ') ... +0088 '. Use metabolite ids (type ''mets'') instead.']); +0089 end +0090 end +0091 indexes=indexes(:); +0092 if returnLogical==true +0093 tempIndexes=false(numel(model.mets),1); +0094 tempIndexes(indexes)=true; +0095 indexes=tempIndexes; +0096 end +0097 return %None of the remaining function needs to run if metcomps +0098 case 'ecrxns' +0099 if ~isfield(model,'ec') +0100 error('Type %s cannot be used if no model.ec structure is present.',type) +0101 end +0102 searchIn=model.ec.rxns; +0103 case 'ecenzymes' +0104 if ~isfield(model,'ec') +0105 error('Type %s cannot be used if no model.ec structure is present.',type) +0106 end +0107 searchIn=model.ec.enzymes; +0108 case 'ecgenes' +0109 if ~isfield(model,'ec') +0110 error('Type %s cannot be used if no model.ec structure is present.',type) +0111 end +0112 searchIn=model.ec.genes; +0113 otherwise +0114 if contains(objects,{'rxns','mets','metnames','metcomps','genes','ecgenes','ecenzymes','ecrxns'}) +0115 error('The second and third parameter provided to run getIndexes are likely switched. Note that the second parameter is the object to find the index for, while the third parameter is the object type (''rxns'', ''mets'', ''metnames'', ''metcomps'', ''genes'', ''ecgenes'', ''ecenzymes'' or ''ecrxns'').') +0116 else +0117 error('Incorrect value of the ''type'' parameter. Allowed values are ''rxns'', ''mets'', ''metnames'', ''metcomps'', ''genes'', ''ecgenes'', ''ecenzymes'' or ''ecrxns''.'); +0118 end +0119 end +0120 +0121 if iscell(objects) +0122 if strcmpi(type,'metnames') +0123 % metnames allows multiple matches per name (same metabolite in different +0124 % compartments), so return a cell array of index vectors. 0125 for i=1:numel(objects) -0126 if isKey(searchMap, objects{i}) -0127 indexes(i) = searchMap(objects{i}); -0128 else -0129 error(['Could not find object ''' objects{i} ''' in the model']); -0130 end -0131 end -0132 end -0133 else -0134 %Now it's either a logical mask or an array with indexes. We want indexes. -0135 if islogical(objects) -0136 indexes=find(objects); -0137 else -0138 indexes=objects; +0126 indexes{i}=find(strcmp(objects(i),searchIn)); +0127 end +0128 else +0129 % Build a hash map once (O(m)) for O(1) per-object lookup instead of +0130 % O(m) strcmp per object. +0131 searchMap = containers.Map(searchIn, 1:numel(searchIn)); +0132 for i=1:numel(objects) +0133 if isKey(searchMap, objects{i}) +0134 indexes(i) = searchMap(objects{i}); +0135 else +0136 error(['Could not find object ''' objects{i} ''' in the model']); +0137 end +0138 end 0139 end -0140 end -0141 -0142 if returnLogical==true -0143 tempIndexes=false(numel(searchIn),1); -0144 tempIndexes(indexes)=true; -0145 indexes=tempIndexes; -0146 end -0147 -0148 indexes=indexes(:); -0149 if iscell(indexes) && length(indexes)==1 -0150 indexes=indexes{1}; -0151 end -0152 end +0140 else +0141 %Now it's either a logical mask or an array with indexes. We want indexes. +0142 if islogical(objects) +0143 indexes=find(objects); +0144 else +0145 indexes=objects; +0146 end +0147 end +0148 +0149 if returnLogical==true +0150 tempIndexes=false(numel(searchIn),1); +0151 tempIndexes(indexes)=true; +0152 indexes=tempIndexes; +0153 end +0154 +0155 indexes=indexes(:); +0156 if iscell(indexes) && length(indexes)==1 +0157 indexes=indexes{1}; +0158 end +0159 end
    Generated by m2html © 2005
    \ No newline at end of file diff --git a/docs/html/queries/printFluxes.html b/docs/html/queries/printFluxes.html index 7da53f05..2764163d 100644 --- a/docs/html/queries/printFluxes.html +++ b/docs/html/queries/printFluxes.html @@ -74,8 +74,8 @@

    DESCRIPTION ^CROSS-REFERENCE INFORMATION ^

    @@ -138,8 +138,8 @@

    SOURCE CODE ^% 0047 % Examples 0048 % -------- -0049 % printFluxes(model, fluxes, onlyExchange, cutOffFlux, outputFile, ... -0050 % outputString, metaboliteList); +0049 % printFluxes(model, fluxes, 'onlyExchange', false, 'cutOffFlux', 10^-6, ... +0050 % 'outputFile', 'fluxes.txt', 'metaboliteList', {'glucose'}); 0051 0052 p=parseRAVENargs(varargin, {'onlyExchange',true; 'cutOffFlux',10^-8; 'outputFile',[]; 'outputString',[]; 'metaboliteList',{}}); 0053 onlyExchange=p.onlyExchange; diff --git a/docs/html/reconstruction/kegg/getModelFromKEGG.html b/docs/html/reconstruction/kegg/getModelFromKEGG.html index ad8c1b5e..51ae7361 100644 --- a/docs/html/reconstruction/kegg/getModelFromKEGG.html +++ b/docs/html/reconstruction/kegg/getModelFromKEGG.html @@ -52,7 +52,9 @@

    DESCRIPTION ^SOURCE CODE ^% Returns 0025 % ------- 0026 % model : struct -0027 % a model structure with all reactions and the metabolites used in them. -0028 % KOModel : struct -0029 % a model structure representing KEGG Orthology ids and their associated -0030 % genes. KO ids are stored as reactions. -0031 % -0032 % Notes -0033 % ----- -0034 % The model output can be used as a template for fillGaps after the genes -0035 % and rxnGeneMat fields are removed: -0036 % model = rmfield(model, 'genes') and model = rmfield(model, 'rxnGeneMat'). -0037 % -0038 % See also -0039 % -------- -0040 % getKEGGModelForOrganism, getPhylDist, fillGaps -0041 -0042 ravenPath=findRAVENroot(); +0027 % a model structure with the reactions that the keep* options retain (by +0028 % default all except those labelled as "general reaction") and the +0029 % metabolites used in them. +0030 % KOModel : struct +0031 % a model structure representing KEGG Orthology ids and their associated +0032 % genes. KO ids are stored as reactions. +0033 % +0034 % Notes +0035 % ----- +0036 % The model output can be used as a template for fillGaps after the genes +0037 % and rxnGeneMat fields are removed: +0038 % model = rmfield(model, 'genes') and model = rmfield(model, 'rxnGeneMat'). +0039 % +0040 % See also +0041 % -------- +0042 % getKEGGModelForOrganism, getPhylDist, fillGaps 0043 -0044 p=parseRAVENargs(varargin, {'keepSpontaneous',true; 'keepUndefinedStoich',true; ... -0045 'keepIncomplete',true; 'keepGeneral',false}); -0046 keepSpontaneous=p.keepSpontaneous; -0047 keepUndefinedStoich=p.keepUndefinedStoich; -0048 keepIncomplete=p.keepIncomplete; -0049 keepGeneral=p.keepGeneral; -0050 -0051 modelFile=fullfile(ravenPath,'src','reconstruction','kegg','keggModel.mat'); -0052 if isfile(modelFile) -0053 fprintf(['Importing the global KEGG model from ' strrep(modelFile,'\','/') '... ']); -0054 load(modelFile,'model','KOModel','isSpontaneous','isUndefinedStoich','isIncomplete','isGeneral'); -0055 fprintf('COMPLETE\n'); -0056 else -0057 [model,KOModel,isSpontaneous,isUndefinedStoich,isIncomplete,isGeneral]=buildGlobalKEGGModel(ravenPath); -0058 fprintf(['Saving the global KEGG model to ' strrep(modelFile,'\','/') ' for future use... ']); -0059 save(modelFile,'model','KOModel','isSpontaneous','isUndefinedStoich','isIncomplete','isGeneral','-v7.3'); -0060 fprintf('COMPLETE\n'); -0061 end -0062 -0063 if keepSpontaneous==false -0064 model=removeReactions(model,intersect(isSpontaneous,model.rxns),true,true); -0065 end -0066 if keepUndefinedStoich==false -0067 model=removeReactions(model,intersect(isUndefinedStoich,model.rxns),true,true); -0068 end -0069 if keepIncomplete==false -0070 model=removeReactions(model,intersect(isIncomplete,model.rxns),true,true); -0071 end -0072 if keepGeneral==false -0073 model=removeReactions(model,intersect(isGeneral,model.rxns),true,true); -0074 end -0075 end +0044 ravenPath=findRAVENroot(); +0045 +0046 p=parseRAVENargs(varargin, {'keepSpontaneous',true; 'keepUndefinedStoich',true; ... +0047 'keepIncomplete',true; 'keepGeneral',false}); +0048 keepSpontaneous=p.keepSpontaneous; +0049 keepUndefinedStoich=p.keepUndefinedStoich; +0050 keepIncomplete=p.keepIncomplete; +0051 keepGeneral=p.keepGeneral; +0052 +0053 modelFile=fullfile(ravenPath,'src','reconstruction','kegg','keggModel.mat'); +0054 if isfile(modelFile) +0055 fprintf(['Importing the global KEGG model from ' strrep(modelFile,'\','/') '... ']); +0056 load(modelFile,'model','KOModel','isSpontaneous','isUndefinedStoich','isIncomplete','isGeneral'); +0057 fprintf('COMPLETE\n'); +0058 else +0059 [model,KOModel,isSpontaneous,isUndefinedStoich,isIncomplete,isGeneral]=buildGlobalKEGGModel(ravenPath); +0060 fprintf(['Saving the global KEGG model to ' strrep(modelFile,'\','/') ' for future use... ']); +0061 save(modelFile,'model','KOModel','isSpontaneous','isUndefinedStoich','isIncomplete','isGeneral','-v7.3'); +0062 fprintf('COMPLETE\n'); +0063 end +0064 +0065 if keepSpontaneous==false +0066 model=removeReactions(model,intersect(isSpontaneous,model.rxns),true,true); +0067 end +0068 if keepUndefinedStoich==false +0069 model=removeReactions(model,intersect(isUndefinedStoich,model.rxns),true,true); +0070 end +0071 if keepIncomplete==false +0072 model=removeReactions(model,intersect(isIncomplete,model.rxns),true,true); +0073 end +0074 if keepGeneral==false +0075 model=removeReactions(model,intersect(isGeneral,model.rxns),true,true); +0076 end +0077 end
    Generated by m2html © 2005
    \ No newline at end of file From b3ff5891e1e48392724698e46d818ee12b8ee45b Mon Sep 17 00:00:00 2001 From: Eduard Kerkhoven Date: Sun, 1 Nov 2026 11:07:08 +0100 Subject: [PATCH 42/48] writeYAMLmodel: write no enzymes entry for an ec reaction without enzymes (#18) The enzymes header with nothing under it is not valid YAML. readYAMLmodel reads a missing entry, and the two forms written earlier, as a reaction without enzymes. --- CHANGELOG.md | 4 +++ src/io/writeYAMLmodel.m | 5 +-- tests/function_tests/tIO.m | 73 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 80 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f3dddaf..ceb86576 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### I/O + +* `writeYAMLmodel` writes an ec reaction without enzymes (a row of zeros in `model.ec.rxnEnzMat`) with no `enzymes` entry (was: `- enzymes: !!omap` with nothing under it, which is not valid YAML). `readYAMLmodel` reads a missing `enzymes` entry as a reaction without enzymes, and still reads `- enzymes: !!omap` with no entries and `- enzymes: !!omap []` from files written earlier. + ### Manipulation * `mergeCompartments`: a merged metabolite is the copy already in compartment `s`, if any, diff --git a/src/io/writeYAMLmodel.m b/src/io/writeYAMLmodel.m index 6dd86094..22df44db 100755 --- a/src/io/writeYAMLmodel.m +++ b/src/io/writeYAMLmodel.m @@ -224,9 +224,10 @@ function writeField(model,fid,fieldName,type,pos,name,keyIndent) end elseif strcmp(fieldName,'rxnEnzMat') - %S: create header & write each enzyme in a new line - fprintf(fid,'%s: !!omap\n',name); + %enzymes: create header & write each enzyme in a new line. A + %reaction without enzymes has no entry at all. if sum(field(pos,:) ~= 0) > 0 + fprintf(fid,'%s: !!omap\n',name); model.enzymes = model.enzymes(field(pos,:) ~= 0); model.coeffs = field(pos,field(pos,:) ~= 0); %Sort metabolites: diff --git a/tests/function_tests/tIO.m b/tests/function_tests/tIO.m index 093445f1..2ce5dd9c 100644 --- a/tests/function_tests/tIO.m +++ b/tests/function_tests/tIO.m @@ -287,6 +287,60 @@ function writeReadYAMLRoundTrip(testCase) testCase.verifyEqual(numel(m2.rxns), numel(testCase.model.rxns)); end + function writeYAMLmodelOmitsEnzymesOfEcReactionWithoutEnzymes(testCase) + % An ec reaction whose rxnEnzMat row is empty has no enzymes + % entry in the file; the reaction before and after keep theirs. + m = testCase.ecYamlModel(); + f = [tempname '.yml']; + testCase.addTeardown(@() delete(f)); + evalc('writeYAMLmodel(m, f);'); + txt = fileread(f); + % two ec reactions with enzymes and two ec-enzymes entries + testCase.verifyEqual(numel(strfind(txt, '- enzymes:')), 4); + lines = splitlines(txt); + idx = find(strcmp(strtrim(lines), '- id: ec_R2')); + testCase.verifyEqual(numel(idx), 1); + testCase.verifyFalse(contains(lines{idx+1}, 'enzymes')); + testCase.verifyFalse(contains(lines{idx+2}, 'enzymes')); + end + + function readYAMLmodelReadsEcReactionWithoutEnzymes(testCase) + % The reaction without enzymes has a zero row in rxnEnzMat and + % keeps its id and kcat; the other rows are unchanged. + m = testCase.ecYamlModel(); + f = [tempname '.yml']; + testCase.addTeardown(@() delete(f)); + evalc('writeYAMLmodel(m, f);'); + evalc('m2 = readYAMLmodel(f);'); + testCase.verifyEqual(m2.ec.rxns, m.ec.rxns); + testCase.verifyEqual(m2.ec.kcat, m.ec.kcat, 'AbsTol', 1e-9); + testCase.verifyEqual(full(m2.ec.rxnEnzMat), m.ec.rxnEnzMat); + end + + function readYAMLmodelReadsOlderEmptyEnzymesForms(testCase) + % Files written earlier carry a content-less "enzymes: !!omap" + % or "enzymes: !!omap []" for a reaction without enzymes. + m = testCase.ecYamlModel(); + f = [tempname '.yml']; + testCase.addTeardown(@() delete(f)); + evalc('writeYAMLmodel(m, f);'); + txt = fileread(f); + forms = {' - enzymes: !!omap', ' - enzymes: !!omap []'}; + for i = 1:numel(forms) + lines = splitlines(txt); + idx = find(strcmp(strtrim(lines), '- id: ec_R2')); + lines = [lines(1:idx+1); forms(i); lines(idx+2:end)]; + g = [tempname '.yml']; + testCase.addTeardown(@() delete(g)); + fid = fopen(g, 'w'); + fprintf(fid, '%s', strjoin(lines, newline)); + fclose(fid); + evalc('m2 = readYAMLmodel(g);'); + testCase.verifyEqual(m2.ec.rxns, m.ec.rxns); + testCase.verifyEqual(full(m2.ec.rxnEnzMat), m.ec.rxnEnzMat); + end + end + function readYAMLmodelKeepsSubsystemsAligned(testCase) % Reactions without a subsystem sit between, before and after % those with one: each subsystem stays with its own reaction, @@ -583,4 +637,23 @@ function writeExcelRoundTrips(testCase) end end + + methods (Access = private) + function m = ecYamlModel(testCase) + % Model with three ec reactions; the second has no enzymes. + m = testCase.model; + m.ec.geckoLight = false; + m.ec.rxns = {'ec_R1'; 'ec_R2'; 'ec_R3'}; + m.ec.kcat = [13.7; 5; 2]; + m.ec.source = {'brenda'; 'standard'; ''}; + m.ec.notes = {''; ''; ''}; + m.ec.eccodes = {'1.1.1.1'; ''; ''}; + m.ec.genes = m.genes(1:2); + m.ec.enzymes = {'P0A1'; 'P0A2'}; + m.ec.mw = [51000; 40000]; + m.ec.sequence = {'MABC'; 'MDEF'}; + m.ec.concs = [NaN; NaN]; + m.ec.rxnEnzMat = [1 2; 0 0; 0 1]; + end + end end From 69fc6d22ed6d8c29da06ac446fd870bb458cc884 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 1 Nov 2026 10:08:43 +0000 Subject: [PATCH 43/48] doc: regenerate HTML documentation for b3ff589 [skip ci] --- docs/html/io/writeYAMLmodel.html | 769 ++++++++++++++++--------------- 1 file changed, 385 insertions(+), 384 deletions(-) diff --git a/docs/html/io/writeYAMLmodel.html b/docs/html/io/writeYAMLmodel.html index b27db4ec..9f4f19f0 100644 --- a/docs/html/io/writeYAMLmodel.html +++ b/docs/html/io/writeYAMLmodel.html @@ -295,398 +295,399 @@

    SOURCE CODE ^end 0225 0226 elseif strcmp(fieldName,'rxnEnzMat') -0227 %S: create header & write each enzyme in a new line -0228 fprintf(fid,'%s: !!omap\n',name); +0227 %enzymes: create header & write each enzyme in a new line. A +0228 %reaction without enzymes has no entry at all. 0229 if sum(field(pos,:) ~= 0) > 0 -0230 model.enzymes = model.enzymes(field(pos,:) ~= 0); -0231 model.coeffs = field(pos,field(pos,:) ~= 0); -0232 %Sort metabolites: -0233 [model.enzymes,order] = sort(model.enzymes); -0234 model.coeffs = model.coeffs(order); -0235 for i = 1:length(model.enzymes) -0236 writeField(model, fid, 'coeffs', 'flt', i, [' - ' model.enzymes{i}], 6) -0237 end -0238 end -0239 -0240 elseif strcmp(fieldName,'subSystems') -0241 %subsystem: omitted entirely when the reaction carries none -0242 %(matching every other optional field), otherwise a block list — -0243 %always a list, even for a single subsystem, since a reaction can -0244 %have more than one and the reader already expects the list form. -0245 if iscell(field) -0246 if pos > numel(field); return; end -0247 list = field{pos}; -0248 else -0249 if pos > size(field, 1); return; end -0250 list = field(pos, :); -0251 end -0252 if ischar(list); list = {list}; end -0253 list = list(~cellfun('isempty', list)); -0254 if isempty(list) -0255 return -0256 end -0257 fprintf(fid,'%s:\n',name); -0258 indentStr = regexprep(name,'(^\s*).*','$1'); -0259 for i = 1:length(list) -0260 emitListItem(fid, [indentStr ' -'], list{i}, keyIndent+2) -0261 end -0262 -0263 elseif strcmp(fieldName,'eccodes') -0264 %eccodes (GECKO ec-rxns section only): ;-joined string -> a -0265 %block list if more than one code, else a bare scalar. -0266 if isempty(field{pos}) -0267 return -0268 end -0269 list = strip(strsplit(strrep(field{pos},' ',''),';')); -0270 list = list(~cellfun('isempty',list)); -0271 if isempty(list) -0272 return -0273 elseif isscalar(list) -0274 emitScalarLine(fid, name, list{1}, keyIndent) -0275 else -0276 fprintf(fid,'%s:\n',name); -0277 indentStr = regexprep(name,'(^\s*).*','$1'); -0278 for i = 1:length(list) -0279 emitListItem(fid, [indentStr ' -'], list{i}, keyIndent+2) -0280 end -0281 end -0282 -0283 elseif sum(pos) > 0 -0284 %All other fields: -0285 if strcmp(type,'txt') -0286 value = field{pos}; -0287 if ~isempty(value) -0288 emitScalarLine(fid, name, value, keyIndent) -0289 end -0290 elseif strcmp(type,'flt') -0291 if ~isnan(field(pos)) -0292 emitScalarLine(fid, name, formatFloat(full(field(pos))), keyIndent, true) -0293 end -0294 end -0295 end -0296 end +0230 fprintf(fid,'%s: !!omap\n',name); +0231 model.enzymes = model.enzymes(field(pos,:) ~= 0); +0232 model.coeffs = field(pos,field(pos,:) ~= 0); +0233 %Sort metabolites: +0234 [model.enzymes,order] = sort(model.enzymes); +0235 model.coeffs = model.coeffs(order); +0236 for i = 1:length(model.enzymes) +0237 writeField(model, fid, 'coeffs', 'flt', i, [' - ' model.enzymes{i}], 6) +0238 end +0239 end +0240 +0241 elseif strcmp(fieldName,'subSystems') +0242 %subsystem: omitted entirely when the reaction carries none +0243 %(matching every other optional field), otherwise a block list — +0244 %always a list, even for a single subsystem, since a reaction can +0245 %have more than one and the reader already expects the list form. +0246 if iscell(field) +0247 if pos > numel(field); return; end +0248 list = field{pos}; +0249 else +0250 if pos > size(field, 1); return; end +0251 list = field(pos, :); +0252 end +0253 if ischar(list); list = {list}; end +0254 list = list(~cellfun('isempty', list)); +0255 if isempty(list) +0256 return +0257 end +0258 fprintf(fid,'%s:\n',name); +0259 indentStr = regexprep(name,'(^\s*).*','$1'); +0260 for i = 1:length(list) +0261 emitListItem(fid, [indentStr ' -'], list{i}, keyIndent+2) +0262 end +0263 +0264 elseif strcmp(fieldName,'eccodes') +0265 %eccodes (GECKO ec-rxns section only): ;-joined string -> a +0266 %block list if more than one code, else a bare scalar. +0267 if isempty(field{pos}) +0268 return +0269 end +0270 list = strip(strsplit(strrep(field{pos},' ',''),';')); +0271 list = list(~cellfun('isempty',list)); +0272 if isempty(list) +0273 return +0274 elseif isscalar(list) +0275 emitScalarLine(fid, name, list{1}, keyIndent) +0276 else +0277 fprintf(fid,'%s:\n',name); +0278 indentStr = regexprep(name,'(^\s*).*','$1'); +0279 for i = 1:length(list) +0280 emitListItem(fid, [indentStr ' -'], list{i}, keyIndent+2) +0281 end +0282 end +0283 +0284 elseif sum(pos) > 0 +0285 %All other fields: +0286 if strcmp(type,'txt') +0287 value = field{pos}; +0288 if ~isempty(value) +0289 emitScalarLine(fid, name, value, keyIndent) +0290 end +0291 elseif strcmp(type,'flt') +0292 if ~isnan(field(pos)) +0293 emitScalarLine(fid, name, formatFloat(full(field(pos))), keyIndent, true) +0294 end +0295 end +0296 end 0297 end -0298 -0299 function writeMetadata(model, fid) -0300 % Writes the metaData block. The `date` field is preserved when the model -0301 % carries one (model.date), so round-trips don't churn on every write; if -0302 % absent it's filled with the current date. -0303 -0304 fprintf(fid, '- metaData: !!omap\n'); -0305 emitMetaField(fid, 'id', valueOrDefault(model,'id','blankID')); -0306 emitMetaField(fid, 'name', valueOrDefault(model,'name','blankName')); -0307 if isfield(model,'version') && ~isempty(model.version) -0308 emitMetaField(fid, 'version', model.version); -0309 end -0310 if isfield(model,'date') && ~isempty(model.date) -0311 dateValue = model.date; -0312 else -0313 dateValue = datestr(now, 29); %#ok<DATST> % 29 = yyyy-mm-dd -0314 end -0315 emitMetaField(fid, 'date', dateValue); -0316 if isfield(model,'annotation') -0317 annoFields = {'defaultLB','defaultUB','givenName','familyName', ... -0318 'authors','email','organization','taxonomy','note','sourceUrl'}; -0319 for k = 1:numel(annoFields) -0320 f = annoFields{k}; -0321 if isfield(model.annotation, f) && ~isempty(model.annotation.(f)) -0322 emitMetaField(fid, f, model.annotation.(f)); -0323 end -0324 end -0325 % Any other annotation field is a caller-specific provenance key with -0326 % no RAVEN-defined slot (e.g. a tool-specific `_version` key). Emit it -0327 % too, sorted alphabetically for deterministic output, instead of -0328 % silently dropping it — readYAMLmodel.m's matching `otherwise` case -0329 % restores it on read, so a write/read round trip is lossless. -0330 extraFields = sort(setdiff(fieldnames(model.annotation), annoFields)); -0331 for k = 1:numel(extraFields) -0332 f = extraFields{k}; -0333 if ~isempty(model.annotation.(f)) -0334 emitMetaField(fid, f, model.annotation.(f)); -0335 end -0336 end -0337 end -0338 % gecko_light is emitted at the top level (see geckoLight emission near -0339 % the GECKO ec-* sections) so that metaData stays a pure provenance -0340 % block. -0341 end -0342 -0343 function v = valueOrDefault(model, field, defaultVal) -0344 if isfield(model, field) && ~isempty(model.(field)) -0345 v = model.(field); -0346 else -0347 v = defaultVal; -0348 end +0298 end +0299 +0300 function writeMetadata(model, fid) +0301 % Writes the metaData block. The `date` field is preserved when the model +0302 % carries one (model.date), so round-trips don't churn on every write; if +0303 % absent it's filled with the current date. +0304 +0305 fprintf(fid, '- metaData: !!omap\n'); +0306 emitMetaField(fid, 'id', valueOrDefault(model,'id','blankID')); +0307 emitMetaField(fid, 'name', valueOrDefault(model,'name','blankName')); +0308 if isfield(model,'version') && ~isempty(model.version) +0309 emitMetaField(fid, 'version', model.version); +0310 end +0311 if isfield(model,'date') && ~isempty(model.date) +0312 dateValue = model.date; +0313 else +0314 dateValue = datestr(now, 29); %#ok<DATST> % 29 = yyyy-mm-dd +0315 end +0316 emitMetaField(fid, 'date', dateValue); +0317 if isfield(model,'annotation') +0318 annoFields = {'defaultLB','defaultUB','givenName','familyName', ... +0319 'authors','email','organization','taxonomy','note','sourceUrl'}; +0320 for k = 1:numel(annoFields) +0321 f = annoFields{k}; +0322 if isfield(model.annotation, f) && ~isempty(model.annotation.(f)) +0323 emitMetaField(fid, f, model.annotation.(f)); +0324 end +0325 end +0326 % Any other annotation field is a caller-specific provenance key with +0327 % no RAVEN-defined slot (e.g. a tool-specific `_version` key). Emit it +0328 % too, sorted alphabetically for deterministic output, instead of +0329 % silently dropping it — readYAMLmodel.m's matching `otherwise` case +0330 % restores it on read, so a write/read round trip is lossless. +0331 extraFields = sort(setdiff(fieldnames(model.annotation), annoFields)); +0332 for k = 1:numel(extraFields) +0333 f = extraFields{k}; +0334 if ~isempty(model.annotation.(f)) +0335 emitMetaField(fid, f, model.annotation.(f)); +0336 end +0337 end +0338 end +0339 % gecko_light is emitted at the top level (see geckoLight emission near +0340 % the GECKO ec-* sections) so that metaData stays a pure provenance +0341 % block. +0342 end +0343 +0344 function v = valueOrDefault(model, field, defaultVal) +0345 if isfield(model, field) && ~isempty(model.(field)) +0346 v = model.(field); +0347 else +0348 v = defaultVal; 0349 end -0350 -0351 function emitMetaField(fid, key, value) -0352 % Emit one ` - key: value` line inside the metaData omap block. -0353 if islogical(value) -0354 if value; value = 'true'; else; value = 'false'; end -0355 emitScalarLine(fid, [' - ' key], value, 2, true); -0356 elseif isnumeric(value) -0357 % defaultLB/defaultUB are genuinely floats (reaction bounds); -0358 % every other metaData annotation value that reaches here numeric -0359 % (e.g. a stray numeric taxonomy id) is formatted the same way, since -0360 % none of RAVEN's own metaData fields are conceptually integers. -0361 emitScalarLine(fid, [' - ' key], formatFloat(double(value)), 2, true); -0362 else -0363 if ~ischar(value) && ~isstring(value) -0364 value = char(value); -0365 end -0366 emitScalarLine(fid, [' - ' key], char(value), 2); -0367 end +0350 end +0351 +0352 function emitMetaField(fid, key, value) +0353 % Emit one ` - key: value` line inside the metaData omap block. +0354 if islogical(value) +0355 if value; value = 'true'; else; value = 'false'; end +0356 emitScalarLine(fid, [' - ' key], value, 2, true); +0357 elseif isnumeric(value) +0358 % defaultLB/defaultUB are genuinely floats (reaction bounds); +0359 % every other metaData annotation value that reaches here numeric +0360 % (e.g. a stray numeric taxonomy id) is formatted the same way, since +0361 % none of RAVEN's own metaData fields are conceptually integers. +0362 emitScalarLine(fid, [' - ' key], formatFloat(double(value)), 2, true); +0363 else +0364 if ~ischar(value) && ~isstring(value) +0365 value = char(value); +0366 end +0367 emitScalarLine(fid, [' - ' key], char(value), 2); 0368 end -0369 -0370 function s = formatFloat(x) -0371 % Format every numeric field the same way: a whole number still gets an -0372 % explicit ".0" (e.g. "1000.0", not "1000"; "2.0" for a charge, not "2"). -0373 if isinf(x) -0374 if x > 0 -0375 s = '.inf'; -0376 else -0377 s = '-.inf'; -0378 end -0379 return; -0380 end -0381 if x == floor(x) && abs(x) < 1e16 -0382 s = sprintf('%.1f', x); % e.g. 1000 -> "1000.0" -0383 else -0384 % Shortest text that reads back as exactly x (15 to 17 significant -0385 % digits), as Python's repr does, so 1/3 is written 0.3333333333333333 -0386 % and a value is never rounded on the way out. -0387 for digits = 15:17 -0388 s = sprintf(['%.' num2str(digits) 'g'], x); -0389 if str2double(s) == x -0390 break -0391 end -0392 end -0393 end +0369 end +0370 +0371 function s = formatFloat(x) +0372 % Format every numeric field the same way: a whole number still gets an +0373 % explicit ".0" (e.g. "1000.0", not "1000"; "2.0" for a charge, not "2"). +0374 if isinf(x) +0375 if x > 0 +0376 s = '.inf'; +0377 else +0378 s = '-.inf'; +0379 end +0380 return; +0381 end +0382 if x == floor(x) && abs(x) < 1e16 +0383 s = sprintf('%.1f', x); % e.g. 1000 -> "1000.0" +0384 else +0385 % Shortest text that reads back as exactly x (15 to 17 significant +0386 % digits), as Python's repr does, so 1/3 is written 0.3333333333333333 +0387 % and a value is never rounded on the way out. +0388 for digits = 15:17 +0389 s = sprintf(['%.' num2str(digits) 'g'], x); +0390 if str2double(s) == x +0391 break +0392 end +0393 end 0394 end -0395 -0396 function writeAnnotationSimple(model, fid, miriamsField, miriamValsField, miriamNamesField, pos, indent, subIndent) -0397 % Emit a bare `annotation: !!omap` block from MIRIAM cross-references only -0398 % (used for genes and compartments, which carry no extra non-MIRIAM -0399 % annotation key and have no established reference format to follow — -0400 % compartments in particular are just a flat {id: name} map with no -0401 % per-compartment annotation at all — so the layout here follows only -0402 % readYAMLmodel's own expectation that a compartment's annotation -0403 % sub-entries are indented >=6 spaces). Keys are emitted in alphabetical -0404 % order, matching the sort order used elsewhere in this file. -0405 if nargin < 7 -0406 indent = ' '; -0407 end -0408 if nargin < 8 -0409 subIndent = length(indent)+2; -0410 end -0411 if ~isfield(model, miriamsField) || isempty(model.(miriamsField){pos}) -0412 return -0413 end -0414 names = model.(miriamNamesField); -0415 values = model.(miriamValsField); -0416 entries = collectMiriamEntries(names, values, pos); -0417 if isempty(entries) -0418 return -0419 end -0420 [~,order] = sort({entries.name}); -0421 entries = entries(order); -0422 fprintf(fid, '%s- annotation: !!omap\n', indent); -0423 emitAnnotationEntries(fid, entries, subIndent); -0424 end -0425 -0426 function entries = collectMiriamEntries(names, values, pos) -0427 entries = struct('name', {}, 'list', {}, 'forceList', {}); -0428 for j = 1:size(values, 2) -0429 v = values{pos, j}; -0430 if isempty(v); continue; end -0431 list = strip(strsplit(strrep(v, ' ', ''), ';')); -0432 entries(end+1) = struct('name', names{j}, 'list', {list}, 'forceList', false); %#ok<AGROW> -0433 end +0395 end +0396 +0397 function writeAnnotationSimple(model, fid, miriamsField, miriamValsField, miriamNamesField, pos, indent, subIndent) +0398 % Emit a bare `annotation: !!omap` block from MIRIAM cross-references only +0399 % (used for genes and compartments, which carry no extra non-MIRIAM +0400 % annotation key and have no established reference format to follow — +0401 % compartments in particular are just a flat {id: name} map with no +0402 % per-compartment annotation at all — so the layout here follows only +0403 % readYAMLmodel's own expectation that a compartment's annotation +0404 % sub-entries are indented >=6 spaces). Keys are emitted in alphabetical +0405 % order, matching the sort order used elsewhere in this file. +0406 if nargin < 7 +0407 indent = ' '; +0408 end +0409 if nargin < 8 +0410 subIndent = length(indent)+2; +0411 end +0412 if ~isfield(model, miriamsField) || isempty(model.(miriamsField){pos}) +0413 return +0414 end +0415 names = model.(miriamNamesField); +0416 values = model.(miriamValsField); +0417 entries = collectMiriamEntries(names, values, pos); +0418 if isempty(entries) +0419 return +0420 end +0421 [~,order] = sort({entries.name}); +0422 entries = entries(order); +0423 fprintf(fid, '%s- annotation: !!omap\n', indent); +0424 emitAnnotationEntries(fid, entries, subIndent); +0425 end +0426 +0427 function entries = collectMiriamEntries(names, values, pos) +0428 entries = struct('name', {}, 'list', {}, 'forceList', {}); +0429 for j = 1:size(values, 2) +0430 v = values{pos, j}; +0431 if isempty(v); continue; end +0432 list = strip(strsplit(strrep(v, ' ', ''), ';')); +0433 entries(end+1) = struct('name', names{j}, 'list', {list}, 'forceList', false); %#ok<AGROW> 0434 end -0435 -0436 function writeAnnotation(model, fid, kind, pos) -0437 % Emit the per-entry `annotation` block, fusing MIRIAM cross-references -0438 % with the non-MIRIAM annotation keys: `smiles` for metabolites and -0439 % `ec-code` (EC numbers) for reactions — both are written inside -0440 % `annotation` rather than as top-level entry keys. `ec-code` is always -0441 % emitted as a list, even when there is a single code. All keys (MIRIAMs -0442 % and the extra key together) are emitted in alphabetical order. -0443 switch kind -0444 case 'met' -0445 miriamsField = 'metMiriams'; -0446 miriamNamesField = 'newMetMiriamNames'; -0447 miriamValsField = 'newMetMiriams'; -0448 extraName = 'smiles'; -0449 extraField = 'metSmiles'; -0450 case 'rxn' -0451 miriamsField = 'rxnMiriams'; -0452 miriamNamesField = 'newRxnMiriamNames'; -0453 miriamValsField = 'newRxnMiriams'; -0454 extraName = 'ec-code'; -0455 extraField = 'eccodes'; -0456 otherwise -0457 error('writeAnnotation:unsupportedKind', 'Unsupported kind: %s', kind); -0458 end -0459 -0460 hasMiriams = isfield(model, miriamsField) && ~isempty(model.(miriamsField){pos}); -0461 hasExtra = isfield(model, extraField) && pos <= numel(model.(extraField)) ... -0462 && ~isempty(model.(extraField){pos}); -0463 -0464 if ~hasMiriams && ~hasExtra -0465 return; -0466 end -0467 -0468 entries = struct('name', {}, 'list', {}, 'forceList', {}); -0469 if hasMiriams -0470 entries = collectMiriamEntries(model.(miriamNamesField), model.(miriamValsField), pos); -0471 end -0472 if hasExtra -0473 % EC numbers / SMILES are always emitted as a list, even when there is -0474 % a single value — unlike a MIRIAM entry, which collapses to a bare -0475 % scalar when there is only one. -0476 codes = strip(strsplit(strrep(model.(extraField){pos}, ' ', ''), ';')); -0477 codes = codes(~cellfun('isempty', codes)); -0478 if ~isempty(codes) -0479 entries(end+1) = struct('name', extraName, 'list', {codes}, 'forceList', true); %#ok<AGROW> -0480 end -0481 end -0482 if isempty(entries) -0483 return -0484 end -0485 [~,order] = sort({entries.name}); -0486 entries = entries(order); -0487 -0488 fprintf(fid, ' - annotation: !!omap\n'); -0489 emitAnnotationEntries(fid, entries, 6); -0490 end -0491 -0492 function emitAnnotationEntries(fid, entries, keyIndent) -0493 % Shared tail for writeAnnotation / writeAnnotationSimple: entries are -0494 % already sorted by name; emit each as a bare scalar (single value) or a -0495 % block list (multiple values), at the given key indent. -0496 pad = repmat(' ', 1, keyIndent); -0497 for e = entries -0498 if numel(e.list) == 1 && ~e.forceList -0499 emitScalarLine(fid, [pad '- ' e.name], e.list{1}, keyIndent) -0500 else -0501 fprintf(fid, '%s- %s:\n', pad, e.name); -0502 for k = 1:numel(e.list) -0503 emitListItem(fid, [pad ' -'], e.list{k}, keyIndent+2) -0504 end -0505 end -0506 end +0435 end +0436 +0437 function writeAnnotation(model, fid, kind, pos) +0438 % Emit the per-entry `annotation` block, fusing MIRIAM cross-references +0439 % with the non-MIRIAM annotation keys: `smiles` for metabolites and +0440 % `ec-code` (EC numbers) for reactions — both are written inside +0441 % `annotation` rather than as top-level entry keys. `ec-code` is always +0442 % emitted as a list, even when there is a single code. All keys (MIRIAMs +0443 % and the extra key together) are emitted in alphabetical order. +0444 switch kind +0445 case 'met' +0446 miriamsField = 'metMiriams'; +0447 miriamNamesField = 'newMetMiriamNames'; +0448 miriamValsField = 'newMetMiriams'; +0449 extraName = 'smiles'; +0450 extraField = 'metSmiles'; +0451 case 'rxn' +0452 miriamsField = 'rxnMiriams'; +0453 miriamNamesField = 'newRxnMiriamNames'; +0454 miriamValsField = 'newRxnMiriams'; +0455 extraName = 'ec-code'; +0456 extraField = 'eccodes'; +0457 otherwise +0458 error('writeAnnotation:unsupportedKind', 'Unsupported kind: %s', kind); +0459 end +0460 +0461 hasMiriams = isfield(model, miriamsField) && ~isempty(model.(miriamsField){pos}); +0462 hasExtra = isfield(model, extraField) && pos <= numel(model.(extraField)) ... +0463 && ~isempty(model.(extraField){pos}); +0464 +0465 if ~hasMiriams && ~hasExtra +0466 return; +0467 end +0468 +0469 entries = struct('name', {}, 'list', {}, 'forceList', {}); +0470 if hasMiriams +0471 entries = collectMiriamEntries(model.(miriamNamesField), model.(miriamValsField), pos); +0472 end +0473 if hasExtra +0474 % EC numbers / SMILES are always emitted as a list, even when there is +0475 % a single value — unlike a MIRIAM entry, which collapses to a bare +0476 % scalar when there is only one. +0477 codes = strip(strsplit(strrep(model.(extraField){pos}, ' ', ''), ';')); +0478 codes = codes(~cellfun('isempty', codes)); +0479 if ~isempty(codes) +0480 entries(end+1) = struct('name', extraName, 'list', {codes}, 'forceList', true); %#ok<AGROW> +0481 end +0482 end +0483 if isempty(entries) +0484 return +0485 end +0486 [~,order] = sort({entries.name}); +0487 entries = entries(order); +0488 +0489 fprintf(fid, ' - annotation: !!omap\n'); +0490 emitAnnotationEntries(fid, entries, 6); +0491 end +0492 +0493 function emitAnnotationEntries(fid, entries, keyIndent) +0494 % Shared tail for writeAnnotation / writeAnnotationSimple: entries are +0495 % already sorted by name; emit each as a bare scalar (single value) or a +0496 % block list (multiple values), at the given key indent. +0497 pad = repmat(' ', 1, keyIndent); +0498 for e = entries +0499 if numel(e.list) == 1 && ~e.forceList +0500 emitScalarLine(fid, [pad '- ' e.name], e.list{1}, keyIndent) +0501 else +0502 fprintf(fid, '%s- %s:\n', pad, e.name); +0503 for k = 1:numel(e.list) +0504 emitListItem(fid, [pad ' -'], e.list{k}, keyIndent+2) +0505 end +0506 end 0507 end -0508 -0509 function emitScalarLine(fid, prefix, value, keyIndent, bareNumeric) -0510 % Emit "PREFIX: VALUE\n" (double-quoted when needed), where PREFIX is the -0511 % " - key" part with no trailing colon and value is the raw, -0512 % unescaped scalar content. -0513 % -0514 % bareNumeric (default false): value is already a pre-formatted number -0515 % (from formatFloat) that must never be quoted — numbers are never -0516 % ambiguous once formatted. -0517 if nargin < 5 -0518 bareNumeric = false; -0519 end -0520 fprintf(fid, '%s:', prefix); -0521 emitScalarCore(fid, length(prefix)+1, value, keyIndent, bareNumeric) -0522 end -0523 -0524 function emitListItem(fid, dashPrefix, value, keyIndent) -0525 % Emit "DASHPREFIX VALUE\n" for a plain sequence entry (no key, just the -0526 % "- " marker) — e.g. a subsystem, EC code, SMILES or MIRIAM list item. -0527 % Unlike emitScalarLine, no colon is written. -0528 fprintf(fid, '%s', dashPrefix); -0529 emitScalarCore(fid, length(dashPrefix), value, keyIndent, false) -0530 end -0531 -0532 function emitScalarCore(fid, ~, value, ~, bareNumeric) -0533 % Shared tail for emitScalarLine / emitListItem: writes a single space -0534 % after ':' or '-', then the value, double-quoted when needed (matching -0535 % Prettier's YAML default). No folding — this format never wraps a -0536 % scalar across lines, however long, so the two unused positional -0537 % arguments (column and key indent) only exist to keep every call site's -0538 % signature stable. -0539 if bareNumeric -0540 fprintf(fid, ' %s\n', value); -0541 return -0542 end -0543 if needsQuote(value) -0544 escaped = strrep(value, '\', '\\'); -0545 escaped = strrep(escaped, '"', '\"'); -0546 fprintf(fid, ' "%s"\n', escaped); -0547 else -0548 fprintf(fid, ' %s\n', value); -0549 end +0508 end +0509 +0510 function emitScalarLine(fid, prefix, value, keyIndent, bareNumeric) +0511 % Emit "PREFIX: VALUE\n" (double-quoted when needed), where PREFIX is the +0512 % " - key" part with no trailing colon and value is the raw, +0513 % unescaped scalar content. +0514 % +0515 % bareNumeric (default false): value is already a pre-formatted number +0516 % (from formatFloat) that must never be quoted — numbers are never +0517 % ambiguous once formatted. +0518 if nargin < 5 +0519 bareNumeric = false; +0520 end +0521 fprintf(fid, '%s:', prefix); +0522 emitScalarCore(fid, length(prefix)+1, value, keyIndent, bareNumeric) +0523 end +0524 +0525 function emitListItem(fid, dashPrefix, value, keyIndent) +0526 % Emit "DASHPREFIX VALUE\n" for a plain sequence entry (no key, just the +0527 % "- " marker) — e.g. a subsystem, EC code, SMILES or MIRIAM list item. +0528 % Unlike emitScalarLine, no colon is written. +0529 fprintf(fid, '%s', dashPrefix); +0530 emitScalarCore(fid, length(dashPrefix), value, keyIndent, false) +0531 end +0532 +0533 function emitScalarCore(fid, ~, value, ~, bareNumeric) +0534 % Shared tail for emitScalarLine / emitListItem: writes a single space +0535 % after ':' or '-', then the value, double-quoted when needed (matching +0536 % Prettier's YAML default). No folding — this format never wraps a +0537 % scalar across lines, however long, so the two unused positional +0538 % arguments (column and key indent) only exist to keep every call site's +0539 % signature stable. +0540 if bareNumeric +0541 fprintf(fid, ' %s\n', value); +0542 return +0543 end +0544 if needsQuote(value) +0545 escaped = strrep(value, '\', '\\'); +0546 escaped = strrep(escaped, '"', '\"'); +0547 fprintf(fid, ' "%s"\n', escaped); +0548 else +0549 fprintf(fid, ' %s\n', value); 0550 end -0551 -0552 function tf = needsQuote(s) -0553 % Whether this scalar needs quoting when written: either because a plain -0554 % (bare) reading would resolve to a non-string YAML type (bool/int/float/ -0555 % null/timestamp — this writer never emits explicit type tags), or -0556 % because the text itself is not valid as a plain block scalar (leading -0557 % indicator character, a mid-string ": "/" #", leading/trailing -0558 % whitespace, or embedded control characters). Implements the YAML 1.2 -0559 % rules for resolving implicit scalar types and analyzing scalar style, -0560 % and also quotes the YAML 1.1 booleans y, n, yes, no, on and off in any -0561 % case, which YAML 1.1 loaders such as PyYAML read as booleans. -0562 if isempty(s) -0563 tf = true; -0564 return -0565 end -0566 -0567 resolverPattern = [ ... -0568 '^(true|True|TRUE|false|False|FALSE)$|' ... -0569 '^([Yy]|[Nn]|[Yy][Ee][Ss]|[Nn][Oo]|[Oo][Nn]|[Oo][Ff][Ff])$|' ... -0570 '^[-+]?[0-9][0-9_]*\.[0-9_]*([eE][-+]?[0-9]+)?$|' ... -0571 '^[-+]?[0-9][0-9_]*[eE][-+]?[0-9]+$|' ... -0572 '^[-+]?\.[0-9_]+([eE][-+][0-9]+)?$|' ... -0573 '^[-+]?\.(inf|Inf|INF)$|' ... -0574 '^\.(nan|NaN|NAN)$|' ... -0575 '^[-+]?0b[01_]+$|' ... -0576 '^[-+]?0o?[0-7_]+$|' ... -0577 '^[-+]?[0-9_]+$|' ... -0578 '^[-+]?0x[0-9a-fA-F_]+$|' ... -0579 '^(~|[Nn]ull|NULL)$|' ... -0580 '^[0-9]{4}-[0-9]{2}-[0-9]{2}$' ... -0581 ]; -0582 if ~isempty(regexp(s, resolverPattern, 'once')) -0583 tf = true; -0584 return -0585 end -0586 -0587 first = s(1); -0588 if any(first == '#,[]{}&*!|>''"%@`') -0589 tf = true; return -0590 end -0591 followedByWs = (numel(s) == 1) || isWs(s(2)); -0592 if (first == '?' || first == ':') && followedByWs -0593 tf = true; return -0594 end -0595 if first == '-' && followedByWs -0596 tf = true; return -0597 end -0598 if isWs(first) || isWs(s(end)) -0599 tf = true; return -0600 end -0601 if any(s == char(10)) || any(s == char(13)) -0602 tf = true; return -0603 end -0604 for i = 2:numel(s) -0605 ch = s(i); -0606 if ch == ':' && ((i == numel(s)) || isWs(s(i+1))) -0607 tf = true; return -0608 end -0609 if ch == '#' && isWs(s(i-1)) -0610 tf = true; return -0611 end -0612 end -0613 tf = false; -0614 end -0615 -0616 function tf = isWs(ch) -0617 tf = ch == ' ' || ch == char(9) || ch == char(10) || ch == char(13); -0618 end +0551 end +0552 +0553 function tf = needsQuote(s) +0554 % Whether this scalar needs quoting when written: either because a plain +0555 % (bare) reading would resolve to a non-string YAML type (bool/int/float/ +0556 % null/timestamp — this writer never emits explicit type tags), or +0557 % because the text itself is not valid as a plain block scalar (leading +0558 % indicator character, a mid-string ": "/" #", leading/trailing +0559 % whitespace, or embedded control characters). Implements the YAML 1.2 +0560 % rules for resolving implicit scalar types and analyzing scalar style, +0561 % and also quotes the YAML 1.1 booleans y, n, yes, no, on and off in any +0562 % case, which YAML 1.1 loaders such as PyYAML read as booleans. +0563 if isempty(s) +0564 tf = true; +0565 return +0566 end +0567 +0568 resolverPattern = [ ... +0569 '^(true|True|TRUE|false|False|FALSE)$|' ... +0570 '^([Yy]|[Nn]|[Yy][Ee][Ss]|[Nn][Oo]|[Oo][Nn]|[Oo][Ff][Ff])$|' ... +0571 '^[-+]?[0-9][0-9_]*\.[0-9_]*([eE][-+]?[0-9]+)?$|' ... +0572 '^[-+]?[0-9][0-9_]*[eE][-+]?[0-9]+$|' ... +0573 '^[-+]?\.[0-9_]+([eE][-+][0-9]+)?$|' ... +0574 '^[-+]?\.(inf|Inf|INF)$|' ... +0575 '^\.(nan|NaN|NAN)$|' ... +0576 '^[-+]?0b[01_]+$|' ... +0577 '^[-+]?0o?[0-7_]+$|' ... +0578 '^[-+]?[0-9_]+$|' ... +0579 '^[-+]?0x[0-9a-fA-F_]+$|' ... +0580 '^(~|[Nn]ull|NULL)$|' ... +0581 '^[0-9]{4}-[0-9]{2}-[0-9]{2}$' ... +0582 ]; +0583 if ~isempty(regexp(s, resolverPattern, 'once')) +0584 tf = true; +0585 return +0586 end +0587 +0588 first = s(1); +0589 if any(first == '#,[]{}&*!|>''"%@`') +0590 tf = true; return +0591 end +0592 followedByWs = (numel(s) == 1) || isWs(s(2)); +0593 if (first == '?' || first == ':') && followedByWs +0594 tf = true; return +0595 end +0596 if first == '-' && followedByWs +0597 tf = true; return +0598 end +0599 if isWs(first) || isWs(s(end)) +0600 tf = true; return +0601 end +0602 if any(s == char(10)) || any(s == char(13)) +0603 tf = true; return +0604 end +0605 for i = 2:numel(s) +0606 ch = s(i); +0607 if ch == ':' && ((i == numel(s)) || isWs(s(i+1))) +0608 tf = true; return +0609 end +0610 if ch == '#' && isWs(s(i-1)) +0611 tf = true; return +0612 end +0613 end +0614 tf = false; +0615 end +0616 +0617 function tf = isWs(ch) +0618 tf = ch == ' ' || ch == char(9) || ch == char(10) || ch == char(13); +0619 end
    Generated by m2html © 2005
    \ No newline at end of file From a957c80f8a2a080a316d952142a61199efead149 Mon Sep 17 00:00:00 2001 From: Eduard Kerkhoven Date: Sun, 1 Nov 2026 12:53:48 +0100 Subject: [PATCH 44/48] CHANGELOG: name 2.11.4 as the last 2.x release --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ceb86576..f980b75f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -92,7 +92,7 @@ ## 3.0.0b1 — 2026-09-07 -First beta toward 3.0. Changes below are relative to RAVEN 2.9.3, the last 2.x release: +First beta toward 3.0. Changes below are relative to RAVEN 2.9.3. The last 2.x release is 2.11.4: a reorganized codebase, fewer toolbox dependencies, hardened I/O and MILP algorithms, plus capabilities backported from the `raven-toolbox` (Python) port. From 82a999af88f6d953511f87419873729523714278 Mon Sep 17 00:00:00 2001 From: Eduard Kerkhoven Date: Sun, 1 Nov 2026 18:14:11 +0100 Subject: [PATCH 45/48] doc: README with the documentation links and the RAVEN logo for dark mode (#19) --- README.md | 42 +++++++++++++++++++++------------------ _config.yml | 2 +- img/RAVEN.png | Bin 17737 -> 0 bytes img/raven-logo-white.png | Bin 0 -> 10765 bytes img/raven-logo.png | Bin 0 -> 10920 bytes 5 files changed, 24 insertions(+), 20 deletions(-) delete mode 100644 img/RAVEN.png create mode 100644 img/raven-logo-white.png create mode 100644 img/raven-logo.png diff --git a/README.md b/README.md index 52147e85..9f07297b 100644 --- a/README.md +++ b/README.md @@ -1,41 +1,45 @@ - + + + RAVEN + -[![Current release](https://img.shields.io/github/release/CompMetEng/RAVEN/all.svg)](https://github.com/CompMetEng/RAVEN/releases/) +[![Release](https://img.shields.io/github/v/release/CompMetEng/RAVEN?include_prereleases)](https://github.com/CompMetEng/RAVEN/releases/) +[![Documentation](https://readthedocs.org/projects/raven-docs/badge/?version=latest)](https://raven-docs.readthedocs.io/en/latest/) [![GitHub Discussions](https://img.shields.io/github/discussions-search?query=repo%3ACompMetEng%2FRAVEN&label=GitHub%20Discussions)](https://github.com/CompMetEng/RAVEN/discussions) [![Zenodo](https://zenodo.org/badge/DOI/10.5281/zenodo.3689518.svg)](https://doi.org/10.5281/zenodo.3689518) -[![MATLAB File Exchange](https://www.mathworks.com/matlabcentral/images/matlab-file-exchange.svg)](https://se.mathworks.com/matlabcentral/fileexchange/112330-raven-toolbox) + +**[Documentation](https://raven-docs.readthedocs.io/en/latest/)** · [Installation](https://raven-docs.readthedocs.io/en/latest/installation/index.html) · [Guide](https://raven-docs.readthedocs.io/en/latest/guide/index.html) · [API reference](https://raven-docs.readthedocs.io/en/latest/api/index.html) · [Migrate from RAVEN 2](https://raven-docs.readthedocs.io/en/latest/migrate.html) · [Citations](https://raven-docs.readthedocs.io/en/latest/references.html) ## About RAVEN -The **RAVEN** (Reconstruction, Analysis and Visualization of Metabolic Networks) Toolbox is a software suite for MATLAB that allows for semi-automated reconstruction of genome-scale models (GEMs). It makes use of published models and/or the KEGG database, coupled with extensive gap-filling and quality control features. The software suite also contains methods for visualizing simulation results and omics data, as well as a range of methods for performing simulations and analyzing the results. The software is a useful tool for system-wide data analysis in a metabolic context and for streamlined reconstruction of metabolic networks based on protein homology. +The **RAVEN** (Reconstruction, Analysis and Visualization of Metabolic Networks) Toolbox is a software suite for MATLAB for the semi-automated reconstruction of genome-scale metabolic models (GEMs). It reconstructs from published models or from the KEGG database, with gap-filling and quality control, and it simulates and analyses models and visualises simulation results and omics data. For context-specific model extraction, `ftINIT` is the method to use; `getINITModel` and `runINIT` are the original tINIT implementation, kept to reproduce models built with it. + +RAVEN has a Python port, [raven-toolbox](https://github.com/CompMetEng/raven-toolbox), built on [cobrapy](https://github.com/opencobra/cobrapy). The documentation covers both toolboxes, and [RAVEN 3 and raven-toolbox](https://raven-docs.readthedocs.io/en/latest/raven3-vs-raven-toolbox.html) lists the functions that exist in one of them and the functions that return a different result in each. -The latest stable release is **RAVEN 2.11.4**, maintained in [SysBioChalmers/RAVEN](https://github.com/SysBioChalmers/RAVEN). +This repository is where RAVEN 3 is developed. RAVEN 3 is a breaking release: the code is reorganised into functional folders, optional function arguments are passed by position, by name or both, external binaries and KEGG reference data are fetched on demand, several MATLAB toolbox dependencies are dropped, and low-value and duplicate functions are removed. The code can change until 3.0.0 is tagged. The last RAVEN 2 release is 2.11.4, maintained in [SysBioChalmers/RAVEN](https://github.com/SysBioChalmers/RAVEN). [Migrating from RAVEN 2](https://raven-docs.readthedocs.io/en/latest/migrate.html) lists every change for users who upgrade. -> **This repository is where RAVEN 3 is under active development**, with the upcoming **3.0.0b1** beta as its first release, targeting a final **3.0.0**. RAVEN 3 is a deliberate breaking release: the codebase has been reorganized into functional folders, optional function arguments can now be passed positionally, by name, or both, external binaries and KEGG reference data are fetched on demand instead of bundled in the repository, several MATLAB toolbox dependencies were dropped, and a number of low-value or duplicate functions were removed alongside new capabilities. Until 3.0.0 is tagged, the code can still change. See the [migration guide](#documentation) for the full list of changes for users upgrading from RAVEN 2. +## Get started -For context-specific model extraction, use `ftINIT`; `getINITModel` and `runINIT` are the original tINIT implementation, kept for reproducing models built with it. +1. Install RAVEN as described in the [installation instructions](https://raven-docs.readthedocs.io/en/latest/installation/raven.html). The Add-Ons manager of MATLAB installs the latest stable release, which is RAVEN 2; a RAVEN 3 pre-release is installed from this repository, and the instructions describe how. `checkRaven` verifies the installation and the solver. +2. Follow [Getting started](https://raven-docs.readthedocs.io/en/latest/guide/getting-started.html) in the guide, which has MATLAB and Python examples side by side, or open a script in the [`tutorials`](https://github.com/CompMetEng/RAVEN/tree/develop/tutorials) folder. The tutorials come from the original RAVEN paper and are described in [Legacy tutorials](https://raven-docs.readthedocs.io/en/latest/tutorials/index.html). +3. The [GEM reconstruction protocol](https://raven-docs.readthedocs.io/en/latest/protocol/index.html) reconstructs a model for *Hansenula polymorpha* from start to finish. +## Documentation and support -## Documentation -Installation instructions, a user guide and the API reference are published at [raven-docs](https://raven-docs.readthedocs.io/en/latest/). +The installation instructions, the guide, the protocol, the tuned parameter defaults and the API reference for MATLAB and Python are at [raven-docs](https://raven-docs.readthedocs.io/en/latest/). The function help texts in this repository generate the MATLAB part of the API reference. Use [GitHub Discussions](https://github.com/CompMetEng/RAVEN/discussions) for questions and comments, and [issues](https://github.com/CompMetEng/RAVEN/issues) for bug reports. -Full user documentation, including a migration guide for users upgrading from RAVEN 2 to RAVEN 3, is published at [raven-docs](https://raven-docs.readthedocs.io/en/latest/). +## Cite us +If you use RAVEN in your work, please cite: -## Cite Us -If you use RAVEN in your scientific work, please cite: > Wang H, Marcišauskas S, Sánchez BJ, Domenzain I, Hermansson D, Agren R, Nielsen J, Kerkhoven EJ. (2018) RAVEN 2.0: A versatile toolbox for metabolic network reconstruction and a case study on _Streptomyces coelicolor_. PLoS Comput Biol 14(10): e1006541. doi:[10.1371/journal.pcbi.1006541](https://doi.org/10.1371/journal.pcbi.1006541). -All releases are also archived in [Zenodo](https://doi.org/10.5281/zenodo.3689518), so you can cite the specific version of RAVEN used in your study +All releases are archived on [Zenodo](https://doi.org/10.5281/zenodo.3689518), which allows citing the version used. If you use ftINIT, please also cite: -If you use ftINIT in your scientific work, please cite: > Gustafsson J, Anton M, Roshanzamir F, Jörnsten R, Kerkhoven EJ, Robinson JL, Nielsen J. (2023) Generation and analysis of context-specific genome-scale metabolic models derived from single-cell RNA-Seq data. Proc Natl Acad Sci 120(6): e2217868120. doi:[10.1073/pnas.2217868120](https://doi.org/10.1073/pnas.2217868120) -For crediting supporting work, please cite doi:[10.1002/msb.145122](http://msb.embopress.org/content/10/3/721) (`tInit`); doi:[10.1371/journal.pcbi.1000859](http://journals.plos.org/ploscompbiol/article?id=10.1371/journal.pcbi.1000859) (`randomsampling`). For crediting RAVEN 1, cite doi:[10.1371/journal.pcbi.1002980](http://journals.plos.org/ploscompbiol/article?id=10.1371/journal.pcbi.1002980). For more details, see [wiki#cite-us](https://github.com/SysBioChalmers/RAVEN/wiki#cite-us). - -## Contact Us -Use [GitHub Discussions](https://github.com/CompMetEng/RAVEN/discussions) for support, to ask questions or leave comments. +[Citations](https://raven-docs.readthedocs.io/en/latest/references.html) lists the papers behind individual methods, among them the first RAVEN paper (2013), random sampling and the INIT method. ## Contributing -Contributions are always welcome! Please read the [Contributor guidelines](https://github.com/CompMetEng/RAVEN/blob/develop/.github/CONTRIBUTING.md) to get started. RAVEN 3 development happens on the `develop` branch of this repository; RAVEN 2.x is maintained in [SysBioChalmers/RAVEN](https://github.com/SysBioChalmers/RAVEN). +Contributions are welcome. Read the [contributing guidelines](https://github.com/CompMetEng/RAVEN/blob/develop/.github/CONTRIBUTING.md) first. RAVEN 3 is developed on the `develop` branch, and RAVEN 2 is maintained in [SysBioChalmers/RAVEN](https://github.com/SysBioChalmers/RAVEN). diff --git a/_config.yml b/_config.yml index 13d66dce..9e2e16f8 100644 --- a/_config.yml +++ b/_config.yml @@ -1,4 +1,4 @@ theme: jekyll-theme-minimal -logo: img/RAVEN.png +logo: img/raven-logo.png show_downloads: true keep_files: ["docs/html/"] diff --git a/img/RAVEN.png b/img/RAVEN.png deleted file mode 100644 index a411f80b31e1b88635bee1cb3d276807efc1d139..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17737 zcmXt91yEJr*F{A^Qo2EqlI{*^>F$#5?nfyINOv~~2uOE>kCbld?(T-~@cYj<<2d^6 zJ$d)pXRo!_4pxwpKt{kvfPsNQmi#QL1OxL@5&XLs{w4TZV*3Lv42;-2Nl_sc_q2m_ zHw{d6obCea0x4C+&c*Vp#U{g}l%!5CiI$7+X$ zyoL|i2?P*Xpoas8jz02_3NyS;ntz%-hxoVnxu8r*EuRR`fXli&4y$#n7;I`{#7=@`x-QWh03v^_BbFxGg^NL`0i{@5?nFpG~5UXph1~p$W4?AwQ^6+ z2HTv6G~}=NgpV*mlXpm-BYZVlPS>3c7-kGhp^(#ckLr~s^yml_1SBZ6RN0g{0?z47 zv!m%vC4~YP^|{Y~Gi$fm<72EcXGS_(9z9zgDQCqgZF9e%6e;|}G%zY8^o~oG6$!dc zEmb@j|54LBx0}C9WP>S8)kU?!|Y1teZeIZrq_9+5JRR+*5D548R&ILO**h-5TY6*qDIz4PbK^*HeN_4mk+g*$js zd2$8Q<~2&eqU2KPsBci%$E;-5bFNh6(Z28y>7F`{lRpydx2_5C&C*i(jZBP5-U{f*Kuc9orvGo`Y#POCzYd8 zn=Hd;LtPd*laX-KDzSZ-dhjh-$hc;{3EHwx>j?j8Mjd*KrNQhg{4lHf{?9A$?)3=Y z_{|KM6D3?JkM|=@paFVtw&py#5P8H}oL*QFV-%rZm!FN(%REE!SU55> zmCe&bX5X_=MOf=M1xk-YxSF)$tt&k~T{`R3T>pYF=ks#+QKL0s?Lfj8ACV}*k$!Hexdo5_$qoRWDS-y72swXN-}EDQW9TyjZj)xUbW zT4h>X(d3fH8qX5|Gghv;srD#NEL{+K_uXIER~zhLj*!yN9Xe*R-|oU`biEGQTt{ zw91^2`Mkbvz*$zr|8zwvvpg*4$n9XTE%rTnMaw}nmUtj>SmV5bMN{N#DNCjQ~>K>jFn<)%{qr5K^tM&oE)8(5AvV;e(2!Q@gzEn z$T>H3h>+>m$e!FqSigE~FoUskg9R@*Hf;urzDbwfRD-SzVA` zSrGQKJ%V$nT+EJ#M`P3*poyQ7&yH{NssBw&W3^w#f5+5rP+DHyM@n*|Kv5`f>iT9g zlgE`w=p7RW{PWP<`xCVEBFX0;Pf%n6!6w@froj5@Of%Iv+~nlg`#zq zLK8QUWz4*?Wqow+a3KsH@J@T|W_Br8dPEAF=M6ZC#!7{GV(_|c3LRdh407+*|2uVC z9Go_S?$@0j0XpI_*mhzPDO)QUa8Ra}3J3=10%kx=jYa%j=bBkrV%bvgpN%^%zzsRJUGT(xpp?rU^*K=uG;rpT zDw`IOqyq1?fBS7%sO?6Ar!xBI;cfEho&{XMeP?n%DlvbVR>g1M9m4>G&9hT`CpV{y zOUEncVIzb`^OWxJ<66$=)>9pAIIJI4cmEMpz>XG=P6OY<(>}J}_60%3N_YtXCglpi%MX zwKNu^PMd2y$Kc)#c5lxU%Jas0hMtu!G%wXGC9TJ)WUMvY`K@sY;j$!y5mgHD0;4~7 zwhDNFpn>anYY6LJ8lgq#d4_MSG-SMAavt%?NJ?*2nV(508(W01sFd}ZC`~Y4-%qAy zwLGyM@wv~3?W^DTlk!Zaz+D>O(GCG*hpq*`3h zT5a1Ryf0ba^t2JwK3iaWB3bnLN0Z0OK_Sn_K7}GeOvluycVL?>T_{4aZauqX;}U_hmRreGK>~xi0wB)L1-urjRk+Si4(mRiEoSTHpuR$S%Dfsf-Ll!V)bvy)Iw0D zu8Z~2E;v@H1%VVh%72GN@-v!nKcYw1lDV7z% zlkvtOxrjmw&4;h8o1HtMg%&&D|4AeJyv{LDj6&@m>|?@SlqOW-qp?=aGw~WG&z|FK zf`?2|#fhWbSHrqtD!nEwg%fJ9s6yk_x&EtNd;j!<IQd z6E%Ikyhx%T0sCL5H;dt_-?=kI0s$MUH%bbJ-C%_>j$}-y!#p+-qeV)BWS+df=oJAE z`{4uJ@jPbvbp3rIFF15bdRfgxCz7{4&hgsOLuMe9&5)I9yW-$>048{bT7F^86TsyUQq z<^EcLi%323`=eHAZ#A*jmb`8>Rsf(;rS8VY%2d6cOaDGKPWI$ZVA$Jg|3BS~?^gxM zXDT>Rp-kkG=&;OYecf3tfx|lr-4-I-)Run+Hj=RHSI;TVG8l<@u}+wGFesm`ZsUs= zWI1!3Pw)k>`N59Ue5*UtOM^WcE&z_S9YT? zG(8_}4psCq5pNBOF~xjrxJnjVunx1 zjaoT!}Jr0fT%T(fCqCUTFps4EkA^MVSQm4^%tE-q3yV2c# zyU=4?-2O-1fN&kkASy98)OE5os$9+{^$Yf9_W?DEoo9(9mKWkW)V*`2@5Cl9ilq0N zl%C(Hv-9;`9YcpT$)Cr@(JCY9T6fiv`~x^(YR$`B4Cv7a0rLxklf%;mQe7-BO7I_V zJeO>&US=-wRho_(qvSvSh?LAED_#8ay*(#e!%_>fU12ZtK83v~zGpJ|ph$T|-5uAX zw*-t%?IaK&Jak1NDSRtuYc*CbMnx-$(nk^|!$q^L3tc1ZHlW zHVXc8SfxwNktcGHMwYGoF-@b(kdgflXra8-wgay5Y6XZD$DYE9x38YciGRKaKi5{c z!=$9*2|6WX@w}`Ii0ZSpCU0!`%cVTd(k*j`IL~zhR_?Hr&~;&@zEXZ^JE0ur=7$>e zQ8j_y8hgqyB^4DfD3(Jlt-+rD$oz`f1qP8K%n~xe9{t`ok+#WBgOyB`O(l8!?-s6z00$Ce^K7KF*(LdCIFgy&7 zE=*_ts4zjt4@;6N>1fdkVLa$mM9i1)wp_;k5+4 z8*3=wrw$cJ1o1YF{^b-pPc?^2b2)V^zaxiV;H@q%Iyc=8b|O6OBbsB2mg-EHK|A(s zmj9f_)K5eBqCp7rgf_}*jo_!fQsMftpjr3RTs2%}&#R0>4=GIgPFGc$$V>(o%&rpW zk>Tl{l#2_izwsaS8b#4+iw_08F0W^3_mbZsVx@Xj8ZAXviIYfZf2ZD6|3rqiHFQqt zM~;L{Ku~+2Uv7xlOKb;*YIl{i#P>~cqoeZ^!3+2-dZ;59DMFH6o-j^Gt4X&kg8j6z zcGJ)Kuk`eyPrF#_b(P(lUcZ&KE!z0z4ECfmzB1q_c!=UtwW}x$MbyacPu>_;yc#7& z<>DzP-bqf#_;&KTt}!M-b~lbuyV)amw$_Aup+v+9rR}i;n`;aag}=iR(&2uo7&_Elw(#86q8GL}~^#;n` z%lzwL8W19tm+i-8H$khs|0*AWy&*!jx2cw_;&<|AXgQZKCmh7HgZNV{Y$5EWYX>_+ zt{xL>I4`y2v_&9iJE7aWs2SzDs^A`u>AD}NM}D6=!p85suM@5oG=mw4Iuw9dA)r|y zjT`9TtU&Jd8K1*dZ(?mr*sdo-kwz8VOJABo{1ve?k(~fi70;CtBdat^RIA0>b+MfL z4rN}?&Nbs2aDE`3ryK?Gk31J(1vBcLF-(YH3j$boGQ7bv(3@~9RW;zh1Y|e5MPYrPme{t!vKW!@m=REk8L-3%J{`l z$gJJJ334M5jnadS@^@^qjCwWk2Q&e~hbx;Qni&Ua&FZ^Tzg6l-e034JLT{&jYb}Dq z)#P9~frw&4ud=kh8}11{v$Z;Vk@jFe6bui8*`4yHRw2%(H|Y~=;2q$A5`(lKz4X{* zMRqX!$lnHwdY$YZWE*W3P2$Ip3tbNM{ta*9Ce(?i+3bOuRZdi&M;YlL#rf_&ntZu( zLS@L#_$pv*lRB+Tt${QC=yd9*X*t#4ap?xv#(c(XGylaF@koqc`#F}7PTAT~8`*B? z>A;6ZNTBqYjD0Y`0jIoo``=4!`b`g`CoK#%lOY-uE82L*08L`s88IBQ9)MfCPa**! zb+i+Oif^m<=8HZzx|p9_%&P|oFE;3o+ccq8m1S}BxD0%ffd@}IRupW& z7p<$5$1pG}v&a2NI8yl{sC}aOXMP^<4St9Vy~O`umH)|~kk=SRoCjSU|I~6Pk@0x9 zCca%vk#Ko0>Yw~+1w?X50y;!c4Hn8o7K0foM$56URmlUNa#`uGCvR2V2wb`Ps9o7& zeH|k?oqb<-gtr8C(MZl!R&d&Dd2%v>>Eb}mq#$`c$35)p9bO9=+ju%&|44+5(21Wu z$ZnTG`DP3{gW17Ibs2DCMZ))0`X(E;zCPvmI`x}l z&Tsj{6edbHwet(`B8^QA=X*26IV}Sd7F`g&mz=xPMSoT_2NJUQ_YGM@Zz{WJZxin~ z6N}VO7*(5@wDqNoxfgyL$cDLzl2t0tQ4;v0lxvA8-ik;JUmnc`&&eu}kIAPs^t#5^ z6?o=0Vn5-=A>th?Z@qd-Yw&tN3913%pxyXyy==e0-`SJGS;o43hCZ*oH6lmzGXutU1Vr;{UF?ojvLrpCdXheiaVo^iaaQ?;yiNN zX8UBHs{f*7J0VvCZK*H!jlDL353^Fcy!A}SvC!K92=boFg}u|{Y)UkeAXv@%FF@6u z-z9O^g-Hm!J2>h7j@IT_4!=?Wp~zAdQO7Bu(_3CSoUODVk`toBpImK`jkN^w!$F|QI`1}Jz1`M#zf;_p z)#oGjBMfBVK{CvY-;BxaLgpqQP_^|j&Qz7=f&Ki zW+D?4CEb6Fe-9tU-_Pvy=iSg->{@yULhQ$h78}=FcFCCS$-J8I07#lw?`2>);sroJ zKOh6Mmd$E42!IosDSgSsM zglI%kwLwSAkRzPpPNa|&wNz!6v5 zbsIqMMC2=Ob^m!s^}i1wPJiFK24cl#*ArvgiwbZngr`V9?Wk>V(Lvm9We4UlnS>NPe*GBj?pzsD}VvKtb|BdjSRk0ay!sB(Z{ zaD@%Rp#5g(eoV&yE=VhFXE+2jb}}-Ph}BjqJ1!vrVt+YP8*g z#B~}>_yq&(NaRrN8>ADznzG6I(@VP=N9LTbnbPt5%r4`>OUj{ear4NKa9)oE+o;mU zce$^3vLf%YSygxAxTq|JA&(Axt2#RjIYQU-R z9VpMd@lA8Edh<)O#w$1{fQ#<8QnvGqlGsdXh&Rq~iN3&AwWU@f6u#Z4fXs(0EO73- z#;<>v?<6F;KBd!;#mb{!({fcL?_)BeAj*!S+?WyGofIq8i2V)mX#X9l4 zl~%DzvgkEVxL|Eywj%D#j zqZJzy+{HCKuBEjSP^5%$TMr$k#huygn<3xfqN?Fm5vnS8E@D2%zysc|grmQYbAMLF zuFX+1i05+r^~3uxtW%#5mDK4(?)~B@F_pF6v!ccproEJQs=3&N{OAB2jkANm6?eoj04{f*eSCnntQ`_ zzzc24S=X+YBQP;~=%gpYMr_tzj*cgWs@Y=QQ=RG#LLJg_s6iMM?SBUt=^AmX0Fuhz z<2f7aGyQ93bRtLD-n_B8JjCZKGaj}=2dz;|D6Ptyz`c9bJJ4Zj-#QeD^m$N(DhPlR zZvVsT0&?5FVmC+M-Px_%i`LhiI+%=6UDsCp>{e(RfteVb5L3>pM~6F zJKA!*S+BrV_BkU^x7AaJN3VtHICJSOValLmcD_~ZWX6)L!eY=_Muz13vmbF4louiDfv`lrM+nXr=pPttx@rRv;vybzu&Rakt-KC73nw8C;x1RO}qU z+hI}be|kq3#^aO`HpmTp-O3lJ!X0ev~vB;gIWkzPoXl2WOw2lv&alFw{f|7a_g4^00x*ngUEvmjovtD^V%%phq(>u zHg?$jy6V*IPxrFgxNp1Z=WwdO1Dg$8{NRR&>)oRfn`aExGwpb4zgGUD-SPZztkJ?B ze|`!y-2pU5(kJ6-CeM*!UBuh<05IBj1Ka?e{K;L)0UhiMjpciGRA=*rA9M4&fOL}2 z*q94Y`!)7N7?aXHim&o(R9K(0;Algm z9>~COd*yrFlHCsZQ{}hqUYtK3Vb&Ud6DGBr&JmbXg+awUKgnP>fUm0oDOy0rzNdtx zd-LJqaIAZnv+WH6m!>PDPQ91Y;#iw(u%Tz3@ZM#v>qTfu}%|*_09i%2>_-xfO+4qSrMufa36v%Lbtxh{cABlqo)h>@PDMO4% z?sl<^CaJ48G#R-f$~4&|8DbqZQZq`^7&;w%T%V}KX9`M*Qq&6-fC6LHW+Jo%fI1z@ zN%Lybo<9do7B8@_YpiCzLjO!N+tuwObaqE!>~3?Az|F3g%1S11Fot%UWT2j z{spZ{c|)^N`>F1pjhsc=;o!QJ55Z>O@|b|JwOXPgH&iS3;ELG0fB=r<;m(x|Ubr}K zzZG_g?$-pqbCQH2q(v`shIhONdDC>J%Dzi2?-Z`Plbg!?$Y7v5*c-=}!%v#CMIjtQ z?cfbkLL~U|zOsp5^Os>v9*_t|svK{|lET|pPrkDd08nc=SskI3Td@{`RP|}+0?<;p zCwm}fNPvyN6I{hX>kqc7{S4yg`J(+*K`)($E4_iS{IRq#t10mjVuQFQ?>+HfZ-cP1 zd;WwU@fCy&4k=Vvyow^PFg-D@#h|eHbfyr>BlIp-0{OUai$bM13OQTJ$_sIGZ@gr% zJ(K6d7I~Arogpp&G0>e0ujcfriKQdYI#6vr1YdTcJ!RdG*7ai$Ls)-=yJ3lOP8W zwZuloN&X%2*8K|L2k0MwL@A2>nL-W5Cu!9Txd%%W?Lfeb*~L?Vnm<`0d|A73M zpxe$HQ%3j=k*Q_}8xU>Q7IURdtO81u_vJeT5WAV4VD1U}ZnU;FC0d6yKE6J*E%im(?lQ>M%1y5u80j~6b=~JRl+PR{=7TW&*r-+^vt4RL zL**<|E%7Up*CV!tO^T+$k^%{Lj4Jg(+U;1kzI|rB3|l;@cyBlDE!bq0Tb|7YOQTlze>irp6{12+|-C+Y#C==^us!f{pX~Dz?Dw|1_S)Q8a;(+l~{&u^8?qq%k^KbXR z;Xa)Cdkn4kJk`vtkG{FXW9L%vJ=3K`;;Hj`7`i%n>3#tJd3dNq3X5^Tz~H*;vs1k0 zA-}Uy?xewGZvZW4?o#vGQ!7Zz<@hK|FaO6@+92u@F2nsVZelL(L`pwHf-Z9jfIHE$ z#M~_-f-Ya2(6*{kk-i>#o?6TXx1YmcwjQa{Tai&PW z+?yn^wpsAaWeQ*=x<7zRUc1VJ5w3sdV|QdK-*}5r=u9Z5#_Hc^HuFb=J3fq>7|4J^ zTx@Ih+8qWSSNX|}{%pAf0Uk%q0iuc<8VSmT`n10qg|-CT~4 zT%%4yy8C3Q-l$_k(~)AS%n5>tbUD%8SGs~!j$hwSLNm+cYyLh)*AXXORRRKH^0yXo z6%oi_f(uvk9NWc8ji*)qYcel4Rb=s*{x_prq>S&H7^Zv0to*Gw1GqDQ;7E_i*_lWG zYfKWL<9c(u5H1AgsQOF2Ol^bmqJb|CVADmsftE74lF>uN6+@bZGMptL;vD(9S`NTd zl(gK4v>Xy?ug9SJHGBt@(}|H`l=r}rG(%z^@SzNvzoWEXVbkmT2`A9=ie$DyVvDsV z%w(?9=c=p^$7IubQb+`R7%14&4`@KF2H=a4@W?amDxoQ?Dd&b_Zh6U_vRq-|dymCM z8ClF~%C6BVnDDA@CO(_Z<8+m(z}?64aCdu-~H0K|0FbyBpCw`Odvr)@p^{LP<4fWeZXsJH8_(D z*=X6ErRFC44a%Uo3qbc^Ltw@KFFr1<(t1Izwz&3)_b+K+sZvE4 zkd7qTv)L%nmLShPwQ5{-{5Q6*z=7?!FdD;8$VR(qG~^N`hTW@ACbKBSVOeA3V8_r_y; zbje@2qV;`thQEKa>Ug7RgUT9YvZTC}hzaMlce9!Nx>B=^E1k=~KqXgV+ft`|B=ues zCY1-o5%QCG0=L}0g@$!jp~vQoS9dNeC)IvK~Qz}7x0U@2521WEiuWhX?&Wn6=~R^ zqVqJ4tQaGeASApmKyDwyq}O@J%{o`InazdNb7ElxcXP8TKdi7>?()q5H^r2?ML?u* z%-jXYl}d)xP!aJ09b6sVNiyp#?u7h5$dy)<05-zSPTma#5fBn${5~hp!~m!Yy z#>2#m0os>}*UFpv1&mNly#Ug6;Vb)KcJ(*VrKoTMzc{|-H-% z6Cf;bh{~{LY>|>t@uei-Pr6Stfwk$ks#v3@1mu6E!U^NJM-xSA*;%Mp9n6avhnojy zuYTjjkWbNNkY%w_kfa2OmfF?T0v9}GULB{G`4I4V9sXsa6gd!i0sSuSY+FzEf%MgV zOF7VqZ2ZN{6tIm7@+98-9o^vot*!b;0A&JVp_0+xH=dcZ*DS?Yc4w#3M<{?MBgD?& za@ql;y=L?BREq>S6=t5A4KkKC*>CDvQH1`>o1wS2D3*a#_OecNF;E=#{#(%zxR{OE z^tRQ-<*fgl{W9f9J#mFgE(TVZoNN-mM1^bzeZ=~v`J{H_-gd?D z1lv?T*j{W9^}RIpKHr&55t;X0Uw;igZ9@R$3r)IJp-$_0`6K^<{+TG*=(d4iu;|Z- z+sY)d+|h)On#Nb%H$qk~>&nubEM~&?Ihk^NC{rrPXc^BJ7D6GAhbjMB`rTpNCtjg< zcv6#`nTFet@66kPGR_rc$$=OjMc_KI6hyoFIBc!K-Xz$zzh)(3q8<`zIPKO-%3;29E zDT-03>Y;@q91u|=zxZo<-u>9jaFFVYwwwKH8z{r9g9^vkyw?sz)@TN1qkeQ@<_UVP z;NQCbRO$l}w&j1MP+Nr51w@Mu`#{iZ_w=Ia!f@q+TCCVgDCig%S5zewS1 zC9P=IAdnUQV|{6!5e%RWkR66!w(0Q$85k&Hc&l+Kwg=Qp;#9-KleDlxQ9rX8Cc+Y? zxP>U=y=3ILU2k(f8fjK0EzMFWBiyb(@XQ+FLNPCvN0f0GoRUwTC6315qKZa-3dy2! zP7k=;kj2QT7qLXf^+%lQHo6AooRdWhovB!c7l)3=jlKdXuD>C#M)Q-dzXX(l&-bGH zoCuU#w0-IR9H4XQQcZ;&d=7c&&eEAE20Pl0DlFyaNsQCxcf*PZJmL3IpMh&LI^Mh_ zOtA&u^Zg}a`#^)FzZ=pZgf*L)cR2LW85?Vxdox79CQG|s2_k49Zi&D7RjC{LM!5%< z645lmIozSh09r7t-(r`jk@;KFRz-mKmv7vxcvdS+$Veubz z6|$OG3Cd@Bh2Xyzsi@}={?kK4TuP9;0vsu&7*ZPFJXPj2=iQ3CI32o0O(T8NBjSQR zVS$AfIeJNwV5`H!u2ekF*=W8DjW^Mm?ew>`{~0X`n$j6=pyYj08u!;&DRL6@SgHY? zXJu25oY;Ilk?Ozcp!YvIBl~>i^w^GvHGyO;pn(Jh%O?VAAsIu@8XREW^nHx@rwJXq_~)b=(x0)Yi4c+ zffJX3CIYvd&qmm282Ak`kfW8V4^+cfyJK2q)2TA-iZA-hT6muYF?Hv5@mjf2{pPbH znLdN&+|O>{-#Q&BM*s9t8iyUS&@y@jZWpJAPMSJn#zd*uE0M|7JOZagTj8NtFr~m7 zK2?->pDe;G4Z4^gUESs*?@__I2xKf|gJ?x!#*HbP3zXGpW#si%D5* z)LZ~p>rW-gO~&M14j|R!0#zf?KSV1NGqPnunFmt9Bw(z$hCClx0`n~@uG=KQ-D6b~ zVltXZFagT6q#6c!MF6PqgJ1zk^-m3RSRz?gu19W>AK#A`?ogAZNuqlt$qmP@;fTa` zoZpDB6n4I^@cX0)DO5oxM)O$tJ7|h*W%7tVW<=OS6-m6jx?gYXqF^{im3;|{slFt_ z7kZ3#hloyu2L9})YID=GLmfvnX4#2aY6KZJ_pL`G0F?!NFWx*0pki+7X<1o})O<4A6-kTRYq8NF%vVhbnTS!Zp&6@*&pLo=f*?#C%C6rny3lLLc0VDG=$^muG1Kkl0# zSq_Q3kKNu?y>D}87J3J1A?`W(6X1nCNH|$&6AMI3nJxC_vSds}66DL|T&-)%bI7T23 z2&*h-|Jj)nqw|I2C{X3-m2rlL{bvWi)nD{U0%Z?KYxlAubvcy|$(iRY;zV`p!B!W6 zWNtQZ0%IyKgRh=^yq3sQn=FOWkI24{0f;y5o((_{=S}avZ31fF5P0i-#(q6M=7nyY zUDB!5FVBC3`q$ABbJuf5tbtv~ar!ZmQFm~&Wux=wO78=D@JRoEwPMy9XCt}yJBUUv z`r9%L0zdb+eNQaVo+^IVcCl0MyB7l>IZ!_iV2}M$GdbAw2AS#G6%M|69jb`?1JUDI z`eM_7eT~H|IaMU6sT?kgp@Lm{#F%*vwk(qr`RaF6No`im=x5(E^~-~M;0_HoIwmD! z#t%`qOvuK6*ErVEB;-g(&~EcPsa!+|Qr=$;s6`ZYgN)Spc^r4~p@6&JpPt`kYpeSj z1>#-8uaQfqGG#NVkj0+m44Mqkk1YALA`$5Ut1@7OZ|sbFX8aLr!}=}B0=|Plf<-CM zsCIfx!*kiS$C>||Xb@K^uBOO4BPfbULFDQeskhtjRPB6Y#TxP4DX)kDkOTsxgrYJw zmDlH6gOC^l+*vNy;fdof&iTX~ZhuJ(D?aB8ED=!tk)Ldfs=WRHe-@p|8zW~Z&iy*O!TjItNe$rM0QH!OP^j)M58a(7T^EV zJX@`}SZ}5c=qdJ(QpqKMVj%CS@m6|@&23&>Kw8uaM5YEnczIg_mzz(gM4&xgrGQD; zqe!r{Rkya}4+Ysn;`vK;5P7Y$d{h!RCy~la*{GJML9?o9{9u6g>oc4&m|Xjq*Oejx z2ElYr{}B+}11GK$oQ!jE3Cd&)C193hr$97;~4b6I`gYP|y{Z#ix)O?@rf>e(N4W>( zZ=Z=sL;#7P_eV+&2Kt4pTm0}#lXGojm+XtQe~-25WX z=^9y9{BRCX2Ev|t)f(YJ6)WZR4+;df0L3ZyAmLw02!`ayT&6xBQQs6Z1ZOfki9yZE zlYsy$^N^21%lIX7Wt_O?AB|bUr;E~(L$ zD8CPByp-sZsV29T9^gGV8PX|PO(D-m>BZ1lRFz4Ml*yH~);lhbBml!zs6dJ=oe=B0 zgB}rt`MDCHcU0&ER8K6vqAZ=QpL0&P3+V`;t)rHb_qs%7waxCSDQGO!G(_>}kX6&jcxyp{_IU+B!TlyFiwG*S$3)f$9%vTB^3VZf+4kd8JKzlcv2 z@Fsq^MMeq)MRes{OZ=YD@b}}->JgHqx_pc%P!3VIvP`;CZHNhio7hxXsO3{xt1Ah)BC&o zYsqlp6JXv451ykjGe#k|Sf8cUl^_qCnWw;JBx6V=hHH=b4gDQph)*$?g9kVE+<=_d zVpnOz(gmn^Y`;`14mGU&ki695D6FzvAvdE6I!B6+&0U@8PXe?Ocj6@D;p)yxt;fdi zZ-Q>4mM)-h7iE+<1+h%YN+2(P%j>xk?~@!m<5{j(yL>vz8~TSlbx+I+Z+?$?-1;9I zEEWJPjVfbbZ3`Zbj8^3nQU)3Tvg|!n(l|2MKm(9#qlyWdUh*=26SZOw+{%F;#5$Pv zyBDa}bTrQ}gOh4$;-wM>i1~eI;enz9DKBJfZzkg)TiPO=Di@6WRm>nU+6XuQhyFK^ zVYSxhEii0|!4Fp}R2n}|l=mR)A;NTQkijQLl*k~Gkp0!^N$eU&Vef=n~pWs-vs*?yOtYgV!I?1;$ji5`Q{xuSao|^ zN>Iv~0Ldu@<;Aw(Be*lDQ~DsJ5o=(r^%RB-%E1p)IOCQ0cWuwM# zU_R$WEqAp%flAVE>dbbMF#|*6Y0tU*zf3aAtH}Nm!*Y*tDGj@0&G0NWFi&XrcF#c^ zXY7Y>%;_q5U~b6V4W{E6046?{3zFY1nvD7joT-OnMS^15=lX1)orxTu*W5<{D8s;1 zzAq3`4OGH{(0PH#4%Fv&Gs%#l<-T3k|9U9QinN;$`vZBC?#X6VKXF%y0-;QsB@Zy; z+%wN>gh|o_sBOmSxO3>CXGp*Co54R$)Z=qDP_R8vWen;D^DS-{JpEgKYS0M>(&Zx- zP2exR^x&jR*DDYg-u1-h`7JY%6%0Fqmf8_=zHR4&LR#5^5{Ncp_WmNIOr#TDV{ z*)aWUU%$%o=F~)by??K8dVB?K8rZKpD1Dr9OYT0&8Wr|v;jjIjqFN_T5ycNW{RA_} zO0>Z5jn-m)5CIAajHy+DZkpNUbE*A2lP+pudQhsBo?T>$YCpS`MzLwlsck8Pz77+p zI0VQW=v3Px<>Njh4O&tk1REqNl^x3bwPy4>Q-z(h09hw z9JkI_Zevln>Jzo{%qO^qpJlX!CG)sprS$s0$tRdS#T75eRZy$4>|&`YQvIdcBK)AG zs*^^fX+yTIaUF8O`@v3P<=QM2y(L4Ty?>m(%KNz#9VSWtOyJ{b8;J#}6tywjVTg!rEZ&bPaFH4PCQ9I5FA`nxbuI1P zJ1_{odoG!6&^=pS^!>H?`>G(0EkI|gS}6Rh8uDD7C3)-#=g{5Y+|>svdL+a)KSC}E zltWg}F@?p$=Q{0mvO!OR#B%F}2tJ0P3rqDKlcgK)MKtz#721vsY=^CR{>K)yTjuDfR62IZ^ z`)AK~?>%`>+?{ih^>sB!iI|8mFfd3pUjPg-FtC)+-@g;!p+B(=^kXqF_@^}i&x}JB zj|$y#Se)7uPI5VGIB}>!*f@@~0J0jPXD#2U$Pze!u@pRq`2^p|VmV&Mem(haKOdYP zb66P@OSzy}OO=g{qm7k=%fr*JUCi!1Q}wbU{7WYiS>I4yblb6$S6A_+rl-2TVr@Hg z>18>UJ?A2b?6bsW6DIT&N&vJ(SVD=Qv(Qa>M^ysOxW7VQ)KGuFVe)=fxN^mp#_A#T zpe867bjXNi3wf2OgFf+Y?+26JcXu72f>2%)sc0F@p#u9BTOx2ijzX0nxkyM1(ZMnP0`(6=R@b8 zqB#1v(P$iB5YT*n*Un8L$~l5v8bb(m?!NO37J^COrsC<;aDg8val$uo^D#<6E1-{6 zbwhk_QOiUuSfi8t5AdJQvnxDE519#ycn`D_F$>5FzU-y{4@QtXrWB%E+t)YI0SS1; z+^rF;OgJ#f3Wy(Fk#~B)`(YLAD*+vh5qV8P+f0z519FHFGgH#58$brz;9L%Mh0v^1 zqN($QQ3V?{e@xEdgo;B=W6D8)F|u)baL#dWFwd|-lH^x3S2A=Tb5qp=$=nd_1Gp2B zIrtL+glwE>8sK4GExSM&BsLPb6C?Pag%M1&Jdoc<64gHNz$NH)7)zscw(26$JcrX? zwis%VX38;IcNA1g^=7QHWHs%Kbhn{=sd<#vM z##clGW4Ae)>!m5E2jIt(Sw4qqgj!D4N#$P!W+uONL{*S7^NL|2r{5zUB=XY@C*HA2 z8|31*d{tGhSqi+y9IZtS5*f|L|5Ke&D?$#7c^&crAIwFgD1+H)KQ*}&K=ePx(!4}y zWGXOssjm@b{Gu>WX)A^bR55CicI5%Jz7Bbsx*@E-!oLyhWUt;|rs&3s*vY{#Ow4|e zmrF_0&?4(6PKF47*<6fi^A;K;4nR(r8b>%V{)>%>4dPW-W#RWvb$tAm7}S~#3Nm;PW1oo;)TA11f>MWdFF4>8oq6G$^S3ALP+l@fH?OzD~cLhKT6Y|{6kT(zOiks zsOye-*P3mCru1%4^QJW)%RQ_sV=6|WdHNr9y~$o{VzZ&m$S%yBnsT~-RNjAmi3$ZJ z-u|Za$;~!@5L^?W=Yk4#yQ|x7CkQbedANq8vdkh76!J*VqoPmF(i_7xT%(sFK_gb<0d=za1Q1y1ax2N2 z|96J-aJ+gmSQz8?SqVhaO#w|+@M0mm!8(xqwx1c5BQzJ+scEPi2F~M6S4{nxJ2If2O+$XkrA?Y|x4?m-$ zJ3_PnKu~wmG7(#0Ll3|kK_>BLBGpav!G)F&~J7`5gTbl%H+!emBGgh6Fkc#d|to?TH~Ggd+JK@TQo4XZd?Q#7L`Xhv|?1 zUxg}>M<`GcMdHJ6z@rsEj@Ccm89UiE1&4AgR6f=Pw@U%26rt*1BFB zBBFrhu>o?PB#Hzc6ic?r>?#XVvyrtV)MVn%nJ3|PF{P8kVU@9;FMCnmqNH9CP1!H4Kc$Wj6emAC3yx}1?<}h zng5c@$FoqZKtiDI(Ci(4dW(Odjcqs8sf?pp(j4tRp}Gz`;!6%nwK4P5Ty@qCixAWC2@%vg`MI|%w7OCQM`r+bsWNg)`~nIp@@_D<5; z^QN*AXJdE=ij1boIRYx=V~#kxj`7JZjyH$pqTWY>>du71Iv`95YHv;ycth@0PMb-0 zHr4tJBVnxApdZjQP|S0KqCA|-y_|BY>FeSG{O-gI6 zjqKL{bsA-smcRw%A`Az5Ax3sxi|O!tiaEvN4Xu(F$OOA|e>%)Z0M_|N?A&gwQi`N& zEj8h952DnmLDt6eLR4S5P&BhgAMl4{eqGp817}EB-c&SVU6$42)@u41TBe*{O;9qr z2~=91*Ct#SRRUX2fAJ4iFV4+yQW7$zU;7Dtd=n|hvAHyCCji9_5A7+&j#=?BQugVz zArklZ`pxMgbJ1M2k`)*GJOZlPVQM#XBrZ(D^Y&^v^Oye~5*jAiORG%mJy8GTMT)!3 zGK1eLW<>QTkTyiX$6^&Q5oR%$w}8=B34QVh#+r}K=X7<=z8w2kjAmX8d)Qt8#pQMI zSeieHv*bG&pIu+(a#GQ{h0($O-XiOnE#}PAWXC%e=4e&CrC+}}+4&_+wTDEeROTXZ zkJ=DoeF^);CC$cg4Qd|A`9l=^umX^x=kw zh50j{K%SRreTv*MW0sW`0lPqEn@V6i#EmA=fdPb69ZTuYR}~#aZzx^xP_kYg7%kkr z4t$O;a;zWOr|ISPCP! zIv1yb`cL22Q>Haa`4ArLQPJ3w^kL%O_Fjn6w=MMr$;X_$@2!7Q-g4S^?bwD{Z4D>1 z3J5sDE{K=!`2HF9ndO3pf^@2v@90yXS3o165Go7tfqx|CTuRQZh$QGr zmV3NLc84|3C{e9QAVb}fO!|m*|GH5wDPU=LXcaSTZj-%R(8o8nM0Za@a7O_qVx%NeEW& zoOI7e;fw2w?E&J>{0-^#cT>sbT~=kua~i9PTc1B)iy^p|1AdZBJi~12XgM8W0E4!3 zDU~F?d9?|9*x4F<&0F@=t%O7Eg%*_5vGa4NuZB$1 zHB>1q`MZ>9R!PbB5md5{QFzL#1jR_%)xZB{phvI zJ-e<7tvE1^txvq)|C22YLIl>C8y$p0Rin`snhFMluLhlC#k%yXTd#yv7!}0s?1EZZ zJX`{SPrfPy*?+ZRBI+wL8!iE4ATSUCgvF|e4U5w!+IQB=r1UG9_N1KM<4rBUEX?ud zVQ^;9@WoeshI-F+jtv0V<&o$s&soW)piytDZP z?bH;9P?RE{SMk&W`07&=443l|bvw1J~OBM17vuPvC z>`;3xnP&a`Dm-ZQw_|imP)NFsuK(iggMq3c3^c%DiF5=j>>+gPR0~_K9-o_9!MnxK z@W)4!)l9`wF`c3ll>Ye6#TWyo(^EFNA?Jw@iJFkncZomd&|1(tM|D%Pl`= z5bvgS{%-+aPkkaFnayGa-t6mRNCH*;OQpQ99Rl=Ro__X0QH+@x>1sLSd}j5`*U&0h z&SGxG)k_PtS`=89JnLWE?^%agu+_A8y@o&v*D2Kbd9vYJ(cWp-k1be@7{y4idY-uH zUAl`TQV)BV+)R7YH%R4U)r3S9MG!6T??o(?YfdzZczx;k6ti?O`Odr^R@XY|*2e!f zSRf3Bn#yN-mz#oGz;1h5cbE%icb@vo;^pi`sYU#*bR9fr(9L{ggm|7pebm0{KT!mK z8jZiyu4J}U`yXG(iq4rsip#Jgd+jg0L^omYo$bNm?<%pZM{iu4QF1I2q90Jd-lRXB zC+W8+ZG{pk@?k~eDEH(2tVdq`Ro?6CX-LaAW<4*0-AEl1gI~r%%i&LaupqCp zydZiZh69JM+xFa{65WbBni>sq<_xNf$xD}kEOS0-L6E}sB7)$|0%D(hufMdAuX>)- zz~_ckO8Z2`S~0#<%JEx7%VVJ(Ty`ZAaxAI`$gPW$P2seXJbk|@pQhH$+hxaM zfL@4y_~=>SJ+(Aal%E+xaNYJb z(!%;@`-T*PkF2#-?wIRGvU?cpMSp+W4~5jBI6YIJsdh?tw@)a3ybAk$az8_81ionM zBA>6yXU?*+SN|!p$ZRUE4i+D)t*2{3wa8k7Ii3GQs&h)19l;bVTqcD+ACav#t|pB2 zWdF+ybaz@Kbayee z^H&aew?m%6gR8Nr0dga~KVoi64s)_2*bh&?3}k>f#m+^UHZ#c}-wWdy#iX)$dCLH` z7r}g8x%ZqVIkR=mm#SBX_yr>CS)pEp0FMg32^RusLu~S{gcQlR^NtTjR@{x>LJh5E z90?dIr`jpKnczCrShk=!OjBnws@o@& zV{g-vF+GW!D2I-}49vvMNS%t2is=oxx|_wFg*AvV)ejjSegb(7e|yw$@;1M3vh}m> z8rY8pmAUC|$HAXcbd`2au-sCeyj4FI*$6q!rcz!_DLjjDYJB#!XO)2rwt|>7^)(f` z#`8*vc<|n}bmvz`Btp8#&Xz|=LlGf=P$a9Dx4BSi=Ib)Pw*uPzZ%4}ZehoFZ$LT!p zVV&2PQoh=%kC4o+xEJ%<5t2%cYO6u|C<$Gh#+J41$Vt9gNn3P(+!BoX>ugVU4{lpd zHTQ1YNHkNjsosBLFW04M;SiB>6_cy5l9Wb4u`fX+OP`Yk+U;QWy7TDpS^IkU^S2Y< z9lPq{fpGna!rRlW^)wR~R9N25@FPa$s6?z{+2vJIvq#*o;MA+YiGJ9;EYT05f_7r>rk;{HRG};8;T|^S>_nTF7@LOVPl}u;5K4Di#udIJN^X!X2 z*h@3`k_SSYPPdVu%g1vy;rq(hGGAJ$re_pKSM%`8A^X|;o+hlc{6%<~F^!IZtFkQi zg)KTonRq9B62#qAB-=^suKq+$=_9x^7pLZ)5vVEGeW8xJuAj%PPQtPY3^*dZOZl#E zh%{m@#;%ZGUTqMEOw{ifb*rCsOtN7pmocclu#`%5$4WUR$diVID0_9b%TLlM{o3mj z5uH+9&p;7cH)Zs(NW7ZYUWsozkzbr`fzlNfi{OV?Kv^pChjv3U8)J9)+kD%PJY)87 zCM=HBqjJbtc6`FuFmzuSoevBfbb&GbSK(~ba#^V8OA59x4C%pb-TsQJb4QhRm!)*$ zMB|;#kp+9ASmK`%xMR$AZewMNUu3#t5Aez} zmaKN2kEFLWbI!^E(us4L0PMA^)dXM{Z^v_?z(&DML+|^*)i8_i1;d8%CEvk06^aLCiG*w zJvTx_If)ITKN~QU5i;_{><=xVO7mI6V3VHQ;_PclWz5dp6Ie=wQmx^CG;@!awZHbT z{=UTFinbz9Ok45;HQaE7t)6xTVK#sDmxaF-HeeQBE7yjNz2#O!F8&ngskrp=kxi}X z#@vLNB#USMy`8X0ub?Q%Z$Z~itZ_u})`DNx{c0?AFIb$bYR%fg)L62>Yh@+XG-Eew zLbxMuDJIEd_N?#CTi zO}cQ*U@W^_vkcxnT?KZf;jg3E+Wqc8VSI#}OtgMNBp<@$Yt%yXu5I7ZD; z=!=AS8!>bdk0qJ$grCTiy#X=K8w_!vRR7tp-ZTzk+~W%9lO0ChGZ)Io-qbxE%HoDo zGtmV(L@}|ur**GCsqH+*;_3_`oV=0!@v9}_OPk0Vu~!W2FK3xQXiaWj*LIXQn8!=r zuLxen&4f4sY}Sa2ge9qm7x7v4)18eI(soFF2&?@Ye{Z;aJx~DNOOQFVen&H>{`e>N z26Ndj zl5s;m?D(zx@R1dLm5Cd%Z)~o%6PJ8NAvn}uQYX((f<+^=QL=SLF?roqbENrm3c4ik zg!7}*UgUe}UMT(=rQF4s%H*X$hdEn=D*yI#zYNbh|(s4rSPFgoAoM8V1WX`6T zksnY^pfT$2VUg3Z$3GZ1VGSjpkpkrH#923jcLtm3c%Bdx?z>u+x>>(g#~nSNUwL5E zYgtZG=4T|-qfh|G89m_P<(IXZqCs8H6&d}WYwDS#ziTX5iKnj7XJ7cHhE8#}#1%(# zR^4t9cOWY;CFv2$0a1$9p+G=DVM&Wy1ggMXH<6Qa4pINv-yf)uvXXs-%?;7junolv zkx?&+&d{ZN=Km7!a;$WZ(1^cc2rsKe^Xi?6!PyUa5*{6ywr`lS`5rU}HZ)ePJ&{%> z?_zg;@c&t}^qGSE>N#E-^ z_&7eY%yjB?0@O!pGS?npN-+Uc4e8p3_y5) z*mVPoJ?i<@Rl2)__6r#hy~Ul13zirMIuu>Nz%4^W$EdS!#<%-+7?mSyy3Qqj0{0Cc% zT8&nE>NL3(BN3~HyC25GvN>Pg-rIkaHn`a63uKKZ9^CUfMF)M-gGDMi=V-)PR|;Fq zT;qa_wNsRM?3Y$Zu${Hlu*nM*qJx9#_F+5s>&z<|)7O5eM}scPu$8V}_K>b(39;xb z6O{P<7fE&HT+qz#EkkrQYCh3kc4}FRd#>mt8aC_=wMoB1nO&JlvId~pckDg zqnYb_JXroXgF{{Goyv~E`3L*IXZB#!H!x`(Z6Sur496s(E1|O;fGsM*tH=+G%ABR2 zcn_5dA@fRj=M}eB_(nbNnm1)w`5VXCRMGo2&90As)awET1`6}k9qtC!Thg(!FCE+V zoE&RRj+jnu|GOhZS>~y50TYDXGpSC1)~oH*N)+YB$~*^;sLI}h&8gIvn4Fk0|I(0w zeN?nizZDQ)80Hdl^~$yun&`b!fBu*s@YgY>ck>r>{+Ou%r`{Fw*ow&8sHveKP? z71hQm6I2$l5bsw5R3y8#zob1R@Js)ZPMO%7?!w0jGUPhxmsbTF2W6qsSgqK_2Yz&H zs4$7Ox2@rRn@{YdNT=THPS0>PN@dv7O9f-8#q>LfRL3UlxQ-1LPK?W!Nm!i+enpan za}P48hOu53>Q!z%#A_LEey((9WNu4#}7 z{p%q_hNzu(p?-H!{9smHWpd6)mYVp1U(-nZKt|%b*ZT7k6W|IOVuQ7hq8G-RLSy8B z8Q#9JNE&JVlhD!1lc|F8x|e}~a9F?w+zk0GC69TNO2}|O))VyPb;RM2S*O>MAUj zCORrm9TSf=eA28%y`%TFlt&bzQ%du3kL~bUAg;`@myi;**RHUTiVBk%^%WjUt(L)u zj<^$PQ&391O^hHxJb7J{Ni+ef6D`J}4Vm)kjvrAxm;2U&w`~Fp8k$2;E(4xLu&ajE zkr1jslO+w|&H(d8l}1wM7$@o<5L}kB3Jw&e##aOna%uEpuO4p~js-12FI#jZ6*F;*zn*rV?14k^CV7QLt9Ek?* zq>^=Mr@}#7_}uzdh~N7SrZ;P9FRazWqe@3iLdI=D#HYex@{Kg4-DQ^d_{3@_EM0_bR1U{C0dz@(@))e0lxr z!rCY?1LjV2+5TD47q8pLAupk!W@)+_ABXGWXa$B9(wQ#u%*9;X6{r|=CB4Ae>-8mA zMNg-dc=}cUbqOSUD?^EPXe`n|(Cqa4-5WW}rH?VzX7eN4B}}Bm_>}6&j%E*y6mZ3l zfB{9GFuzXg&h&3Odc$^EEcGX3jSi=BJIl8oB7ug!{jev2$><)yyS)+qT5hRK1Q^nw|X+4|A{Acp_s@R7eFS?sZDQMpJdt`oRB zw1xi2VRj71^IYsdN7O?PoQAhG!H;#lhYvJ5w9!JmIRBQOv9|y^PyNaqQl*Q(c~PnO z;aq#P)!BwP;j^^6=O@mWzJti3V+`HTEhhIm0agFRabuOJxr>m7e;yWzjbu{Y4$vtt z_9POrT>rhU-^&NNX1`##yB>-&Lxx&i6%4}shhM%Lr<}R`A1vpen!7vOd5ejer>|K%T%AMIN@*;uqnb*24c2wEt81 zRp_#Viw#Fa3u)ZQx>KOAr_mzxmFVDCO{s-f@Kv;%D;k}>e|JA7e(R2Is2zsz~rqJ*UO6t>?@0S*ZnsB$(#<9^6`YA}{elWW?ztOvzD z$mwWl4abDc>#%%Yv4OryIqP8kZT$s>fQCElPZ1MKG488~@L3Llr2lmz^9FoAd(*-t z5+8pxs`F`QK09m2YWq~F>?8rk+Jo@^QAV&tY~5jvKv{&9Ag%$&R)n|~ z2E*+$cd!P^cwpVy3n`ikzTLB;Iz(!s8|e-kA^z6lXgz;@X<79l8U4r-n<((xagYEX za~z1vQ_sJ9EIvio>1LWY@k&6@>)wpJCx?lNMPx8@Bo8UH@M);he^fP@+i+e-49ZkCY+?YTZZRqGJf~EWbLcym|IA%>ShjIM$YOWpF z#?mWLDd^45A>(O*xGxR{rNN@q1HWUUv6^DnNQbz%9BKgCmFv58JN}~()F4xOFFLA# zh;l8t!+P+9UTur%nOuj;z-wz|L>+o8V~Xwe8!QGU_U9j`OhESFE@lB0RGkj1$`y8n z&c8f+=m^R^9d4(0K%EyMh>)<1tMk5&rI&qTN0a}rnu1g|NP1}rZPZ^z)Gph7kDzl8 zK1vqCJG(Dcli)f8c`6!7p%@hB#75AYIl4+`TijBbX!v^_MhAuaM39r=Sa=5v zrDx)xtpeQ*eMGV9I#+vo>ym_*X6TScl+%Z3P1eS%AuR5PxV}(|Ah^TPVvUrcFwem0 zRbbK@HZZX&q}x3=CJN9Sv}j+dCMY{=+|%hEel5L& z@yBH0mmE+X) z@v~G}4J6nI%3ldgfnJJ>&9E_Bg>l^n45m$>&O^{hV7YsQiT-5xvoMzAPrOV{6fTh- zaeW`nK)=4vOUpAwL?LnhHgpP6ow1-^$No7^S=nMA8ZuBcD`{p1b_0v?ra8%8_W29H zVmKxgC7fr|f?u?xIXAi2?peanJ#9eAl;sFM*CsC_5Zw^<5~^+r=SYA$n~V|mtYTa# zWh)b@1GuZcU5pIq@;j@_PCXmb56LRVJjAgA&=4M^{OLUb7MkznqLm=s3C%cI{dACsMGxUvL|y*cKt1d zDGZUuKE`a=%Tr|ze#RVo0?o}QPiZLq#x|hQIIz-a4nTUJq5Bw^I(6$0$(0)_utNA@ zZa^JfNMQOzId4_-WPhQ&%@(&&l-51-UX69aQ>&IC$LwV_cB*`u+f%^I0Wl5)e>Qft z;ZRHjsH{@_%{5Ybs4LdQ^L@MBJN=6y=} zN|EP#Ex)Vi9-Cki>?JY6LuE?*KT z3mY*4H__vtRExo|Jz8E ztxJQBQCm|+L0J)uNM(5{=BovR{xMk^_0ql@Vx;3zb26;PqU6HMj6PS(zdvNlRnwa6Uv{bPWVXfdAhojWzg+zJT(Ka7 z7WYK=!z1FV9hy4^gB48)*0jyo&GDz72i3QH}?jEg4n6%lpUK zcdKXLwQ(N-9q4VU&YpM5`AB4cm1hymzLyiNhOLbbri8hzGrZL|vr0&7ATZB8(1lBe z(k+NIK3*zjDaI~F`aN7rpXF>-34^qdxD8Qi3QKZ%eHc=fXzDp z+X;3fW(AV+`bM!Z_64bXEgmpXPs1Qm*}QHPr=@Q)5iM05KrgMC6Jre;VLLPc&jQ#@X2H~2S!06{$zJZN!8vGt z(ElTh(ySqLze!oHMkDBvl`r#unT72Gmcwy*%h_kmrN5=@T?eB=F|)^S7*gxuW!VqD z3>F3Bdl=rVLZ9(y^+&{(`_=0r%`_>{T%l93QtnYaJfR$f56NSoqM=RXrsjDbcJjWa zm01sc;OUBGn!0+2y_!}=*{}49tF1F55OX698NUwx?@*@Ds&sNR64gw${J4hMfa?z?CFdbZ9Y@o&hX~49kW%F5D@Ik*y6`^UUF&rfb6y(b-#?6J8@Y5z zMFu$w>plV+VI8rz6_8)FFhaJIfj6 zr3bmcze%I0fgT>W=y%SzL}Sv_NXo-&iUOuy=0fW6^5#-X@$zKeXLJ~((P(T;HH68B zfi#rczEDKjLeHND1iI%w%Y$Hh?a8sU zK@MH??2RV8p$9JLx~V*qEPlSY?r4_}ajlW|{~O3xMHJqDB3Jjyh#-VCu*l`MiOYv{ zwvX0NdG`1rv!nAI8zWf(&M544U=i1Aso#w+LMVL1q|b|^)$Xrl!+rLfd9XnyAKSEC+ SlnXuPiJ_^k3#eBG#{M6y!Ig&q literal 0 HcmV?d00001 diff --git a/img/raven-logo.png b/img/raven-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..e4c38b9a031176c9ae1b5a42f4ba89529b874893 GIT binary patch literal 10920 zcmXY1by(A17l(~*N$KuxX+cU5kd$UHx{)585>lc_qeBGAA>E@zfk{tt2+}bmm3oK2 z_n$p`?!D*S&&hkg_nhRX`dXw!3`AI1Sfr1&)s3*Q07{tuUkLCpzd*yMu~=9FK9AK^ zO+x1Pi`;UVoPIPT@<|dN3%zBu~WVpc@m{^P}w3-bdXnETi#Jz**?CO3x1VXyi*Bj zZ?Elcsd7T0Cz1wTap$AYkchv){^+sj{pdQNAaDUIWBWBBdpyO>lx{id))ialp$mG3 z=p~>V&y$g$u*@MNiY3INT#tY~K67h?p$v674?i=Pah?3?|7*jR0iOzXaU!v zyP{_*YKC~9xGfPek%b$*$J544W3br5NPR(UAvqsiw#A|)VYlXZF%+**|8cBnDG;EhB91E|#Y+U(;h16F;G5%J0KVgI5epIEOooG4 z&~`!sl60v%Ot!<_1GtC?BuR0ej@y4ad%%Ea4#*ka@s`1v?N0>OF0Nk_;3Ayhe(+84 zdd}Ye@NhYB<*3awAd><(^|8vavT6P=jCDPaFNc}GB-hz-P!1EGcDCg_Ij~j}_uD@m zk&o;K{dHI}QNh783-R>HFEI1KUG7h$R))qpVRWPh<^8n(h)~zbCcf;mK+}sU^pA6a@0y*F{tg`ZNF~(EB1?X4&jb}m>v~*pWGIRw~X|1_4S5Re@|tDg8AuxJWNhb$m7W`?&%SpuPQ_NGH^2P zz*D9ctHPfbGfzDky{(dbpxpN94r*WWmIZCkjx!HX6z?|YpF74=O*D|eUTZ-&El`xg zRd=-3sl}@orm~XB6=8Q0Q*dN$xg}rkn_q1q0l9dJP;Qz#pn?-rDKEj%MT$CJHpHV7 zuZZsWJ3%~1(8O0szZZbe<>z-v8<~rfEiBWf|gU6<3Hd&i(-We zyhqHIR}|kR0E%=Dr7%hh{l=Z?y9nXPfV$wKu*lDQ8ian{c@IIB_Q^{?Vrc8Fy&fVO zb*U!&=}yX5NyW@cOSfWu4TQ5gD;Yb+A1-k+o-N&J&+wu9e$e~?c)}PEgjI}F47f>e zf+28}ah8YMJ7{x~yD0Ow?>JK@=EU@C1)t|)!xhvuMniIcR?>Fm;dTP9fET=IYc%)W z?$q1-F89ALxU9|&9_EmDHvtC0|m$y@^bJ8RM%gYAdbQNr+1 z4;QT4K6$_)el2huFCAwH+vDB)TtAlQ0XG?lFGuOY0iEa00bS1m1Fl%PLQ0gnV-A61 zSNR(tCF{aeE2YS;G{1n_VG{vITe4P0WM|?*h2jKZH6_#KIJ}`A{LCZFZ;L9NwD#zz z4$A--MW}w(``QC<;W&9AE8lUB%-DWlh*u3QxlzwKDMh_5VYTuy!MIcxF;CmqxA|9{X?j@e4 zBZ^5ejRwZ+#0>IE$(}cQJsg!ju5GNEp@CpPE>h9#nJKH3nVVkzmFMPQ@2_uT`nHKO z?=CpnUlXH2#YQNpjNiZyTKQR9ZG3R^LcPv;Tx3tPrj@hfV>QF!YVFU!Gy4qIeA@Sq z+Bnzh-q&F-!vz?aK;-S^neE}P8+@y`SMULi&=j#mb2}iQh(FOlMkgHqMkQI6?i&a zYvsAug!G=nD$(H|zFUjq_9Wi4ZKRsZdKf9=vV1Qqk@M!_ZC0pRFRysTLn~9FIc)~Rwr4_L7>R-H-UXX|r8YCytL+RNchrBtm~Au;RFV$ zIXUfJ_VO#bm_5-xgxkC|xn&yP$ZB&v`+@U7LUfzKkaYp;Ek*)Wc29zCE7;~fp?^#V zvu8Q!h0LGbc_Vj0R_lj}jPERIir^YrbJ%dXiqce)bT(K1OZUOft(-nQf^;ky+##R} zk+*w^EXeL`;FcX^m0g@R)a}?$%;ytFBpcYcVF2_AZMEH}SqA1`ziIk3?1h9akERqS zw4bj+OLx^#e1QSK)V?%>`=>DJ63)Z7LUu)h#AP%I<@080PNvUJKe}3E(rNelZ6U_q zHJ8Q5LM#-=G?P%`xiC;qGp>P%YKj~;ir-|E1w*hd)P3mMN~6%LqW(~q^jWgGY~6AY=dYQGH&OHTuivIh9VNQTkjwG8 zMp9o8$QDWT`#*(O*VbHpmdvsbbyi2yN`*a`9~X{6-}~d_CgE5E0&#BG7=J>>F^?S@mQT&vp_rv1KwRTK}n= zQu&XQ#S66`_=)z9zF7O>dV7?Rjlp+Ug;5rZ)U7_MXGT^#^8zd{R(W)gPbVbFFWzz} zNy-15QV8j1E%h(5GV_I{{gIQRD6g+7 zayZ;=*uoC6*;SX^6Tq`)UpWoXc|QDbk-9K)Z^GlOgUrfOA!&ob=8>QC+2XpZLYVYO z!Tv)NxX`P53FjYcOdP>ej4#C%%7;@y~0D zkb$g<<$6N3U=;+1HAex{3wrr5LC6hhXX>QLSmlugaVv3w9Du<#7hm-fy!b&?h0AUO zI%O{L>Po`f-R84{n!Gfp8JpG5A>D05sS`6~^!Rd!0K{466R82(11nz1eiEU{fb0`)B*NiS`41uvIKCWVVSXA3=yY-e`R*5zx(B+}nfi z?NimC#=tREaV2i<{xWmFrBI$)3t{uwYx8sO*Di!jf|r7hY#aDt?VEMkq41(~Tb~XY zSJq3^&`cN3Vr@&}=yPdr7r~;Kg1(S)C@+ah7kk{ONjzd1p5X3;ppK{)zecPM`KOv& zMlN7o_G2XdUcf)Ad$;E#raP25RiZ{^2V&t$ODPamHI40JjyPeRIh!Z9C`Z_#c7 z2(v%yO#~NAf&!7G#OJp7g(RL!?-=*iimAmrBW#yy@x`z?#@obk&$oc_`y2!X^g0Rg$$j z!Nc!aw#2}3;Em))VD9U*XwxInrobYRU)D!NXwDZ43xMFP^#N%m`wy%3i|a)i5q;&H zt=o%9O^v!2)NxbwC-`#wpl;wr{CrU>YbGJT8;unCa9Yu@!o^es-=a8uNbl;INFUw_ z#PAD=SqHj#HcyJ!RUyuxy2W1kL4MI|AKU(=_@5tSd%RrtJXzcekx#y!YNt7-xBw6{ zI2#^;{F|@HhMD8K>YGD+)Ddb@U~B`Zk07vTu&{RIVQ@;tEmc@p`lyD>r2j&~WrdQ2 zx-8F!f{^ZIQ9zwMa@@Ujn`MzT&?~w2kHT!*PD(voxjTsk_THYjY1|gZGka9eM2r0j zJwjIwh#Jh8-eD&r&eaVJ&+q*B5O-`Yl(Ni1lp^K9{mM@~x#9kJ=#TyTHmYDv(P?go zE%w0#!&<7%Ag%dUs}Eba$x1f4xBu04a(shb;!kg=NB)urCOQMKT!#|X7W zO&2`C+dmu-ERj0jW18)z&xi3%4OK8XRz>Z#G0w;K&?8RxOZnTgz(w`0&mgtGwWVNY zfae=j*NSP)&{+Ei;$A6~1I8xINI9M60@`6km*;D58MXrP>oLD)ER3 zcybEdDaRP5>buXfKc)WSV}7s#Mf^UlID6NRRZ#CTf9)l;&1`~$vUf*YPX9d8SkEV+ zZZ-^4H6!=Iio)9LTfb*Qf8@PW!XsqauhTf|Ds7_6TqEaZscm)=oJ4%z_3zgt zIf)ykox~d^&?8Y2{E*JPdF#HyEFXLsvovyLy=Tf~-;8guWR-Q(prQvm^40}qwuYJ1 zbKx)gLBZu`3@OY^iYr2%u|0eu=MJT^)<;U!ETS_fwDnBOjy2-S8sYFng{345f}|6A z;zIJ#5qS4OdO@fRbE}U-v~#fcWJ++v9sej}r#pRCF7*uZnD_LSJgq(JYsbEO_!s_P56WDF;>w~q~KT}s1XG5seu{b;C`$CLFZ zLs5t9iciD~*L}k_L&K{VjIH*ygbS%U4;W4ZQ~C+CZ)54CGWsU-ABUQ-LU3pBUn}qU zhN%p?n;BCn`H2kZZCSJ)ZUL$eEey)zi#N*b0vc=5>c`oiTf+;vA>dOp4GJZyto?ZH zjB93DdA&fJO2fLXP}WwR-Cf~tSZ}FFg^cYbrOSK`d|SKhLd>5@-$&NG+YPJOygX0g zoalP)8AMk$d@-)j6)WlvnS3QYN`0QL6IbbC-xKqcbqes*(TpTeu z(LDPu5`RxKl1T9PDGZ`&Yz_8XbBs_p7^X_P^1FwY^8SX~y&ks}I(0mcJN61&}dyU;Xy zft+Y6pP$=y;~fL0H%n8uFq1w;QkZw3Ja#(>w=dyTt&hQxoEPU&jbEmKr+ic-D6OR@$=qU8Yk3=TKz_7LWi1Og$I@xm}sP+$Q zsa>X5F(PVod)pyPQ8jPV-))vY5&|i@8^I_CS&Q1ER0Cnc8xG&uT$Iut%jhmsLotkrl^=rS^ zyq^XmR<=a@mkM*IsF%=*$+(aGY+gbYIU>e!mcOKKy|1p>v#qJ4tHvv=n99tR^nEt$ zDNUS58KNdZMUYRG(!5py`0umZ^llxrFVem=_YCnV?k9X%=k zG}wMSFI~f=0f`;F0SyJ)UCFRt1JTL&mwwr{0)6+6naPEp*qpp}=-U)Qh3znKjIG8q z*F!hT8fm$N35tHXTCKU-x@+K$2K-pU0CnR%;E|g976JF{GKqh56Q9;Lq6ZOV(QC~= z+03TSli@(Tzoyw|cS}q7whi5su`hZ1h?dKOF2RkC!eByIT`HOti}jbr$xRv@3Dfd^Lo>bgOO?_WO<_d4<_s4q0f0#Hib|} zvDS$9hC%suQ$&Al|N1d-N?R5nm6lUL=bW}W9GKp)^ z;li~a@8gac8*6{!XAsTr^=duJKh`;UI$It^AT#hZVN7c2h!#Zv(vyOT5e_R9Pc9-> z&xXMJ$OreCqlgE$ynipnpWtCGJWT##=ule?a(Gqlxt`V!MS9hKE2IKUq?StH11ql5 zPG#G$y~iK%i%mgk?u*QylB}!ahdwG98UC~~D<9}PlY02|vJ8~rJ7LfHA$^Q{kLqRr z%4meUog}2~)^b%QEe5^UwRo%CpPttdd{A2u#7K*Z7_>1>TBs1|6uVk zB^2EVB^{$F!cxh(=f+{%TO?{2<(WfSOf6Q*HH|-DhgKZ*j*;06k?#wAid$pK1Az zWM+RADNFzjb$&g9dUL1>@aH={ZLG0%rWh;fQ>8xOCA^8$TBKh4i~6IkBN=u!=w&EJ z@2~i_v|YPE7iLJ`hDr4-gF>5A)OH$9dW#=SQ$`J9f?i}L&KRW|J<$V$%++j%^yF*F zVZ4#$zc*yjU1)uqaSkV)v>*qc(+@-IOi+=R<2)%vIk7#ha3hY=*xU12X)PsS^LS8e z*W+nfkPmN)c)<%it*~%?zow~HaX<_3I6#h=Jfta$b;wGo?I;J9pP1LH94PhJbF3Vx zF}4o}p(>CCmG4AYYuZ{18X8!(b5px@g39}s@6OLU-jehKwShl!q?PBVWntwsyNPl| z8;$iUX>Fa}>1A^%I(4W^9F$f2_>MEnEa{6!VMIo`WB#Fub0D$<`ND7+_6U-ygn|0q z)42ARxRKI)d)Y1%#wiIEhdQ~CO0FDQ9O%v@YD1Y;aypEKaavZ?#jVPS9I9k5fApX_ z&{AXU)Q_L`Q$TMJJV#nNh^l&tVa(M}reW9x^R93}JXY?mU5(D%ZWU%m>thZT%ixvW z-jzTc*#mtcB`WVt5g`>7GIK_Ax(gOFk_*CAgSjp)Ez#E+a0__Bv>7K&H(vA523G~V zpnX@^dgDE!v`w0?1<)Ybjn^}*M?DZsHMxt4P3m~b=s8R>_$fOt8$F^#q2tx-*XWSY z>R*pO@it%I3Wi%Pg};(x4Jo;5#k*zHT^S=B*L5Lx9u&ePoQbh5>BYSM1C#!z{Sj~G zIhMVnNdFl4U2}$%3`}^wb_v)f%20Wt|D|dx*`Isqe5VCeBx!(!a3~fJ1}aP)i=Dq% z_n_SFH9xq0hm@bA?6SaZQGTjXn*C*tL<@J)#-=n(Zl^VD_`ScP++)A}mn%FUYN1L! zvrdAFn|1JnP&%c3^OQvIN#WapzV974B`L$kQTD&H9OvS$!h*`FsR# zZhwNQGiaYkWn9+hDVorKZ}$9gbYgPlX|w8biyfJy7w0*K$~_ZPYGs5u{|F6@Y*cxYh@x%6tJv`f;Rh%`_3Qrw~j7*DjB-N9rj7`mtl8vm7+RGK9vp?0m ziE0#~`Hu)}_ND~EBLT6dW|{&ClzRwVdm4iViF(NOu33h!R3H*4gzQ;HQ}J*DLQd#F z!L0H(gQ3@5X(zduXR_`q;x;bSBW7l~I$6&#;=XxLV*8uAh7T*#`0x_&?XBDds!vEh zGlOWrhC6PK71r7i@hN5ZT-!zjQ#Rs)ua`6Yz$V1t0jrH`D~t4u31C9>-Go^Cr?PaSq~{(0nzHS{xrtyw^Q3`0h(R z68;FI2sLPV3<-Akca0Qx0_W}!USCPw*{!`~`YaZs+3ss;P!np%Hsw#KUU@t3Oh3KN zZm&uDZ2a)GrEesyS>8eet)XfoMkVYMQ*4RAh{~j5IrXd}_k?dnNW~Jtn6Ts>V;Nq7 zXfmtGL*Vkqzwm*_j$vxYlQtjfZ<1y%UK2ybX!s^DWiXD06AlGsqZy*SsMMMLLR==B zGV>Pcjp-ZV(Y$LpPy;fQfb|lBj!1aO7A}5qo&w(yvZW}Lp2ZIuWQ6mpn=bYnC}KLKWnZmv z`a{WJ@a8sb)Mp2$T9doe@U469993}!yE$=~T{Wt$DfQ=$P;xXZBrL4bN_PMk#UHcv zr@G*n?G9FKv$8Jvx4y_5EJO;nI4r-(-J~Aa>AaVJe@6BEL*W}A0)5Nh8 z=#L1;J(Ii@Du`28I_X9S?ge4aO{K9C1Y$?j@jdv_a}!Z5Nz76 z?d`VwG`;A^hP^8$M#0Xr_suQ_o@7}VJsE~f81JUF#pi_=zX*MB8pxHTiL+2MutwOt zkXYt54j8%@s{f|EBL`U9Mh8t75sW( zu6usZ+~s=;d;k@kFr<=cykL;swij7#~xHk!x3{ZSnOya`ur1AUj^Gp|lS+Z}d zHm`Vn6*kDC$Y(91!qAF#xH;~qzb*K?XS8N<)8DekB`|i2btdP1IoIl$_{Y*qW6n6` z(kwUTm|>RVdYENz!K^ne*IcKPRM%_&WmB9i)QSY;Q;pl@^8c&{)v`px|(@aQqcY!(1=3_52>D_KdxVc-4%RjPu9FA+Q_cjhw zO^3ErzjctE|C4)p=30%FD}jIPF8li_h2D_S1CO^fI&V`;%97es)c)ciZlC@j?J(@y zY=PJI`Zt3N??uM}?rlO^!F&`7--v4L@VW^X=b~2=SN*Hg%c$<+b!?lz<8gNqjU+3u zla3(szu9OP5ttRi9_I1EDJ2Y6>`Wij$F{pKYGzD|Mt zVSMxJY18^ZK{@?x;nHuacjpP>RUh}dTDAF~c82CTBj+Pu>{*^}I>UUS^(29Xkk=Il zAF^yT{d)ae*uMYda0#$DDbKo89pyTq5YlLc+j|*&Qgb)L@OdahtxJ@bQE&2H1?F8U zdiKzyI#xqWhM0r2OHB3zD0nqyE%aF`b>lKhiHe}fP#qVZ0owmHw)~biS_RDi$&|_a zoK`577n3BP4jj~2rrP&&to^F5_#$q;Q`wPTD&|G~`?1iN(M@phy|OoxXe`EP`aJQuKCpk-mc*zue01*zi&binke&=(19@8w_L4n@jrxJQ zfppNI<)Z9!F_(=6a|U5A9+1XjIV#eOf~;_A4zAcQkX(F=w?gM2t;PzTkY<>s6vc=_ zt$7k{q3~qDJ@C~Bd*wkRxw#L4wOj<483H1!FN;OmuKn#bH$;or;lK;lon5h=)R~FK z&lew@^BhG&mp!iUde5GIYcHtK&b6gaq>;_ghIRQ`l>=oR_?e8Jv}P#S!fl$)U&Mp_cs1!2MOA~_Cypob7U92tyW|>fufeb3VB~17ju1Xma#I!g$KPBou$;3Z;Oc+< zH|CgU-`A1wRr3HBOb6f_1CTeqEgUL$4))qCIY)(R0aO4(!_9WKmy*P(54tK`|8+1~ zt3zh{h+YR*tk&PcNhDbNpUAuLE^aE->%&ez75;{>T-bq}+1lls{$42sii*YnkDnmbmNKZ9EKci%E9$-JHPeHd4rxFBe-{o5hfP29gCOSr5;4?s+ zLoA0#$VcpKF}q4Tuwijq?;1qzehQs_=#40?G=xYuj5KZEg*anIy+D zOj}nNv-F*|fh-BW{vZ#R3PADkwEF=#3;hs93F0s`Je`Z4Yh1j8mU0r%Yd!jC_Z~_Z zbkA_?N%@Or24Yy_0*9xwLC-^&QspmN%(BdL+NFXdBN^B4!!qb`^YU2)EL4dVBz%CG ei{}%&W%W;!1i$SR7sYg-Vm;Q-SFcqA#r_X{o~YRX literal 0 HcmV?d00001 From 8b754bdc35f004398d7a5789934c2b55677c4957 Mon Sep 17 00:00:00 2001 From: Eduard Kerkhoven Date: Sun, 1 Nov 2026 21:26:48 +0100 Subject: [PATCH 46/48] Add GNU GPL v3 license file Added the GNU General Public License version 3 to the project. --- LICENSE | 674 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 674 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..f288702d --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. From 84da78fdad30d6cad01dab93d344154973973818 Mon Sep 17 00:00:00 2001 From: Eduard Kerkhoven Date: Sun, 1 Nov 2026 21:27:48 +0100 Subject: [PATCH 47/48] Delete LICENSE.md --- LICENSE.md | 541 ----------------------------------------------------- 1 file changed, 541 deletions(-) delete mode 100644 LICENSE.md diff --git a/LICENSE.md b/LICENSE.md deleted file mode 100644 index 4ffedce5..00000000 --- a/LICENSE.md +++ /dev/null @@ -1,541 +0,0 @@ -GNU GENERAL PUBLIC LICENSE -========================== - -Version 3, 29 June 2007 - -Copyright © 2007 Free Software Foundation, Inc. <> - -Everyone is permitted to copy and distribute verbatim copies of this license -document, but changing it is not allowed. - -## Preamble - -The GNU General Public License is a free, copyleft license for software and other -kinds of works. - -The licenses for most software and other practical works are designed to take away -your freedom to share and change the works. By contrast, the GNU General Public -License is intended to guarantee your freedom to share and change all versions of a -program--to make sure it remains free software for all its users. We, the Free -Software Foundation, use the GNU General Public License for most of our software; it -applies also to any other work released this way by its authors. You can apply it to -your programs, too. - -When we speak of free software, we are referring to freedom, not price. Our General -Public Licenses are designed to make sure that you have the freedom to distribute -copies of free software (and charge for them if you wish), that you receive source -code or can get it if you want it, that you can change the software or use pieces of -it in new free programs, and that you know you can do these things. - -To protect your rights, we need to prevent others from denying you these rights or -asking you to surrender the rights. Therefore, you have certain responsibilities if -you distribute copies of the software, or if you modify it: responsibilities to -respect the freedom of others. - -For example, if you distribute copies of such a program, whether gratis or for a fee, -you must pass on to the recipients the same freedoms that you received. You must make -sure that they, too, receive or can get the source code. And you must show them these -terms so they know their rights. - -Developers that use the GNU GPL protect your rights with two steps: (1) assert -copyright on the software, and (2) offer you this License giving you legal permission -to copy, distribute and/or modify it. - -For the developers' and authors' protection, the GPL clearly explains that there is -no warranty for this free software. For both users' and authors' sake, the GPL -requires that modified versions be marked as changed, so that their problems will not -be attributed erroneously to authors of previous versions. - -Some devices are designed to deny users access to install or run modified versions of -the software inside them, although the manufacturer can do so. This is fundamentally -incompatible with the aim of protecting users' freedom to change the software. The -systematic pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we have designed -this version of the GPL to prohibit the practice for those products. If such problems -arise substantially in other domains, we stand ready to extend this provision to -those domains in future versions of the GPL, as needed to protect the freedom of -users. - -Finally, every program is threatened constantly by software patents. States should -not allow patents to restrict development and use of software on general-purpose -computers, but in those that do, we wish to avoid the special danger that patents -applied to a free program could make it effectively proprietary. To prevent this, the -GPL assures that patents cannot be used to render the program non-free. - -The precise terms and conditions for copying, distribution and modification follow. - -## TERMS AND CONDITIONS - -### 0. Definitions. - -“This License” refers to version 3 of the GNU General Public License. - -“Copyright” also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - -“The Program” refers to any copyrightable work licensed under this -License. Each licensee is addressed as “you”. “Licensees” and -“recipients” may be individuals or organizations. - -To “modify” a work means to copy from or adapt all or part of the work in -a fashion requiring copyright permission, other than the making of an exact copy. The -resulting work is called a “modified version” of the earlier work or a -work “based on” the earlier work. - -A “covered work” means either the unmodified Program or a work based on -the Program. - -To “propagate” a work means to do anything with it that, without -permission, would make you directly or secondarily liable for infringement under -applicable copyright law, except executing it on a computer or modifying a private -copy. Propagation includes copying, distribution (with or without modification), -making available to the public, and in some countries other activities as well. - -To “convey” a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through a computer -network, with no transfer of a copy, is not conveying. - -An interactive user interface displays “Appropriate Legal Notices” to the -extent that it includes a convenient and prominently visible feature that (1) -displays an appropriate copyright notice, and (2) tells the user that there is no -warranty for the work (except to the extent that warranties are provided), that -licensees may convey the work under this License, and how to view a copy of this -License. If the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - -### 1. Source Code. - -The “source code” for a work means the preferred form of the work for -making modifications to it. “Object code” means any non-source form of a -work. - -A “Standard Interface” means an interface that either is an official -standard defined by a recognized standards body, or, in the case of interfaces -specified for a particular programming language, one that is widely used among -developers working in that language. - -The “System Libraries” of an executable work include anything, other than -the work as a whole, that (a) is included in the normal form of packaging a Major -Component, but which is not part of that Major Component, and (b) serves only to -enable use of the work with that Major Component, or to implement a Standard -Interface for which an implementation is available to the public in source code form. -A “Major Component”, in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system (if any) on which -the executable work runs, or a compiler used to produce the work, or an object code -interpreter used to run it. - -The “Corresponding Source” for a work in object code form means all the -source code needed to generate, install, and (for an executable work) run the object -code and to modify the work, including scripts to control those activities. However, -it does not include the work's System Libraries, or general-purpose tools or -generally available free programs which are used unmodified in performing those -activities but which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for the work, and -the source code for shared libraries and dynamically linked subprograms that the work -is specifically designed to require, such as by intimate data communication or -control flow between those subprograms and other parts of the work. - -The Corresponding Source need not include anything that users can regenerate -automatically from other parts of the Corresponding Source. - -The Corresponding Source for a work in source code form is that same work. - -### 2. Basic Permissions. - -All rights granted under this License are granted for the term of copyright on the -Program, and are irrevocable provided the stated conditions are met. This License -explicitly affirms your unlimited permission to run the unmodified Program. The -output from running a covered work is covered by this License only if the output, -given its content, constitutes a covered work. This License acknowledges your rights -of fair use or other equivalent, as provided by copyright law. - -You may make, run and propagate covered works that you do not convey, without -conditions so long as your license otherwise remains in force. You may convey covered -works to others for the sole purpose of having them make modifications exclusively -for you, or provide you with facilities for running those works, provided that you -comply with the terms of this License in conveying all material for which you do not -control copyright. Those thus making or running the covered works for you must do so -exclusively on your behalf, under your direction and control, on terms that prohibit -them from making any copies of your copyrighted material outside their relationship -with you. - -Conveying under any other circumstances is permitted solely under the conditions -stated below. Sublicensing is not allowed; section 10 makes it unnecessary. - -### 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - -No covered work shall be deemed part of an effective technological measure under any -applicable law fulfilling obligations under article 11 of the WIPO copyright treaty -adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention -of such measures. - -When you convey a covered work, you waive any legal power to forbid circumvention of -technological measures to the extent such circumvention is effected by exercising -rights under this License with respect to the covered work, and you disclaim any -intention to limit operation or modification of the work as a means of enforcing, -against the work's users, your or third parties' legal rights to forbid circumvention -of technological measures. - -### 4. Conveying Verbatim Copies. - -You may convey verbatim copies of the Program's source code as you receive it, in any -medium, provided that you conspicuously and appropriately publish on each copy an -appropriate copyright notice; keep intact all notices stating that this License and -any non-permissive terms added in accord with section 7 apply to the code; keep -intact all notices of the absence of any warranty; and give all recipients a copy of -this License along with the Program. - -You may charge any price or no price for each copy that you convey, and you may offer -support or warranty protection for a fee. - -### 5. Conveying Modified Source Versions. - -You may convey a work based on the Program, or the modifications to produce it from -the Program, in the form of source code under the terms of section 4, provided that -you also meet all of these conditions: - -* **a)** The work must carry prominent notices stating that you modified it, and giving a -relevant date. -* **b)** The work must carry prominent notices stating that it is released under this -License and any conditions added under section 7. This requirement modifies the -requirement in section 4 to “keep intact all notices”. -* **c)** You must license the entire work, as a whole, under this License to anyone who -comes into possession of a copy. This License will therefore apply, along with any -applicable section 7 additional terms, to the whole of the work, and all its parts, -regardless of how they are packaged. This License gives no permission to license the -work in any other way, but it does not invalidate such permission if you have -separately received it. -* **d)** If the work has interactive user interfaces, each must display Appropriate Legal -Notices; however, if the Program has interactive interfaces that do not display -Appropriate Legal Notices, your work need not make them do so. - -A compilation of a covered work with other separate and independent works, which are -not by their nature extensions of the covered work, and which are not combined with -it such as to form a larger program, in or on a volume of a storage or distribution -medium, is called an “aggregate” if the compilation and its resulting -copyright are not used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work in an aggregate -does not cause this License to apply to the other parts of the aggregate. - -### 6. Conveying Non-Source Forms. - -You may convey a covered work in object code form under the terms of sections 4 and -5, provided that you also convey the machine-readable Corresponding Source under the -terms of this License, in one of these ways: - -* **a)** Convey the object code in, or embodied in, a physical product (including a -physical distribution medium), accompanied by the Corresponding Source fixed on a -durable physical medium customarily used for software interchange. -* **b)** Convey the object code in, or embodied in, a physical product (including a -physical distribution medium), accompanied by a written offer, valid for at least -three years and valid for as long as you offer spare parts or customer support for -that product model, to give anyone who possesses the object code either (1) a copy of -the Corresponding Source for all the software in the product that is covered by this -License, on a durable physical medium customarily used for software interchange, for -a price no more than your reasonable cost of physically performing this conveying of -source, or (2) access to copy the Corresponding Source from a network server at no -charge. -* **c)** Convey individual copies of the object code with a copy of the written offer to -provide the Corresponding Source. This alternative is allowed only occasionally and -noncommercially, and only if you received the object code with such an offer, in -accord with subsection 6b. -* **d)** Convey the object code by offering access from a designated place (gratis or for -a charge), and offer equivalent access to the Corresponding Source in the same way -through the same place at no further charge. You need not require recipients to copy -the Corresponding Source along with the object code. If the place to copy the object -code is a network server, the Corresponding Source may be on a different server -(operated by you or a third party) that supports equivalent copying facilities, -provided you maintain clear directions next to the object code saying where to find -the Corresponding Source. Regardless of what server hosts the Corresponding Source, -you remain obligated to ensure that it is available for as long as needed to satisfy -these requirements. -* **e)** Convey the object code using peer-to-peer transmission, provided you inform -other peers where the object code and Corresponding Source of the work are being -offered to the general public at no charge under subsection 6d. - -A separable portion of the object code, whose source code is excluded from the -Corresponding Source as a System Library, need not be included in conveying the -object code work. - -A “User Product” is either (1) a “consumer product”, which -means any tangible personal property which is normally used for personal, family, or -household purposes, or (2) anything designed or sold for incorporation into a -dwelling. In determining whether a product is a consumer product, doubtful cases -shall be resolved in favor of coverage. For a particular product received by a -particular user, “normally used” refers to a typical or common use of -that class of product, regardless of the status of the particular user or of the way -in which the particular user actually uses, or expects or is expected to use, the -product. A product is a consumer product regardless of whether the product has -substantial commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - -“Installation Information” for a User Product means any methods, -procedures, authorization keys, or other information required to install and execute -modified versions of a covered work in that User Product from a modified version of -its Corresponding Source. The information must suffice to ensure that the continued -functioning of the modified object code is in no case prevented or interfered with -solely because modification has been made. - -If you convey an object code work under this section in, or with, or specifically for -use in, a User Product, and the conveying occurs as part of a transaction in which -the right of possession and use of the User Product is transferred to the recipient -in perpetuity or for a fixed term (regardless of how the transaction is -characterized), the Corresponding Source conveyed under this section must be -accompanied by the Installation Information. But this requirement does not apply if -neither you nor any third party retains the ability to install modified object code -on the User Product (for example, the work has been installed in ROM). - -The requirement to provide Installation Information does not include a requirement to -continue to provide support service, warranty, or updates for a work that has been -modified or installed by the recipient, or for the User Product in which it has been -modified or installed. Access to a network may be denied when the modification itself -materially and adversely affects the operation of the network or violates the rules -and protocols for communication across the network. - -Corresponding Source conveyed, and Installation Information provided, in accord with -this section must be in a format that is publicly documented (and with an -implementation available to the public in source code form), and must require no -special password or key for unpacking, reading or copying. - -### 7. Additional Terms. - -“Additional permissions” are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. Additional -permissions that are applicable to the entire Program shall be treated as though they -were included in this License, to the extent that they are valid under applicable -law. If additional permissions apply only to part of the Program, that part may be -used separately under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - -When you convey a copy of a covered work, you may at your option remove any -additional permissions from that copy, or from any part of it. (Additional -permissions may be written to require their own removal in certain cases when you -modify the work.) You may place additional permissions on material, added by you to a -covered work, for which you have or can give appropriate copyright permission. - -Notwithstanding any other provision of this License, for material you add to a -covered work, you may (if authorized by the copyright holders of that material) -supplement the terms of this License with terms: - -* **a)** Disclaiming warranty or limiting liability differently from the terms of -sections 15 and 16 of this License; or -* **b)** Requiring preservation of specified reasonable legal notices or author -attributions in that material or in the Appropriate Legal Notices displayed by works -containing it; or -* **c)** Prohibiting misrepresentation of the origin of that material, or requiring that -modified versions of such material be marked in reasonable ways as different from the -original version; or -* **d)** Limiting the use for publicity purposes of names of licensors or authors of the -material; or -* **e)** Declining to grant rights under trademark law for use of some trade names, -trademarks, or service marks; or -* **f)** Requiring indemnification of licensors and authors of that material by anyone -who conveys the material (or modified versions of it) with contractual assumptions of -liability to the recipient, for any liability that these contractual assumptions -directly impose on those licensors and authors. - -All other non-permissive additional terms are considered “further -restrictions” within the meaning of section 10. If the Program as you received -it, or any part of it, contains a notice stating that it is governed by this License -along with a term that is a further restriction, you may remove that term. If a -license document contains a further restriction but permits relicensing or conveying -under this License, you may add to a covered work material governed by the terms of -that license document, provided that the further restriction does not survive such -relicensing or conveying. - -If you add terms to a covered work in accord with this section, you must place, in -the relevant source files, a statement of the additional terms that apply to those -files, or a notice indicating where to find the applicable terms. - -Additional terms, permissive or non-permissive, may be stated in the form of a -separately written license, or stated as exceptions; the above requirements apply -either way. - -### 8. Termination. - -You may not propagate or modify a covered work except as expressly provided under -this License. Any attempt otherwise to propagate or modify it is void, and will -automatically terminate your rights under this License (including any patent licenses -granted under the third paragraph of section 11). - -However, if you cease all violation of this License, then your license from a -particular copyright holder is reinstated (a) provisionally, unless and until the -copyright holder explicitly and finally terminates your license, and (b) permanently, -if the copyright holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - -Moreover, your license from a particular copyright holder is reinstated permanently -if the copyright holder notifies you of the violation by some reasonable means, this -is the first time you have received notice of violation of this License (for any -work) from that copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - -Termination of your rights under this section does not terminate the licenses of -parties who have received copies or rights from you under this License. If your -rights have been terminated and not permanently reinstated, you do not qualify to -receive new licenses for the same material under section 10. - -### 9. Acceptance Not Required for Having Copies. - -You are not required to accept this License in order to receive or run a copy of the -Program. Ancillary propagation of a covered work occurring solely as a consequence of -using peer-to-peer transmission to receive a copy likewise does not require -acceptance. However, nothing other than this License grants you permission to -propagate or modify any covered work. These actions infringe copyright if you do not -accept this License. Therefore, by modifying or propagating a covered work, you -indicate your acceptance of this License to do so. - -### 10. Automatic Licensing of Downstream Recipients. - -Each time you convey a covered work, the recipient automatically receives a license -from the original licensors, to run, modify and propagate that work, subject to this -License. You are not responsible for enforcing compliance by third parties with this -License. - -An “entity transaction” is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an organization, or -merging organizations. If propagation of a covered work results from an entity -transaction, each party to that transaction who receives a copy of the work also -receives whatever licenses to the work the party's predecessor in interest had or -could give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if the predecessor -has it or can get it with reasonable efforts. - -You may not impose any further restrictions on the exercise of the rights granted or -affirmed under this License. For example, you may not impose a license fee, royalty, -or other charge for exercise of rights granted under this License, and you may not -initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging -that any patent claim is infringed by making, using, selling, offering for sale, or -importing the Program or any portion of it. - -### 11. Patents. - -A “contributor” is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The work thus -licensed is called the contributor's “contributor version”. - -A contributor's “essential patent claims” are all patent claims owned or -controlled by the contributor, whether already acquired or hereafter acquired, that -would be infringed by some manner, permitted by this License, of making, using, or -selling its contributor version, but do not include claims that would be infringed -only as a consequence of further modification of the contributor version. For -purposes of this definition, “control” includes the right to grant patent -sublicenses in a manner consistent with the requirements of this License. - -Each contributor grants you a non-exclusive, worldwide, royalty-free patent license -under the contributor's essential patent claims, to make, use, sell, offer for sale, -import and otherwise run, modify and propagate the contents of its contributor -version. - -In the following three paragraphs, a “patent license” is any express -agreement or commitment, however denominated, not to enforce a patent (such as an -express permission to practice a patent or covenant not to sue for patent -infringement). To “grant” such a patent license to a party means to make -such an agreement or commitment not to enforce a patent against the party. - -If you convey a covered work, knowingly relying on a patent license, and the -Corresponding Source of the work is not available for anyone to copy, free of charge -and under the terms of this License, through a publicly available network server or -other readily accessible means, then you must either (1) cause the Corresponding -Source to be so available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner consistent with -the requirements of this License, to extend the patent license to downstream -recipients. “Knowingly relying” means you have actual knowledge that, but -for the patent license, your conveying the covered work in a country, or your -recipient's use of the covered work in a country, would infringe one or more -identifiable patents in that country that you have reason to believe are valid. - -If, pursuant to or in connection with a single transaction or arrangement, you -convey, or propagate by procuring conveyance of, a covered work, and grant a patent -license to some of the parties receiving the covered work authorizing them to use, -propagate, modify or convey a specific copy of the covered work, then the patent -license you grant is automatically extended to all recipients of the covered work and -works based on it. - -A patent license is “discriminatory” if it does not include within the -scope of its coverage, prohibits the exercise of, or is conditioned on the -non-exercise of one or more of the rights that are specifically granted under this -License. You may not convey a covered work if you are a party to an arrangement with -a third party that is in the business of distributing software, under which you make -payment to the third party based on the extent of your activity of conveying the -work, and under which the third party grants, to any of the parties who would receive -the covered work from you, a discriminatory patent license (a) in connection with -copies of the covered work conveyed by you (or copies made from those copies), or (b) -primarily for and in connection with specific products or compilations that contain -the covered work, unless you entered into that arrangement, or that patent license -was granted, prior to 28 March 2007. - -Nothing in this License shall be construed as excluding or limiting any implied -license or other defenses to infringement that may otherwise be available to you -under applicable patent law. - -### 12. No Surrender of Others' Freedom. - -If conditions are imposed on you (whether by court order, agreement or otherwise) -that contradict the conditions of this License, they do not excuse you from the -conditions of this License. If you cannot convey a covered work so as to satisfy -simultaneously your obligations under this License and any other pertinent -obligations, then as a consequence you may not convey it at all. For example, if you -agree to terms that obligate you to collect a royalty for further conveying from -those to whom you convey the Program, the only way you could satisfy both those terms -and this License would be to refrain entirely from conveying the Program. - -### 13. Use with the GNU Affero General Public License. - -Notwithstanding any other provision of this License, you have permission to link or -combine any covered work with a work licensed under version 3 of the GNU Affero -General Public License into a single combined work, and to convey the resulting work. -The terms of this License will continue to apply to the part which is the covered -work, but the special requirements of the GNU Affero General Public License, section -13, concerning interaction through a network will apply to the combination as such. - -### 14. Revised Versions of this License. - -The Free Software Foundation may publish revised and/or new versions of the GNU -General Public License from time to time. Such new versions will be similar in spirit -to the present version, but may differ in detail to address new problems or concerns. - -Each version is given a distinguishing version number. If the Program specifies that -a certain numbered version of the GNU General Public License “or any later -version” applies to it, you have the option of following the terms and -conditions either of that numbered version or of any later version published by the -Free Software Foundation. If the Program does not specify a version number of the GNU -General Public License, you may choose any version ever published by the Free -Software Foundation. - -If the Program specifies that a proxy can decide which future versions of the GNU -General Public License can be used, that proxy's public statement of acceptance of a -version permanently authorizes you to choose that version for the Program. - -Later license versions may give you additional or different permissions. However, no -additional obligations are imposed on any author or copyright holder as a result of -your choosing to follow a later version. - -### 15. Disclaimer of Warranty. - -THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. -EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES -PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER -EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE -QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE -DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - -### 16. Limitation of Liability. - -IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY -COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS -PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, -INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE -PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE -OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE -WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. - -### 17. Interpretation of Sections 15 and 16. - -If the disclaimer of warranty and limitation of liability provided above cannot be -given local legal effect according to their terms, reviewing courts shall apply local -law that most closely approximates an absolute waiver of all civil liability in -connection with the Program, unless a warranty or assumption of liability accompanies -a copy of the Program in return for a fee. \ No newline at end of file From 76339c1b601fbb938d3e142b5fb5e421b8237a05 Mon Sep 17 00:00:00 2001 From: Eduard Kerkhoven Date: Sun, 1 Nov 2026 21:35:57 +0100 Subject: [PATCH 48/48] Prepare 3.0.0b2 release (#20) Version 3.0.0b2 and the changelog of the changes since 3.0.0b1. --- CHANGELOG.md | 71 ++++++++++++++++++++++++++++++++++++++++++++++------ version.txt | 2 +- 2 files changed, 64 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f980b75f..423aec56 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,33 @@ # Changelog -## Unreleased +## 3.0.0b2 — 2026-11-01 + +Second beta toward 3.0. Changes below are relative to 3.0.0b1. + +### Breaking changes + +* The folder layout changed (see Repository layout and downloads); code that adds RAVEN folders to the path or addresses binaries by path needs the new paths. +* `fetchRavenDataAsset` is replaced by `fetchToolboxAsset`. +* `gapFillMILP` returns `[addedRxns, newModel, exitFlag]` and has no `weights` option. +* `sortModel` keeps the directions of the reactions unless `sortReversible` is requested (was: it sorted them by default). +* `parseTaskList` refuses a binary `.xls` file. +* `gapReport`, `scaleBiomassFraction`, `getModelFromHomology` and `getIndexes` raise errors in cases they accepted before (see below). + +### Repository layout and downloads + +* The MATLAB code is in `src/` (was: folders in the repository root), the tests in `tests/` (was: `testing/`), the generated help pages in `docs/html/` (was: `doc/`), the readme files in `docs/readme/`, the logo in `img/` (was: `RAVEN.png`), and the tutorials in `tutorials/` with data under `tutorials/data` (was: `tutorial/`). The external software is in `src/external` (was: `software/`). `checkRaven`, `downloadRavenBinaries`, `getBlast`, `getDiamond`, `getKEGGModelForOrganism`, `optimizeProb`, `setRavenSolver` and `updateDocumentation` use the new paths. +* `parseHPA`, `parseHPArna` and `scoreModel` are in `INIT/` (was: `omics/`, which is removed). `tOmics.m` is merged into `tINIT.m`. +* `fetchToolboxAsset` replaces `fetchRavenDataAsset`, and assets are downloaded from `CompMetEng/toolbox-assets` (was: raven-data). `downloadRavenBinaries`, `buildGlobalKEGGModel`, `getKEGGModelForOrganism`, `getModelFromKEGG`, `getPhylDist`, `keggDataVersion` and `checkRaven` use it. +* `fetchToolboxAsset` downloads to a `.part` file and checks the SHA-256 sum listed in the `manifest-v3` of toolbox-assets. A mismatch deletes the file and raises an error; an asset that the manifest does not list, or a manifest that cannot be read, gives a warning. +* The repository, documentation and version-check links point to `CompMetEng`. ### I/O -* `writeYAMLmodel` writes an ec reaction without enzymes (a row of zeros in `model.ec.rxnEnzMat`) with no `enzymes` entry (was: `- enzymes: !!omap` with nothing under it, which is not valid YAML). `readYAMLmodel` reads a missing `enzymes` entry as a reaction without enzymes, and still reads `- enzymes: !!omap` with no entries and `- enzymes: !!omap []` from files written earlier. +* `writeYAMLmodel` writes an ec reaction without enzymes (a row of zeros in `model.ec.rxnEnzMat`) with no `enzymes` entry (was: `- enzymes: !!omap` with nothing under it, which is not valid YAML). `readYAMLmodel` reads a missing `enzymes` entry as a reaction without enzymes, and reads `- enzymes: !!omap` with no entries and `- enzymes: !!omap []` from files written by earlier versions. +* `readYAMLmodel` reads notes maps (as written by cobrapy and raven-toolbox) into `metNotes` and `rxnNotes` (was: the error "Unknown entry"), reads folded scalars whose continuation starts with `-`, and keeps one `model.subSystems` entry per reaction (was: empty entries were dropped, so later subsystems sat on the wrong reaction). +* `exportForGit` raises `RAVEN:notMainBranch` when a RAVEN checkout is on a branch other than `main` or `master`. +* `writeYAMLmodel` writes the enzymes of each `ec-rxns` entry indented under `- enzymes` (was: at the indentation of the key, which ruamel.yaml could not read), quotes the YAML 1.1 booleans (`y`, `n`, `yes`, `no`, `on`, `off` in any case), and writes non-integers with the shortest digits that read back exactly. `printOrange` prints `\n` as a line break. +* `exportForGit`: the txt table is headed `Rxn id`, writes bounds and objective with the digits needed to read them back and pads no field. The output folder is created with its parents also when `subDirs` is false, and the logical flags are validated. ### Manipulation @@ -51,13 +74,13 @@ * `ftINIT` with `resolveTies`: each main-extraction step takes its fluxes from the least-total-flux solution for the tie-broken on/off state, so the direction and magnitude - at which the next step forces a reaction no longer depend on the solver's vertex. + at which the next step forces a reaction do not depend on the solver's vertex. That phase stops at a relative or absolute gap of 1e-6. * `ftINIT` with `resolveTies`: among the least-total-flux solutions, each main-extraction step takes its fluxes from the one with the least id-rank-weighted backward flux, so the direction of a flux that carries the same total either way no longer depends on the solver. The id-rank phases rank a merged reaction by the smallest original reaction id in - its group, so they no longer depend on which member names it. + its group, so they do not depend on which member names it. * `ftINITInternalAlg` with `resolveTies`: when the primary solve stops at the time limit, the tie-break phases are skipped and the primary solution is kept, as in raven-toolbox (was: the phases ran on the unproven incumbent). The count and id-rank phases get half of @@ -81,10 +104,42 @@ * `grRuleToDNF`: help states the grammar it reads, that of `parseGrRule` (`and`/`or` in any case, `&`, `&&`, `|`, `||`, operators as whole words, so `G1&G2` is one gene id), and that a malformed rule raises `RAVEN:badGrRule`. - -* `checkRaven`: when the binaries cannot be downloaded, the message names the - `RAVEN--full.zip` bundle attached to each release for an offline install (was: - "the `*-binaries` RAVEN release"). +* `checkRaven`: when the binaries cannot be downloaded, the message names the `RAVEN--full.zip` bundle attached to each release for an offline install (was: "the `*-binaries` RAVEN release"). + +### Homology and curation + +* `getModelFromHomology` has the options `complexPolicy` (`flag`, `keep` or `drop`: how a reaction is treated when an `and`-linked subunit has no ortholog), `keepGeneFree` (reactions of the template without a grRule are transferred) and `preserveNotes` (`rxnNotes` and `rxnConfidenceScores` of the template are kept; `rxnReferences` are always kept). A gene that several template genes reach appears once per grRule. The draft has no version, date or description, and the descriptive annotation of the template is not copied (was: those of the templates). A preferred order that removes no gene is accepted (was: an error). +* `getModelFromHomology` raises an error that names the gene ids of the new organism that split in a grRule (whitespace, parentheses, `|` or `&`, for example the UniProt id `sp|P33284|HXK_KLULA`) (was: the ids were accepted). +* New: `hitsFromSuppOrth` reads a `suppOrth.tsv` into a blast structure for `getModelFromHomology`, in both directions, with filters on support, predictors, identity and E-value and the option `proteinToGene` for a protein to gene mapping. `runSuppOrth` runs the `suppOrth` command and reads its output. +* `curateModelFromTables` reads the tables by header name in any order, with optional columns, and writes every supplied field to existing entities (was: `metNotes` and `rxnReferences` were not written). The MIRIAM columns follow the core columns, and grRules go to the right reaction when existing and new reactions are mixed. An empty grRules, lb, ub or rev cell leaves the existing value; the new option `overwriteWithEmpty` (default `true`) decides whether other empty cells clear a field. Cells are read without evaluating text: numeric cells accept plain decimals, `Inf` and `NaN`, and lb, ub and rev accept `true` and `false`; commas, hexadecimal numbers and expressions raise `RAVEN:badInput`. A file, column, gene-rule or repeated-id error raises `RAVEN:badInput`. +* `scaleBiomassFraction` raises an error when the balancing component would get a fraction of 0 or less (was: the balancing pseudoreaction was inverted), when it has no current fraction (was: balancing was skipped) or when it is not a component of the biomass. The new argument `targetTotal` sets the balanced total of the biomass. +* `setGAM` and `scaleBiomassPseudoreaction` raise an error before the model changes: `setGAM` when the NGAM reaction is missing and `scaleBiomassPseudoreaction` when the proton metabolite is missing. `setGAM` refuses `ngamValue` without `ngamRxn`, and an empty `ngamRxn` counts as not given. +* `sortModel` keeps the directions of the reactions unless `sortReversible` is requested (was: the default sorted them). `generateNewIds` takes the numerically largest id and counts only finite integer suffixes. +* `replaceMets`: the final `contractModel` call is restricted to the surviving metabolite (was: unrelated duplicate reactions were merged as well). +* `addIdentifierPrefix` prefixes whole gene ids in grRules (was: an id with a dash, dot or colon, for example `5-HT1A`, got a prefix at every word start, so `exportModel` failed). +* `getIndexes` with an ambiguous `name[comp]` raises an error that names the matching metabolites. +* `gapFillMILP` adds database reactions only and does not reverse draft reactions, which keep their bounds. The second output `reversedRxns` and the option `weights` are removed. `bigM` replaces infinite bounds of universal reactions; a finite bound is used as it is (was: bounds capped at `bigM`). +* `prepINITModel` raises `RAVEN:badInput` when the task structure has no `id` field or a non-text id. +* `getKEGGModelForOrganism` works with the kegg118 reference model, which has no `rxnNotes` (was: an error when adding the provenance note). +* `gapReport` reports the metabolites that are produced or consumed with the boundary closed for every model (was: only for models with an `unconstrained` field). It closes the reactions of `getExchangeRxns` when the model has no `unconstrained` field, and stops with `RAVEN:infeasibleModel` when the model is infeasible under its own bounds or with its boundary closed, for example with a forced ATP maintenance flux. + +### Solvers + +* `optimizeProb` returns the logical `res.hitTimeLimit` for every solver. `runINIT` and `ftINITInternalAlg` raise "Time limit reached without finding a solution" (was: "The problem is infeasible" with every solver except Gurobi). `getMinNrFluxes` returns exit code -2 for a solution that is not proven optimal at the time limit (was: 1 with Gurobi), and -1 with a warning when no solution came back. +* `optimizeProb` solves MILPs, including those of `gapFillMILP`, `fillGaps`, `getMinimalMedium`, `getMinNrFluxes`, `ftINIT` and `runINIT`, with the bundled GLPK and on the GLPK of the COBRA Toolbox, with a `RAVEN:glpkMILP` warning (was: refused). + +### Sampling and reporting + +* `reporterMetabolites` uses the population standard deviation for the background, which gives the scores of raven-toolbox (was: the sample standard deviation). The score is 0 when the spread of the background is 0 or at rounding level, P-values outside [0, 1] are refused, and metabolites without a score are dropped from all result fields. +* `sampleChebyshevCenter` passes a sparse constraint matrix, so `randomSampling` with the method `'chrr'` works with Gurobi (was: Gurobi rejected the dense matrix). +* `sampleACHR` corrects the flux vectors towards `S*v = 0` every few steps and before each sample, and keeps them within the bounds. It raises the warning `sampleACHR:steadyStateNotRestored` when a sample keeps a residual above 1e-9 relative (was: the residual of `S*v` grew exponentially, up to 9 after 100 steps). +* `haveFlux` returns all reactions as blocked when the simplification removes every reaction (was: a GLPK error on an empty matrix). + +### Tasks + +* `parseTaskList` reads `false` and `0` as false, trims `;` lists, ignores the case of headers, keeps empty CHANGED bounds as `NaN`, and reads `no` as false. `fitTasks` leaves a bound unchanged when the CHANGED LB or UB is `NaN`. An absent IN LB, IN UB, OUT LB or OUT UB column gives the default bounds 0 and 1000. Comment rows and orphan rows are skipped with a warning, a non-numeric bound raises `RAVEN:badInput`, and a binary `.xls` file is refused with `RAVEN:badInput` (save it as `.xlsx` or `.txt`). +* `checkTasks` warns when a task metabolite matches several model metabolites. `removeLowScoreGenes` keeps the first gene of the rule on a tie. +* `ftINIT` removes the genes of removed reactions from the output. ### Documentation diff --git a/version.txt b/version.txt index a1513ac3..274c38c1 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -3.0.0b1 \ No newline at end of file +3.0.0b2 \ No newline at end of file